Skip to content

fix(vscode): harden Copilot agent install#738

Open
Snakeblack wants to merge 18 commits into
Gentleman-Programming:mainfrom
Snakeblack:fix/vscode-copilot-agent-hardening
Open

fix(vscode): harden Copilot agent install#738
Snakeblack wants to merge 18 commits into
Gentleman-Programming:mainfrom
Snakeblack:fix/vscode-copilot-agent-hardening

Conversation

@Snakeblack

@Snakeblack Snakeblack commented Jun 2, 2026

Copy link
Copy Markdown

🔗 Linked Issue

Closes #504

Depends on: #731
Related context: #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 3 of the VS Code Copilot SDD subagents stack.

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

This PR depends on #731. Because the stack is currently opened from a contributor fork and upstream dependency branches are not available as base branches, this PR targets main; reviewers should use the focused diff below for this work unit.

Focused review diff:
Snakeblack/gentle-ai@feat/vscode-copilot-subagents-pr2...fix/vscode-copilot-agent-hardening


📝 Summary

  • Hardens VS Code Copilot agent installation on Windows by using a replace-existing atomic file replacement path.
  • Keeps only the user-facing sdd-orchestrator visible in VS Code while preserving internal phase agents for delegation.
  • Strips stale model: frontmatter when VS Code model assignments are missing or invalid, and updates SDD golden fixtures.

📂 Changes

File / Area What Changed
internal/components/filemerge Adds platform-specific atomic replacement, using MoveFileEx on Windows
internal/components/sdd Hides managed internal agents and strips stale VS Code model frontmatter safely
internal/assets/claude/agents Marks managed internal SDD/JD agents as not user-invocable
testdata/golden Updates expected SDD agent and VS Code instructions output
Tests Adds regression coverage for Windows replacement, VS Code visibility, and model cleanup contracts

🧠 Size Exception Rationale

This PR requests size:exception.

The focused hardening diff is slightly above the 400-line budget because the installer fix, visibility contract, stale model cleanup, and golden fixtures must be reviewed together. Splitting them would hide the important invariant: VS Code should expose only the orchestrator to users while internal agents remain available for delegation and safe sync.


🧪 Test Plan

Linux/LF full suite

go test ./...
go vet ./...

Focused golden/contract checks

go test ./internal/components -run "TestGoldenSDD_(Claude|VSCode)$" -count=1
git diff --check

Results:

  • Unit tests pass (go test ./...) in Linux/LF verification clone
  • E2E tests pass (cd e2e && ./docker-test.sh)
  • Manually tested locally — VS Code install completed after closing VS Code and rerunning the rebuilt local binary

Note: local Windows full-suite runs still have known CRLF/env-sensitive golden noise unrelated to this PR. Final validation was done in a clean Linux/LF runner.


🤖 Automated Checks

Check Status Description
Check PR Cognitive Load ⚠️ Requests size:exception; focused work unit is cohesive
Check Issue Reference PR body contains Closes #504
Check Issue Has status:approved #504 has status:approved
Check PR Has type:* Label type:bug requested; maintainer label 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
  • I have added the appropriate type:* label to this PR — maintainer label needed (type:bug)
  • Unit tests pass (go test ./...)
  • E2E tests pass (cd e2e && ./docker-test.sh)
  • I have updated documentation if necessary
  • My commits follow Conventional Commits format
  • My commits do not include Co-Authored-By trailers

💬 Notes for Reviewers

The key review contract is VS Code visibility: the native dropdown should expose sdd-orchestrator only. Phase agents are intentionally hidden from users but still available for orchestration/delegation.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added full VS Code Copilot SDD native agents (coordinator + phase executors) with structured result contracts.
    • Added VS Code Copilot model assignment support with validation and persistence.
    • Improved VS Code integration to support embedded sub-agents.
  • Improvements

    • Install/sync now preserves VS Code model assignments and surfaces non-fatal sync warnings.
    • Managed Claude internal agents are hidden from the VS Code picker and restored on uninstall.
  • Documentation

    • Expanded Copilot/VS Code agent layering and model-assignment behavior docs.
  • Tests

    • Expanded coverage for assignments, visibility, warnings, uninstall restoration, and VS Code integration behavior.

@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

Adds VS Code Copilot native SDD sub-agent support, VS Code model assignment persistence and rendering, managed Claude agent visibility updates, uninstall restore handling, and cross-platform atomic file replacement. Documentation now describes the VS Code Copilot SDD flow and model-assignment fallback behavior.

Changes

VS Code Copilot SDD Agents

Layer / File(s) Summary
Data model, state contracts, and persistence
internal/model/selection.go, internal/model/selection_test.go, internal/state/state.go, internal/state/state_test.go, internal/app/app.go, internal/app/app_test.go, internal/cli/run.go, internal/cli/run_state_test.go, internal/cli/sync.go, internal/cli/sync_test.go, internal/cli/install_test.go, internal/tui/model.go, internal/tui/model_test.go
VSCodeModelAssignments is added to selection, override, install state, sync, and TUI flows, and preserved through merge, restore, persist, and round-trip behavior.
VS Code adapter, docs, and embedded 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/*.md, README.md, docs/agents.md, go.mod
VS Code sub-agent support is enabled, the new agent templates are embedded and validated, managed Claude agent frontmatter is updated to user-invocable: false, and the VS Code Copilot docs are updated.
Agent-scoped CodeGraph guidance sync
internal/components/communitytool/codegraph_guidance.go, internal/cli/sync.go, internal/cli/sync_test.go
CodeGraph guidance refresh, cleanup, and injection are narrowed to selected agent IDs, and sync planning/tests are updated to pass those IDs through.
Cross-platform atomic file replacement
internal/components/filemerge/replace_default.go, internal/components/filemerge/replace_windows.go, internal/components/filemerge/writer.go, internal/components/filemerge/writer_test.go
WriteFileAtomic uses platform-specific atomic replacement helpers, with tests for overwrite behavior.
VS Code model assignment resolution and picker visibility
internal/components/sdd/vscode_models.go, internal/components/sdd/vscode_models_test.go, internal/components/sdd/vscode_agent_visibility.go, internal/components/sdd/vscode_agent_visibility_test.go
Adds model-assignment validation and frontmatter rewriting for VS Code agents, plus managed Claude visibility hiding and restore helpers.
SDD injection orchestration extensions for VS Code
internal/components/sdd/inject.go, internal/components/sdd/inject_test.go
Extends injection with VS Code model assignment rendering, warning propagation, managed-agent hiding, .agent.md handling, and VS Code-specific orchestrator content; adds coverage for generated files and idempotency.
Cleanup and uninstall restore
internal/components/uninstall/service.go, internal/components/uninstall/service_test.go
Adds VS Code managed-agent restore behavior to uninstall planning and verifies restore/remove cleanup paths.
CLI and app pipeline wiring for VSCodeModelAssignments and warnings
internal/app/app.go, internal/app/app_test.go, internal/cli/run.go, internal/cli/run_state_test.go, internal/cli/sync.go, internal/cli/sync_test.go, internal/cli/install_test.go, internal/tui/model.go, internal/tui/model_test.go
Threads VS Code assignments through install, sync, and persisted state; collects and renders sync warnings; and updates tests for load, merge, and warning behavior.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested labels: type:bug, size:exception

Suggested reviewers: Alan-TheGentleman

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR covers sub-agent support and default agent assets, but the summaries don't show the named-profile generator/removal flow required by #504. Add the named-profile generation and cleanup APIs (or an equivalent profile-specific file flow) and verify the multi-mode path end to end.
Out of Scope Changes check ⚠️ Warning The diff includes unrelated TUI and codegraph-guidance changes even though the linked issue is backend-only. Split out the TUI and codegraph-guidance edits into separate PRs so this change stays focused on VS Code Copilot backend support.
Docstring Coverage ⚠️ Warning Docstring coverage is 35.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 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 theme: hardening VS Code Copilot agent installation.
✨ 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: 8

🤖 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 current code for VSCodeModelAssignments only handles
the case when the map is non-empty, but it lacks handling for the explicit
"reset to defaults" signal when the map is non-nil but empty. Add an else branch
to the existing if condition that checks len(selection.VSCodeModelAssignments) >
0. In this else branch, set current.VSCodeModelAssignments to nil to clear it
when an explicit empty map is provided. This else branch should mirror the
pattern already implemented for other assignment maps like
ClaudeModelAssignments to maintain consistent nil vs empty-map override
semantics throughout the function.

In `@internal/assets/assets_test.go`:
- Around line 577-579: The substring match check using strings.Contains in the
frontmatterKeyLine(frontmatter, "tools") validation will incorrectly flag tool
names containing "agent" as a substring. Parse the tools line to extract
individual tool names and check for an exact match of "agent" rather than using
substring matching. This can be done by splitting the tools string by commas or
spaces and checking if "agent" is one of the resulting exact tool names.
- Around line 693-701: The current implementation in requireAgentsAllowlist uses
a fragile string count check that assumes 2-space YAML indentation. While this
is acceptable for controlled embedded assets as noted in the comment, document
this known limitation by adding a code comment above the Count check explaining
why this specific format assumption is acceptable in this context and what would
need to change if YAML formatting requirements evolve in the future.
- Around line 548-586: The sdd-orchestrator.agent.md file is missing the
required agent tool declaration in its frontmatter. The test
TestVSCodeNativeAgentAssetsFrontmatter validates this requirement via the
requireInlineTool call for the coordinator agent, which checks for the presence
of the agent tool. Add the agent tool to the frontmatter of the orchestrator
agent asset file to match the requirement being validated by the test and ensure
consistency with the phase agents that do include this tool.

In `@internal/cli/sync_test.go`:
- Around line 2092-2119: The
TestRunSyncWithSelectionPropagatesVSCodeModelWarnings test lacks explanatory
comments about why it uses a fresh temp home directory and what behavior is
expected. Add a comment block immediately before the RunSyncWithSelection
function call that explains the test setup: the fresh temp home has no models
cache, which makes the VSCode model assignments unvalidated, and the sync should
emit a cache warning while omitting the model line from the generated agent
file.

In `@internal/cli/sync.go`:
- Around line 803-812: The function stateModelAssignmentsToModel is defined
identically in two separate files, creating unnecessary duplication. Remove the
duplicate definition from one of the locations and either move the function to a
shared package location (such as within the state or model package) or keep it
in one place and update the other location to import and use the centralized
version instead. This ensures a single source of truth and prevents
inconsistencies if the function logic needs to be updated in the future.

In `@internal/components/sdd/vscode_agent_visibility.go`:
- Around line 15-52: The function hideManagedClaudeInternalAgentsForVSCode
modifies user config files without creating backups, violating the backup
requirement for user config modifications. Before calling
filemerge.WriteFileAtomic for each file that gets modified by
hideClaudeAgentFromVSCodePicker, create a backup of the original file content.
Track the backups created (likely in the InjectionResult or a similar structure)
so their paths can be returned or used for restoration. Additionally, create a
companion restore function that reverts the modified files back to their
backed-up versions, similar to how injection functions typically have
corresponding undo/restore counterparts.

In `@internal/state/state.go`:
- Line 148: The mergeExplicitAgentInstallState function currently restores
several model assignment fields from newState when non-nil (ModelAssignments,
ClaudeModelAssignments, KiroModelAssignments, CodexModelAssignments,
CodexCarrilModelAssignments, CodexPhaseModelAssignments, and Persona), but it
fails to restore VSCodeModelAssignments. Add a check in the
mergeExplicitAgentInstallState function that restores VSCodeModelAssignments
from newState when it is non-nil, following the same pattern used for the other
model assignment fields being restored from newState.
🪄 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: e9115036-799f-4966-87be-bcc583bdd147

📥 Commits

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

⛔ 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 (51)
  • README.md
  • docs/agents.md
  • go.mod
  • 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/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/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/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/app/app.go Outdated
Comment thread internal/assets/assets_test.go
Comment thread internal/assets/assets_test.go Outdated
Comment thread internal/assets/assets_test.go
Comment thread internal/cli/sync_test.go
Comment thread internal/cli/sync.go Outdated
Comment thread internal/components/sdd/vscode_agent_visibility.go
Comment thread internal/state/state.go
@Snakeblack Snakeblack force-pushed the fix/vscode-copilot-agent-hardening branch from cbc7686 to b1ae7f1 Compare June 29, 2026 22:53

@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: 6

Caution

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

⚠️ Outside diff range comments (1)
internal/components/filemerge/writer.go (1)

96-105: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Add rollback/backup handling around the committed replace.

After Line 96 swaps the temp file into place, Lines 103-105 can still return an error. Callers then see a failed write even though the user config was already mutated and the previous contents are gone. internal/components/sdd/inject.go uses this helper for user-owned VS Code/Copilot config files, so the overwrite path still has no restore route if the final sync fails or the install aborts mid-flight.

As per path instructions, "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/filemerge/writer.go` around lines 96 - 105, The committed
replace in writer.go can still fail after replaceFileAtomic has already swapped
the temp file into place, leaving user-owned config mutated without any restore
path. Update the write flow around writeFileAtomic/replaceFileAtomic and the
final syncDirFn call to create and retain a backup of the original file before
the swap, then restore or roll back from that backup if sync or later steps
fail. Make sure the recovery path is wired through the helper used by
internal/components/sdd/inject.go so user config writes always have
backup/restore handling.

Source: Path instructions

🤖 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 from v0.38.0 to
v0.44.0 or newer to remove the vulnerable version. After bumping the module,
ensure any code that depends on golang.org/x/sys/windows, especially
internal/components/filemerge/replace_windows.go, still builds and resolves
against the updated dependency.

In `@internal/assets/assets_test.go`:
- Around line 635-637: The helper is using a prefix heuristic that can drift
from the actual runtime contract. Update isClaudeInternalAgentName to check
against the same fixed managed filename set used by the runtime logic in
vscode_agent_visibility.go, instead of matching all sdd-* / jd-* names. Reuse
the existing managed list/constant or shared helper so this test stays aligned
with the install/uninstall behavior.

In `@internal/cli/sync.go`:
- Around line 994-1000: Preserve the nil-vs-empty semantics for
VSCodeModelAssignments in sync selection handling: the current len(...) check in
the VSCode assignment merge logic treats nil and an explicit empty map the same,
which breaks the override contract described in selection.go. Update the sync
path around the persistedState.VSCodeModelAssignments merge so only a nil
selection map is backfilled from state, while a non-nil empty map remains an
intentional reset to defaults; keep the behavior localized to the selection
population logic that builds selection.VSCodeModelAssignments.

In `@internal/components/sdd/vscode_agent_visibility.go`:
- Around line 68-77: The restore/cleanup path in the backup handling block is
swallowing failures, so fix the logic around os.Stat, filemerge.WriteFileAtomic,
and os.Remove in vscode_agent_visibility.go to surface any restore or cleanup
errors instead of treating them as success. Only ignore the explicit not-exist
case for the backup check, return wrapped errors for unexpected stat/read/write
failures, and do not discard the os.Remove(backupPath) result so callers know
when the Claude agent restore or backup cleanup did not complete.

In `@internal/components/sdd/vscode_models.go`:
- Around line 143-167: The warnings emitted by loadVSCodeModelCatalog are still
unique per agentKey, so dedupWarnings cannot collapse shared cache failures.
Update loadVSCodeModelCatalog to return normalized cache-level warning strings
that omit agent-specific data while preserving the failure reason and cache
path, and keep the agentKey only at the call site if needed for context. Make
the same normalization in the other matching catalog-load path mentioned in the
diff so identical model-cache failures dedupe correctly across phases.

In `@internal/components/uninstall/service.go`:
- Around line 539-541: The uninstall flow in
restoreClaudeVisibilityBackupOperation appends a write that restores Claude
agent files and deletes .backup files, but those paths are not being captured in
targets for rollback. Update the scheduling logic around adapter.Agent() ==
model.AgentVSCodeCopilot to snapshot the affected ~/.claude/agents/* files and
the corresponding backup files before the write is added, so any later failure
can restore them cleanly.

---

Outside diff comments:
In `@internal/components/filemerge/writer.go`:
- Around line 96-105: The committed replace in writer.go can still fail after
replaceFileAtomic has already swapped the temp file into place, leaving
user-owned config mutated without any restore path. Update the write flow around
writeFileAtomic/replaceFileAtomic and the final syncDirFn call to create and
retain a backup of the original file before the swap, then restore or roll back
from that backup if sync or later steps fail. Make sure the recovery path is
wired through the helper used by internal/components/sdd/inject.go so user
config writes always have backup/restore handling.
🪄 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: 27140f52-4ac2-4027-b1ea-e990a3e591fb

📥 Commits

Reviewing files that changed from the base of the PR and between cbc7686 and b1ae7f1.

⛔ 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/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_agent_visibility_test.go
  • internal/components/sdd/vscode_models.go
  • internal/components/sdd/vscode_models_test.go
  • internal/components/uninstall/service.go
  • internal/model/selection.go
  • internal/model/selection_test.go
  • internal/state/state.go
  • internal/state/state_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

🌐 Web query:

What is the first patched version for GO-2026-5024 in golang.org/x/sys, and is v0.38.0 affected?

💡 Result:

The first patched version for GO-2026-5024 (also tracked as CVE-2026-39824) in the golang.org/x/sys module is v0.44.0 [1][2][3]. Yes, v0.38.0 is affected by this vulnerability because it is below the patched version of v0.44.0 [1][4][5]. The vulnerability involves an integer overflow in the NewNTUnicodeString function within the golang.org/x/sys/windows package [1][2][4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

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

printf '\n== replace_windows.go ==\n'
cat -n internal/components/filemerge/replace_windows.go | sed -n '1,120p'

Repository: Gentleman-Programming/gentle-ai

Length of output: 2390


Bump golang.org/x/sys to v0.44.0 or newer. v0.38.0 is affected by GO-2026-5024/CVE-2026-39824, and internal/components/filemerge/replace_windows.go imports golang.org/x/sys/windows directly.

🧰 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 from
v0.38.0 to v0.44.0 or newer to remove the vulnerable version. After bumping the
module, ensure any code that depends on golang.org/x/sys/windows, especially
internal/components/filemerge/replace_windows.go, still builds and resolves
against the updated dependency.

Source: Linters/SAST tools

Comment thread internal/assets/assets_test.go
Comment thread internal/cli/sync.go
Comment on lines +994 to +1000
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

Preserve the nil-vs-empty override contract for VS Code assignments.

internal/model/selection.go documents VSCodeModelAssignments as nil = no override and non-nil empty map = reset to defaults. This len(...) == 0 check collapses both cases, so an explicit clear still reloads assignments from state.json and sync never returns the generated .agent.md files to default-model behavior.

Suggested fix
-	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 {
+		selection.VSCodeModelAssignments = StateModelAssignmentsToModel(persistedState.VSCodeModelAssignments)
+	}
📝 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 {
selection.VSCodeModelAssignments = StateModelAssignmentsToModel(persistedState.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/sync.go` around lines 994 - 1000, Preserve the nil-vs-empty
semantics for VSCodeModelAssignments in sync selection handling: the current
len(...) check in the VSCode assignment merge logic treats nil and an explicit
empty map the same, which breaks the override contract described in
selection.go. Update the sync path around the
persistedState.VSCodeModelAssignments merge so only a nil selection map is
backfilled from state, while a non-nil empty map remains an intentional reset to
defaults; keep the behavior localized to the selection population logic that
builds selection.VSCodeModelAssignments.

Comment thread internal/components/sdd/vscode_agent_visibility.go Outdated
Comment thread internal/components/sdd/vscode_models.go Outdated
Comment thread internal/components/uninstall/service.go
@Snakeblack Snakeblack force-pushed the fix/vscode-copilot-agent-hardening branch from b1ae7f1 to 1fedc6e Compare June 30, 2026 06:33

@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: 6

🤖 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 `@docs/agents.md`:
- Around line 134-135: Clarify the `Code/User/` paths in this section so
cross-platform readers know where to find the files. Update the references for
the global instructions and MCP config to include a platform-specific base
directory or a platform-agnostic note, and keep the symbols
`Code/User/prompts/gentle-ai.instructions.md` and `Code/User/mcp.json` easy to
recognize. Use the same path style consistently across the docs, matching the
surrounding guidance in `docs/agents.md`.

In `@internal/assets/vscode/agents/sdd-propose.agent.md`:
- Around line 7-9: The agent allowlist for sdd-propose.agent.md is missing the
memory MCP operations used in this phase. Update the agent’s tools configuration
to include mem_search, mem_get_observation, and mem_save explicitly, or switch
to allowing the full MCP server if that is the intended access pattern. Make the
change in the agent metadata where the tools list is defined so the phase can
invoke the memory tools successfully.

In `@internal/cli/run.go`:
- Around line 718-723: `shouldRefreshWindowsEngram()` is treating duplicate PATH
segments as separate binary hits because `engramBinaryDirsOnPath()` counts
repeated directories, so update the Windows shadow-check to deduplicate PATH
entries before deciding Engram is shadowed. Adjust the logic in
`shouldRefreshWindowsEngram()` and any related helper around
`engramBinaryDirsOnPath()` so only unique binary directories are counted,
preserving the existing OS/package-manager guards and preventing false
shadow-repair paths.

In `@internal/components/sdd/inject.go`:
- Around line 1734-1738: The early json.Unmarshal fallback in mergeJSONFile is
skipping the migration for JSONC inputs, so deprecated command.*.prompt fields
can survive unchanged. Update this path to use the same JSONC-tolerant parse
flow as the rest of mergeJSONFile, then run the rename/drop migration on the
parsed object before re-encoding so stale keys are removed consistently.

In `@internal/components/sdd/vscode_agent_visibility.go`:
- Around line 43-51: The hide flow in the managed Claude agent write path leaves
a stale backup behind when filemerge.WriteFileAtomic fails, so remove the backup
file before returning the error. Update the write logic in
vscode_agent_visibility.go around the backupPath/os.WriteFile and
filemerge.WriteFileAtomic steps so a failed hide write cleans up the newly
created .backup, and keep the restore behavior in
RestoreManagedClaudeInternalAgentsForVSCode aligned with this backup lifecycle.

In `@README.md`:
- Line 195: The README path reference is ambiguous because
`Code/User/prompts/gentle-ai.instructions.md` lacks a base directory. Update the
wording in the installation/docs text that mentions Gentle-AI’s VS Code layers
so the `Code/User/` location is prefixed with a clear platform-specific or
platform-agnostic base path, matching the style used for `~/.copilot/agents` and
`~/.copilot/skills`. Use the existing documentation sentence as the anchor and
revise the path notation so readers on macOS, Linux, and Windows can resolve it
unambiguously.
🪄 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: 4e6defea-42d8-4a47-86bc-cf78902bbd86

📥 Commits

Reviewing files that changed from the base of the PR and between b1ae7f1 and 1fedc6e.

⛔ 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 (55)
  • README.md
  • docs/agents.md
  • go.mod
  • 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/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/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_engram_download_test.go
  • internal/cli/run_state_test.go
  • internal/cli/sync.go
  • internal/cli/sync_test.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_agent_visibility_test.go
  • internal/components/sdd/vscode_models.go
  • internal/components/sdd/vscode_models_test.go
  • internal/components/uninstall/service.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: 6

🤖 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 `@docs/agents.md`:
- Around line 134-135: Clarify the `Code/User/` paths in this section so
cross-platform readers know where to find the files. Update the references for
the global instructions and MCP config to include a platform-specific base
directory or a platform-agnostic note, and keep the symbols
`Code/User/prompts/gentle-ai.instructions.md` and `Code/User/mcp.json` easy to
recognize. Use the same path style consistently across the docs, matching the
surrounding guidance in `docs/agents.md`.

In `@internal/assets/vscode/agents/sdd-propose.agent.md`:
- Around line 7-9: The agent allowlist for sdd-propose.agent.md is missing the
memory MCP operations used in this phase. Update the agent’s tools configuration
to include mem_search, mem_get_observation, and mem_save explicitly, or switch
to allowing the full MCP server if that is the intended access pattern. Make the
change in the agent metadata where the tools list is defined so the phase can
invoke the memory tools successfully.

In `@internal/cli/run.go`:
- Around line 718-723: `shouldRefreshWindowsEngram()` is treating duplicate PATH
segments as separate binary hits because `engramBinaryDirsOnPath()` counts
repeated directories, so update the Windows shadow-check to deduplicate PATH
entries before deciding Engram is shadowed. Adjust the logic in
`shouldRefreshWindowsEngram()` and any related helper around
`engramBinaryDirsOnPath()` so only unique binary directories are counted,
preserving the existing OS/package-manager guards and preventing false
shadow-repair paths.

In `@internal/components/sdd/inject.go`:
- Around line 1734-1738: The early json.Unmarshal fallback in mergeJSONFile is
skipping the migration for JSONC inputs, so deprecated command.*.prompt fields
can survive unchanged. Update this path to use the same JSONC-tolerant parse
flow as the rest of mergeJSONFile, then run the rename/drop migration on the
parsed object before re-encoding so stale keys are removed consistently.

In `@internal/components/sdd/vscode_agent_visibility.go`:
- Around line 43-51: The hide flow in the managed Claude agent write path leaves
a stale backup behind when filemerge.WriteFileAtomic fails, so remove the backup
file before returning the error. Update the write logic in
vscode_agent_visibility.go around the backupPath/os.WriteFile and
filemerge.WriteFileAtomic steps so a failed hide write cleans up the newly
created .backup, and keep the restore behavior in
RestoreManagedClaudeInternalAgentsForVSCode aligned with this backup lifecycle.

In `@README.md`:
- Line 195: The README path reference is ambiguous because
`Code/User/prompts/gentle-ai.instructions.md` lacks a base directory. Update the
wording in the installation/docs text that mentions Gentle-AI’s VS Code layers
so the `Code/User/` location is prefixed with a clear platform-specific or
platform-agnostic base path, matching the style used for `~/.copilot/agents` and
`~/.copilot/skills`. Use the existing documentation sentence as the anchor and
revise the path notation so readers on macOS, Linux, and Windows can resolve it
unambiguously.
🪄 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: 4e6defea-42d8-4a47-86bc-cf78902bbd86

📥 Commits

Reviewing files that changed from the base of the PR and between b1ae7f1 and 1fedc6e.

⛔ 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 (55)
  • README.md
  • docs/agents.md
  • go.mod
  • 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/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/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_engram_download_test.go
  • internal/cli/run_state_test.go
  • internal/cli/sync.go
  • internal/cli/sync_test.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_agent_visibility_test.go
  • internal/components/sdd/vscode_models.go
  • internal/components/sdd/vscode_models_test.go
  • internal/components/uninstall/service.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 (6)
docs/agents.md (1)

134-135: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Clarify the Code/User/ path notation for cross-platform readers.

Same issue as README.md: Code/User/prompts/gentle-ai.instructions.md and Code/User/mcp.json lack a base directory prefix. Consider using ~/.vscode/Code/User/... (Linux), ~/Library/Application Support/Code/User/... (macOS), or %APPDATA%/Code/User/... (Windows), or add a platform-agnostic note.

🤖 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 `@docs/agents.md` around lines 134 - 135, Clarify the `Code/User/` paths in
this section so cross-platform readers know where to find the files. Update the
references for the global instructions and MCP config to include a
platform-specific base directory or a platform-agnostic note, and keep the
symbols `Code/User/prompts/gentle-ai.instructions.md` and `Code/User/mcp.json`
easy to recognize. Use the same path style consistently across the docs,
matching the surrounding guidance in `docs/agents.md`.
internal/assets/vscode/agents/sdd-propose.agent.md (1)

7-9: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

For GitHub Copilot custom .agent.mdfiles in VS Code, does thetools:frontmatter need to explicitly list MCP tools such asmem_search, mem_get_observation, and mem_save, or are MCP tools available implicitly?

💡 Result:

For GitHub Copilot custom.agent.md files in VS Code, MCP tools are available implicitly; you do not need to explicitly list them in the tools: frontmatter unless you wish to restrict access or specify only a subset of available tools [1][2][3][4]. By default, if the tools: property is omitted from the frontmatter of your.agent.md file, the custom agent is granted access to all available tools, which includes all MCP server tools that have been properly configured and enabled in your environment (e.g., repository or global settings) [1][2][3][4]. If you prefer to control which tools an agent can use, you can use the tools: property to manage access: - To allow only specific MCP tools, you can list them individually (e.g., tools: ["server_name/tool_name"]) [1][4]. - To allow all tools from a specific MCP server, you can use the wildcard syntax (e.g., tools: ["server_name/*"]) [1][4]. - If you wish to disable access to all tools, you can set tools: []. The agent will autonomously determine when to use the enabled tools based on your prompt and context [5]. Always ensure that any MCP servers you rely on are correctly configured in your project or global settings, as the tools: frontmatter serves primarily as a filter for what is already available to the agent [1][4].

Citations:


🌐 Web query:

GitHub Copilot custom agent .agent.mdfrontmattertools:MCP server wildcardserver_name/* custom tools allowlist behavior

💡 Result:

In GitHub Copilot custom agents, the tools property in the .agent.md YAML frontmatter controls which tools are available to an agent [1]. When configuring tools from Model Context Protocol (MCP) servers, you can use a server-specific wildcard syntax to manage access [1][2]. Wildcard and Filtering Behavior: - Enable all available tools: You can omit the tools property entirely or explicitly use tools: ["*"] [1]. This enables all tools from all configured MCP servers and built-in sources [1][2]. - Enable all tools from a specific server: You can use the syntax server_name/* to allowlist every tool provided by a specific MCP server [1][2]. - Enable specific tools: To limit access, you can list individual tools using the server_name/tool_name format [1][2]. Important Implementation Notes: - Resolution Priority: When tools is unset, it defaults to enabling all tools [1][3]. - Tool Naming: GitHub Copilot typically expects MCP tools to be referenced by their server name followed by a slash (e.g., server_name/tool_name) [1][4]. - Known Issues: There have been reports of inconsistencies where wildcard configurations like server_name/* or ["*"] may not consistently expose tools to the model or may be misinterpreted by the underlying MCP gateway, depending on the specific version of the Copilot CLI or the SDK in use [5][6][7]. Users have also noted that if tools: ["*"] is incorrectly processed as a literal string rather than a glob by a backend component, it can result in all tools being filtered out rather than allowed [5][6]. Developers seeking to use advanced pattern-based filtering (e.g., prefix globbing such as server_name/pattern_*) have requested this as a feature, as the current implementation generally requires either specific tool names or the * wildcard for an entire server [8].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file='internal/assets/vscode/agents/sdd-propose.agent.md'
nl -ba "$file" | sed -n '1,60p'

printf '\n--- sibling agent references ---\n'
rg -n "mem_search|mem_get_observation|mem_save|tools:" internal/assets/vscode/agents -n

Repository: Gentleman-Programming/gentle-ai

Length of output: 209


Add the memory MCP tools to this agent’s allowlist
tools: [read, search, edit] excludes the mem_search, mem_get_observation, and mem_save operations this phase invokes. Add those MCP tools explicitly, or allow the whole MCP server if that’s intended.

🤖 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/vscode/agents/sdd-propose.agent.md` around lines 7 - 9, The
agent allowlist for sdd-propose.agent.md is missing the memory MCP operations
used in this phase. Update the agent’s tools configuration to include
mem_search, mem_get_observation, and mem_save explicitly, or switch to allowing
the full MCP server if that is the intended access pattern. Make the change in
the agent metadata where the tools list is defined so the phase can invoke the
memory tools successfully.
internal/cli/run.go (1)

718-723: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Deduplicate identical PATH entries before treating Engram as shadowed.

shouldRefreshWindowsEngram() currently fires on any second engram.exe hit, but engramBinaryDirsOnPath() counts duplicate PATH segments separately. A single directory repeated twice can therefore send Windows installs down the shadow-repair branch and fail with the "cannot safely repair PATH order" error even though there is only one real binary location.

Suggested fix
 func engramBinaryDirsOnPath(pathEntries []string, goos string) []string {
 	var dirs []string
+	seen := make(map[string]struct{}, len(pathEntries))
 	for _, entry := range pathEntries {
 		entry = strings.Trim(strings.TrimSpace(entry), `"`)
 		if entry == "" {
 			continue
 		}
+		cleaned := filepath.Clean(entry)
+		key := cleaned
+		if goos == "windows" {
+			key = strings.ToLower(cleaned)
+		}
+		if _, ok := seen[key]; ok {
+			continue
+		}
 		binaryName := "engram"
 		if goos == "windows" {
 			binaryName = "engram.exe"
 		}
-		candidate := filepath.Join(entry, binaryName)
+		candidate := filepath.Join(cleaned, binaryName)
 		if _, err := os.Stat(candidate); err == nil {
-			dirs = append(dirs, entry)
+			seen[key] = struct{}{}
+			dirs = append(dirs, cleaned)
 		}
 	}
 	return dirs
 }

Also applies to: 756-772

🤖 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 718 - 723, `shouldRefreshWindowsEngram()`
is treating duplicate PATH segments as separate binary hits because
`engramBinaryDirsOnPath()` counts repeated directories, so update the Windows
shadow-check to deduplicate PATH entries before deciding Engram is shadowed.
Adjust the logic in `shouldRefreshWindowsEngram()` and any related helper around
`engramBinaryDirsOnPath()` so only unique binary directories are counted,
preserving the existing OS/package-manager guards and preventing false
shadow-repair paths.
internal/components/sdd/inject.go (1)

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

Don't skip the migration on JSONC inputs.

mergeJSONFile already accepts commented opencode.json files, but this early json.Unmarshal fallback returns the original bytes unchanged. If a user has JSONC plus stale command.*.prompt fields, those deprecated keys survive sync and OpenCode will still reject the config. Run this rename/drop step through the same JSONC-tolerant parse path as the rest of the merge flow.

🤖 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 1734 - 1738, The early
json.Unmarshal fallback in mergeJSONFile is skipping the migration for JSONC
inputs, so deprecated command.*.prompt fields can survive unchanged. Update this
path to use the same JSONC-tolerant parse flow as the rest of mergeJSONFile,
then run the rename/drop migration on the parsed object before re-encoding so
stale keys are removed consistently.
internal/components/sdd/vscode_agent_visibility.go (1)

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

Remove the backup if the hide write fails.

WriteFileAtomic is atomic here, so a failure leaves the original agent file untouched. Keeping the freshly written .backup makes RestoreManagedClaudeInternalAgentsForVSCode think the file was hidden and can later roll back to stale content, overwriting newer user edits during uninstall.

Proposed fix
 		backupPath := path + ".backup"
 		if err := os.WriteFile(backupPath, content, 0o644); err != nil {
 			return InjectionResult{}, fmt.Errorf("backup managed Claude agent %s: %w", fileName, err)
 		}
 
 		writeResult, err := filemerge.WriteFileAtomic(path, []byte(updated), 0o644)
 		if err != nil {
+			if removeErr := os.Remove(backupPath); removeErr != nil && !errors.Is(removeErr, os.ErrNotExist) {
+				return InjectionResult{}, fmt.Errorf("write managed Claude agent %s: %w (cleanup backup %s: %v)", fileName, err, backupPath, removeErr)
+			}
 			return InjectionResult{}, fmt.Errorf("write managed Claude agent %s: %w", fileName, err)
 		}

As per path instructions, internal/components/**/*.go: Any write to user config must have a backup/restore path.

📝 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.

		// Back up original content before write
		backupPath := path + ".backup"
		if err := os.WriteFile(backupPath, content, 0o644); err != nil {
			return InjectionResult{}, fmt.Errorf("backup managed Claude agent %s: %w", fileName, err)
		}

		writeResult, err := filemerge.WriteFileAtomic(path, []byte(updated), 0o644)
		if err != nil {
			if removeErr := os.Remove(backupPath); removeErr != nil && !errors.Is(removeErr, os.ErrNotExist) {
				return InjectionResult{}, fmt.Errorf("write managed Claude agent %s: %w (cleanup backup %s: %v)", fileName, err, backupPath, removeErr)
			}
			return InjectionResult{}, fmt.Errorf("write managed Claude agent %s: %w", fileName, 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_agent_visibility.go` around lines 43 - 51, The
hide flow in the managed Claude agent write path leaves a stale backup behind
when filemerge.WriteFileAtomic fails, so remove the backup file before returning
the error. Update the write logic in vscode_agent_visibility.go around the
backupPath/os.WriteFile and filemerge.WriteFileAtomic steps so a failed hide
write cleans up the newly created .backup, and keep the restore behavior in
RestoreManagedClaudeInternalAgentsForVSCode aligned with this backup lifecycle.

Source: Path instructions

README.md (1)

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

Clarify the Code/User/ path notation for cross-platform readers.

The path Code/User/prompts/gentle-ai.instructions.md is ambiguous without a prefix. On macOS/Linux this is typically ~/.vscode/Code/User/... (actually ~/Library/Application Support/Code/User/ on macOS, ~/.config/Code/User/ on Linux), and on Windows it's %APPDATA%/Code/User/.... Readers may not know which base directory to resolve against. Consider prefixing with ~/.vscode/ or %APPDATA%/ (or using a platform-agnostic note) to match how ~/.copilot/agents/ is documented on the same line.

🤖 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 `@README.md` at line 195, The README path reference is ambiguous because
`Code/User/prompts/gentle-ai.instructions.md` lacks a base directory. Update the
wording in the installation/docs text that mentions Gentle-AI’s VS Code layers
so the `Code/User/` location is prefixed with a clear platform-specific or
platform-agnostic base path, matching the style used for `~/.copilot/agents` and
`~/.copilot/skills`. Use the existing documentation sentence as the anchor and
revise the path notation so readers on macOS, Linux, and Windows can resolve it
unambiguously.

@Snakeblack Snakeblack force-pushed the fix/vscode-copilot-agent-hardening branch from 1fedc6e to d36fea5 Compare July 1, 2026 06:38

@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: 8

🤖 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/components/communitytool/codegraph_guidance.go`:
- Around line 217-225: The file tracking in the guidance sync flow is counting
unchanged prompt files as modified because `injectCodeGraphGuidanceForAgent`
appends `file` to `result.Files` even when `changed` is false. Update the logic
in `codeGraphGuidanceForAgent` so `result.Files` only includes entries when
`changed` is true, while still preserving `result.Changed` behavior and the
existing error handling.
- Around line 145-156: `HasConfiguredCodeGraphForAgents` is using an unscoped
fallback that can return true for agents outside the selected `agentIDs`. Update
the fallback in this function so it only succeeds when the detected CodeGraph
wiring matches one of the requested agents, using the existing
`status.Agents`/`agentIDs` filtering logic rather than calling
`hasDetectedCodeGraphToolWiring(homeDir)` globally. Keep the agent scope
consistent with `InjectCodeGraphGuidanceForAgents` and the rest of the
agent-scoped sync path.

In `@internal/components/sdd/inject_test.go`:
- Around line 1567-1582: The shared SDD file assertion list in inject_test.go is
missing sdd-status-contract.md, so the VS Code install test no longer validates
that Inject() writes it. Re-add sdd-status-contract.md to the sharedFiles slice
in the test that checks shared convention files, keeping the os.Stat and
non-empty assertions unchanged so the regression is covered again.

In `@internal/components/sdd/inject.go`:
- Around line 335-341: The user-config write paths in inject.go still bypass the
backup/restore flow for non-VS Code adapters by calling
filemerge.WriteFileAtomic in the prompt and sub-agent write branches. Update the
touched write sites in the relevant helper paths (for example the
prompt/sub-agent write logic around the writeResult/writeErr handling) so every
user-config write goes through the backup-enabled path, not just
adapter.Agent()==model.AgentVSCodeCopilot. Ensure the same restore-capable
behavior is used consistently across all referenced write locations.
- Around line 2446-2465: The backup logic in writeFileWithBackup is taking a
fresh snapshot from disk on each write, which can preserve intermediate managed
content instead of the user’s original state. Update the inject flow to capture
the pre-sync bytes once per target path and reuse that snapshot across the whole
run, including the VS Code instructions path handled by injectFileAppend and the
later trigger-rules/strict-tdd-mode rewrites. Also ensure backups are only
created when the write actually changes content, not for no-op writes that
return Changed=false.

In `@internal/components/uninstall/service.go`:
- Around line 547-552: The uninstall flow is scheduling restore operations for
user config without backing up the .backup companions first, so a later failure
can’t roll back the consumed backup files. Update the logic around
adapter.SystemPromptFile, targets, and restoreUserFileBackupOperation so both VS
Code branches also add the corresponding path + ".backup" files to the
rollback/snapshot set before appending restore ops. Apply the same fix in the
other restore block referenced by the comment so every user config write has a
backup/restore path.
- Around line 1258-1296: The restore/delete flow in the uninstall service should
stop on unexpected backup or read errors instead of falling through to deletion.
In the backup handling block, treat any os.Stat(backupPath) error other than
not-exist as fatal, and in the managed-content check, if os.ReadFile(path)
fails, return that error rather than continuing to remove the file. Update the
logic in the restore helper that contains backupPath, isManaged, and
removeFileIfExists so only verified cases proceed to deletion.

In `@internal/tui/model_test.go`:
- Line 6171: Remove the unresolved merge-conflict marker left in the test file,
which is making the Go source invalid. Clean up the conflicting content in the
affected test section of model_test.go by resolving the merge around the test
block and ensuring no conflict markers remain in the final version.
🪄 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: 99db4bad-1ce6-46c6-bc09-7b83e70e5a14

📥 Commits

Reviewing files that changed from the base of the PR and between 1fedc6e and d36fea5.

⛔ 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 (44)
  • 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_engram_download_test.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_agent_visibility_test.go
  • internal/components/sdd/vscode_models.go
  • internal/components/sdd/vscode_models_test.go
  • internal/components/uninstall/service.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
  • internal/tui/model.go
  • internal/tui/model_test.go

Comment on lines +145 to +156
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.

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

Make the fallback agent-scoped.

HasConfiguredCodeGraphForAgents filters status.Agents by agentIDs, but the fallback hasDetectedCodeGraphToolWiring(homeDir) ignores that scope. If CodeGraph is wired only for one agent, syncing a different selected agent can still return true here and drive InjectCodeGraphGuidanceForAgents to rewrite the wrong prompt files. That breaks the new agent-scoped sync 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/components/communitytool/codegraph_guidance.go` around lines 145 -
156, `HasConfiguredCodeGraphForAgents` is using an unscoped fallback that can
return true for agents outside the selected `agentIDs`. Update the fallback in
this function so it only succeeds when the detected CodeGraph wiring matches one
of the requested agents, using the existing `status.Agents`/`agentIDs` filtering
logic rather than calling `hasDetectedCodeGraphToolWiring(homeDir)` globally.
Keep the agent scope consistent with `InjectCodeGraphGuidanceForAgents` and the
rest of the agent-scoped sync path.

Comment on lines +217 to +225
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)

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 | 🟡 Minor | ⚡ Quick win

Only report files that actually changed.

result.Files appends every non-empty prompt path, even when changed is false. Downstream, codeGraphGuidanceSyncStep.Run appends res.Files whenever any agent changed, so a multi-agent sync can overcount FilesChanged and list unchanged prompt files as modified. Only add file when changed is true.

🤖 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 -
225, The file tracking in the guidance sync flow is counting unchanged prompt
files as modified because `injectCodeGraphGuidanceForAgent` appends `file` to
`result.Files` even when `changed` is false. Update the logic in
`codeGraphGuidanceForAgent` so `result.Files` only includes entries when
`changed` is true, while still preserving `result.Changed` behavior and the
existing error handling.

Comment on lines +1567 to +1582
sharedFiles := []string{
"persistence-contract.md",
"engram-convention.md",
"openspec-convention.md",
"sdd-phase-common.md",
"skill-resolver.md",
}
for _, fileName := range sharedFiles {
sharedPath := filepath.Join(home, ".copilot", "skills", "_shared", fileName)
info, err := os.Stat(sharedPath)
if err != nil {
t.Fatalf("expected shared SDD convention file %q: %v", sharedPath, err)
}
if info.Size() == 0 {
t.Fatalf("expected shared SDD convention file %q to be non-empty", sharedPath)
}

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

Re-add sdd-status-contract.md to this assertion list.

Inject() still writes skills/_shared/sdd-status-contract.md, but this narrowed list no longer checks it, so that regression now slips past the VS Code install test.

🤖 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_test.go` around lines 1567 - 1582, The shared
SDD file assertion list in inject_test.go is missing sdd-status-contract.md, so
the VS Code install test no longer validates that Inject() writes it. Re-add
sdd-status-contract.md to the sharedFiles slice in the test that checks shared
convention files, keeping the os.Stat and non-empty assertions unchanged so the
regression is covered again.

Comment on lines +335 to +341
var writeResult filemerge.WriteResult
var writeErr error
if adapter.Agent() == model.AgentVSCodeCopilot {
writeResult, writeErr = writeFileWithBackup(promptPath, []byte(updated), 0o644)
} else {
writeResult, writeErr = filemerge.WriteFileAtomic(promptPath, []byte(updated), 0o644)
}

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

Apply the backup path to all touched user-config writes, not just VS Code.

These branches still send non-VS Code prompt and sub-agent writes through filemerge.WriteFileAtomic, so the new restore contract only holds for one adapter family. As per path instructions, install/sync code under internal/components/**/*.go must give every user-config write a backup/restore path.

Also applies to: 383-389, 717-730, 1877-1882

🤖 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 335 - 341, The user-config
write paths in inject.go still bypass the backup/restore flow for non-VS Code
adapters by calling filemerge.WriteFileAtomic in the prompt and sub-agent write
branches. Update the touched write sites in the relevant helper paths (for
example the prompt/sub-agent write logic around the writeResult/writeErr
handling) so every user-config write goes through the backup-enabled path, not
just adapter.Agent()==model.AgentVSCodeCopilot. Ensure the same restore-capable
behavior is used consistently across all referenced write locations.

Source: Path instructions

Comment thread internal/components/sdd/inject.go
Comment thread internal/components/uninstall/service.go
Comment thread internal/components/uninstall/service.go
}
}

<<<<<<< HEAD

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 | 🔴 Critical | ⚡ Quick win

Remove the unresolved merge-conflict marker.

Line 6171 contains <<<<<<< HEAD, which makes this test file invalid Go and will break go test/CI until the conflict is resolved.

🤖 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/tui/model_test.go` at line 6171, Remove the unresolved
merge-conflict marker left in the test file, which is making the Go source
invalid. Clean up the conflicting content in the affected test section of
model_test.go by resolving the merge around the test block and ensuring no
conflict markers remain in the final version.

@Snakeblack Snakeblack force-pushed the fix/vscode-copilot-agent-hardening branch from d36fea5 to 3df9409 Compare July 1, 2026 08:14
@Snakeblack Snakeblack force-pushed the fix/vscode-copilot-agent-hardening branch from 3df9409 to 76ce695 Compare July 1, 2026 08:28

@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

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 (1)

657-666: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider reusing cli.StateModelAssignmentsToModel for the OpenCode ModelAssignments block too.

VSCodeModelAssignments now converts via the shared cli.StateModelAssignmentsToModel helper, while the adjacent ModelAssignments block (lines 657-663) still hand-rolls the identical ProviderID/ModelID/Effort conversion. Consolidating onto the shared helper would remove the duplication.

🤖 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 ProviderID/ModelID/Effort mapping that
VSCodeModelAssignments already uses through cli.StateModelAssignmentsToModel.
Update the OpenCode ModelAssignments block in the selection-building logic to
reuse cli.StateModelAssignmentsToModel as well, and keep the existing fallback
condition that only fills it when selection.ModelAssignments is empty and
s.ModelAssignments is present.
internal/tui/model_test.go (1)

6939-7011: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Fix the unresolved merge marker and missing closing brace in internal/tui/model_test.go internal/tui/model_test.go:6171,7009-7011

  • <<<<<<< HEAD is still present at line 6171.
  • TestStrictTDDForward also needs one more closing } before func TestCodexAndVSCodeCopilotPresetFlowTransitionsToVSCodePickerAfterCodex.
🤖 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/tui/model_test.go` around lines 6939 - 7011, Resolve the merge
artifact in internal/tui/model_test.go by removing the leftover conflict marker
in the test file and restoring the missing closing brace after
TestStrictTDDForward. Make sure the TestStrictTDDForward function is properly
closed before
TestCodexAndVSCodeCopilotPresetFlowTransitionsToVSCodePickerAfterCodex, and
verify the surrounding test block compiles cleanly.
♻️ Duplicate comments (3)
internal/tui/model_test.go (1)

6171-6171: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Unresolved merge-conflict marker still present.

Line 6171 still contains a literal <<<<<<< HEAD marker, which makes this file invalid Go and breaks go test/CI. This was flagged in a prior review pass but the current code shows it unresolved.

🤖 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/tui/model_test.go` at line 6171, Remove the unresolved
merge-conflict marker from model_test and make sure no literal conflict markers
remain in the file. Clean up the affected test section around the lingering HEAD
marker so the Go source is valid again and the test file can compile under go
test/CI.
internal/assets/assets_test.go (1)

622-646: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

isClaudeInternalAgentName still uses the prefix heuristic instead of the runtime-managed filename set.

This still matches every sdd-*/jd-* asset by prefix, so the test would silently pass a future non-managed public Claude agent that happens to share the prefix, decoupling it from the actual fixed list patched/restored in internal/components/sdd/vscode_agent_visibility.go.

🤖 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 622 - 646, The test helper is
still using a prefix-based check for Claude internal agent names, which can
drift from the actual managed list. Update
TestClaudeInternalAgentAssetsAreHiddenFromVSCodePicker and
isClaudeInternalAgentName to use the same runtime-managed filename set used by
internal/components/sdd/vscode_agent_visibility.go, so the test verifies only
the exact assets patched/restored there instead of matching all sdd-* and jd-*
files by prefix.
internal/cli/sync.go (1)

1043-1049: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Nil-vs-empty override contract for VSCodeModelAssignments still broken.

len(selection.VSCodeModelAssignments) == 0 treats an explicit empty map (intentional reset to defaults) the same as nil (no override), so a user clearing VS Code model assignments still gets persisted assignments reloaded from state.json — the reset never sticks. run.go's mergeExplicitAgentInstallState (lines 239-241, same PR) already uses the correct != nil check for the equivalent state.InstallState field, confirming this is the intended pattern here too.

🐛 Proposed fix
-	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 {
+		selection.VSCodeModelAssignments = StateModelAssignmentsToModel(persistedState.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/sync.go` around lines 1043 - 1049, The `VSCodeModelAssignments`
merge in `sync.go` is still using a length check, which incorrectly treats an
explicit empty map as “unset” and reloads persisted assignments. Update the
`selection.VSCodeModelAssignments` fallback logic to distinguish nil from empty
by checking against nil, matching the `mergeExplicitAgentInstallState` pattern
in `run.go`, so an intentional reset to defaults is preserved.
🤖 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/components/uninstall/service_test.go`:
- Around line 799-829: The restore/uninstall test currently verifies only a
single application of each operation, so it does not lock in idempotency for
restoreUserFileBackupOperation. Update the test around the op.apply loop in
service_test.go to apply the same agentPath and promptPath operations a second
time after the backup has been consumed, and assert the files still contain
originalContent and originalPromptContent and that the backup paths remain
absent. Use the existing op.apply, agentPath, promptPath, backupPath, and
promptBackupPath symbols to keep the check anchored to the restore behavior.

In `@internal/components/uninstall/service.go`:
- Around line 1254-1304: The restoreUserFileBackupOperation apply flow can
delete a file it just restored on a later uninstall pass because it falls back
to the managed-content deletion path after removing path+".backup". Update
restoreUserFileBackupOperation so a file restored from backup is never
reconsidered for deletion on subsequent runs, either by tracking that the backup
existed/was restored and short-circuiting the no-backup branch, or by tightening
the managed-marker check to only explicit installer sentinels instead of broad
substrings like "managed" and "gentle-ai".

---

Outside diff comments:
In `@internal/app/app.go`:
- Around line 657-666: The ModelAssignments conversion in app.go is duplicating
the same ProviderID/ModelID/Effort mapping that VSCodeModelAssignments already
uses through cli.StateModelAssignmentsToModel. Update the OpenCode
ModelAssignments block in the selection-building logic to reuse
cli.StateModelAssignmentsToModel as well, and keep the existing fallback
condition that only fills it when selection.ModelAssignments is empty and
s.ModelAssignments is present.

In `@internal/tui/model_test.go`:
- Around line 6939-7011: Resolve the merge artifact in
internal/tui/model_test.go by removing the leftover conflict marker in the test
file and restoring the missing closing brace after TestStrictTDDForward. Make
sure the TestStrictTDDForward function is properly closed before
TestCodexAndVSCodeCopilotPresetFlowTransitionsToVSCodePickerAfterCodex, and
verify the surrounding test block compiles cleanly.

---

Duplicate comments:
In `@internal/assets/assets_test.go`:
- Around line 622-646: The test helper is still using a prefix-based check for
Claude internal agent names, which can drift from the actual managed list.
Update TestClaudeInternalAgentAssetsAreHiddenFromVSCodePicker and
isClaudeInternalAgentName to use the same runtime-managed filename set used by
internal/components/sdd/vscode_agent_visibility.go, so the test verifies only
the exact assets patched/restored there instead of matching all sdd-* and jd-*
files by prefix.

In `@internal/cli/sync.go`:
- Around line 1043-1049: The `VSCodeModelAssignments` merge in `sync.go` is
still using a length check, which incorrectly treats an explicit empty map as
“unset” and reloads persisted assignments. Update the
`selection.VSCodeModelAssignments` fallback logic to distinguish nil from empty
by checking against nil, matching the `mergeExplicitAgentInstallState` pattern
in `run.go`, so an intentional reset to defaults is preserved.

In `@internal/tui/model_test.go`:
- Line 6171: Remove the unresolved merge-conflict marker from model_test and
make sure no literal conflict markers remain in the file. Clean up the affected
test section around the lingering HEAD marker so the Go source is valid again
and the test file can compile under go test/CI.
🪄 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: cd3496a6-d6a6-4d06-b8ae-f382aa646016

📥 Commits

Reviewing files that changed from the base of the PR and between d36fea5 and 3df9409.

📒 Files selected for processing (16)
  • README.md
  • internal/app/app.go
  • internal/assets/assets_test.go
  • internal/cli/run.go
  • internal/cli/run_engram_download_test.go
  • internal/cli/run_state_test.go
  • internal/cli/sync.go
  • internal/cli/sync_test.go
  • internal/components/sdd/inject.go
  • internal/components/sdd/vscode_agent_visibility.go
  • internal/components/sdd/vscode_agent_visibility_test.go
  • internal/components/sdd/vscode_models.go
  • internal/components/uninstall/service.go
  • internal/components/uninstall/service_test.go
  • internal/tui/model.go
  • internal/tui/model_test.go

Comment on lines +799 to +829
for _, op := range ops {
if op.path == agentPath || op.path == promptPath {
if _, _, err := op.apply(op.path); err != nil {
t.Fatalf("op.apply(%q) error = %v", op.path, err)
}
}
}

// Verify both files were restored to original content and backups removed
gotAgent, err := os.ReadFile(agentPath)
if err != nil {
t.Fatalf("ReadFile(agent) error = %v", err)
}
if string(gotAgent) != originalContent {
t.Errorf("agent content = %q, want %q", string(gotAgent), originalContent)
}
if _, err := os.Stat(backupPath); !os.IsNotExist(err) {
t.Errorf("backup file %s should be removed", backupPath)
}

gotPrompt, err := os.ReadFile(promptPath)
if err != nil {
t.Fatalf("ReadFile(prompt) error = %v", err)
}
if string(gotPrompt) != originalPromptContent {
t.Errorf("prompt content = %q, want %q", string(gotPrompt), originalPromptContent)
}
if _, err := os.Stat(promptBackupPath); !os.IsNotExist(err) {
t.Errorf("prompt backup file %s should be removed", promptBackupPath)
}
}

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

Consider adding a second-apply assertion to lock in idempotency.

This test only applies each op once. Re-running op.apply(op.path) after the backup is consumed would exercise the "no backup" fallback and catch the idempotency/data-loss regression flagged in restoreUserFileBackupOperation (restored user content being deleted on a second run).

🤖 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/uninstall/service_test.go` around lines 799 - 829, The
restore/uninstall test currently verifies only a single application of each
operation, so it does not lock in idempotency for
restoreUserFileBackupOperation. Update the test around the op.apply loop in
service_test.go to apply the same agentPath and promptPath operations a second
time after the backup has been consumed, and assert the files still contain
originalContent and originalPromptContent and that the backup paths remain
absent. Use the existing op.apply, agentPath, promptPath, backupPath, and
promptBackupPath symbols to keep the check anchored to the restore behavior.

Comment thread internal/components/uninstall/service.go
@Snakeblack Snakeblack force-pushed the fix/vscode-copilot-agent-hardening branch from 76ce695 to 9867240 Compare July 1, 2026 08:32
@Snakeblack Snakeblack force-pushed the fix/vscode-copilot-agent-hardening branch from 9867240 to 28d0885 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