fix(sdd): gate CodeGraph guidance for subagents#998
Conversation
📝 WalkthroughWalkthroughAdds ChangesCodeGraph Guidance Propagation to SDD Sub-agents
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR fixes inconsistent propagation of CodeGraph “search-order” guidance into generated SDD/JD/review sub-agent prompts by injecting the guidance only when CodeGraph is explicitly selected or is already configured, while avoiding unwanted default-on behavior when only the CodeGraph CLI is present.
Changes:
- Adds CodeGraph guidance injection helpers for Markdown prompts and OpenCode inline sub-agent prompts, gated on an explicit guidance string.
- Plumbs gated guidance through SDD inject + OpenCode profile overlay generation, and adds extensive regression coverage (including Kimi YAML control-file behavior).
- Wires CLI install/sync paths to compute CodeGraph guidance only when CodeGraph is selected/configured (plus legacy handling).
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| internal/components/sdd/prompts.go | Adds shared helpers to inject CodeGraph guidance into Markdown prompts and OpenCode inline sub-agent prompts. |
| internal/components/sdd/prompts_test.go | Adds behavior-level tests ensuring guidance is injected only when enabled and remains idempotent; includes Kimi YAML control-file regression tests. |
| internal/components/sdd/profiles.go | Propagates gated guidance into generated OpenCode profile overlays (for inline sub-agent prompts). |
| internal/components/sdd/inject.go | Plumbs guidance through shared prompt generation, OpenCode inline prompt inlining, and Markdown sub-agent prompt writes. |
| internal/cli/sync.go | Updates sync-stage planning/gating for CodeGraph guidance handling and passes CodeGraph guidance into SDD injection options. |
| internal/cli/sync_test.go | Updates sync tests for the renamed guidance gating helper. |
| internal/cli/run.go | Adds gated guidance selection helpers for SDD install/apply flows based on selection/config/legacy state. |
| internal/cli/run_community_tool_test.go | Adds coverage for “selected or configured” gating and apply/sync behavior around SDD prompt guidance injection. |
Comments suppressed due to low confidence (1)
internal/cli/sync.go:447
shouldHandleCodeGraphGuidance(r.homeDir)only considers existing configuration/legacy markers. If the sync flow allows selecting CodeGraph for the current run viaSelection.CommunityTools, this step will not be planned, so same-run enablement would not refresh/inject managed CodeGraph guidance (only previously-configured installations would). Consider incorporatingr.selection.CommunityToolsinto the gating and ensuring the sync step injects guidance when CodeGraph is selected (not just when already configured).
if shouldHandleCodeGraphGuidance(r.homeDir) {
apply = append(apply, codeGraphGuidanceSyncStep{
id: "sync:community-tool:codegraph-guidance",
homeDir: r.homeDir,
changedFiles: &r.changedFiles,
})
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| StrictTDD: s.selection.StrictTDD, | ||
| PreserveOpenCodeOrchestratorPrompt: profileStrategy == model.SDDProfileStrategyExternalSingleActive, | ||
| Profiles: profiles, | ||
| CodeGraphGuidanceMarkdown: codeGraphGuidanceMarkdownForSDD(s.homeDir, nil), |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/cli/sync.go`:
- Line 698: The SDD guidance is being built with a nil tool selection, so the
first prompt can miss a community CodeGraph tool selected in the same sync run.
Update the `componentSDD` setup in `sync.go` to use `s.selection.CommunityTools`
when calling `codeGraphGuidanceMarkdownForSDD`, or refresh the guidance before
`codeGraphGuidanceSyncStep` runs, so `componentSDD` reflects same-run
selections.
In `@internal/components/sdd/prompts_test.go`:
- Around line 344-571: The new CodeGraph guidance tests only verify first-run
behavior; add an idempotency check that re-runs the same write/install path and
confirms the guidance is not duplicated and no extra changes are reported.
Update the relevant tests around WriteSharedPromptFiles and Inject to call them
twice for the enabled case, then assert each prompt file or agent prompt still
contains exactly one codegraph guidance block and the second run remains
unchanged. Use the existing symbols WriteSharedPromptFiles, Inject,
InjectOptions{CodeGraphGuidanceMarkdown: ...}, and the shared/native prompt
helpers to locate the assertions.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: aa1d76ec-206c-478a-a669-4e4723e867d4
📒 Files selected for processing (8)
internal/cli/run.gointernal/cli/run_community_tool_test.gointernal/cli/sync.gointernal/cli/sync_test.gointernal/components/sdd/inject.gointernal/components/sdd/profiles.gointernal/components/sdd/prompts.gointernal/components/sdd/prompts_test.go
| StrictTDD: s.selection.StrictTDD, | ||
| PreserveOpenCodeOrchestratorPrompt: profileStrategy == model.SDDProfileStrategyExternalSingleActive, | ||
| Profiles: profiles, | ||
| CodeGraphGuidanceMarkdown: codeGraphGuidanceMarkdownForSDD(s.homeDir, nil), |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect sync selection wiring and step ordering for CodeGraph guidance.
rg -nP 'codeGraphGuidanceMarkdownForSDD|CommunityTools|codeGraphGuidanceSyncStep|shouldHandleCodeGraphGuidance' internal/cli/sync.goRepository: Gentleman-Programming/gentle-ai
Length of output: 712
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== sync.go around guidance and step ordering ==\n'
sed -n '420,760p' internal/cli/sync.go
printf '\n== run.go around comparable call site ==\n'
sed -n '860,950p' internal/cli/run.go
printf '\n== definition/usages of codeGraphGuidanceMarkdownForSDD ==\n'
rg -n "func codeGraphGuidanceMarkdownForSDD|codeGraphGuidanceMarkdownForSDD\(" internal/cliRepository: Gentleman-Programming/gentle-ai
Length of output: 16805
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== codeGraphGuidanceMarkdownForSDD implementation ==\n'
sed -n '1360,1425p' internal/cli/run.go
printf '\n== CommunityTools references in sync/selection plumbing ==\n'
rg -n "CommunityTools|CommunityTool" internal/cli internal/modelRepository: Gentleman-Programming/gentle-ai
Length of output: 5024
Pass selected community tools into SDD guidance in sync
componentSDD in sync reads guidance before codeGraphGuidanceSyncStep runs, so passing nil here ignores a just-selected CodeGraph tool and can omit guidance from the first SDD prompt. Use s.selection.CommunityTools (or move the guidance refresh earlier) so same-run selections are reflected.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/cli/sync.go` at line 698, The SDD guidance is being built with a nil
tool selection, so the first prompt can miss a community CodeGraph tool selected
in the same sync run. Update the `componentSDD` setup in `sync.go` to use
`s.selection.CommunityTools` when calling `codeGraphGuidanceMarkdownForSDD`, or
refresh the guidance before `codeGraphGuidanceSyncStep` runs, so `componentSDD`
reflects same-run selections.
| func TestWriteSharedPromptFilesOmitCodeGraphGuidanceByDefault(t *testing.T) { | ||
| home := t.TempDir() | ||
|
|
||
| if _, err := WriteSharedPromptFiles(home, nil); err != nil { | ||
| t.Fatalf("WriteSharedPromptFiles() error = %v", err) | ||
| } | ||
|
|
||
| for _, phase := range SharedPromptPhases() { | ||
| path := filepath.Join(SharedPromptDir(home), phase+".md") | ||
| content, err := os.ReadFile(path) | ||
| if err != nil { | ||
| t.Fatalf("ReadFile(%q) error = %v", path, err) | ||
| } | ||
| text := string(content) | ||
| if strings.Contains(text, "<!-- gentle-ai:codegraph-guidance -->") || strings.Contains(text, "codegraph init <project-root>") { | ||
| t.Fatalf("%s unexpectedly contains CodeGraph guidance by default", phase) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func TestWriteSharedPromptFilesIncludeCodeGraphGuidanceWhenEnabled(t *testing.T) { | ||
| home := t.TempDir() | ||
| guidance := communitytool.CodeGraphGuidanceMarkdown() | ||
|
|
||
| if _, err := WriteSharedPromptFiles(home, nil, guidance); err != nil { | ||
| t.Fatalf("WriteSharedPromptFiles() error = %v", err) | ||
| } | ||
|
|
||
| for _, phase := range SharedPromptPhases() { | ||
| path := filepath.Join(SharedPromptDir(home), phase+".md") | ||
| content, err := os.ReadFile(path) | ||
| if err != nil { | ||
| t.Fatalf("ReadFile(%q) error = %v", path, err) | ||
| } | ||
| text := string(content) | ||
| if !strings.Contains(text, "<!-- gentle-ai:codegraph-guidance -->") || !strings.Contains(text, "codegraph init <project-root>") { | ||
| t.Fatalf("%s missing CodeGraph guidance when enabled", phase) | ||
| } | ||
| if count := strings.Count(text, "<!-- gentle-ai:codegraph-guidance -->"); count != 1 { | ||
| t.Fatalf("%s has %d CodeGraph guidance sections, want 1", phase, count) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func TestInjectOpenCodeSingleModeSubagentPromptsOmitCodeGraphGuidanceByDefault(t *testing.T) { | ||
| home := t.TempDir() | ||
| mockNoPackageManager(t) | ||
|
|
||
| if _, err := Inject(home, opencodeAdapter(), model.SDDModeSingle); err != nil { | ||
| t.Fatalf("Inject(single) error = %v", err) | ||
| } | ||
|
|
||
| agentsMap := readOpenCodeAgents(t, filepath.Join(home, ".config", "opencode", "opencode.json")) | ||
| for _, agentName := range sddInstalledSubAgentsForCodeGraphTest() { | ||
| prompt := agentPrompt(t, agentsMap, agentName) | ||
| if strings.Contains(prompt, "<!-- gentle-ai:codegraph-guidance -->") || strings.Contains(prompt, "codegraph init <project-root>") { | ||
| t.Fatalf("%s unexpectedly contains CodeGraph guidance by default", agentName) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func TestInjectOpenCodeSingleModeSubagentPromptsIncludeCodeGraphGuidanceWhenEnabled(t *testing.T) { | ||
| home := t.TempDir() | ||
| mockNoPackageManager(t) | ||
|
|
||
| if _, err := Inject(home, opencodeAdapter(), model.SDDModeSingle, InjectOptions{CodeGraphGuidanceMarkdown: communitytool.CodeGraphGuidanceMarkdown()}); err != nil { | ||
| t.Fatalf("Inject(single) error = %v", err) | ||
| } | ||
|
|
||
| agentsMap := readOpenCodeAgents(t, filepath.Join(home, ".config", "opencode", "opencode.json")) | ||
| for _, agentName := range sddInstalledSubAgentsForCodeGraphTest() { | ||
| prompt := agentPrompt(t, agentsMap, agentName) | ||
| if !strings.Contains(prompt, "<!-- gentle-ai:codegraph-guidance -->") || !strings.Contains(prompt, "codegraph init <project-root>") { | ||
| t.Fatalf("%s missing CodeGraph guidance when enabled", agentName) | ||
| } | ||
| if count := strings.Count(prompt, "<!-- gentle-ai:codegraph-guidance -->"); count != 1 { | ||
| t.Fatalf("%s has %d CodeGraph guidance sections, want 1", agentName, count) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func TestInjectOpenCodeMultiModeSubagentPromptFilesIncludeCodeGraphGuidanceWhenEnabled(t *testing.T) { | ||
| home := t.TempDir() | ||
| mockNoPackageManager(t) | ||
|
|
||
| if _, err := Inject(home, opencodeAdapter(), model.SDDModeMulti, InjectOptions{CodeGraphGuidanceMarkdown: communitytool.CodeGraphGuidanceMarkdown()}); err != nil { | ||
| t.Fatalf("Inject(multi) error = %v", err) | ||
| } | ||
|
|
||
| for _, phase := range SharedPromptPhases() { | ||
| path := filepath.Join(SharedPromptDir(home), phase+".md") | ||
| content, err := os.ReadFile(path) | ||
| if err != nil { | ||
| t.Fatalf("ReadFile(%q) error = %v", path, err) | ||
| } | ||
| text := string(content) | ||
| if !strings.Contains(text, "<!-- gentle-ai:codegraph-guidance -->") || !strings.Contains(text, "codegraph init <project-root>") { | ||
| t.Fatalf("%s missing CodeGraph guidance when enabled", phase) | ||
| } | ||
| } | ||
|
|
||
| agentsMap := readOpenCodeAgents(t, filepath.Join(home, ".config", "opencode", "opencode.json")) | ||
| inlineSubagents := append(sddJudgmentDaySubAgentsForCodeGraphTest(), sddReviewSubAgentsForCodeGraphTest()...) | ||
| for _, agentName := range inlineSubagents { | ||
| prompt := agentPrompt(t, agentsMap, agentName) | ||
| if !strings.Contains(prompt, "<!-- gentle-ai:codegraph-guidance -->") || !strings.Contains(prompt, "codegraph init <project-root>") { | ||
| t.Fatalf("%s missing CodeGraph guidance in multi-mode inline prompt when enabled", agentName) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func TestInjectNativeSDDSubagentsIncludeCodeGraphGuidanceWhenEnabled(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| agentID model.AgentID | ||
| }{ | ||
| {name: "claude", agentID: model.AgentClaudeCode}, | ||
| {name: "cursor", agentID: model.AgentCursor}, | ||
| {name: "kiro", agentID: model.AgentKiroIDE}, | ||
| {name: "kimi", agentID: model.AgentKimi}, | ||
| } | ||
|
|
||
| for _, tc := range tests { | ||
| t.Run(tc.name, func(t *testing.T) { | ||
| home := t.TempDir() | ||
| adapter := mustAdapter(t, tc.agentID) | ||
|
|
||
| if _, err := Inject(home, adapter, model.SDDModeSingle, InjectOptions{CodeGraphGuidanceMarkdown: communitytool.CodeGraphGuidanceMarkdown()}); err != nil { | ||
| t.Fatalf("Inject(%s) error = %v", tc.name, err) | ||
| } | ||
|
|
||
| for _, agentName := range nativeMarkdownSubAgentsForCodeGraphTest(tc.agentID) { | ||
| path := filepath.Join(adapter.SubAgentsDir(home), agentName+".md") | ||
| content, err := os.ReadFile(path) | ||
| if err != nil { | ||
| t.Fatalf("ReadFile(%q) error = %v", path, err) | ||
| } | ||
| text := string(content) | ||
| if !strings.Contains(text, "<!-- gentle-ai:codegraph-guidance -->") || !strings.Contains(text, "codegraph init <project-root>") { | ||
| t.Fatalf("%s native subagent missing CodeGraph guidance when enabled", agentName) | ||
| } | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| for _, agentName := range []string{"jd-judge-a", "review-risk"} { | ||
| if !containsString(nativeMarkdownSubAgentsForCodeGraphTest(model.AgentClaudeCode), agentName) { | ||
| t.Fatalf("native coverage helper missing %s", agentName) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func TestInjectNativeSDDSubagentsOmitCodeGraphGuidanceByDefault(t *testing.T) { | ||
| home := t.TempDir() | ||
|
|
||
| if _, err := Inject(home, claudeAdapter(), model.SDDModeSingle); err != nil { | ||
| t.Fatalf("Inject(claude) error = %v", err) | ||
| } | ||
|
|
||
| for _, agentName := range nativeMarkdownSubAgentsForCodeGraphTest(model.AgentClaudeCode) { | ||
| path := filepath.Join(home, ".claude", "agents", agentName+".md") | ||
| content, err := os.ReadFile(path) | ||
| if err != nil { | ||
| t.Fatalf("ReadFile(%q) error = %v", path, err) | ||
| } | ||
| text := string(content) | ||
| if strings.Contains(text, "<!-- gentle-ai:codegraph-guidance -->") || strings.Contains(text, "codegraph init <project-root>") { | ||
| t.Fatalf("%s native subagent unexpectedly contains CodeGraph guidance by default", agentName) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func TestInjectKimiYAMLSubagentsOmitCodeGraphGuidanceByDefault(t *testing.T) { | ||
| home := t.TempDir() | ||
|
|
||
| if _, err := Inject(home, kimiAdapter(), model.SDDModeSingle); err != nil { | ||
| t.Fatalf("Inject(kimi) error = %v", err) | ||
| } | ||
|
|
||
| for _, fileName := range kimiYAMLSubagentFilesForCodeGraphTest() { | ||
| path := filepath.Join(home, ".kimi", "agents", fileName) | ||
| content, err := os.ReadFile(path) | ||
| if err != nil { | ||
| t.Fatalf("ReadFile(%q) error = %v", path, err) | ||
| } | ||
| text := string(content) | ||
| if strings.Contains(text, " instructions: |-") || strings.Contains(text, "codegraph init <project-root>") { | ||
| t.Fatalf("%s YAML unexpectedly contains CodeGraph guidance by default", fileName) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func TestInjectKimiYAMLSubagentsRemainControlFilesWhenCodeGraphEnabled(t *testing.T) { | ||
| home := t.TempDir() | ||
|
|
||
| if _, err := Inject(home, kimiAdapter(), model.SDDModeSingle, InjectOptions{CodeGraphGuidanceMarkdown: communitytool.CodeGraphGuidanceMarkdown()}); err != nil { | ||
| t.Fatalf("Inject(kimi) error = %v", err) | ||
| } | ||
|
|
||
| for _, fileName := range kimiYAMLSubagentFilesForCodeGraphTest() { | ||
| path := filepath.Join(home, ".kimi", "agents", fileName) | ||
| content, err := os.ReadFile(path) | ||
| if err != nil { | ||
| t.Fatalf("ReadFile(%q) error = %v", path, err) | ||
| } | ||
| text := string(content) | ||
| for _, want := range []string{" system_prompt_path: ./", " exclude_tools:"} { | ||
| if !strings.Contains(text, want) { | ||
| t.Fatalf("%s YAML missing %q:\n%s", fileName, want, text) | ||
| } | ||
| } | ||
| for _, forbidden := range []string{" instructions: |-", "<!-- gentle-ai:codegraph-guidance -->", "codegraph init <project-root>"} { | ||
| if strings.Contains(text, forbidden) { | ||
| t.Fatalf("%s YAML unexpectedly contains %q:\n%s", fileName, forbidden, text) | ||
| } | ||
| } | ||
|
|
||
| markdownPath := strings.TrimSuffix(path, ".yaml") + ".md" | ||
| markdownContent, err := os.ReadFile(markdownPath) | ||
| if err != nil { | ||
| t.Fatalf("ReadFile(%q) error = %v", markdownPath, err) | ||
| } | ||
| markdownText := string(markdownContent) | ||
| if !strings.Contains(markdownText, "<!-- gentle-ai:codegraph-guidance -->") || !strings.Contains(markdownText, "codegraph init <project-root>") { | ||
| t.Fatalf("%s referenced Markdown prompt missing CodeGraph guidance when enabled", markdownPath) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Consider adding an idempotency assertion for the enabled path.
The new tests cover default-off vs enabled-on, but none re-run Inject/WriteSharedPromptFiles to confirm guidance is injected exactly once on a second run (no duplicate codegraph-guidance sections, no spurious Changed). Given the install/sync idempotency requirement, a double-run case for guidance injection would close a meaningful gap.
As per path instructions: "Install/sync operations must be idempotent (running twice equals running once)."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/components/sdd/prompts_test.go` around lines 344 - 571, The new
CodeGraph guidance tests only verify first-run behavior; add an idempotency
check that re-runs the same write/install path and confirms the guidance is not
duplicated and no extra changes are reported. Update the relevant tests around
WriteSharedPromptFiles and Inject to call them twice for the enabled case, then
assert each prompt file or agent prompt still contains exactly one codegraph
guidance block and the second run remains unchanged. Use the existing symbols
WriteSharedPromptFiles, Inject, InjectOptions{CodeGraphGuidanceMarkdown: ...},
and the shared/native prompt helpers to locate the assertions.
Source: Path instructions
Closes #997
PR Type
Summary
Changes
internal/cli/run.gointernal/cli/sync.gointernal/components/sdd/inject.gointernal/components/sdd/profiles.gointernal/components/sdd/prompts.gointernal/cli/*_test.go,internal/components/sdd/prompts_test.goTest Plan
go test ./internal/cli -run 'CodeGraphGuidance|Component.*CodeGraph|Sync.*CodeGraph|Apply.*CodeGraph|InstallRuntimeStagePlanAddsCommunityToolStepsInSelectionOrder|CommunityToolInstallStepUsesInjectableInstaller'go test ./internal/components/sdd -run 'CodeGraph|Subagent|Kimi'go test ./internal/components/sdd ./internal/components/communitytoolContributor Checklist
type:*labelCo-Authored-BytrailersSummary by CodeRabbit
New Features
Bug Fixes