Skip to content

feat(tui): expose VS Code Copilot SDD profiles in welcome and profile screens#509

Closed
Snakeblack wants to merge 12 commits into
Gentleman-Programming:mainfrom
Snakeblack:feat/vscode-copilot-tui-profiles
Closed

feat(tui): expose VS Code Copilot SDD profiles in welcome and profile screens#509
Snakeblack wants to merge 12 commits into
Gentleman-Programming:mainfrom
Snakeblack:feat/vscode-copilot-tui-profiles

Conversation

@Snakeblack

@Snakeblack Snakeblack commented May 11, 2026

Copy link
Copy Markdown

🔗 Linked Issue

Closes #506

Stacked on PR #505 (backend). This branch contains PR #505's commit plus the TUI integration commits. When PR #505 merges, GitHub will rebase this one onto main automatically.


🏷️ PR Type

  • type:feature — New feature (non-breaking change that adds functionality)

📝 Summary

  • Adds a VS Code SDD Profiles entry to the welcome menu (parallel to the OpenCode one) so users on VS Code Copilot can manage SDD profiles from the wizard, not just the Go API.
  • Reuses the existing ScreenProfiles / ScreenProfileCreate / ScreenProfileDelete screens via an ActiveProfileAdapter field — single source of truth, no duplicated handlers.
  • Ships a VS Code-specific model picker (VSCodeModelPickerState) backed by the live OpenCode models cache (github-copilot provider entry), with full per-phase assignment parity to OpenCode profiles — including an independent sdd-orchestrator row separate from the 10 SDD phase rows.
  • Fixes Agent Duplication: Resolves duplication in VS Code Copilot by surgically adding user-invocable: false to the YAML frontmatter of Claude-specific agents. Copilot respects this masking flag and hides them from its UI, while Claude Code continues to use them seamlessly.

Why now? Same context as the linked issue and PR #505: with Copilot moving to AI Credits in June, per-phase model assignment is the cost lever for enterprise teams already standardized on Copilot + VS Code. Also fixes crucial agent duplication bugs where both environments operate in the same workspace.


📂 Changes

File / Area What Changed
internal/assets/claude/agents/*.md Added user-invocable: false to frontmatter to mask these agents from VS Code Copilot's @ menu, fixing duplication and model incompatibility errors.
internal/agents/vscode/vscode_profiles.go New DetectVSCodeProfiles(agentsDir) — scans sdd-*-{name}.agent.md, dedupes, returns sorted []model.Profile. Missing dir = empty (not error).
internal/agents/vscode/vscode_profiles_detect_test.go New — 5 tests covering happy path, empty/missing dir, non-SDD files filtered, default unsuffixed files excluded.
internal/tui/model.go New hasDetectedVSCode(), vscodeAgentsDirFn, readVSCodeProfilesFn, VSCodeProfileList, ActiveProfileAdapter. ScreenProfiles / ProfileCreate / ProfileDelete handlers branch on adapter. VS Code create/delete call GenerateVSCodeProfileFiles / RemoveVSCodeProfileAgents directly (no sync). confirmVSCodeProfileCreateStep1 extracts OrchestratorModel from assignments map on Continue; copies only phase-keyed entries into PhaseAssignments.
internal/tui/model_profiles_vscode_test.go New — TUI integration tests: adapter detection, menu rendering, ActiveProfileAdapter routing, profile create writes files end-to-end, delete removes files without sync, RenderProfiles / RenderProfileDelete wording is adapter-aware.
internal/tui/screens/welcome.go Adds showVSCodeProfiles bool, vscodeProfileCount int params. New menu entry VS Code SDD Profiles (N) next to OpenCode.
internal/tui/screens/profiles.go RenderProfiles takes adapterLabel string — title and subtitle adapt.
internal/tui/screens/profile_delete.go RenderProfileDelete takes isVSCode bool — wording adapts (opencode.json vs ~/.copilot/agents/), button label adapts (Delete & Sync vs Delete).
internal/tui/screens/vscode_model_picker.go VSCodeModelPickerState + RenderVSCodeModelPicker. Reads live Copilot model catalog from OpenCode cache (opencode.LoadModels + FilterModelsForSDD). Exports VSCodeOrchestratorPhase const. VSCodeModelRows() returns 12 rows: Row 0 = sdd-orchestrator, Row 1 = "Set all phases", Rows 2–11 = 10 SDD phase executors. HandleVSCodeModelPickerNav enter-key: Row 0 assigns only to orchestrator key; Row 1 ("Set all") assigns to 10 sub-agents but NOT orchestrator; Rows 2–11 assign to individual phases.
internal/tui/screens/vscode_model_picker_test.go New — 10 tests: row count/layout, VSCodeOrchestratorPhase value, VSCodeModelPickerOptionCount, and nav semantics for all three row types (orchestrator isolation, Set-all does not touch orchestrator, individual phase assignment).
internal/tui/screens/{welcome,profiles,profile_delete}_test.go Updated existing call sites for the new function signatures.
internal/tui/model_test.go Updated 2 WelcomeOptions call sites for new params.

🧪 Test Plan

Unit tests (focused)

# New tests — all pass:
go test -run "TestHasDetectedVSCode|TestActiveProfileAdapter|TestVSCodeProfile|TestRenderProfiles_AdapterLabel|TestRenderProfileDelete_VSCodeWording|TestDetectVSCodeProfiles|TestWelcomeMenuShowsVSCodeProfiles|TestVSCode|TestHandleVSCodeModelNav" ./internal/tui/... ./internal/agents/vscode/...

Result: all PASS locally.

Static analysis

go vet ./...   # clean
go build ./... # builds without errors

Pre-existing test failures (NOT caused by this PR)
Three tests fail when running go test ./... on this branch: TestStartUninstall_FullRemoveHomebrewManagedBinaryAddsManualAction, TestPresetSelectionNextScreenFlowMatrix, TestCustomPresetPostComponentFlowMatrix. Verified by checking out main directly — these same tests fail on main with the same error signatures (golden file CRLF mismatch on Windows). Pre-existing on upstream, unrelated to VS Code or profiles.


🤖 Automated Checks

Check Expected Note
Check PR Cognitive Load ⚠️ likely fail Diff is ~1300 LOC including substantial new tests (model_profiles_vscode_test.go ~300 LOC, vscode_model_picker_test.go ~170 LOC) and the picker screen (~250 LOC). Requesting size:exception — production logic alone is ~600 LOC; most of the diff is test coverage required to keep the adapter branching safe.
Check Issue Reference Closes #506
Check Issue Has status:approved Issue #506 is status:needs-review — submitter has no maintainer permissions
Check PR Has type:* Label type:feature needs maintainer to apply
Unit Tests ⚠️ partial New tests pass; 3 pre-existing failures on main will still show (documented above)

✅ Contributor Checklist

  • PR is linked to issue (feat(tui): VS Code Copilot SDD profiles wizard and management screens #506)
  • Within 400-line budget — requesting size:exception
  • type:feature label — needs maintainer
  • Unit tests pass for new code (the 3 pre-existing failures exist on main)
  • E2E tests — run in CI
  • Documentation: linked issue covers the user-facing flow
  • Conventional Commits format
  • No Co-Authored-By trailers

💬 Notes for Reviewers

  • Architecture (hybrid approach): a single ActiveProfileAdapter model.AgentID field in Model threads the active adapter through the shared screens. The screens take an adapter label (OpenCode / VS Code) or isVSCode bool — no duplicated screen constants for list/create/delete. The only adapter-specific screen is VSCodeModelPickerState, because the OpenCode picker reads from a dynamic cache and isn't reusable.
  • Orchestrator row isolation: sdd-orchestrator (Row 0) is intentionally separate from "Set all phases" (Row 1). Selecting "Set all" bulk-assigns the 10 sub-agent phases but deliberately does not touch the orchestrator model — matching the design intention that the orchestrator and the phase runners can be different models (e.g. Sonnet for orchestration, GPT-4o-mini for cheaper phases). This is enforced in both HandleVSCodeModelPickerNav and confirmVSCodeProfileCreateStep1.
  • Dynamic model catalog: the picker loads models from the OpenCode cache (~/.opencode/models.json) filtered to github-copilot. If the cache is missing or the provider absent, a ConfigWarning is surfaced inline — no crash, no empty list silently.
  • No sync for VS Code: profile create/delete write .agent.md files directly. The sync pipeline is invoked only for OpenCode.
  • Idempotency: GenerateVSCodeProfileFiles returns only the paths that actually changed (via filemerge.WriteFileAtomic). Re-confirming an unchanged profile does not flip InjectionResult.Changed.
  • What's still deferred: VS Code profile cleanup in refreshUninstallProfiles; profile edit UX (technically works as re-generate but not polished).
  • Pre-existing test failures on main: documented above. Happy to open a tracking issue for them if useful — not blockers for this PR.

Snakeblack added 12 commits May 11, 2026 17:52
Enable per-phase sub-agents for VS Code Copilot via .agent.md files,
matching the multi-mode capability already shipped for OpenCode, Cursor
and Kiro.

- VS Code adapter now reports SupportsSubAgents() == true and exposes
  SubAgentsDir(homeDir) == ~/.copilot/agents/ plus EmbeddedSubAgentsDir()
  == "vscode/agents".
- 10 embedded .agent.md templates under internal/assets/vscode/agents/
  (one per SDD phase, sdd-init through sdd-onboard). Templates omit the
  model field on the default set so Copilot uses the user's default
  model.
- New profile generator GenerateVSCodeProfileFiles(profile, agentsDir)
  ([]string, error) reads templates, resolves {{VSC_MODEL}} via a
  provider/model → Copilot display name table (vscModelEntries) and
  writes suffixed files atomically.
- inject.go step 2c writes named-profile files for VS Code Copilot
  when SDD mode is multi and a non-default profile is configured;
  step-3c handles the default unsuffixed set unchanged.
- RemoveVSCodeProfileAgents(agentsDir, profileName) removes the 10
  suffixed phase files for a named profile; default profile rejected,
  non-gentle-ai files preserved.
- Post-injection verification extended to recognize the .agent.md
  extension and validate sdd-apply.agent.md plus sdd-verify.agent.md
  are non-empty.

TUI integration (welcome menu entry, profile create/edit screens, VS
Code-specific model picker) lands in a separate PR.
… screens

Make the VS Code Copilot SDD multi-mode profiles reachable from the
gentle-ai wizard, mirroring the OpenCode profile flow.

- New backend helper `vscode.DetectVSCodeProfiles(agentsDir)` scans
  `~/.copilot/agents/sdd-*-{name}.agent.md` and returns deduplicated
  profile names. Missing directory is not an error.
- New `Model.hasDetectedVSCode()`, `Model.VSCodeProfileList`, and
  `Model.ActiveProfileAdapter` thread the active adapter through the
  shared profile screens.
- Welcome menu shows a new "VS Code SDD Profiles (N)" entry next to
  the existing OpenCode one when VS Code Copilot is detected.
- `RenderProfiles` and `RenderProfileDelete` take an adapter label /
  isVSCode flag and adapt their wording (no more hardcoded
  "OpenCode SDD Profiles" / "opencode.json").
- Profile create for VS Code uses a new `VSCodeModelPickerState`
  driven by the static `vscModelEntries` table (9 known Copilot
  models). Per-phase model assignment works exactly as for OpenCode,
  with the model field resolved via `VSCodeModelID`.
- Profile create/delete for VS Code bypass the sync pipeline and
  call `vscode.GenerateVSCodeProfileFiles` /
  `vscode.RemoveVSCodeProfileAgents` directly — VS Code profiles
  are file-based, not JSON-merged.
- `SyncDoneMsg` and `ScreenProfiles` entry refresh both profile
  lists so badges stay accurate across both adapters.

Test coverage:
- `internal/agents/vscode/vscode_profiles_detect_test.go` — 5 cases
  (happy path, empty/missing dir, non-SDD files filtered, default
  unsuffixed files excluded).
- `internal/tui/model_profiles_vscode_test.go` — TUI integration
  tests for adapter detection, menu rendering, ActiveProfileAdapter
  routing, file generation, deletion (no sync), and adapter-aware
  rendering for both OpenCode and VS Code.

Stacked on feat/vscode-copilot-sdd-multimode (PR Gentleman-Programming#1 backend).
Add two regression tests that explicitly invoke Inject() twice with
identical inputs and assert:
  1. The second run reports InjectionResult.Changed = false.
  2. The file inventory in ~/.copilot/agents/ is unchanged (same set,
     same modification times) — proving filemerge.WriteFileAtomic's
     content-equality short-circuit holds across the full VS Code
     injection path.

Coverage:
  - Default profile path (step 3c only, 10 files).
  - Named profile path (step 2c writes 10 suffixed files on top of
    step 3c's 10 defaults — total 20 files).

These tests blind the recent fix at inject.go:456 (changed = changed
|| len(profileFiles) > 0) against future regressions where a
contributor might re-introduce the unconditional Changed=true that
broke the idempotency contract.
The VS Code model picker previously listed only a hardcoded set of 9
display names from vscModelEntries. That static list:

  - missed models GitHub Copilot exposes today (e.g. newer Claude
    revisions, GPT-5 family, etc.) and would silently fall behind
    whenever Copilot adds a model;
  - stored assignments as {ProviderID: "copilot", ModelID: <display>},
    which does not match what VSCodeModelID expects when resolving
    {{VSC_MODEL}} during injection (it matches by ID substring against
    vscModelEntries, then falls back to ProviderID/ModelID).

This change makes VSCodeModelPickerState load its catalog from the same
OpenCode models cache (~/.cache/opencode/models.json) that the OpenCode
profile picker uses, restricted to the `github-copilot` provider entry.
The picker now reflects whatever Copilot currently advertises and the
catalog refreshes whenever the user runs `opencode sync`.

Changes:
  - screens.VSCodeModelPickerState gains Models []opencode.Model and
    ConfigWarning string fields populated at construction time via the
    new screens.NewVSCodeModelPickerState(cachePath) helper.
  - HandleVSCodeModelPickerNav stores assignments as
    {ProviderID: "github-copilot", ModelID: <real model.ID>} so
    inject.go's VSCModelID substring matcher resolves them correctly.
  - When the cache is missing or lacks a github-copilot entry, the
    picker shows a "run opencode sync" warning instead of failing.
  - Model.go entry points (handleProfileNameInput and the edit-mode
    branch of confirmProfileCreate) initialize the picker via the new
    helper, with the OpenCode-only branch unchanged.
  - Drop now-unused exports VSCodeStaticModels and VSCodeModelEntry
    from internal/agents/vscode/vscode_profiles.go.

User-visible outcome: when creating a VS Code SDD profile and assigning
per-phase models, the picker shows the full Copilot catalog the user
sees in the install wizard, not a static 9-item list.
The vscode-sdd-profiles-research.md doc was a working/exploration
artifact useful during design but does not belong in the shipped
codebase. The architectural decisions it captured are reflected in
proposal/spec/design/tasks (kept in engram and locally in
openspec/changes/, which is gitignored).
…ates agents

VS Code Copilot scans both \`.agent.md\` (its native format) and Claude
\`.md\` agent files in parallel. When a user installs SDD multi-mode for
both VS Code Copilot and Claude Code through the gentle-ai wizard, the
8 sub-agent phases that both adapters ship (sdd-apply, sdd-archive,
sdd-design, sdd-explore, sdd-propose, sdd-spec, sdd-tasks, sdd-verify)
appear twice in the VS Code Agent customizations panel.

This is not a bug — each agent file is correct and functional in its
own host — but it looks broken to a user who sees \"sdd-verify\" listed
twice and assumes gentle-ai wrote duplicates. The case was traced down
in PR Gentleman-Programming#505 manual testing.

Changes:
  - New ScreenSDDDuplicateAgentsWarning with render function that lists
    exactly the 8 phases that duplicate (sdd-init and sdd-onboard are
    excluded because the Claude adapter does not ship them as
    sub-agents).
  - shouldWarnAboutDuplicateAgents() returns true when SDD is selected
    AND both AgentVSCodeCopilot and AgentClaudeCode are in the agent
    set. Extensible: add other Claude-format adapters to the check as
    they are introduced.
  - Wired into the ScreenSDDMode handler: when SDDModeMulti is chosen
    and the warning condition holds, the warning screen is shown before
    the normal post-SDDMode flow (ModelPicker / StrictTDD / etc.).
  - Extracted advanceFromSDDModeSelection() helper so both the SDDMode
    handler (when no warning is required) and the warning's \"Continue
    anyway\" path can share the same forward navigation logic.
  - Warning screen offers two options: \"Continue anyway\" resumes the
    normal flow; \"Back to adapter selection\" returns to ScreenSDDMode
    so the user can unselect one adapter.

Test coverage:
  - TestShouldWarnAboutDuplicateAgents (7 subtests) covers the
    detection helper across all relevant adapter combinations.
  - TestSDDMode_TriggersDuplicateAgentsWarning verifies that selecting
    multi-mode with the offending combination routes to the warning.
  - TestSDDMode_NoWarningWhenNotDuplicating verifies that a benign
    combination skips the warning.
  - TestSDDDuplicateAgentsWarning_ContinueAdvances and
    TestSDDDuplicateAgentsWarning_BackReturnsToSDDMode cover both
    branches of the warning handler.
  - TestRenderSDDDuplicateAgentsWarning_ListsExpectedPhases pins the
    rendered phase list to the contract so a future contributor cannot
    silently drop or add a phase.
Without an orchestrator template, SDD multi-mode on VS Code Copilot
relies on the default chat agent inferring the phase sequence from
sub-agent description fields alone. Weak Copilot models routinely
skip phases or reorder them, breaking the SDD contract.

This change adds an explicit orchestrator template that mirrors the
pattern OpenCode, Claude Code, and Kiro already use:

  - New internal/assets/vscode/agents/sdd-orchestrator.agent.md
    embedded template with tools: ['agent'], an agents: whitelist of
    the 10 phases, user-invocable: true, and a body that documents
    the strict explore → propose → spec → design → tasks → apply →
    verify → archive sequence plus the init / onboard utility flows.
  - {{VSC_PROFILE_SUFFIX}} placeholder so the same template serves
    both the default (unsuffixed) install and named profiles where
    every phase reference is suffixed with -{name}.
  - vscode.OrchestratorPhase constant + generateOrchestratorAgent()
    helper that renders the orchestrator inline for named profiles
    (separate from the embedded template path used by 3c).
  - GenerateVSCodeProfileFiles now writes 11 files per profile
    (orchestrator + 10 phases). RemoveVSCodeProfileAgents already
    matched the suffix pattern, so it cleans up the orchestrator
    automatically.
  - inject.go step 3c resolves {{VSC_PROFILE_SUFFIX}} to empty for
    the default set; post-check conditionally validates the
    orchestrator file only when the adapter ships one (VS Code does,
    Claude Code does not — Claude uses CLAUDE.md as the root
    orchestrator prompt instead of a separate agent file).

Tests:
  - TestGenerateAgentFile_Orchestrator_DefaultProfile_HasAllRequiredFields
    pins the YAML contract (tools, agents, user-invocable, etc.).
  - TestGenerateAgentFile_Orchestrator_NamedProfile_SuffixesAgentNames
    proves the agents: whitelist AND body references get suffixed in
    lockstep — without that, the orchestrator would dispatch to
    nonexistent agents.
  - TestGenerateAgentFile_Orchestrator_OrchestratorModelAssignment
    covers three model resolution paths (omit / known / fallback).
  - TestGenerateVSCodeProfileFiles_IncludesOrchestrator regression
    guard against the previous 10-file design.
  - TestRemoveVSCodeProfileAgents_AlsoRemovesOrchestrator regression
    guard against the orchestrator lingering after delete.
  - Existing idempotency tests updated for the new counts (11 default,
    22 with one named profile).

Docs:
  - docs/prd-vscode-profiles.md captures the full product spec in
    English, mirroring the format of docs/prd-opencode-profiles.md.
    Covers problem statement (June 2026 AI Credits transition +
    enterprise lock-in), vision, scope, detailed requirements,
    technical design, UX flows, edge cases, and open questions.
- Add VSCodeOrchestratorPhase const (re-export of vscodeagent.OrchestratorPhase)
- Prepend orchestrator as Row 0 in VSCodeModelRows(); shift Set-all to Row 1,
  SDD phases to rows 2-11 (12 rows total, option count is now 14)
- Update HandleVSCodeModelPickerNav: Row 0 assigns only to sdd-orchestrator key,
  Row 1 (Set all) assigns to 10 sub-agents but NOT orchestrator,
  rows 2-11 assign to individual phases
- Update confirmVSCodeProfileCreateStep1: extract OrchestratorModel from
  assignments on Continue, copy only phase keys to PhaseAssignments
- Add vscode_model_picker_test.go: 10 tests for row layout, constant value,
  option count, and nav semantics
Adds user-invocable: false frontmatter to Claude agents to prevent them from showing up as duplicates in VS Code Copilot's chat menu when both tools are installed, while keeping them functional for Claude Code.
@Alan-TheGentleman

Copy link
Copy Markdown
Contributor

Thanks for the contribution. I’m closing this PR because it does not link to an issue with status:approved, which is required by this repo before PR work can proceed.

Please follow the project process:

  1. Open or update the relevant issue with the proposed change and reproduction/context.
  2. Wait for a maintainer to add status:approved.
  3. Open a fresh PR that includes Closes #<issue> and exactly one type:* label.

This keeps review scope clear and prevents CI/process failures.

@Alan-TheGentleman

Alan-TheGentleman commented May 26, 2026

Copy link
Copy Markdown
Contributor

Hey, thanks for contributing!

Per CONTRIBUTING.md, every PR needs an issue linked with status:approved before it can be reviewed — and this PR doesn't satisfy that gate (the linked issue doesn't exist, is closed, or isn't approved).

Closing for now. To pick the work back up:

  1. Open (or find) an issue describing the change
  2. Wait for a maintainer to apply status:approved
  3. Reopen this PR or open a new one with Closes #<issue> in the body

Sorry for the friction — the gate is automated, there's no workaround. Come back when it's ready!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type:feature New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(tui): VS Code Copilot SDD profiles wizard and management screens

2 participants