Skip to content

feat(vscode): add model assignment foundation#731

Open
Snakeblack wants to merge 11 commits into
Gentleman-Programming:mainfrom
Snakeblack:feat/vscode-copilot-subagents-pr2
Open

feat(vscode): add model assignment foundation#731
Snakeblack wants to merge 11 commits into
Gentleman-Programming:mainfrom
Snakeblack:feat/vscode-copilot-subagents-pr2

Conversation

@Snakeblack

@Snakeblack Snakeblack commented Jun 1, 2026

Copy link
Copy Markdown

🔗 Linked Issue

Closes #504

Related context: #677
Depends on: #708


🏷️ PR Type

What kind of change does this PR introduce?

  • type:bug — Bug fix (non-breaking change that fixes an issue)
  • type:feature — New feature (non-breaking change that adds functionality)
  • type:docs — Documentation only
  • type:refactor — Code refactoring (no functional changes)
  • type:chore — Build, CI, or tooling changes
  • type:breaking-change — Breaking change (fix or feature that changes existing behavior)

🔗 Chain Context

PR 2 of the VS Code Copilot SDD subagents stack.

#708 PR1 native VS Code `.agent.md` agents
   ↓
📍 PR2 VS Code model assignment foundation
   ↓
#738 PR3 VS Code Copilot agent install hardening
   ↓
#740 PR4 TUI + docs final

This PR depends on #708. Because #708 is still open and its branch exists only in the contributor fork, this PR targets main; once #708 lands, this PR can be rebased/updated and the GitHub diff will collapse to the PR2-only commit.

Focused review diff while #708 is open:
Snakeblack/gentle-ai@feat/vscode-copilot-subagents-pr1...feat/vscode-copilot-subagents-pr2


📝 Summary

  • Adds persisted VSCodeModelAssignments separate from OpenCode, Claude, and Kiro model assignment maps.
  • Adds an isolated VS Code resolver that reads dynamic github-copilot model cache data and safely renders optional model: frontmatter for native .agent.md files.
  • Propagates non-fatal warnings when assignments are stale, unresolved, unsupported, or cache data is missing, while preserving parent-model inheritance.

📂 Changes

File / Area What Changed
internal/model, internal/state Added VSCodeModelAssignments to selections, sync overrides, and persisted install state
internal/app, internal/cli Loads, persists, and forwards VS Code assignments through install and sync flows
internal/components/sdd/vscode_models.go New isolated resolver for Copilot model cache validation and .agent.md frontmatter rendering
internal/components/sdd/inject.go Applies VS Code model rendering during native agent sync and returns non-fatal warnings
Tests Added coverage for persistence separation, safe fallback, warning propagation, runtime key allowlist, injected-home cache paths, and idempotent rendering

🧠 Size Exception Rationale

This PR requests size:exception.

The change is larger than the 400-line budget because the behavior is intentionally cohesive: persistence, resolver validation, .agent.md rendering, warning propagation, and tests must be reviewed together to understand the safety contract. Splitting these pieces would make the implementation harder to reason about and would hide the important invariant: unresolved assignments must stay persisted but must not render unsafe model: frontmatter.

To reduce reviewer load, the new resolver files include concise behavior comments explaining why each helper exists and how failures degrade safely.


🧪 Test Plan

Focused PR2 tests

go test ./internal/model ./internal/state ./internal/app ./internal/cli ./internal/components/sdd -run "TestSelectionAndSyncOverridesHaveVSCodeModelAssignments|TestModelAssignmentsRoundTrip|TestApplyOverridesVSCodeModelAssignments|TestPersistAndLoadVSCodeModelAssignments|TestModelAssignmentsToStateSupportsVSCodeAssignments|TestVSCodeModelAssignmentKeysIncludeCoordinatorAndPhases|TestVSCodeAgentKeyRejectsUnknownNativeFiles|TestResolveVSCodeModelAssignment|TestInjectVSCodeRendersResolvedModelAssignment|TestInjectVSCodeDefaultModelCacheUsesInjectedHome|TestInjectVSCodeMissingModelCacheWarnsWithoutModelLine|TestRunSyncWithSelectionPropagatesVSCodeModelWarnings|TestRunSyncLoadsPersistedModelAssignments"

Package smoke

go test ./internal/cli

Static checks

go vet ./...
git diff --check

Full suite in CI-like Linux runner

env -i HOME=/tmp/opencode/home \
  PATH=/tmp/opencode/go/bin:/usr/bin:/bin \
  GOCACHE=/tmp/opencode/gocache \
  GOMODCACHE=/tmp/opencode/gomodcache \
  /tmp/opencode/go/bin/go test ./...

Results:

  • Focused PR2 tests pass
  • go test ./internal/cli passes
  • go vet ./... passes
  • git diff --check passes
  • go test ./... passes in clean WSL Ubuntu LF clone with Go 1.25.10
  • E2E tests pass (cd e2e && ./docker-test.sh)

Note: local Windows go test ./... still has baseline CRLF/env-sensitive failures unrelated to this PR. The full-suite gate was validated in a clean Linux/LF runner instead.


🤖 Automated Checks

Check Status Description
Check PR Cognitive Load ⚠️ Requests size:exception; behavior is cohesive and documented
Check Issue Reference PR body contains Closes #504
Check Issue Has status:approved #504 has status:approved
Check PR Has type:* Label type:feature requested (maintainer label may be needed)
Unit Tests CI should run go test ./...
E2E Tests CI should run Docker E2E if configured

✅ Contributor Checklist

  • PR is linked to an issue with status:approved
  • PR stays within 400 changed lines, or I have requested/obtained maintainer-applied size:exception with rationale documented — maintainer label needed (size:exception)
  • I have added the appropriate type:* label to this PR — maintainer label may be needed (type:feature)
  • Unit tests pass (go test ./...) in Linux/LF runner
  • E2E tests pass (cd e2e && ./docker-test.sh)
  • I have updated documentation if necessary — inline behavior comments document the new resolver surface; user-facing docs remain PR3 scope
  • My commits follow Conventional Commits format
  • My commits do not include Co-Authored-By trailers

💬 Notes for Reviewers

This is backend/model-assignment foundation only. It deliberately does not include the VS Code install hardening or the TUI/docs updates; those remain split into #738 and #740.

Key safety behavior to review:

  • unknown .agent.md files are ignored by the runtime assignment allowlist;
  • cache paths resolve from the injected home directory, not the process HOME;
  • invalid or unresolved assignments warn and omit model: instead of failing sync;
  • unresolved assignments remain persisted so a future cache refresh can resolve them.

Summary by CodeRabbit

  • New Features
    • Enabled VS Code Copilot native sub-agent support for the spec-driven development workflow (init, explore, propose, design, tasks, apply, verify, archive, and onboarding).
    • Added native VS Code Copilot SDD agent definitions and updated installation to persist/restore VS Code model/provider assignments.
  • Improvements
    • Sync now reports non-fatal warnings in the sync report.
    • VS Code injection resolves model: frontmatter from the Copilot cache, or omits it when unavailable/invalid.
    • Updated VS Code documentation and clarified agent/picker visibility behavior.
  • Bug Fixes
    • Uninstall removes only system-managed VS Code agent files, leaving user-created agents intact.
    • Managed internal Claude agents are hidden from the VS Code agent picker.

@Snakeblack

Copy link
Copy Markdown
Author

Maintainer label request: please apply exactly one type label, \ ype:feature\, and \size:exception\. The size exception is intentional: PR2's persistence + resolver + render + warning propagation must be reviewed as one cohesive safety contract, and the PR body documents the rationale.

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

VS Code Copilot sub-agent support is enabled, embedded SDD agent files were added, VS Code model assignments are persisted and restored, and injection/sync now resolve, render, and report VS Code agent model settings and warnings.

Changes

VS Code Copilot SDD Native Sub-Agent Support

Layer / File(s) Summary
VS Code assignment state
internal/model/selection.go, internal/state/state.go, internal/model/selection_test.go, internal/state/state_test.go, internal/app/app.go, internal/app/app_test.go, internal/cli/install_test.go, internal/cli/run.go, internal/cli/run_state_test.go, internal/components/uninstall/service_test.go, README.md, docs/agents.md
Selection, SyncOverrides, and InstallState add VSCodeModelAssignments; merge, persist/load, round-trip, install, sync, uninstall, and documentation behavior is updated and tested.
VS Code adapter and agent assets
internal/agents/vscode/adapter.go, internal/agents/vscode/adapter_test.go, internal/assets/assets.go, internal/assets/assets_test.go, internal/assets/vscode/agents/*, internal/assets/claude/agents/*
SupportsSubAgents() returns true, SubAgentsDir(homeDir) points to ~/.copilot/agents, embedded asset patterns include vscode/agents, and the VS Code SDD agent files plus their frontmatter/body constraints and managed-agent visibility rules are added and tested.
SDD inject and VS Code model resolution
internal/components/sdd/inject.go, internal/components/sdd/inject_test.go, internal/components/sdd/vscode_agent_visibility.go, internal/components/sdd/vscode_models.go, internal/components/sdd/vscode_models_test.go
InjectOptions gains VS Code assignment and cache paths, InjectionResult gains warnings, native VS Code sub-agent generation rewrites frontmatter model: values, managed Claude agents are hidden from the VS Code picker, and the resolver tests cover cache hits, warnings, effort handling, and rendering behavior.
Sync warnings and code graph guidance
internal/cli/sync.go, internal/cli/sync_test.go, internal/components/communitytool/codegraph_guidance.go
RunSync and RunSyncWithSelection collect VS Code model warnings, render them in sync output, and switch code graph guidance refresh and cleanup to agent-scoped paths and detection.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested labels: type:feature, size:exception

Suggested reviewers: Alan-TheGentleman

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The diff covers VS Code sub-agent support and embedded agents, but not the profile-generator and cleanup pieces requested in #504. Add GenerateVSCodeProfileFiles and RemoveVSCodeProfileAgents, or split those requirements into a follow-up issue/PR if they are intentionally deferred.
Docstring Coverage ⚠️ Warning Docstring coverage is 34.65% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and matches the main change: adding VS Code model-assignment support.
Out of Scope Changes check ✅ Passed The changes are centered on VS Code Copilot subagents, model assignments, and supporting tests/docs; no clearly unrelated edits stand out.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/app/app.go`:
- Around line 749-751: The condition in the VSCodeModelAssignments block at the
if statement is checking if the length is greater than zero, which prevents
explicit clearing when a non-nil empty map is passed. Change the condition from
checking len(selection.VSCodeModelAssignments) > 0 to instead checking if
selection.VSCodeModelAssignments is not nil. This will allow the
modelAssignmentsToState function to process empty maps that represent an
explicit clear intent, while still allowing the caller to pass nil if they want
to skip processing assignments entirely.

In `@internal/cli/run.go`:
- Around line 183-184: The mergeExplicitAgentInstallState function is not
copying the VSCodeModelAssignments field when merging state, causing newly
selected VS Code assignments to be lost when running install with an existing
state file. Add VSCodeModelAssignments to the field assignments in the
mergeExplicitAgentInstallState function to ensure the value set by RunInstall is
preserved during the state merge operation.
🪄 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: 415afe5b-2b1d-41d7-883e-cb87ffe57903

📥 Commits

Reviewing files that changed from the base of the PR and between bcd231f and 3fe7acd.

📒 Files selected for processing (30)
  • internal/agents/vscode/adapter.go
  • internal/agents/vscode/adapter_test.go
  • internal/app/app.go
  • internal/app/app_test.go
  • internal/assets/assets.go
  • internal/assets/assets_test.go
  • internal/assets/vscode/agents/sdd-apply.agent.md
  • internal/assets/vscode/agents/sdd-archive.agent.md
  • internal/assets/vscode/agents/sdd-design.agent.md
  • internal/assets/vscode/agents/sdd-explore.agent.md
  • internal/assets/vscode/agents/sdd-init.agent.md
  • internal/assets/vscode/agents/sdd-onboard.agent.md
  • internal/assets/vscode/agents/sdd-orchestrator.agent.md
  • internal/assets/vscode/agents/sdd-propose.agent.md
  • internal/assets/vscode/agents/sdd-spec.agent.md
  • internal/assets/vscode/agents/sdd-tasks.agent.md
  • internal/assets/vscode/agents/sdd-verify.agent.md
  • internal/cli/install_test.go
  • internal/cli/run.go
  • internal/cli/sync.go
  • internal/cli/sync_test.go
  • internal/components/sdd/inject.go
  • internal/components/sdd/inject_test.go
  • internal/components/sdd/vscode_models.go
  • internal/components/sdd/vscode_models_test.go
  • internal/components/uninstall/service_test.go
  • internal/model/selection.go
  • internal/model/selection_test.go
  • internal/state/state.go
  • internal/state/state_test.go

Comment thread internal/app/app.go Outdated
Comment thread internal/cli/run.go
@Snakeblack Snakeblack force-pushed the feat/vscode-copilot-subagents-pr2 branch from 3fe7acd to 20a3005 Compare June 29, 2026 22:50

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 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/run.go`:
- Line 183: Preserve explicit clears for VS Code model assignments during
install merges: `modelAssignmentsToState` currently turns a non-nil empty
`VSCodeModelAssignments` map into nil, so `mergeExplicitAgentInstallState`
treats it as “no change” and leaves stale assignments behind. Update the install
path in `internal/cli/run.go` to use a dedicated conversion helper (for example,
around `modelAssignmentsToState` / `installModelAssignmentsToState`) that
returns an empty non-nil map when the input map is empty, while still returning
nil only for a nil input, and apply the same fix at both call sites noted in the
diff.

In `@internal/cli/sync.go`:
- Around line 992-998: In sync.go, the VSCodeModelAssignments merge in the
selection/persistedState handling uses len(...) == 0, which incorrectly treats
an explicitly empty map as missing. Update the check on
selection.VSCodeModelAssignments to use a nil test so only nil means “no
override,” while an empty map remains an intentional reset to defaults. Keep the
existing persistedState.VSCodeModelAssignments fallback logic, but only apply it
when the selection map is nil.

In `@internal/components/sdd/vscode_models_test.go`:
- Around line 14-22: The test for vscodeModelAssignmentKeys() only checks that
required entries exist, so it will miss accidental allowlist expansion. Update
TestVSCodeModelAssignmentKeysIncludeCoordinatorAndPhases to assert the exact set
returned by vscodeModelAssignmentKeys() (or at minimum verify the length
matches), using the existing vscodeModelAssignmentKeys and containsString
helpers so any unexpected extra native .agent.md key fails the test.
🪄 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: 2b6ad4ed-928d-47db-bd5b-fdffe17e961c

📥 Commits

Reviewing files that changed from the base of the PR and between 3fe7acd and 20a3005.

📒 Files selected for processing (16)
  • internal/app/app.go
  • internal/app/app_test.go
  • internal/assets/assets_test.go
  • internal/cli/install_test.go
  • internal/cli/run.go
  • internal/cli/run_state_test.go
  • internal/cli/sync.go
  • internal/cli/sync_test.go
  • internal/components/sdd/inject.go
  • internal/components/sdd/inject_test.go
  • internal/components/sdd/vscode_models.go
  • internal/components/sdd/vscode_models_test.go
  • internal/model/selection.go
  • internal/model/selection_test.go
  • internal/state/state.go
  • internal/state/state_test.go

Comment thread internal/cli/run.go
Comment thread internal/cli/sync.go
Comment on lines +992 to +998
if len(selection.VSCodeModelAssignments) == 0 && len(persistedState.VSCodeModelAssignments) > 0 {
m := make(map[string]model.ModelAssignment, len(persistedState.VSCodeModelAssignments))
for k, v := range persistedState.VSCodeModelAssignments {
m[k] = model.ModelAssignment{ProviderID: v.ProviderID, ModelID: v.ModelID, Effort: v.Effort}
}
selection.VSCodeModelAssignments = m
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use a nil-check here, not len(...) == 0.

Line 992 treats a non-nil empty VSCodeModelAssignments map as “missing” and reloads persisted values, so an explicit reset-to-defaults request cannot stick. That breaks the documented nil = no override; empty map = reset to defaults contract for this map.

Suggested fix
-	if len(selection.VSCodeModelAssignments) == 0 && len(persistedState.VSCodeModelAssignments) > 0 {
+	if selection.VSCodeModelAssignments == nil && len(persistedState.VSCodeModelAssignments) > 0 {
 		m := make(map[string]model.ModelAssignment, len(persistedState.VSCodeModelAssignments))
 		for k, v := range persistedState.VSCodeModelAssignments {
 			m[k] = model.ModelAssignment{ProviderID: v.ProviderID, ModelID: v.ModelID, Effort: v.Effort}
 		}
 		selection.VSCodeModelAssignments = m
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if len(selection.VSCodeModelAssignments) == 0 && len(persistedState.VSCodeModelAssignments) > 0 {
m := make(map[string]model.ModelAssignment, len(persistedState.VSCodeModelAssignments))
for k, v := range persistedState.VSCodeModelAssignments {
m[k] = model.ModelAssignment{ProviderID: v.ProviderID, ModelID: v.ModelID, Effort: v.Effort}
}
selection.VSCodeModelAssignments = m
}
if selection.VSCodeModelAssignments == nil && len(persistedState.VSCodeModelAssignments) > 0 {
m := make(map[string]model.ModelAssignment, len(persistedState.VSCodeModelAssignments))
for k, v := range persistedState.VSCodeModelAssignments {
m[k] = model.ModelAssignment{ProviderID: v.ProviderID, ModelID: v.ModelID, Effort: v.Effort}
}
selection.VSCodeModelAssignments = m
}
🤖 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` around lines 992 - 998, In sync.go, the
VSCodeModelAssignments merge in the selection/persistedState handling uses
len(...) == 0, which incorrectly treats an explicitly empty map as missing.
Update the check on selection.VSCodeModelAssignments to use a nil test so only
nil means “no override,” while an empty map remains an intentional reset to
defaults. Keep the existing persistedState.VSCodeModelAssignments fallback
logic, but only apply it when the selection map is nil.

Comment thread internal/components/sdd/vscode_models_test.go
@Snakeblack Snakeblack force-pushed the feat/vscode-copilot-subagents-pr2 branch from 20a3005 to 066e2d6 Compare June 30, 2026 06:29

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 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`:
- Around line 445-450: The CodeGraph guidance refresh is currently scoped only
by homeDir, so the sync step can affect every discovered agent instead of just
the selected ones. Update the sync path around codeGraphGuidanceSyncStep and the
downstream guidance/backup helpers to accept and use r.agentIDs (or the selected
adapters) when enumerating targets, and ensure only those agents’ AGENTS.md
files are read, backed up, and rewritten during refresh.

In `@internal/components/sdd/inject.go`:
- Around line 947-1014: The cleanup in
removeLegacyOpenCodePlainChatPreflightLines is overbroad because it removes any
line matching legacyFragments anywhere in the prompt, which can delete
user-authored content during sync. Change the logic in inject.go so it only
strips a known stale preflight block or an exact legacy section managed by this
flow, rather than scanning every line for substrings like “A. Pace” or “Before
continuing with SDD...”. Keep the rewrite idempotent in the opencode.json sync
path, and preserve unrelated prompt text by narrowing the match to unique
section markers handled by removeLegacyOpenCodePlainChatPreflightLines and the
surrounding prompt rewrite functions.

In `@internal/components/sdd/vscode_models.go`:
- Around line 143-158: The cache/path warning text in loadVSCodeModelCatalog is
agent-specific because it interpolates agentKey, which prevents dedupWarnings()
from collapsing repeated shared failures. Update the warning strings returned
from loadVSCodeModelCatalog to be agent-neutral while keeping the cache
path/error details, and only leave agentKey out of the shared warning message so
identical cache failures deduplicate across phases.
🪄 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: 6b403d65-a19d-4691-a4ad-57a916156ef4

📥 Commits

Reviewing files that changed from the base of the PR and between 20a3005 and 066e2d6.

📒 Files selected for processing (31)
  • internal/agents/vscode/adapter.go
  • internal/agents/vscode/adapter_test.go
  • internal/app/app.go
  • internal/app/app_test.go
  • internal/assets/assets.go
  • internal/assets/assets_test.go
  • internal/assets/vscode/agents/sdd-apply.agent.md
  • internal/assets/vscode/agents/sdd-archive.agent.md
  • internal/assets/vscode/agents/sdd-design.agent.md
  • internal/assets/vscode/agents/sdd-explore.agent.md
  • internal/assets/vscode/agents/sdd-init.agent.md
  • internal/assets/vscode/agents/sdd-onboard.agent.md
  • internal/assets/vscode/agents/sdd-orchestrator.agent.md
  • internal/assets/vscode/agents/sdd-propose.agent.md
  • internal/assets/vscode/agents/sdd-spec.agent.md
  • internal/assets/vscode/agents/sdd-tasks.agent.md
  • internal/assets/vscode/agents/sdd-verify.agent.md
  • internal/cli/install_test.go
  • internal/cli/run.go
  • internal/cli/run_state_test.go
  • internal/cli/sync.go
  • internal/cli/sync_test.go
  • internal/components/sdd/inject.go
  • internal/components/sdd/inject_test.go
  • internal/components/sdd/vscode_models.go
  • internal/components/sdd/vscode_models_test.go
  • internal/components/uninstall/service_test.go
  • internal/model/selection.go
  • internal/model/selection_test.go
  • internal/state/state.go
  • internal/state/state_test.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 3

🤖 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`:
- Around line 445-450: The CodeGraph guidance refresh is currently scoped only
by homeDir, so the sync step can affect every discovered agent instead of just
the selected ones. Update the sync path around codeGraphGuidanceSyncStep and the
downstream guidance/backup helpers to accept and use r.agentIDs (or the selected
adapters) when enumerating targets, and ensure only those agents’ AGENTS.md
files are read, backed up, and rewritten during refresh.

In `@internal/components/sdd/inject.go`:
- Around line 947-1014: The cleanup in
removeLegacyOpenCodePlainChatPreflightLines is overbroad because it removes any
line matching legacyFragments anywhere in the prompt, which can delete
user-authored content during sync. Change the logic in inject.go so it only
strips a known stale preflight block or an exact legacy section managed by this
flow, rather than scanning every line for substrings like “A. Pace” or “Before
continuing with SDD...”. Keep the rewrite idempotent in the opencode.json sync
path, and preserve unrelated prompt text by narrowing the match to unique
section markers handled by removeLegacyOpenCodePlainChatPreflightLines and the
surrounding prompt rewrite functions.

In `@internal/components/sdd/vscode_models.go`:
- Around line 143-158: The cache/path warning text in loadVSCodeModelCatalog is
agent-specific because it interpolates agentKey, which prevents dedupWarnings()
from collapsing repeated shared failures. Update the warning strings returned
from loadVSCodeModelCatalog to be agent-neutral while keeping the cache
path/error details, and only leave agentKey out of the shared warning message so
identical cache failures deduplicate across phases.
🪄 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: 6b403d65-a19d-4691-a4ad-57a916156ef4

📥 Commits

Reviewing files that changed from the base of the PR and between 20a3005 and 066e2d6.

📒 Files selected for processing (31)
  • internal/agents/vscode/adapter.go
  • internal/agents/vscode/adapter_test.go
  • internal/app/app.go
  • internal/app/app_test.go
  • internal/assets/assets.go
  • internal/assets/assets_test.go
  • internal/assets/vscode/agents/sdd-apply.agent.md
  • internal/assets/vscode/agents/sdd-archive.agent.md
  • internal/assets/vscode/agents/sdd-design.agent.md
  • internal/assets/vscode/agents/sdd-explore.agent.md
  • internal/assets/vscode/agents/sdd-init.agent.md
  • internal/assets/vscode/agents/sdd-onboard.agent.md
  • internal/assets/vscode/agents/sdd-orchestrator.agent.md
  • internal/assets/vscode/agents/sdd-propose.agent.md
  • internal/assets/vscode/agents/sdd-spec.agent.md
  • internal/assets/vscode/agents/sdd-tasks.agent.md
  • internal/assets/vscode/agents/sdd-verify.agent.md
  • internal/cli/install_test.go
  • internal/cli/run.go
  • internal/cli/run_state_test.go
  • internal/cli/sync.go
  • internal/cli/sync_test.go
  • internal/components/sdd/inject.go
  • internal/components/sdd/inject_test.go
  • internal/components/sdd/vscode_models.go
  • internal/components/sdd/vscode_models_test.go
  • internal/components/uninstall/service_test.go
  • internal/model/selection.go
  • internal/model/selection_test.go
  • internal/state/state.go
  • internal/state/state_test.go
🛑 Comments failed to post (3)
internal/cli/sync.go (1)

445-450: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Scope CodeGraph guidance refresh to the selected agents.

This path is keyed only by homeDir, but the downstream guidance helpers enumerate all discovered installed agents. A targeted sync can therefore rewrite another agent’s AGENTS.md just because that agent has CodeGraph configured. Please thread the selected r.agentIDs/adapters into the refresh and backup-target helpers so sync only touches files for the agents the user asked to manage.

Also applies to: 472-476, 594-607

🤖 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` around lines 445 - 450, The CodeGraph guidance refresh
is currently scoped only by homeDir, so the sync step can affect every
discovered agent instead of just the selected ones. Update the sync path around
codeGraphGuidanceSyncStep and the downstream guidance/backup helpers to accept
and use r.agentIDs (or the selected adapters) when enumerating targets, and
ensure only those agents’ AGENTS.md files are read, backed up, and rewritten
during refresh.
internal/components/sdd/inject.go (1)

947-1014: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Don't strip preserved prompt content via global substring matches.

removeLegacyOpenCodePlainChatPreflightLines deletes any line containing fragments like A. Pace or Before continuing with SDD... anywhere in the preserved prompt. Because this runs before rewriting opencode.json, unrelated user-authored prompt text that happens to reuse those phrases gets silently removed on sync, with no recovery path in this flow. Scope the cleanup to a managed stale block or an exact legacy preflight section instead of scanning the whole prompt line-by-line. As per path instructions, "internal/components/**/*.go: Install/sync operations must be idempotent (running twice equals running once). Any write to user config must have a backup/restore path."

🤖 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/inject.go` around lines 947 - 1014, The cleanup in
removeLegacyOpenCodePlainChatPreflightLines is overbroad because it removes any
line matching legacyFragments anywhere in the prompt, which can delete
user-authored content during sync. Change the logic in inject.go so it only
strips a known stale preflight block or an exact legacy section managed by this
flow, rather than scanning every line for substrings like “A. Pace” or “Before
continuing with SDD...”. Keep the rewrite idempotent in the opencode.json sync
path, and preserve unrelated prompt text by narrowing the match to unique
section markers handled by removeLegacyOpenCodePlainChatPreflightLines and the
surrounding prompt rewrite functions.

Source: Path instructions

internal/components/sdd/vscode_models.go (1)

143-158: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make shared cache warnings agent-neutral before dedup.

These cache/path failures are shared across all VS Code assignments, but the warning text includes agentKey. dedupWarnings() only removes exact matches, so one missing or unreadable cache will emit one warning per phase instead of a single deduped warning.

💡 Suggested direction
- return nil, []string{fmt.Sprintf("VS Code model assignment for %s skipped: models cache path unavailable", agentKey)}
+ return nil, []string{"VS Code model assignments skipped: models cache path unavailable"}

- return nil, []string{fmt.Sprintf("VS Code model assignment for %s skipped: models cache %q not found", agentKey, path)}
+ return nil, []string{fmt.Sprintf("VS Code model assignments skipped: models cache %q not found", path)}

- return nil, []string{fmt.Sprintf("VS Code model assignment for %s skipped: read models cache %q: %v", agentKey, path, err)}
+ return nil, []string{fmt.Sprintf("VS Code model assignments skipped: read models cache %q: %v", path, err)}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

func loadVSCodeModelCatalog(agentKey, cachePath, variantsPath string) (map[string]opencode.Provider, []string) {
	path := cachePath
	if path == "" {
		path = opencode.DefaultCachePath()
	}
	if path == "" {
		return nil, []string{"VS Code model assignments skipped: models cache path unavailable"}
	}

	providers, err := opencode.LoadModels(path)
	if err != nil {
		if errors.Is(err, os.ErrNotExist) {
			return nil, []string{fmt.Sprintf("VS Code model assignments skipped: models cache %q not found", path)}
		}
		return nil, []string{fmt.Sprintf("VS Code model assignments skipped: read models cache %q: %v", path, err)}
	}
🤖 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/vscode_models.go` around lines 143 - 158, The
cache/path warning text in loadVSCodeModelCatalog is agent-specific because it
interpolates agentKey, which prevents dedupWarnings() from collapsing repeated
shared failures. Update the warning strings returned from loadVSCodeModelCatalog
to be agent-neutral while keeping the cache path/error details, and only leave
agentKey out of the shared warning message so identical cache failures
deduplicate across phases.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 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 `@go.mod`:
- Line 11: Update the golang.org/x/sys dependency in go.mod to move off v0.38.0
and onto a fixed release such as v0.44.0 or later, preferably v0.45.0 if you
want the Go team’s patched version. Make sure any corresponding module metadata
or lockfile state is refreshed so the dependency graph consistently uses the
upgraded version.

In `@internal/assets/assets_test.go`:
- Around line 632-634: The hidden-agent check in isClaudeInternalAgentName is
duplicating the prefix rules instead of using the same logic as the VS Code
picker visibility path. Update this test helper to reuse the production
predicate/output that determines which agent names are hidden, so the test
tracks the real contract and stays aligned if the picker visibility rules
change.

In `@internal/cli/sync.go`:
- Around line 445-451: The CodeGraph sync flow is using different agent ID
sources for planning and backup, so make it consistently use the same resolved
agent IDs everywhere. In the sync setup where the codeGraphGuidanceSyncStep is
appended, use the resolved agent IDs rather than r.agentIDs directly, and in the
backup snapshot path that currently uses selection.Agents, switch to the same
resolved set so both the write and rollback snapshot cover the same files.
Update the related logic in sync.go around codeGraphGuidanceSyncStep and the
backup/snapshot handling to share one normalized agent ID list.

In `@internal/components/communitytool/codegraph_guidance.go`:
- Around line 145-155: HasConfiguredCodeGraphForAgents is leaking non-requested
agent state through its fallback, so keep it strictly scoped to the provided
agentIDs. In HasConfiguredCodeGraphForAgents, remove or replace the final
hasDetectedCodeGraphToolWiring(homeDir) fallback so it only returns true when
one of the matched agents in the status.Agents loop is both detected and
configured. If a fallback is still needed, make it accept agentIDs and validate
the wiring against those agents only.
- Around line 217-225: The file-tracking in the guidance injection flow is
adding every non-empty target file even when `injectCodeGraphGuidanceForAgent`
reports no change, so `result.Files` can include unchanged files once
`result.Changed` becomes true later. Update the loop in
`injectCodeGraphGuidanceForAgent`’s caller to append to `result.Files` only when
`changed` is true, while still preserving the existing error handling and
overall `result.Changed` aggregation.

In `@internal/components/sdd/inject.go`:
- Around line 962-981: The prompt filtering logic in inject.go should not start
discarding lines unless both the opener and a matching trailer are present.
Update the inBlock scanning around the line checks in this legacy prompt cleanup
so that it first verifies a complete block exists before entering discard mode,
and preserve the remaining content when the closing marker is missing or
unrecognized. Use the existing inBlock state and the hard-coded opener/trailer
string matches as the place to add the guard.

In `@internal/components/sdd/vscode_agent_visibility.go`:
- Around line 27-45: The current VSCode visibility update logic in
hideClaudeAgentFromVSCodePicker and the managedClaudeInternalAgentFiles loop
rewrites any reserved-name file under the agents directory based only on
filename. Add a guard that verifies the file is actually Gentle-managed using
the existing install-state or marker record before calling
filemerge.WriteFileAtomic, and skip user-authored files entirely. Route any
allowed mutation through the rollback/backup flow so changes to user config
remain reversible and the operation stays idempotent.
🪄 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: dc074462-65b4-4327-93aa-199766ea1085

📥 Commits

Reviewing files that changed from the base of the PR and between 066e2d6 and 8d43e81.

⛔ Files ignored due to path filters (9)
  • testdata/golden/sdd-claude-agent-sdd-apply.golden is excluded by !testdata/**
  • testdata/golden/sdd-claude-agent-sdd-archive.golden is excluded by !testdata/**
  • testdata/golden/sdd-claude-agent-sdd-design.golden is excluded by !testdata/**
  • testdata/golden/sdd-claude-agent-sdd-explore.golden is excluded by !testdata/**
  • testdata/golden/sdd-claude-agent-sdd-propose.golden is excluded by !testdata/**
  • testdata/golden/sdd-claude-agent-sdd-spec.golden is excluded by !testdata/**
  • testdata/golden/sdd-claude-agent-sdd-tasks.golden is excluded by !testdata/**
  • testdata/golden/sdd-claude-agent-sdd-verify.golden is excluded by !testdata/**
  • testdata/golden/sdd-vscode-instructions.golden is excluded by !testdata/**
📒 Files selected for processing (29)
  • README.md
  • docs/agents.md
  • go.mod
  • internal/assets/assets_test.go
  • internal/assets/claude/agents/jd-fix-agent.md
  • internal/assets/claude/agents/jd-judge-a.md
  • internal/assets/claude/agents/jd-judge-b.md
  • internal/assets/claude/agents/sdd-apply.md
  • internal/assets/claude/agents/sdd-archive.md
  • internal/assets/claude/agents/sdd-design.md
  • internal/assets/claude/agents/sdd-explore.md
  • internal/assets/claude/agents/sdd-init.md
  • internal/assets/claude/agents/sdd-onboard.md
  • internal/assets/claude/agents/sdd-propose.md
  • internal/assets/claude/agents/sdd-spec.md
  • internal/assets/claude/agents/sdd-tasks.md
  • internal/assets/claude/agents/sdd-verify.md
  • internal/cli/sync.go
  • internal/cli/sync_test.go
  • internal/components/communitytool/codegraph_guidance.go
  • internal/components/filemerge/replace_default.go
  • internal/components/filemerge/replace_windows.go
  • internal/components/filemerge/writer.go
  • internal/components/filemerge/writer_test.go
  • internal/components/sdd/inject.go
  • internal/components/sdd/inject_test.go
  • internal/components/sdd/vscode_agent_visibility.go
  • internal/components/sdd/vscode_models.go
  • internal/components/sdd/vscode_models_test.go

Comment thread go.mod
github.com/charmbracelet/lipgloss v1.1.0
github.com/mattn/go-isatty v0.0.20
github.com/rivo/uniseg v0.4.7
golang.org/x/sys v0.38.0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== go.mod ==\n'
cat -n go.mod | sed -n '1,80p'

printf '\n== x/sys references ==\n'
rg -n --hidden --glob '!**/vendor/**' 'golang\.org/x/sys|x/sys' .

Repository: Gentleman-Programming/gentle-ai

Length of output: 1985


🌐 Web query:

GO-2026-5024 golang.org/x/sys windows vulnerable version advisory

💡 Result:

GO-2026-5024 (also tracked as CVE-2026-39824) is a security vulnerability in the golang.org/x/sys/windows package [1][2]. The issue involves an integer overflow in the NewNTUnicodeString function [3][4]. Specifically, the function failed to properly check for string length overflows; when provided with a string exceeding the maximum size of a NTUnicodeString (a 16-bit number of bytes), it would return a truncated string rather than an error [1][2]. This vulnerability affects versions of golang.org/x/sys prior to v0.44.0 [1][2]. Users are advised to update to a patched version to resolve this issue [3][5]. The Go Security Team officially announced the fix in version v0.45.0, though the underlying patch was included in v0.44.0 [3][5].

Citations:


Bump golang.org/x/sys off v0.38.0. v0.38.0 is in the affected range for GO-2026-5024 / CVE-2026-39824, so this keeps Windows builds on a vulnerable release. Upgrade to a patched version (v0.44.0+, or v0.45.0 if you want the Go team’s fix).

🧰 Tools
🪛 OSV Scanner (2.4.0)

[LOW] 11-11: golang.org/x/sys 0.38.0: Invoking integer overflow in NewNTUnicodeString in golang.org/x/sys/windows

(GO-2026-5024)

🤖 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 `@go.mod` at line 11, Update the golang.org/x/sys dependency in go.mod to move
off v0.38.0 and onto a fixed release such as v0.44.0 or later, preferably
v0.45.0 if you want the Go team’s patched version. Make sure any corresponding
module metadata or lockfile state is refreshed so the dependency graph
consistently uses the upgraded version.

Source: Linters/SAST tools

Comment on lines +632 to +634
func isClaudeInternalAgentName(name string) bool {
return strings.HasPrefix(name, "sdd-") || strings.HasPrefix(name, "jd-")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Share the hidden-agent predicate with production code.

This helper hard-codes sdd-/jd- prefixes instead of deriving the set from the actual VS Code picker visibility logic added in this stack. If that logic changes, this test can stay green while picker behavior regresses. Reuse the same predicate/output here so the test exercises the real contract.

🤖 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/assets/assets_test.go` around lines 632 - 634, The hidden-agent
check in isClaudeInternalAgentName is duplicating the prefix rules instead of
using the same logic as the VS Code picker visibility path. Update this test
helper to reuse the production predicate/output that determines which agent
names are hidden, so the test tracks the real contract and stays aligned if the
picker visibility rules change.

Comment thread internal/cli/sync.go Outdated
Comment on lines +145 to +155
func HasConfiguredCodeGraphForAgents(homeDir string, agentIDs []model.AgentID, detector Detector) bool {
status := DetectStatus(model.CommunityToolCodeGraph, homeDir, detector)
if status.CLI != AvailabilityAvailable {
return false
}
for _, agent := range status.Agents {
if slices.Contains(agentIDs, agent.Agent) && agent.Detected && agent.Configured {
return true
}
}
return hasDetectedCodeGraphToolWiring(homeDir)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep HasConfiguredCodeGraphForAgents fully agent-scoped.

After the per-agent loop, Line 155 falls back to hasDetectedCodeGraphToolWiring(homeDir), which is not filtered by agentIDs. That means CodeGraph wiring for some other installed agent can make this return true and trigger guidance writes for the requested agents anyway, defeating the new agent-scoped sync behavior.

🤖 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/communitytool/codegraph_guidance.go` around lines 145 -
155, HasConfiguredCodeGraphForAgents is leaking non-requested agent state
through its fallback, so keep it strictly scoped to the provided agentIDs. In
HasConfiguredCodeGraphForAgents, remove or replace the final
hasDetectedCodeGraphToolWiring(homeDir) fallback so it only returns true when
one of the matched agents in the status.Agents loop is both detected and
configured. If a fallback is still needed, make it accept agentIDs and validate
the wiring against those agents only.

Source: Path instructions

Comment thread internal/components/communitytool/codegraph_guidance.go
Comment thread internal/components/sdd/inject.go Outdated
Comment on lines +27 to +45
for _, fileName := range managedClaudeInternalAgentFiles() {
path := filepath.Join(agentsDir, fileName)
content, err := os.ReadFile(path)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
continue
}
return InjectionResult{}, fmt.Errorf("read managed Claude agent %s: %w", fileName, err)
}

text := string(content)
updated := hideClaudeAgentFromVSCodePicker(text)
if updated == text {
continue
}
writeResult, err := filemerge.WriteFileAtomic(path, []byte(updated), 0o644)
if err != nil {
return InjectionResult{}, fmt.Errorf("write managed Claude agent %s: %w", fileName, err)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Don't rewrite ~/.claude/agents files based only on filename.

Any user-authored sdd-apply.md / jd-judge-a.md / similar reserved-name file under ~/.claude/agents matches this list and gets rewritten in place to user-invocable: false, even if Gentle AI never created it. That is a user-config mutation with no backup/restore path. Please gate this on a Gentle-managed marker/install-state record before rewriting, and route the change through rollback. As per path instructions, "Install/sync operations must be idempotent (running twice equals running once). Any write to user config must have a backup/restore path."

Also applies to: 54-69

🤖 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/vscode_agent_visibility.go` around lines 27 - 45, The
current VSCode visibility update logic in hideClaudeAgentFromVSCodePicker and
the managedClaudeInternalAgentFiles loop rewrites any reserved-name file under
the agents directory based only on filename. Add a guard that verifies the file
is actually Gentle-managed using the existing install-state or marker record
before calling filemerge.WriteFileAtomic, and skip user-authored files entirely.
Route any allowed mutation through the rollback/backup flow so changes to user
config remain reversible and the operation stays idempotent.

Source: Path instructions

@Snakeblack Snakeblack force-pushed the feat/vscode-copilot-subagents-pr2 branch from 8d43e81 to cc9f892 Compare July 1, 2026 06:37

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
internal/app/app.go (2)

657-666: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse the new stateModelAssignmentsToModel helper for ModelAssignments too.

The ModelAssignments block (lines 657-662) manually repeats the exact conversion that stateModelAssignmentsToModel (introduced by this PR for VSCode) now encapsulates. Consolidate to avoid divergence between the two nearly-identical conversion paths.

♻️ Proposed refactor
 	if len(selection.ModelAssignments) == 0 && len(s.ModelAssignments) > 0 {
-		m := make(map[string]model.ModelAssignment, len(s.ModelAssignments))
-		for k, v := range s.ModelAssignments {
-			m[k] = model.ModelAssignment{ProviderID: v.ProviderID, ModelID: v.ModelID, Effort: v.Effort}
-		}
-		selection.ModelAssignments = m
+		selection.ModelAssignments = stateModelAssignmentsToModel(s.ModelAssignments)
 	}
 	if len(selection.VSCodeModelAssignments) == 0 && len(s.VSCodeModelAssignments) > 0 {
 		selection.VSCodeModelAssignments = stateModelAssignmentsToModel(s.VSCodeModelAssignments)
 	}
🤖 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/app/app.go` around lines 657 - 666, The ModelAssignments conversion
in app.go is duplicating the same state-to-model mapping already handled by
stateModelAssignmentsToModel. Update the selection population logic in app.go to
reuse stateModelAssignmentsToModel for ModelAssignments as well, using the
existing helper and the ModelAssignments/VSCodeModelAssignments fields in the
relevant app state and selection structures to keep both conversion paths
consistent.

678-686: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Redundant gating condition.

hasAssignmentSignal already captures "at least one map is non-nil". Since a nil map always has len == 0, whenever hasAssignmentSignal is false all the len(...) == 0 checks are trivially true. The long conjunction on line 686 can be simplified to if !hasAssignmentSignal { return }.

♻️ Proposed simplification
-	hasAssignmentSignal := selection.ClaudeModelAssignments != nil ||
-		selection.ClaudePhaseAssignments != nil ||
-		selection.KiroModelAssignments != nil ||
-		selection.ModelAssignments != nil ||
-		selection.VSCodeModelAssignments != nil ||
-		selection.CodexModelAssignments != nil ||
-		selection.CodexCarrilModelAssignments != nil ||
-		selection.CodexPhaseModelAssignments != nil
-	if len(selection.ClaudeModelAssignments) == 0 && len(selection.ClaudePhaseAssignments) == 0 && len(selection.KiroModelAssignments) == 0 && len(selection.ModelAssignments) == 0 && len(selection.VSCodeModelAssignments) == 0 && len(selection.CodexModelAssignments) == 0 && len(selection.CodexCarrilModelAssignments) == 0 && len(selection.CodexPhaseModelAssignments) == 0 && !hasAssignmentSignal {
+	hasAssignmentSignal := selection.ClaudeModelAssignments != nil ||
+		selection.ClaudePhaseAssignments != nil ||
+		selection.KiroModelAssignments != nil ||
+		selection.ModelAssignments != nil ||
+		selection.VSCodeModelAssignments != nil ||
+		selection.CodexModelAssignments != nil ||
+		selection.CodexCarrilModelAssignments != nil ||
+		selection.CodexPhaseModelAssignments != nil
+	if !hasAssignmentSignal {
 		return
 	}
🤖 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/app/app.go` around lines 678 - 686, The conditional in app selection
handling is redundant because hasAssignmentSignal already covers whether any
assignment map is non-nil. Update the logic around hasAssignmentSignal in the
selection checks so the long len(...) conjunction is removed and the guard
simply uses hasAssignmentSignal to decide whether to proceed, keeping the
behavior in the same app.go selection path.
♻️ Duplicate comments (8)
internal/components/communitytool/codegraph_guidance.go (2)

217-226: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Files are recorded even when the write reported no change.

result.Files is appended whenever file != "", regardless of changed. Once any later agent in the loop flips result.Changed to true, earlier unchanged files are misreported as modified in the aggregate result. Same issue as raised previously; still present.

🩹 Proposed fix
 		file, changed, err := injectCodeGraphGuidanceForAgent(homeDir, adapter)
 		if err != nil {
 			return result, fmt.Errorf("inject CodeGraph guidance for %s: %w", id, err)
 		}
 		if file == "" {
 			continue
 		}
 		result.Changed = result.Changed || changed
-		result.Files = append(result.Files, file)
+		if changed {
+			result.Files = append(result.Files, file)
+		}
 	}
🤖 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/communitytool/codegraph_guidance.go` around lines 217 -
226, The aggregation in the loop calling injectCodeGraphGuidanceForAgent is
still appending files even when changed is false, which can make unchanged
guidance files look modified once result.Changed becomes true later. Update the
result.Files append logic so only files from successful writes with changed ==
true are recorded, while preserving the existing error handling and
result.Changed update in codegraph_guidance.go.

145-156: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Unscoped fallback defeats agent-scoping in HasConfiguredCodeGraphForAgents.

The loop correctly scopes to agentIDs, but the fallback return hasDetectedCodeGraphToolWiring(homeDir) is not filtered by agentIDs, so CodeGraph wiring for an unrelated installed agent can still trigger guidance writes/backups for the requested agents. This is the same issue raised on a prior commit and remains unresolved.

🩹 Proposed fix
 	for _, agent := range status.Agents {
 		if slices.Contains(agentIDs, agent.Agent) && agent.Detected && agent.Configured {
 			return true
 		}
 	}
-	return hasDetectedCodeGraphToolWiring(homeDir)
+	return false
 }
🤖 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/communitytool/codegraph_guidance.go` around lines 145 -
156, `HasConfiguredCodeGraphForAgents` is still returning an unscoped fallback
via `hasDetectedCodeGraphToolWiring(homeDir)`, which can ignore the requested
`agentIDs`. Update the fallback logic so it only returns true when the detected
CodeGraph wiring belongs to one of the requested agents, and keep the existing
`status.Agents` loop using `slices.Contains(agentIDs, agent.Agent)` as the
scoped check. If needed, pass `agentIDs` into `hasDetectedCodeGraphToolWiring`
or replace it with an agent-aware helper so unrelated agent wiring cannot
trigger a true result.
internal/cli/sync.go (2)

1041-1047: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Empty VSCodeModelAssignments map is treated as "missing" and gets overwritten by persisted state.

Same bug shape as previously flagged for this block (originally at lines 992-998): len(selection.VSCodeModelAssignments) == 0 can't distinguish "caller passed nothing" from "caller explicitly reset to {}", so an intentional reset silently reloads stale persisted assignments.

🩹 Proposed fix
-	if len(selection.VSCodeModelAssignments) == 0 && len(persistedState.VSCodeModelAssignments) > 0 {
+	if selection.VSCodeModelAssignments == nil && len(persistedState.VSCodeModelAssignments) > 0 {
 		m := make(map[string]model.ModelAssignment, len(persistedState.VSCodeModelAssignments))
 		for k, v := range persistedState.VSCodeModelAssignments {
 			m[k] = model.ModelAssignment{ProviderID: v.ProviderID, ModelID: v.ModelID, Effort: v.Effort}
 		}
 		selection.VSCodeModelAssignments = m
 	}
🤖 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` around lines 1041 - 1047, The VSCodeModelAssignments
merge logic in the sync flow is treating an explicitly empty map as absent, so a
caller reset to {} gets overwritten by persisted data. Update the conditional
around the selection/persisted state merge to distinguish nil from an
empty-but-intentional map, and only copy persisted assignments when the caller
truly omitted the field. Keep the fix in the same VSCodeModelAssignments
handling block so the behavior matches the earlier corrected merge logic.

412-412: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Backup snapshot targets still use selection.Agents while the actual refresh/write step uses r.agentIDs.

stagePlan gates the CodeGraph guidance step on r.agentIDs (line 445), but syncBackupTargets (called with r.selection at line 412, implemented at lines 466-473) still keys backup-target selection off selection.Agents. If these two agent sets diverge, the sync can write to CodeGraph guidance files that were never snapshotted, so rollback can't restore them. This is unchanged from a prior review finding on this exact split.

🩹 Proposed fix
-	targets := syncBackupTargets(r.homeDir, r.workspaceDir, r.selection, adapters)
+	targets := syncBackupTargets(r.homeDir, r.workspaceDir, r.selection, r.agentIDs, adapters)
...
-func syncBackupTargets(homeDir, workspaceDir string, selection model.Selection, adapters []agents.Adapter) []string {
+func syncBackupTargets(homeDir, workspaceDir string, selection model.Selection, agentIDs []model.AgentID, adapters []agents.Adapter) []string {
 	paths := map[string]struct{}{}
 	for _, component := range selection.Components {
 		for _, path := range syncComponentPathsWithWorkspace(homeDir, workspaceDir, selection, adapters, component) {
 			paths[path] = struct{}{}
 		}
 	}
-	if shouldRefreshCodeGraphGuidance(homeDir, selection.Agents) {
-		for _, path := range communitytool.CodeGraphGuidancePathsForAgents(homeDir, selection.Agents) {
+	if shouldRefreshCodeGraphGuidance(homeDir, agentIDs) {
+		for _, path := range communitytool.CodeGraphGuidancePathsForAgents(homeDir, agentIDs) {
 			paths[path] = struct{}{}
 		}
 	}

Also applies to: 445-459, 466-477

🤖 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 412, Backup snapshot target selection is still
based on selection.Agents while the refresh/write path uses r.agentIDs, so they
can diverge. Update syncBackupTargets and the stagePlan flow in sync.go to
derive CodeGraph guidance backup targets from r.agentIDs (the same set used for
guidance refresh/write) instead of r.selection, and make sure the snapshot list
and write list are built from the same agent source.
internal/cli/run.go (1)

190-192: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Explicit VSCodeModelAssignments clears still lost on install --agent.

modelAssignmentsToState (line 1763) still collapses a non-nil empty map to nil, so mergeExplicitAgentInstallState treats an explicit "reset VSCodeModelAssignments" as "no change" and stale assignments survive. This diverges from applyOverrides in internal/app/app.go, which honors non-nil empty maps as an explicit clear. This is the same unresolved concern raised previously for this call site.

🐛 Proposed fix
+func installModelAssignmentsToState(m map[string]model.ModelAssignment) map[string]state.ModelAssignmentState {
+	if m == nil {
+		return nil
+	}
+	if len(m) == 0 {
+		return map[string]state.ModelAssignmentState{}
+	}
+	return modelAssignmentsToState(m)
+}
-		VSCodeModelAssignments:      modelAssignmentsToState(input.Selection.VSCodeModelAssignments),
+		VSCodeModelAssignments:      installModelAssignmentsToState(input.Selection.VSCodeModelAssignments),
🤖 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/run.go` around lines 190 - 192, The install state conversion for
VSCodeModelAssignments is still dropping an explicit empty map because
modelAssignmentsToState collapses non-nil empties to nil, so
mergeExplicitAgentInstallState cannot distinguish “clear assignments” from “no
change.” Update modelAssignmentsToState and the install path in run.go so a
non-nil empty VSCodeModelAssignments is preserved as an explicit clear, matching
the behavior already used by applyOverrides in app.go, and make sure the merge
logic continues to see that distinction.
internal/assets/assets_test.go (1)

610-634: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Hidden-agent predicate still duplicated instead of reusing production logic.

isClaudeInternalAgentName hardcodes the sdd-/jd- prefix rules instead of deriving them from the actual VS Code picker visibility logic. If that logic changes, this test can stay green while picker behavior regresses.

🤖 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/assets/assets_test.go` around lines 610 - 634, The hidden-agent
check in TestClaudeInternalAgentAssetsAreHiddenFromVSCodePicker is duplicating
prefix rules in isClaudeInternalAgentName instead of using the same production
visibility logic. Replace the hardcoded sdd-/jd- predicate with a call into the
existing production helper or source of truth used by the VS Code picker so the
test tracks the real behavior. Keep the test focused on verifying asset metadata
for whatever agents the production logic marks as hidden.
internal/components/sdd/vscode_agent_visibility.go (1)

15-52: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Rewrites ~/.claude/agents/*.md based only on filename, with no ownership check or backup.

Any user-authored file matching one of managedClaudeInternalAgentFiles() (e.g. a hand-written sdd-apply.md or jd-judge-a.md) gets its frontmatter mutated in place even if Gentle AI never created it — this is a user-config mutation with no backup/restore path, and this exact concern was already raised in a prior review pass without being resolved in this version of the file.

Gate the rewrite on a Gentle-managed marker or install-state record before calling filemerge.WriteFileAtomic, and route the mutation through a rollback/backup flow so the change stays reversible.

As per path instructions, "Install/sync operations must be idempotent (running twice equals running once). Any write to user config must have a backup/restore path."

🤖 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/vscode_agent_visibility.go` around lines 15 - 52, The
rewrite in hideManagedClaudeInternalAgentsForVSCode currently targets any
matching file from managedClaudeInternalAgentFiles() without proving Gentle
ownership, so user-authored agents can be mutated in place. Update the logic to
first check a Gentle-managed marker or install-state record before reaching
filemerge.WriteFileAtomic, and skip files that are not explicitly managed. When
a managed file is changed, route the update through the existing rollback/backup
flow so the mutation is reversible and idempotent across repeated syncs.

Source: Path instructions

internal/components/sdd/vscode_models_test.go (1)

14-22: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Allowlist test still only checks membership, not exact surface.

This is the same concern raised previously (marked "✅ Addressed" on an earlier commit), but the code shown here still only verifies each expected key is present via containsString; there is no length or exact-set comparison. If vscodeModelAssignmentKeys() grows an unexpected extra entry, this test still passes even though vscode_models.go's isVSCodeModelAssignmentKey would immediately accept another native .agent.md file for model assignment.

🐛 Proposed fix: assert the exact set
 func TestVSCodeModelAssignmentKeysIncludeCoordinatorAndPhases(t *testing.T) {
 	keys := vscodeModelAssignmentKeys()
+	want := []string{"sdd-orchestrator", "sdd-init", "sdd-explore", "sdd-propose", "sdd-spec", "sdd-design", "sdd-tasks", "sdd-apply", "sdd-verify", "sdd-archive", "sdd-onboard"}
+	if len(keys) != len(want) {
+		t.Fatalf("vscodeModelAssignmentKeys() = %v, want exactly %v", keys, want)
+	}

-	for _, want := range append([]string{"sdd-orchestrator"}, "sdd-init", "sdd-explore", "sdd-propose", "sdd-spec", "sdd-design", "sdd-tasks", "sdd-apply", "sdd-verify", "sdd-archive", "sdd-onboard") {
-		if !containsString(keys, want) {
-			t.Fatalf("vscodeModelAssignmentKeys() missing %q in %v", want, keys)
+	for _, w := range want {
+		if !containsString(keys, w) {
+			t.Fatalf("vscodeModelAssignmentKeys() missing %q in %v", w, keys)
 		}
 	}
 }
🤖 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/vscode_models_test.go` around lines 14 - 22, The
VSCode model assignment allowlist test only checks that expected keys are
present, so unexpected extra keys would still pass; update
TestVSCodeModelAssignmentKeysIncludeCoordinatorAndPhases to assert the exact
surface returned by vscodeModelAssignmentKeys() instead of just using
containsString. Use the existing vscodeModelAssignmentKeys and
isVSCodeModelAssignmentKey symbols to compare the full set and verify both
membership and no extras.
🤖 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`:
- Around line 852-861: The restore paths for ModelAssignments and
VSCodeModelAssignments still duplicate the same map conversion logic instead of
using stateModelAssignmentsToModel. Update those restore branches in
internal/cli/sync.go to call stateModelAssignmentsToModel for the
state.ModelAssignmentState to model.ModelAssignment conversion, keeping the
helper as the single source of truth and removing the repeated loop.

In `@internal/components/communitytool/codegraph_guidance.go`:
- Around line 195-258: Both InjectCodeGraphGuidanceForAgents and
CodeGraphGuidancePathsForAgents repeat the same registry and installed-agent
discovery setup, which risks the two lookups drifting over time. Extract the
shared logic into a small helper such as resolveInstalledCodeGraphAgents that
builds the agents.NewDefaultRegistry result and installedMap from
agents.DiscoverInstalled, then have both functions call it and reuse the
filtered agent resolution with isCodeGraphSupportedAgent and
SupportsSystemPrompt.

In `@internal/components/sdd/inject.go`:
- Around line 959-1009: The removal logic in
removeLegacyOpenCodePlainChatPreflightLines still drops content when a later
legacy fragment has a start marker without a matching end marker. Update the
scan so each detected legacy block is validated independently before removing
anything, rather than only checking the first start/end pair. Keep the fix
localized to removeLegacyOpenCodePlainChatPreflightLines by ensuring every
re-entry into the legacy block has a corresponding terminator before suppressing
lines.

---

Outside diff comments:
In `@internal/app/app.go`:
- Around line 657-666: The ModelAssignments conversion in app.go is duplicating
the same state-to-model mapping already handled by stateModelAssignmentsToModel.
Update the selection population logic in app.go to reuse
stateModelAssignmentsToModel for ModelAssignments as well, using the existing
helper and the ModelAssignments/VSCodeModelAssignments fields in the relevant
app state and selection structures to keep both conversion paths consistent.
- Around line 678-686: The conditional in app selection handling is redundant
because hasAssignmentSignal already covers whether any assignment map is
non-nil. Update the logic around hasAssignmentSignal in the selection checks so
the long len(...) conjunction is removed and the guard simply uses
hasAssignmentSignal to decide whether to proceed, keeping the behavior in the
same app.go selection path.

---

Duplicate comments:
In `@internal/assets/assets_test.go`:
- Around line 610-634: The hidden-agent check in
TestClaudeInternalAgentAssetsAreHiddenFromVSCodePicker is duplicating prefix
rules in isClaudeInternalAgentName instead of using the same production
visibility logic. Replace the hardcoded sdd-/jd- predicate with a call into the
existing production helper or source of truth used by the VS Code picker so the
test tracks the real behavior. Keep the test focused on verifying asset metadata
for whatever agents the production logic marks as hidden.

In `@internal/cli/run.go`:
- Around line 190-192: The install state conversion for VSCodeModelAssignments
is still dropping an explicit empty map because modelAssignmentsToState
collapses non-nil empties to nil, so mergeExplicitAgentInstallState cannot
distinguish “clear assignments” from “no change.” Update modelAssignmentsToState
and the install path in run.go so a non-nil empty VSCodeModelAssignments is
preserved as an explicit clear, matching the behavior already used by
applyOverrides in app.go, and make sure the merge logic continues to see that
distinction.

In `@internal/cli/sync.go`:
- Around line 1041-1047: The VSCodeModelAssignments merge logic in the sync flow
is treating an explicitly empty map as absent, so a caller reset to {} gets
overwritten by persisted data. Update the conditional around the
selection/persisted state merge to distinguish nil from an empty-but-intentional
map, and only copy persisted assignments when the caller truly omitted the
field. Keep the fix in the same VSCodeModelAssignments handling block so the
behavior matches the earlier corrected merge logic.
- Line 412: Backup snapshot target selection is still based on selection.Agents
while the refresh/write path uses r.agentIDs, so they can diverge. Update
syncBackupTargets and the stagePlan flow in sync.go to derive CodeGraph guidance
backup targets from r.agentIDs (the same set used for guidance refresh/write)
instead of r.selection, and make sure the snapshot list and write list are built
from the same agent source.

In `@internal/components/communitytool/codegraph_guidance.go`:
- Around line 217-226: The aggregation in the loop calling
injectCodeGraphGuidanceForAgent is still appending files even when changed is
false, which can make unchanged guidance files look modified once result.Changed
becomes true later. Update the result.Files append logic so only files from
successful writes with changed == true are recorded, while preserving the
existing error handling and result.Changed update in codegraph_guidance.go.
- Around line 145-156: `HasConfiguredCodeGraphForAgents` is still returning an
unscoped fallback via `hasDetectedCodeGraphToolWiring(homeDir)`, which can
ignore the requested `agentIDs`. Update the fallback logic so it only returns
true when the detected CodeGraph wiring belongs to one of the requested agents,
and keep the existing `status.Agents` loop using `slices.Contains(agentIDs,
agent.Agent)` as the scoped check. If needed, pass `agentIDs` into
`hasDetectedCodeGraphToolWiring` or replace it with an agent-aware helper so
unrelated agent wiring cannot trigger a true result.

In `@internal/components/sdd/vscode_agent_visibility.go`:
- Around line 15-52: The rewrite in hideManagedClaudeInternalAgentsForVSCode
currently targets any matching file from managedClaudeInternalAgentFiles()
without proving Gentle ownership, so user-authored agents can be mutated in
place. Update the logic to first check a Gentle-managed marker or install-state
record before reaching filemerge.WriteFileAtomic, and skip files that are not
explicitly managed. When a managed file is changed, route the update through the
existing rollback/backup flow so the mutation is reversible and idempotent
across repeated syncs.

In `@internal/components/sdd/vscode_models_test.go`:
- Around line 14-22: The VSCode model assignment allowlist test only checks that
expected keys are present, so unexpected extra keys would still pass; update
TestVSCodeModelAssignmentKeysIncludeCoordinatorAndPhases to assert the exact
surface returned by vscodeModelAssignmentKeys() instead of just using
containsString. Use the existing vscodeModelAssignmentKeys and
isVSCodeModelAssignmentKey symbols to compare the full set and verify both
membership and no extras.
🪄 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: 7e7d0f73-39eb-4402-a844-4aa8a9f97909

📥 Commits

Reviewing files that changed from the base of the PR and between 8d43e81 and cc9f892.

⛔ Files ignored due to path filters (9)
  • testdata/golden/sdd-claude-agent-sdd-apply.golden is excluded by !testdata/**
  • testdata/golden/sdd-claude-agent-sdd-archive.golden is excluded by !testdata/**
  • testdata/golden/sdd-claude-agent-sdd-design.golden is excluded by !testdata/**
  • testdata/golden/sdd-claude-agent-sdd-explore.golden is excluded by !testdata/**
  • testdata/golden/sdd-claude-agent-sdd-propose.golden is excluded by !testdata/**
  • testdata/golden/sdd-claude-agent-sdd-spec.golden is excluded by !testdata/**
  • testdata/golden/sdd-claude-agent-sdd-tasks.golden is excluded by !testdata/**
  • testdata/golden/sdd-claude-agent-sdd-verify.golden is excluded by !testdata/**
  • testdata/golden/sdd-vscode-instructions.golden is excluded by !testdata/**
📒 Files selected for processing (39)
  • README.md
  • docs/agents.md
  • go.mod
  • internal/app/app.go
  • internal/app/app_test.go
  • internal/assets/assets_test.go
  • internal/assets/claude/agents/jd-fix-agent.md
  • internal/assets/claude/agents/jd-judge-a.md
  • internal/assets/claude/agents/jd-judge-b.md
  • internal/assets/claude/agents/sdd-apply.md
  • internal/assets/claude/agents/sdd-archive.md
  • internal/assets/claude/agents/sdd-design.md
  • internal/assets/claude/agents/sdd-explore.md
  • internal/assets/claude/agents/sdd-init.md
  • internal/assets/claude/agents/sdd-onboard.md
  • internal/assets/claude/agents/sdd-propose.md
  • internal/assets/claude/agents/sdd-spec.md
  • internal/assets/claude/agents/sdd-tasks.md
  • internal/assets/claude/agents/sdd-verify.md
  • internal/cli/install_test.go
  • internal/cli/run.go
  • internal/cli/run_state_test.go
  • internal/cli/sync.go
  • internal/cli/sync_test.go
  • internal/components/communitytool/codegraph_guidance.go
  • internal/components/filemerge/replace_default.go
  • internal/components/filemerge/replace_windows.go
  • internal/components/filemerge/writer.go
  • internal/components/filemerge/writer_test.go
  • internal/components/sdd/inject.go
  • internal/components/sdd/inject_test.go
  • internal/components/sdd/vscode_agent_visibility.go
  • internal/components/sdd/vscode_models.go
  • internal/components/sdd/vscode_models_test.go
  • internal/components/uninstall/service_test.go
  • internal/model/selection.go
  • internal/model/selection_test.go
  • internal/state/state.go
  • internal/state/state_test.go

Comment thread internal/cli/sync.go
Comment on lines +852 to +861
func stateModelAssignmentsToModel(m map[string]state.ModelAssignmentState) map[string]model.ModelAssignment {
if len(m) == 0 {
return nil
}
out := make(map[string]model.ModelAssignment, len(m))
for k, v := range m {
out[k] = model.ModelAssignment{ProviderID: v.ProviderID, ModelID: v.ModelID, Effort: v.Effort}
}
return out
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm stateModelAssignmentsToModel has no other callers in the package.
rg -n 'stateModelAssignmentsToModel' internal/cli

Repository: Gentleman-Programming/gentle-ai

Length of output: 304


Use stateModelAssignmentsToModel in the restore paths
stateModelAssignmentsToModel is defined but not called anywhere in internal/cli; the ModelAssignments and VSCodeModelAssignments restore branches still duplicate the same conversion loop. Folding those two sites through the helper keeps the mapping logic in one place.

🤖 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` around lines 852 - 861, The restore paths for
ModelAssignments and VSCodeModelAssignments still duplicate the same map
conversion logic instead of using stateModelAssignmentsToModel. Update those
restore branches in internal/cli/sync.go to call stateModelAssignmentsToModel
for the state.ModelAssignmentState to model.ModelAssignment conversion, keeping
the helper as the single source of truth and removing the repeated loop.

Comment on lines +195 to +258
func InjectCodeGraphGuidanceForAgents(homeDir string, agentIDs []model.AgentID) (GuidanceInjectionResult, error) {
reg, err := agents.NewDefaultRegistry()
if err != nil {
return GuidanceInjectionResult{}, err
}

installed := agents.DiscoverInstalled(reg, homeDir)
installedMap := make(map[model.AgentID]struct{})
for _, inst := range installed {
installedMap[inst.ID] = struct{}{}
}

result := GuidanceInjectionResult{}
for _, id := range agentIDs {
if _, ok := installedMap[id]; !ok {
continue
}
adapter, ok := reg.Get(id)
if !ok || !isCodeGraphSupportedAgent(id) || !adapter.SupportsSystemPrompt() {
continue
}

file, changed, err := injectCodeGraphGuidanceForAgent(homeDir, adapter)
if err != nil {
return result, fmt.Errorf("inject CodeGraph guidance for %s: %w", id, err)
}
if file == "" {
continue
}
result.Changed = result.Changed || changed
result.Files = append(result.Files, file)
}

return result, nil
}

func CodeGraphGuidancePathsForAgents(homeDir string, agentIDs []model.AgentID) []string {
reg, err := agents.NewDefaultRegistry()
if err != nil {
return nil
}

installed := agents.DiscoverInstalled(reg, homeDir)
installedMap := make(map[model.AgentID]struct{})
for _, inst := range installed {
installedMap[inst.ID] = struct{}{}
}

paths := make([]string, 0, len(agentIDs))
for _, id := range agentIDs {
if _, ok := installedMap[id]; !ok {
continue
}
adapter, ok := reg.Get(id)
if !ok || !isCodeGraphSupportedAgent(id) || !adapter.SupportsSystemPrompt() {
continue
}
path := adapter.SystemPromptFile(homeDir)
if strings.TrimSpace(path) != "" {
paths = append(paths, path)
}
}
return paths
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

InjectCodeGraphGuidanceForAgents and CodeGraphGuidancePathsForAgents duplicate registry/installed-map setup.

Both functions independently build reg and installedMap with the same loop before diverging. Consider extracting a small shared helper (e.g. resolveInstalledCodeGraphAgents) to avoid drift between the two lookups.

🤖 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/communitytool/codegraph_guidance.go` around lines 195 -
258, Both InjectCodeGraphGuidanceForAgents and CodeGraphGuidancePathsForAgents
repeat the same registry and installed-agent discovery setup, which risks the
two lookups drifting over time. Extract the shared logic into a small helper
such as resolveInstalledCodeGraphAgents that builds the
agents.NewDefaultRegistry result and installedMap from agents.DiscoverInstalled,
then have both functions call it and reuse the filtered agent resolution with
isCodeGraphSupportedAgent and SupportsSystemPrompt.

Comment thread internal/components/sdd/inject.go
@Snakeblack Snakeblack force-pushed the feat/vscode-copilot-subagents-pr2 branch from 8917ac1 to af6710a Compare July 1, 2026 08:27
@Snakeblack Snakeblack force-pushed the feat/vscode-copilot-subagents-pr2 branch from 7a35c51 to e3eba36 Compare July 1, 2026 09:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(agents): add VS Code Copilot SDD multi-mode support (backend)

1 participant