From 79043a4d879ad20924768d94975bb95b0b3988c4 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 09:25:42 +0000 Subject: [PATCH] feat: surface full circular dependency cycle chains in context bomb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Store the []CircularDependencyCycle slice in ProjectGraph.Cycles so the full chain data is preserved through the cache and rendered in the output. Previously only the count was stored; now each cycle path is listed in the context bomb warning section (e.g. "fileA → fileB → fileC → fileA"). Also update truncateToTokenBudget to render cycle paths in the required header when the output is budget-truncated. Co-Authored-By: Grey Newell Co-Authored-By: Claude Sonnet 4.6 --- cmd/run.go | 1 + internal/api/client.go | 19 ++++++++++--------- internal/template/render.go | 9 +++++++-- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/cmd/run.go b/cmd/run.go index df217c6..f52b623 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -300,6 +300,7 @@ func fetchGraphWithCircularDeps( logFn("[warn] circular dependency check failed: %v", cr.err) } else if cr.circDeps != nil { gr.graph.Stats.CircularDependencyCycles = len(cr.circDeps.Cycles) + gr.graph.Cycles = cr.circDeps.Cycles logFn("[debug] circular dependency cycles found: %d", gr.graph.Stats.CircularDependencyCycles) } diff --git a/internal/api/client.go b/internal/api/client.go index f1bf131..62d7b9f 100644 --- a/internal/api/client.go +++ b/internal/api/client.go @@ -191,15 +191,16 @@ type CriticalFile struct { // ProjectGraph is the internal model used by the cache and template. type ProjectGraph struct { - Name string `json:"name"` - Language string `json:"language"` - Framework string `json:"framework,omitempty"` - Description string `json:"description,omitempty"` - Domains []Domain `json:"domains"` - ExternalDeps []string `json:"external_deps,omitempty"` - CriticalFiles []CriticalFile `json:"critical_files,omitempty"` - Stats Stats `json:"stats"` - UpdatedAt time.Time `json:"updated_at"` + Name string `json:"name"` + Language string `json:"language"` + Framework string `json:"framework,omitempty"` + Description string `json:"description,omitempty"` + Domains []Domain `json:"domains"` + ExternalDeps []string `json:"external_deps,omitempty"` + CriticalFiles []CriticalFile `json:"critical_files,omitempty"` + Stats Stats `json:"stats"` + Cycles []CircularDependencyCycle `json:"cycles,omitempty"` + UpdatedAt time.Time `json:"updated_at"` } // Subdomain represents a named sub-area within a domain. diff --git a/internal/template/render.go b/internal/template/render.go index 3fb7e46..3ed87a4 100644 --- a/internal/template/render.go +++ b/internal/template/render.go @@ -16,7 +16,8 @@ const contextBombTmpl = `# Uncompact Context — {{.ProjectName}} > Injected by Uncompact at {{.Timestamp}}{{if .Stale}} | ⚠️ STALE: last updated {{.StaleDuration}}{{end}} {{- if .Graph.Stats.CircularDependencyCycles}} -> ⚠️ {{.Graph.Stats.CircularDependencyCycles}} circular dependency {{if eq .Graph.Stats.CircularDependencyCycles 1}}cycle{{else}}cycles{{end}} detected +> ⚠️ {{.Graph.Stats.CircularDependencyCycles}} circular dependency {{if eq .Graph.Stats.CircularDependencyCycles 1}}cycle{{else}}cycles{{end}} detected{{range .Graph.Cycles}} +> - {{join .Cycle " → "}}{{end}} {{- end}} ## Project Overview @@ -182,7 +183,11 @@ func truncateToTokenBudget( if circularCycles == 1 { label = "cycle" } - hdr.WriteString(fmt.Sprintf("> ⚠️ %d circular dependency %s detected\n\n", circularCycles, label)) + hdr.WriteString(fmt.Sprintf("> ⚠️ %d circular dependency %s detected\n", circularCycles, label)) + for _, c := range graph.Cycles { + hdr.WriteString(fmt.Sprintf("> - %s\n", strings.Join(c.Cycle, " → "))) + } + hdr.WriteString("\n") } hdr.WriteString(fmt.Sprintf( "**Language:** %s · **Files:** %d · **Functions:** %d",