mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-11 14:00:29 +00:00
Share recent change section formatting
This commit is contained in:
@@ -172,6 +172,10 @@ The canonical recent-change sentence formatting also lives in
|
||||
`internal/unifiedresources.FormatResourceChangeSummary`, so AI runtime prompt
|
||||
sections and Patrol seed context reuse the same change wording instead of
|
||||
keeping another lane-local formatter.
|
||||
The canonical recent-change section wrapper also lives in
|
||||
`internal/unifiedresources.FormatResourceRecentChangesContext`, so the AI
|
||||
summary and resource-specific context share the same heading and prefix rules
|
||||
instead of rebuilding that section layout locally.
|
||||
The related-resource correlation section now also comes from the shared
|
||||
correlation formatter in `internal/ai/correlation`, so resource chat and
|
||||
incident prompts reuse the same learned-edge wording instead of rebuilding a
|
||||
|
||||
@@ -152,6 +152,9 @@ That same helper now also owns the one-line recent-change summary text used
|
||||
by the AI runtime prompt sections and Patrol seed context, so the change
|
||||
wording itself stays canonical before the surrounding section headers are
|
||||
applied.
|
||||
The same helper now also owns the canonical recent-change section wrapper,
|
||||
so the Patrol page and AI runtime can share the same heading and resource
|
||||
prefix rules instead of rebuilding that section locally.
|
||||
The canonical shared AI resource context now also surfaces policy routing and
|
||||
redaction hints from unified resources, so the Patrol page and resource drawer
|
||||
see the same governance posture that the runtime uses for export boundaries.
|
||||
|
||||
@@ -101,6 +101,9 @@ That same change-presentation helper now also owns the one-line
|
||||
`FormatResourceChangeSummary` used by AI runtime recent-change sections and
|
||||
Patrol seed context, so the change wording itself stays canonical before any
|
||||
section-specific headings are applied.
|
||||
The same helper also owns `FormatResourceRecentChangesContext`, so AI runtime
|
||||
callers share the canonical recent-change section heading and resource
|
||||
prefixing instead of rebuilding that wrapper locally.
|
||||
The backend AI and Patrol graph context renderers now derive their canonical
|
||||
relationship labels, direction, provenance, freshness, and metadata flags
|
||||
from `internal/unifiedresources/relationship_presentation.go`, so the graph
|
||||
|
||||
@@ -518,7 +518,7 @@ func (i *Intelligence) buildRecentChangesContext(resourceID string, resourceTime
|
||||
|
||||
if resourceTimelineStore != nil {
|
||||
if recent, err := resourceTimelineStore.GetRecentChanges(resourceID, since, limit); err == nil && len(recent) > 0 {
|
||||
return formatCanonicalRecentChangesContext(recent, includeResourcePrefix)
|
||||
return unifiedresources.FormatResourceRecentChangesContext(recent, includeResourcePrefix, "##")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -538,29 +538,6 @@ func (i *Intelligence) buildRecentChangesContext(resourceID string, resourceTime
|
||||
return formatMemoryRecentChangesContext(recent, includeResourcePrefix)
|
||||
}
|
||||
|
||||
func formatCanonicalRecentChangesContext(changes []unifiedresources.ResourceChange, includeResourcePrefix bool) string {
|
||||
if len(changes) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
heading := "\n## Recent Changes"
|
||||
if includeResourcePrefix {
|
||||
heading = "\n## Recent Changes Across Infrastructure"
|
||||
}
|
||||
|
||||
lines := []string{heading, "What changed recently:"}
|
||||
for _, change := range changes {
|
||||
entry := unifiedresources.FormatResourceChangeSummary(change)
|
||||
if includeResourcePrefix {
|
||||
if resourceID := strings.TrimSpace(change.ResourceID); resourceID != "" {
|
||||
entry = fmt.Sprintf("%s: %s", resourceID, entry)
|
||||
}
|
||||
}
|
||||
lines = append(lines, "- "+entry)
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func formatMemoryRecentChangesContext(changes []memory.Change, includeResourcePrefix bool) string {
|
||||
if len(changes) == 0 {
|
||||
return ""
|
||||
|
||||
@@ -458,6 +458,33 @@ func TestIntelligence_FormatGlobalContext_FallsBackToChangeDetector(t *testing.T
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntelligence_BuildRecentChangesContext_UsesCanonicalSectionFormatter(t *testing.T) {
|
||||
intel := NewIntelligence(IntelligenceConfig{})
|
||||
store := ur.NewMemoryStore()
|
||||
if err := store.RecordChange(ur.ResourceChange{
|
||||
ID: "change-1",
|
||||
ObservedAt: time.Now().Add(-time.Hour),
|
||||
ResourceID: "node-1",
|
||||
Kind: ur.ChangeRestart,
|
||||
SourceType: ur.SourcePlatformEvent,
|
||||
SourceAdapter: ur.AdapterProxmox,
|
||||
Reason: "node restarted after maintenance",
|
||||
}); err != nil {
|
||||
t.Fatalf("record canonical change: %v", err)
|
||||
}
|
||||
|
||||
ctx := intel.buildRecentChangesContext("node-1", store, nil, true, 5)
|
||||
for _, want := range []string{
|
||||
"## Recent Changes Across Infrastructure",
|
||||
"node-1: **Restart**",
|
||||
"platform_event/proxmox_adapter",
|
||||
} {
|
||||
if !strings.Contains(ctx, want) {
|
||||
t.Fatalf("expected canonical recent-changes context %q to contain %q", ctx, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntelligence_CreatePredictionFinding_LowSeverity(t *testing.T) {
|
||||
intel := NewIntelligence(IntelligenceConfig{})
|
||||
|
||||
|
||||
@@ -4582,15 +4582,11 @@ func (s *Service) buildRecentResourceChangesContext(resourceID string) string {
|
||||
Str("resource_id", resourceID).
|
||||
Msg("failed to load canonical resource timeline context")
|
||||
} else if len(changes) > 0 {
|
||||
var changeInfo []string
|
||||
for _, change := range changes {
|
||||
if len(changeInfo) >= 3 {
|
||||
break
|
||||
}
|
||||
changeInfo = append(changeInfo, unifiedresources.FormatResourceChangeSummary(change))
|
||||
if len(changes) > 3 {
|
||||
changes = changes[:3]
|
||||
}
|
||||
if len(changeInfo) > 0 {
|
||||
return "\n\n### Recent Changes\n" + strings.Join(changeInfo, "\n")
|
||||
if section := unifiedresources.FormatResourceRecentChangesContext(changes, false, "###"); section != "" {
|
||||
return section
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,3 +114,31 @@ func FormatResourceChangeSummary(change ResourceChange) string {
|
||||
}
|
||||
return summary + fmt.Sprintf(" (%s ago)", ago)
|
||||
}
|
||||
|
||||
// FormatResourceRecentChangesContext returns the canonical markdown section
|
||||
// used by AI prompt surfaces for recent unified-resource changes.
|
||||
func FormatResourceRecentChangesContext(changes []ResourceChange, includeResourcePrefix bool, headingLevel string) string {
|
||||
if len(changes) == 0 {
|
||||
return ""
|
||||
}
|
||||
if strings.TrimSpace(headingLevel) == "" {
|
||||
headingLevel = "##"
|
||||
}
|
||||
|
||||
heading := fmt.Sprintf("\n%s Recent Changes", headingLevel)
|
||||
if includeResourcePrefix {
|
||||
heading = fmt.Sprintf("\n%s Recent Changes Across Infrastructure", headingLevel)
|
||||
}
|
||||
|
||||
lines := []string{heading, "What changed recently:"}
|
||||
for _, change := range changes {
|
||||
entry := FormatResourceChangeSummary(change)
|
||||
if includeResourcePrefix {
|
||||
if resourceID := strings.TrimSpace(change.ResourceID); resourceID != "" {
|
||||
entry = fmt.Sprintf("%s: %s", resourceID, entry)
|
||||
}
|
||||
}
|
||||
lines = append(lines, "- "+entry)
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
@@ -92,3 +92,27 @@ func TestFormatResourceChangeSummary(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatResourceRecentChangesContext(t *testing.T) {
|
||||
changes := []ResourceChange{
|
||||
{
|
||||
Kind: ChangeRestart,
|
||||
ResourceID: "vm-1",
|
||||
SourceType: SourcePlatformEvent,
|
||||
SourceAdapter: AdapterProxmox,
|
||||
Reason: "maintenance",
|
||||
ObservedAt: time.Now().Add(-time.Hour),
|
||||
},
|
||||
}
|
||||
|
||||
ctx := FormatResourceRecentChangesContext(changes, true, "###")
|
||||
for _, want := range []string{
|
||||
"### Recent Changes Across Infrastructure",
|
||||
"vm-1: **Restart**",
|
||||
"platform_event/proxmox_adapter",
|
||||
} {
|
||||
if !strings.Contains(ctx, want) {
|
||||
t.Fatalf("expected recent changes context %q to contain %q", ctx, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,7 +374,7 @@ func TestResourceGraphContextUsesCanonicalRelationshipPresentation(t *testing.T)
|
||||
"func (s *Service) buildResourceGraphContext(resourceID string) string",
|
||||
"if graphContext := s.buildResourceGraphContext(resourceID); graphContext != \"\" {",
|
||||
"unifiedresources.FormatResourceGraphContext(resource, 3)",
|
||||
"unifiedresources.FormatResourceChangeSummary(change)",
|
||||
"unifiedresources.FormatResourceRecentChangesContext(changes, false, \"###\")",
|
||||
"type canonicalResourceGetter interface {",
|
||||
"correlationDetector.FormatForContext(resourceID)",
|
||||
}
|
||||
@@ -410,8 +410,7 @@ func TestIntelligenceRecentChangesUseCanonicalSummaryFormatter(t *testing.T) {
|
||||
}
|
||||
source := string(data)
|
||||
requiredSnippets := []string{
|
||||
"unifiedresources.FormatResourceChangeSummary(change)",
|
||||
"formatCanonicalRecentChangesContext(recent, includeResourcePrefix)",
|
||||
"unifiedresources.FormatResourceRecentChangesContext(recent, includeResourcePrefix, \"##\")",
|
||||
}
|
||||
for _, snippet := range requiredSnippets {
|
||||
if !strings.Contains(source, snippet) {
|
||||
|
||||
Reference in New Issue
Block a user