Skip to main content

AI Primitives Hub Full Test Plan

All 19 plans covering the manual verification surface across the extension, the CLI, and the published packages.

This page is not the release gate — Golden Path Test Cases is. Run the golden path for every release. Come here when a PR touches a specific area and you want something concrete to run, or when the whole surface needs reviewing end to end.

PageScope
Golden Path Test CasesThe three mandatory scenarios — start here
TestingHow to run the automated suites
ValidationLocal CI simulation, per-commit checks
ReleasingVersion bump and publish mechanics
Full Test Plan (this page)Area-by-area manual coverage

Relationship To The Golden Path

Rows marked are part of the golden path. The golden path page is the lightweight run sheet for those — three chained scenarios with the setup they need, and nothing else. Coverage detail lives here, not there; here the ⭐ rows also sit in their home plan so an area-focused run does not miss them.

Golden scenarioPlans it draws on
G1 — Collection userTP-01, TP-04, TP-05, TP-07
G2 — Collection authorTP-14, TP-05
G3 — UpdateTP-10

Reading The Automation Column

Every scenario carries the automation that already covers it, so manual effort lands where it is actually needed rather than re-proving what vitest and Mocha already assert. Growing this coverage is tracked in #370.

MarkerMeaningHow to treat it
🟢 AutoThe logic is asserted by a test suiteQuick confirmation. First thing to drop when short on time
🟡 PartialAutomation covers the logic, but not the real host, network, filesystem or renderingFocus on the named gap, not the logic
🔴 ManualNo automated coverage existsFull attention. These justify the whole page

Suite paths are relative to apps/vscode-extension/ for test/…, and to the repository root for packages/… and lib/….

A useful shortcut: if a plan is entirely 🟢, running it is a smoke test. If it contains 🔴 rows, those rows are the plan.

Step-level breakdowns. TP-07, TP-11 and TP-17 each carry a Coverage breakdown section decomposing every scenario into the individual assertions behind it, as [x] covered or [ ] gap. Those three were done first because they carry the release risk — TP-11 and TP-17 are the non-waivable plans, and TP-07 is the core value path. The remaining plans currently document coverage at suite level only; extending them is tracked in #370.

🟢 almost always means "green against a fake"

Before treating a 🟢 as settled, check what the suite talks to. Very little here touches a real network, filesystem or editor:

DoubleWhereWhat it hides
nock — canned HTTP11 suites, including every extension test/e2e/**Real GitHub redirects, rate limits, auth challenges, pagination, archive layout, ETag behaviour
Fake ports — injected HttpClient/TokenProvider, InMemoryFileSystempackages/* (19 suites use InMemoryFileSystem)No real HTTP, no real disk: path casing, permissions, symlinks, partial writes
Mocked vscodetest/mocha.setup.js intercepts require('vscode')Everything outside test/suite/**Activation order, webview lifecycle, context keys, real auth providers
Real filesystemos.tmpdir(), or a repo-local temp dir~25 extension and CLI suites, e.g. user-scope-service, repository-scope-service, mcp-config-service, scaffold-commandNothing, at the fs layer. Still one platform and one host per run

Practical consequence: the install and update flows are 🟢 largely on the strength of nock plus a mocked vscode, so they have never run against real GitHub inside a real editor. Host path routing is the happier case — it is asserted against a real filesystem.

When closing a gap for #370, a new test that fakes the very boundary carrying the risk buys little. Prefer real-fs or contract tests where the risk is I/O.


Host Coverage — VS Code and Kiro

The extension does not just install for Kiro and VS Code — it runs inside both. Kiro is a VS Code fork, so the same VSIX is loaded by both editors and has to behave correctly in each. Every plan below that touches the filesystem or the UI is therefore run twice: once with the extension running in VS Code, once with it running in Kiro.

Nothing is tested in Kiro. The extension runs there fine — Kiro is a VS Code fork and loads the same build — but no automated suite exercises it, because test/runExtensionTests.js calls runTests() without a version option and so always launches VS Code.

That does not mean Kiro behaviour is untested. Be precise about the split, because it changes where manual effort is worth spending:

Kiro concernStatus
Path routing (.kiro/, steering/ folding, skills dirs, git-exclude, unsync)✅ Covered against a real temp filesystem — test/services/repository-scope-service.test.ts (Host-Aware Destinations), test/services/user-scope-service.test.ts
Layout config for the kiro targetpackages/infra/test/stores/layout-config-store.test.ts, packages/app/test/writers/file-tree-writer.test.ts
Signal → target mappingpackages/infra/test/host-app/host-app-target.test.ts
Activation, UI, webviews, settings inside Kiro❌ Not tested there — manual only
The real appName/uriScheme a shipped Kiro build reports❌ Untested — the one input that decides whether the vscode fallback fires

Worth knowing while reading the rest of this page: the harness also never reads the VSCODE_VERSION environment variable, even though the CI matrix sets it to stable and insiders. Both matrix legs currently exercise the same default build, so "tested on Insiders" is not something this document can rely on.

How the host is detected

resolveHostApp (in packages/infra/src/host-app/host-app-target.ts) matches the lowercased combination of vscode.env.appName and vscode.env.uriScheme against ordered rules:

Signal containsResolved target
kirokiro
windsurf or devinwindsurf
insidersvscode-insiders
anything elsevscode (the default .github/ layout)

The failure mode to watch for: detection falls back to vscode when a host is unrecognized. In Kiro that fallback is silent and wrong — content lands in .github/ instead of .kiro/. The extension logs the resolved target on every detection ([host-app] detectHostApp: appName="…", uriScheme="…" -> …), so the Output channel is the fastest way to confirm the host was identified correctly.

packages/infra/test/host-app/host-app-target.test.ts asserts the signal→target mapping for known inputs. What it cannot assert is what appName and uriScheme a real Kiro build actually reports — which is precisely the input that decides whether the fallback fires.

Which plans run per host

PlanVS CodeKiroNote
TP-01 Fresh install and activationHost detection happens here
TP-02 First-run setup
TP-06 Marketplace discoveryWebview theming differs between the two editors
TP-07 Bundle installationDifferent destinations and different kind routing
TP-08 / TP-09 ProfilesActivation writes primitives to the host layout
TP-11 Repository scope and lockfileHost detection decides .github/ vs .kiro/
TP-12 Uninstall and cleanup
TP-15 CLIVia explicit kiro and vscode targets, not detection
TP-17 Upgrade and migration
TP-03, TP-04, TP-05, TP-10, TP-13, TP-14, TP-16, TP-18, TP-19Host-agnostic; run once unless your change touches host handling

Claude Code and Windsurf are install targets, not hosts that run this extension — Claude Code is a CLI and never loads it. Cover them through TP-07 and the CLI's explicit target flags.

How To Read This Page

The plans run in the order written — a user journey where state carries forward, so you are not rebuilding fixtures for every case. Running a subset is normal: pick the plans covering what changed, and check the preceding plans for state a chosen plan depends on.

Plans are happy paths by default — resilience and performance belong in automation, not here. The exception is a small set of rows marked "Reported from testing": failure modes someone actually hit, where the product fails silently or blocks the user with no way out. Those earn a place because a passing happy path does not reveal them.


TP-01 — Fresh Install and Activation

Run the whole plan twice — once in VS Code, once in Kiro.

#ScenarioExpected resultAutomation → where to focus
⭐ 1.1Install the compiled extension into a brand-new VS Code profile, reload, and confirm it runsActivates; Output channel shows a clean startup with no errors🟡 test/suite/integration-scenarios.test.ts activates in a real VS Code host
⭐ 1.2Install the same build into a clean Kiro profile, then reloadActivates identically; no errors caused by the fork🔴 No test covers Kiro — verify manually
⭐ 1.3In each host, read the [host-app] detectHostApp: line in the Output channelResolves to vscode in VS Code and kiro in Kiro — not the vscode fallback while running in Kiro🟡 packages/infra/test/host-app/host-app-target.test.ts covers the mapping. The signals Kiro really reports are untested
1.4Open the command palette and type the AI Primitives Hub: categoryAll 66 contributed commands listed and invocable in both hosts🟡 test/suite/integration-scenarios.test.ts asserts registration for 6 scope commands only (syncAllSources, moveToUser, moveToRepositoryCommit, moveToRepositoryLocalOnly, switchToLocalOnly, switchToCommit) — the file is otherwise placeholders. The other 60 commands are unverified
1.5Open the extension's settings pageAll 9 promptregistry.* settings appear with documented defaults🟡 test/config/package-configuration.test.ts covers only the 4 updateCheck.* settings. autoCheckUpdates, installationScope, enableLogging, githubToken and updateCheck.cacheTTL are unasserted

TP-02 — First-Run Setup

Run in both VS Code and Kiro.

#ScenarioExpected resultAutomation → where to focus
⭐ 2.1Activate for the first time with no prior stateSetup flow appears and leads to a usable state🟢 test/services/setup-state-manager.test.ts, test/e2e/setup-state-flows.test.ts
2.2Complete it, then reloadDoes not reappear; the state it produced is intact🟢 test/services/setup-state-manager.test.ts (+ .property.test.ts)
2.3Inspect hubs seeded from config/defaultHubs.jsonDocumented default hubs present and usable🟡 Seeding logic covered by test/e2e/setup-state-flows.test.ts. The shipped defaultHubs.json content is not validated against reachable hubs
2.4Run Reset First Run (for Testing) and reloadSetup flow reappears from a clean slate🟢 test/services/setup-state-manager.test.ts
2.5Compare the setup flow between the two hostsWording and steps make sense in Kiro too — no VS Code-only terminology that misleads a Kiro user🔴 Copy review; nothing automated

TP-03 — Authentication

Runs before anything touching GitHub. The credential established here carries forward.

#ScenarioExpected resultAutomation → where to focus
⭐ 3.1Configure a token via the githubToken settingPicked up and used for GitHub API calls🟢 packages/infra/test/auth/default-token-provider.test.ts
3.2Remove the setting, provide a token via the environmentEnvironment provider takes over transparently🟢 packages/infra/test/auth/env-token-provider.test.ts
3.3Remove that, authenticate via the gh CLICLI provider used, per the documented precedence🟢 packages/infra/test/auth/gh-cli-token-provider.test.ts, composite-token-provider.test.ts
3.4Run Force GitHub AuthenticationSession re-prompted and refreshed🔴 Depends on the real VS Code auth provider; not mockable in our suites

TP-04 — Hub Onboarding

#ScenarioExpected resultAutomation → where to focus
⭐ 4.1Import Hub against a valid hub repositoryHub appears in the tree, populated with its profiles🟢 packages/app/test/registry/hub-manager.test.ts, packages/infra/test/hub/hub-resolver.test.ts
4.2List HubsEvery hub listed with accurate metadata🟢 packages/infra/test/stores/hub-store.test.ts
4.3Sync Hub after an upstream changeNew and changed profiles appear, progress reported🟢 packages/app/test/registry/load-hub-sources.test.ts, source-sync-queue.test.ts
4.4Import a second hub, then Switch HubActive hub changes; tree and marketplace both refresh🟡 packages/infra/test/stores/active-hub-store.test.ts covers the switch; the coordinated UI refresh only partly, via test/ui/ui-source-sync-refresh.test.ts
4.5Export Hub Configuration, then import into a clean profileRound-trips with no loss🟡 packages/infra/test/hub/validate-hub-config.test.ts validates the shape. The export→import round-trip is not asserted end to end
4.6Open Hub Repository / Open Repository on hub, source, profile and bundle nodesEach opens the correct upstream URL🔴 Opens an external browser; not automatable in-process
4.7Delete HubThat hub and its derived state removed; others untouched🟢 packages/app/test/registry/hub-manager.test.ts, packages/infra/test/stores/hub-store.test.ts

TP-05 — Sources

Golden coverage is the GitHub adapter only. The awesome-copilot, APM, skills and local-path variants are exercised here when your change touches them, and otherwise rely on packages/infra/test/adapters/**.

#ScenarioExpected resultAutomation → where to focus
⭐ 5.1Add Source for a public GitHub repositoryAdded, immediately enumerates its bundles🟢 test/commands/source-commands.test.ts (addSource: prompts, URL validation, GitHub and local types), packages/infra/test/adapters/github-adapter.test.ts
5.2Add Source for an awesome-copilot sourceAdded, enumerates its bundles🟢 packages/infra/test/adapters/awesome-copilot-adapter.test.ts
5.3Sync Source, then Sync All SourcesContent refreshes, progress reported, per-source results visible🟢 packages/app/test/registry/source-sync-queue.test.ts
5.4Sync again with nothing changed upstreamETag/cache short-circuits instead of refetching the tree🟢 packages/infra/test/harvest/etag-store.test.ts, blob-cache.test.ts
5.5Edit SourceChange persists and the next sync uses it🟢 test/commands/source-commands.test.ts (editSource: name, URL, type, priority preserved)
5.6Toggle Source Enabled/DisabledDisabled source drops out of the marketplace and is skipped by Sync All Sources🟡 packages/app/test/registry/load-hub-sources.test.ts asserts only enabled sources are loaded. Marketplace filtering after a live toggle is the gap
5.7Sync a source shipping a README and assetsFetched to the expected location🟢 test/e2e/source-sync-readme-download.test.ts
⭐ 5.8Private GitHub repository using the credential from TP-03Content enumerates normally🔴 Needs a real private repository and a real token. Suites use nock, so authorization is never truly exercised
5.9Remove all credentials, browse and sync the public sourcePublic flows work with no credentials🟡 Provider fallback covered in packages/infra/test/auth/**; the unauthenticated network path is mocked
⭐ 5.10Add a local source pointing at collection content on diskDiscovered and installable🟢 packages/infra/test/adapters/local-adapter.test.ts
5.11GitHub source exposing multiple collections in one repositoryAll discovered and listed separately🟢 test/e2e/github-multiple-collections.test.ts
5.12Remove Source while its bundles are installedSource removed, installed bundles intact🟡 test/commands/source-commands.test.ts (removeSource: confirmation, cancellation) covers the command. That installed bundles survive is not asserted
5.13Add and sync a GitHub source behind enterprise SSO — a repository that answers 307 (SSO redirect) or 502 (CDN failure for SSO-protected repos)The error names SSO/authentication as the cause and suggests the fix — not a bare HTTP 307, and not a silent timeout on sync🟡 packages/infra/src/http/node-http-client.ts follows 301/302/303/307/308, but no test covers an SSO redirect landing on a login page, or a 502, and the hub-import path reports the raw status. Common in enterprise orgs — reported from testing

TP-06 — Marketplace Discovery

Everything in this plan is UI. Automation asserts what the providers return; nobody has automated what the user sees.

#ScenarioExpected resultAutomation → where to focus
6.1Open the Marketplace webview with several sources configuredBundles render with title, description, version, source and icon🟡 test/ui/marketplace-view-provider.test.ts covers the data handed to the webview. Rendering is unverified
6.2Search for a known bundle and apply the filtersRelevant results🟢 packages/app/test/registry/search-registry-bundles.test.ts, packages/infra/test/search/**
⭐ 6.3Open a bundle's details viewDescription, version, contents, source — and the README.md renders correctly🟡 Wiring covered by test/ui/marketplace-view-provider.test.ts. Rendered markdown is unverified
6.4Reload the window with the marketplace openRestores without a blank panel or duplicated content🟡 test/ui/marketplace-view-provider.eventHandling.test.ts
⭐ 6.5Repeat 6.1–6.4 with the extension running in KiroWebviews render and behave the same🔴 No test covers Kiro — verify manually

TP-07 — Bundle Installation

Destinations come from packages/infra/src/writers/default-layouts.json. Note the host-specific routing: Kiro folds prompts/ and instructions/ into steering/ and has no hooks/ or plugins/ route; VS Code routes chatmodes/ into agents/. deployment-manifest.yml and README.md are never copied into the installed layout.

Layout resolution is well covered by unit tests. What they use is a mocked filesystem — so the manual focus is real files on a real machine, in the real host.

#ScenarioExpected resultAutomation → where to focus
⭐ 7.1Install at user scope for VS CodeLands under ~/.copilot/ per the kind routes🟡 Layer resolution covered by packages/app/test/install/layout-resolver.test.ts. Only skills assert a ~/.copilot/ destination, and test/services/skills-service.test.ts compares a path against itself rather than against production code — treat the general kind set as unverified
⭐ 7.2Install at repository scope for VS CodeLands under <workspace>/.github/🟢 test/e2e/repository-level-installation.test.ts, packages/infra/test/writers/repo-scope-writer.test.ts
⭐ 7.3Install at user scope for KiroLands under ~/.kiro/🟢 test/services/user-scope-service.test.ts writes and asserts ~/.kiro/agents/ on a real temp filesystem. (Note: kiro-transformer.test.ts is about agent frontmatter name fields, not routing)
⭐ 7.4Install at repository scope for KiroLands under <workspace>/.kiro/🟢 test/services/repository-scope-service.test.ts (Host-Aware Destinations) asserts files land under .kiro/ and never .github/, plus skills dirs, git-exclude paths and unsync cleanup
7.5Install for Claude Code and WindsurfCorrect per-host transform and layout🟢 packages/app/test/transform/claude-code-transformer.test.ts, windsurf-transformer.test.ts
⭐ 7.6Install a collection containing every primitive kindEvery kind routed correctly; nothing silently dropped🟡 5 of 7 kinds are covered. test/services/repository-scope-service.test.ts exercises prompts, instructions, agents, chatmodes and skills; test/e2e/skills-workflow.test.ts covers four of those. No suite installs hooks/ or plugins/, and none installs all kinds in one collection
⭐ 7.7In Kiro, check where prompts/ and instructions/ landedBoth folded into steering/; nothing left in a prompts/ or instructions/ directory🟢 test/services/repository-scope-service.test.ts asserts both types resolve to .kiro/steering/; packages/infra/test/stores/layout-config-store.test.ts asserts kindRoutes['prompts/'] === 'steering/'
⭐ 7.8Confirm the installed primitives in Copilot and KiroPrompts invocable, instructions/steering applied, agents and skills available🔴 Nothing asserts a real agent consumes our output. Highest-value row in this plan
7.9Install a collection declaring MCP serversServers appear in the mcp.json the host reads, and the host can start them🟡 Broadly covered — see MCP servers and inputs below for the per-case detail and the two gaps
7.10Install at workspace and project scopeFiles land in the corresponding workspace paths🟢 test/services/user-scope-service.test.ts, packages/app/test/install/layout-resolver.test.ts
7.11View Bundle Details on the installed bundleInstalled version, scope and source all accurate🟢 packages/app/test/registry/list-installed-bundles.test.ts
7.12Install a second bundle from a different sourceBoth coexist; neither overwrites the other🟢 packages/app/test/install/install-bundle.test.ts
⭐ 7.13Repeat 7.1–7.8 through the CLI, passing kiro and vscode as explicit targetsSame on-disk result as the extension🟡 packages/cli/test/commands/install.test.ts covers the CLI alone. No test diffs CLI output against extension output

MCP servers and inputs

mcpServers and mcpInputs are declared at the top level of the manifest and land in an mcp.json whose location depends on scope and host. Row 7.9 is the smoke check; these are the cases worth walking.

#ScenarioExpected resultAutomation → where to focus
7.14Install a bundle declaring MCP servers at user scopeServers written to the user-level mcp.json, with a tracking entry so they can be removed later🟡 test/services/mcp-server-manager.test.ts has only 4 tests, two of which are named "may fail if mcp.json has syntax errors". The user-scope path is far weaker than the workspace one — verify it by hand
7.15Install at repository/workspace scope, into a workspace that already has an mcp.json with unrelated servers.vscode/mcp.json (and the .vscode dir) created if absent; new servers merged; pre-existing and other bundles' servers preserved🟢 test/services/mcp-server-manager.repositoryScope.test.ts — 14 tests on a real filesystem, incl. creation, merge, preservation and per-bundle tracking
7.16Install in local-only mode, then in commit mode.vscode/mcp.json added to git exclude for local-only, not for commit🟢 same suite
7.17Install two bundles that declare the same server, then uninstall the one that wonExactly one server stays active; the duplicate is disabled and then re-enabled when the active one goes; identity ignores headers and env, and stdio never collides with remote🟢 mcp-config-service.duplicateDetection.test.ts and .duplicateLifecycle.test.ts
7.18Install a bundle declaring a remote server (HTTP or SSE) using ${bundlePath} and environment variables in the URL and headersType resolved correctly, variables substituted in both URL and headers, disabled/description preserved🟢 mcp-config-service.remoteServers.test.ts, incl. Unix socket and Windows named-pipe URLs
7.19Install a bundle declaring mcpInputs, then a second bundle declaring an input with the same idInputs merged, deduplicated by id keeping the existing definition, and written to mcp.json🟢 mcp-config-service.inputs.test.ts (mergeInputs, mergeServers() with inputs)
7.20Install a bundle whose mcpServers reference a ${input:id} placeholder with no matching entry in mcpInputsThe gap is reported at install or validation time; the user is not left with a server that silently cannot start🔴 The suite covers the opposite direction — removeOrphanedInputs handles inputs defined but unreferenced. Nothing asserts referenced-but-undefined, and deployment-manifest-validator.test.ts does not check it either, though mcp-config-service.ts already scans for ${input:…}. Reported from testing
7.21Check where the mcp.json landed in Kiro versus VS CodeEach host's config is written where that host actually reads it🔴 test/utils/mcp-config-locator.test.ts has 3 tests and none covers Kiro. getVsCodeVariant() branches on Insiders, Cursor and Windsurf but not Kiro, and the workspace path is hardcoded to .vscode

hooks/ and plugins/ are not supported on Kiro, so there is no scenario for them here. If that changes, add a row and assert the routing.

On 7.21. Verify against whatever the current behaviour is meant to be rather than assuming — the per-host MCP path is being addressed separately.

Coverage breakdown

7.1–7.4 — the four host/scope destinations

  • VS Code repository → .github/ preserved (real fs, mock vscode)
  • Kiro repository → .kiro/, never .github/ (real fs, mock vscode)
  • Kiro user → ~/.kiro/agents/ (real fs via tmpdir)
  • getInstallDirectory returns .github-based paths for repository scope, and still supports user and workspace scope — test/services/bundle-installer.repositoryScope.test.ts (mock vscode)
  • Layer resolution: kindRoutes deep-merge, baseDir override, skipPaths inheritance, ${workspaceRoot} substitution incl. a .kiro baseDir (pure)
  • VS Code user scope → ~/.copilot/ for the general kind set. Only skills touch this path, and test/services/skills-service.test.ts asserts a path against itself
  • Installing while running in Kiro — not tested there

7.5 — Claude Code and Windsurf

  • Transformer behaviour per target — packages/app/test/transform/claude-code-transformer.test.ts, windsurf-transformer.test.ts, transformer-registry.test.ts (fake port)
  • Destination paths for these two hosts asserted on a real filesystem

7.6–7.7 — every primitive kind

  • Skills install end to end (nock + mock vscode)
  • prompts/ and instructions/ both → .kiro/steering/ (real fs)
  • Skill id sanitisation, id override from manifest, unknown item types handled, special characters in paths — packages/infra/test/writers/repo-scope-writer.test.ts (fake port)
  • Prompts, instructions, agents, chatmodes and skills all routed — test/services/repository-scope-service.test.ts (real fs)
  • A single collection carrying all kinds at once
  • chatmodes/agents/ folding on VS Code

7.8 — the host agents consume it

  • Prompts invocable in Copilot; instructions applied; agents selectable
  • Steering, agents and skills in effect in Kiro

7.9, 7.14–7.21 — MCP servers and inputs

  • Workspace mcp.json created, merged, and other bundles' servers preserved; per-bundle tracking for uninstall — test/services/mcp-server-manager.repositoryScope.test.ts (real fs)
  • Server-ID conflict, and overwrite honoured — same suite (real fs)
  • Git exclude added for local-only, omitted for commit, cleaned on uninstall — same suite (real fs)
  • Uninstall removes only that bundle's servers — same suite (real fs)
  • Duplicate identity for stdio/HTTP/SSE, ignoring headers and env; first kept, later ones disabled; no stdio↔remote collision — mcp-config-service.duplicateDetection.test.ts (real fs)
  • Duplicate re-enabled when the active server is removed; one stays active until all bundles go — .duplicateLifecycle.test.ts (real fs)
  • Remote HTTP/SSE type guards, URL and header variable substitution, socket and named-pipe URLs — .remoteServers.test.ts (real fs)
  • Input merge, dedupe by id, propagation into the merged config and into mcp.json.inputs.test.ts (real fs)
  • Orphaned inputs removed; inputs referenced from a remote server's headers kept; shared inputs kept while one referencer remains — same suite (real fs)
  • mcpInputs/mcpServers carried from the top-level manifest fields — packages/infra/test/adapters/github-adapter.test.ts, lib/test/generate-manifest.test.ts (fake port / real fs)
  • User-scope install beyond four shallow tests, two of which are named "may fail if mcp.json has syntax errors"
  • A ${input:id} referenced but not defined — the opposite direction to removeOrphanedInputs
  • Per-host config location: no Kiro branch in getVsCodeVariant(), and .vscode hardcoded for workspace scope
  • The host actually starting an installed server

7.10–7.12 — scopes and coexistence

  • Workspace and project scope paths (real fs via tmpdir)
  • Installed bundle listing reflects version, scope and source (fake port)
  • Two bundles coexisting without overwriting (fake port)
  • Install pipeline stage failures wrapped as typed errors (fake port)

7.13 — CLI parity

  • CLI install/uninstall in isolation (real fs via tmpdir)
  • Any test diffing CLI output against extension output

Destination reference

HostUser baseRepository baseNotable routing
vscode~/.copilot<ws>/.githubchatmodes/agents/
vscode-insiders~/.copilot<ws>/.githubsame as vscode
kiro~/.kiro<ws>/.kiroprompts/ and instructions/steering/; no hooks/, no plugins/
claude-code~/.claude<ws>/.claudeprompts/commands/
windsurf~/.codeium/windsurf<ws>/.windsurfprompts/ and instructions/rules/
copilot-cli~/.copilot<ws>/.githubalso skips collections/

TP-08 — Local Profiles and Favorites

Effectively fully covered. test/commands/profile-commands.test.ts walks the whole lifecycle — create, edit, activate (including installing bundles and syncing to Copilot), deactivate (with cleanup prompts and optional uninstall), delete (confirmation, refusing to delete an active profile), export, import, list and profile switching. Run this as a smoke pass unless your change is here; the only residual gap is the Kiro host in 8.6.

#ScenarioExpected resultAutomation → where to focus
8.1Create a profile referencing several primitives, then reloadPersists exactly as authored🟢 test/commands/profile-commands.test.ts (createProfile: name uniqueness, bundle selection), packages/app/test/registry/local-profile-crud.test.ts
8.2Activate itReferenced primitives applied to the workspace🟢 test/commands/profile-commands.test.ts (activateProfile, incl. deactivating others), packages/app/test/registry/activate-registry-profile.test.ts
8.3Edit and re-activateChanges take effect with no stale leftovers🟢 test/commands/profile-commands.test.ts (editProfile: rename, add/remove bundles, ID preserved)
8.4DeactivatePrimitives removed; unrelated files untouched🟢 packages/app/test/registry/deactivate-registry-profile.test.ts
8.5Export Profile, then Import Profile on a clean profileRound-trips completely🟢 test/commands/profile-commands.test.ts covers both halves — export serializes to JSON with bundle configs and prompts for a location; import prompts for the file, validates structure, handles duplicate names and generates a new ID. Plus exportLocalProfile/importLocalProfile in packages/app/test/registry/local-profile-crud.test.ts (note: packages/app/test/search/export-profile.test.ts is a different feature — shortlist→profile export)
⭐ 8.6Activate and deactivate the same profile in VS Code, then in KiroPrimitives written to and removed from that host's layout, not the other's🟡 Activation and deactivation are covered, and path routing is asserted on a real filesystem (see TP-11). Not tested in Kiro — verify there manually
8.7List All ProfilesComplete and accurate🟢 packages/app/test/registry/list-all-profiles.test.ts
8.8Toggle Favorite, then switch Show Favorites / Show All ProfilesFiltered view correct; title actions follow the promptRegistry.favoritesViewActive context key🟢 packages/infra/test/stores/favorites-store.test.ts, test/ui/registry-tree-provider.test.ts
8.9Delete a profileGone from the tree and from List All Profiles🟢 packages/app/test/registry/local-profile-crud.test.ts
⭐ 8.10Activate a profile where one bundle fails to download (e.g. GitHub answers 502)The user sees a clear notification naming the bundles that could not be installed, and the profile is not reported as fully active🔴 No coverage. test/commands/profile-commands.test.ts asserts activation marks the profile active and installs its bundles, but only on the success path. Today a per-bundle failure is silent, so the user believes content was installed when nothing was. Reported from testing

TP-09 — Hub Profiles and Sync

Better automated than it looks. test/commands/hub-sync-commands.test.ts and test/commands/hub-sync-history.test.ts cover the update/diff/sync/review commands and the full history lifecycle including rollback. Run this plan as a smoke pass unless your change is here.

#ScenarioExpected resultAutomation → where to focus
9.1Browse Hub Profiles, then View Hub ProfileContent and metadata readable before committing to anything🟢 test/commands/hub-profile-commands.test.ts, test/services/hub-manager-profiles.test.ts
⭐ 9.2Activate Hub Profile in VS Code, then in KiroPrimitives installed into the correct host layout each time; tree reflects the active state🟡 test/commands/hub-profile-activation-commands.test.ts, test/services/hub-profile-activation.test.ts. Not tested in Kiro — verify there manually
9.3Show Active Hub ProfilesMatches what is actually active on disk🟢 packages/infra/test/stores/profile-activation-store.test.ts
9.4Change upstream, then Check Hub Profile for UpdatesUpdate detected and flagged🟢 test/commands/hub-sync-commands.test.ts (Check For Updates), packages/app/test/registry/detect-updates.test.ts
9.5View Hub Profile ChangesDiff accurately describes what would change🟢 test/commands/hub-sync-commands.test.ts (View Changes)
9.6Sync Hub Profile NowProfile advances to the upstream state🟢 test/commands/hub-sync-commands.test.ts (Sync Profile)
9.7Review and Sync Hub ProfileLists each change, allows opting out per change🟡 test/commands/hub-sync-commands.test.ts (Review And Sync) covers the review dialog and the no-changes case. Opting out of one change while accepting others is not asserted
9.8View Hub Profile Sync HistoryEvery sync recorded in order🟢 test/commands/hub-sync-history.test.ts — records additions, updates, removals and metadata changes; asserts chronological order and limits
9.9Rollback Hub Profile to a previous entryEarlier state restored exactly🟢 test/commands/hub-sync-history.test.ts (Rollback to History Entry) — restores state, records the rollback as a new entry, errors on a non-active profile
9.10Clear Hub Profile Sync History, then Deactivate Hub ProfileHistory clears without touching active state; deactivation removes primitives cleanly🟢 test/commands/hub-sync-history.test.ts (Clear History, including per-profile scoping), test/services/hub-profile-deactivation.test.ts
⭐ 9.11Activate a hub profile where one bundle fails to downloadSame expectation as 8.10 — failures named, profile not reported as fully active🔴 No coverage — see 8.10

TP-10 — Update Lifecycle

#ScenarioExpected resultAutomation → where to focus
⭐ 10.1Publish a newer version upstream, then Check for Bundle UpdatesBundle flagged in the tree with the correct contextValue🟢 test/commands/bundle-commands.checkBundleUpdates.test.ts (selection dialog, "up to date" case), packages/app/test/registry/detect-updates.test.ts
⭐ 10.2Confirm the user is notified of the available updateNotification appears per the configured preference🟡 test/services/notification-manager.property.test.ts covers the policy. The notification a user actually sees is not asserted
⭐ 10.3Update BundleVersion advances; content replaced, not duplicated🟢 test/commands/bundle-commands.updateBundle.test.ts (incl. the versioned-ID-after-consolidation regression), test/e2e/bundle-update-github.test.ts
10.4Enable Auto-Update, then Disable Auto-UpdatecontextValue and available menu entries change to match🟢 test/ui/auto-update-toggle.property.test.ts, test/e2e/context-menu-regression.test.ts
10.5updateCheck.autoUpdate on, with an update availableInstalls in the background and notifies🟢 packages/app/test/update/auto-update.test.ts, test/services/auto-update-service.test.ts
10.6Walk updateCheck.frequency through daily, weekly, manualScheduler honours each value🟢 test/services/update-scheduler.property.test.ts
10.7Check for updates from the CLIProposed automatically, or reported when checked🟢 packages/cli/test/commands/doctor-status-init-update.test.ts
⭐ 10.8Apply the update from the CLI, then verify in Copilot and KiroSame result as the extension; agents pick up new content, stale content gone🔴 The agent-consumption half has no coverage, and cross-layer parity is manual

TP-11 — Repository Scope and Lockfile — cannot be waived

prompt-registry.lock.json is the source of truth for repository scope. A regression corrupts state a whole team shares through Git.

Run every row in both hosts. Repository scope resolves its destination from host detection, so this is where a detection regression does the most damage. This is the best-automated area in the document — which is why the manual focus is narrow and specific: real Git, real clone, real Kiro.

#ScenarioExpected resultAutomation → where to focus
⭐ 11.1Install at repository scope in commit modeCommittable lockfile entry; files land in the repository🟢 test/services/lockfile-manager.test.ts, bundle-installer.repositoryScope.test.ts, packages/app/test/stores/json-lockfile-store.test.ts
⭐ 11.2Install at repository scope in local-only modeEntry marked local-only, excluded from the commit🟢 test/services/repository-scope-service.test.ts (+ .property.test.ts)
⭐ 11.3Do 11.1 with the extension running in VS Code, then in KiroContent lands in <ws>/.github/ under VS Code and <ws>/.kiro/ under Kiro — never .github/ while running in Kiro🟡 test/services/repository-scope-service.test.ts (Host-Aware Destinations) covers both hosts on a real filesystem, including the no-regression case for .github/. What is untested is detection from a real running Kiro — the routing is right if the target is right
11.4Move to Repository (Commit), then (Local Only) from user scopeFiles and lockfile move together; contextValue updates🟢 test/commands/bundle-scope-commands.test.ts (moveToRepository: both modes, cancellation, not-installed and no-workspace errors)
11.5Move to User from each repository modeReverse move complete, no lockfile residue🟢 test/commands/bundle-scope-commands.test.ts (moveToUser), test/services/user-scope-service.unsync.test.ts
11.6Switch to Local Only, then Switch to CommitMode flips in place without reinstalling🟢 test/services/repository-scope-service.test.ts
⭐ 11.7Commit the lockfile, clone fresh elsewhere, activate in the host it was created in, then in the other hostBundles restored from the lockfile alone; a lockfile written under one host is read correctly under the other🟡 test/e2e/lockfile-source-of-truth.test.ts covers restore-from-lockfile. A real clone, and cross-host reads, are manual
11.8Inspect the lockfile after each operationValid, minimal, diff-friendly — no unrelated churn🟡 Shape covered by packages/app/test/stores/json-lockfile-store.test.ts. Diff noise is a human judgement
11.9Open a repository whose lockfile came from the previous majorRead without migration errors or needless rewriting🟡 test/services/lockfile-manager.test.ts has a Backward Compatibility - Legacy SourceId Format suite (legacy hub-prefixed ids, mixed formats, many segments, write-new/preserve-old), and test/e2e/lockfile-source-of-truth.test.ts resolves legacy ids. What is missing is a lockfile captured from an actually shipped release
11.10Delete an upstream source, then Clean Up Stale Repository BundlesStale entry removed; valid entries untouched🟢 test/commands/bundle-commands.cleanupStale.property.test.ts, test/services/scope-conflict-resolver.test.ts

Coverage breakdown

This is the most heavily automated area in the repository — test/services/lockfile-manager.test.ts alone carries well over a hundred assertions, against a real filesystem. Read the unticked boxes as the whole point of running TP-11 by hand.

11.1 / 11.2 — install at repository scope, both modes

  • Lockfile created with $schema, version, generatedAt, generatedBy, 2-space indentation (real fs, mock vscode)
  • Bundle entry records version, sourceId, sourceType, installedAt, file checksums (real fs)
  • commitMode deliberately not written into entries — it is implied by which file the entry lives in (real fs)
  • Commit mode writes prompt-registry.lock.json; local-only writes prompt-registry.local.lock.json (real fs; also packages/app/test/stores/json-lockfile-store.test.ts, fake port)
  • The two lockfiles stay separate; source, hub and profile sections recorded (real fs)
  • Local lockfile added to .git/info/exclude on first local-only install, not duplicated on later ones, skipped when .git is absent (real fs)
  • Repository scope routes through RepositoryScopeService, and LockfileManager is not called for user scope (mock vscode)
  • Writer places prompts, instructions, agents and skills, and honours git-exclude only in local-only mode — packages/infra/test/writers/repo-scope-writer.test.ts (fake port)
  • Behaviour in a repository with an unusual Git setup (worktree, submodule, no .git/info/, pre-existing exclude section from another tool)

11.3 — host-aware destination

  • Kiro routes under .kiro/, never .github/; VS Code keeps .github/ (real fs, mock vscode)
  • getTargetPath/getTargetDirectory host-aware per file type; skills under .kiro/skills/ (real fs)
  • Tracked git-exclude paths equal the paths actually written (real fs)
  • Detection from a real running Kiro — the routing is correct if the resolved target is correct, and that resolution is what is untested

11.4 / 11.5 / 11.6 — moving and switching

  • moveToRepository in both modes, with cancellation and not-installed / no-workspace errors (mock vscode)
  • moveToUser from repository scope (mock vscode)
  • updateCommitMode moves the entry between lockfiles, preserves all metadata, copies the source entry, updates generatedAt, errors when the bundle is missing, emits onLockfileUpdated (real fs)
  • Git-exclude added when moving to local-only and removed when the local lockfile empties (real fs)
  • Other bundles in the source lockfile are preserved (real fs)
  • switchCommitMode scans host-aware directories on a Kiro host (real fs)
  • A move performed while the file is open and dirty in the editor

11.7 — clone fresh and restore

  • listInstalledBundles(repository) returns bundles from the lockfile; empty when absent (nock + mock vscode)
  • Lockfile takes precedence over stale RegistryStorage records (nock + mock vscode)
  • Repository install does not create a RegistryStorage record (nock + mock vscode)
  • sourceId is deterministic and URL-normalised, so a lockfile is portable across hub configurations (nock)
  • An actual git clone into a new directory
  • Restoring on a different OS from the one that wrote the lockfile
  • Reading a lockfile written under one host while running the other

11.8 — lockfile hygiene

  • Atomic write via temp file and rename; no corruption under concurrent writes (real fs)
  • Corrupted lockfile handled gracefully on read; validate() reports missing fields and schema version (real fs)
  • Orphaned sources cleaned up when a bundle is removed; sources still referenced are kept (real fs, fake port)
  • Conflict detected and logged when the same bundle id exists in both lockfiles (real fs)
  • Diff noise across a realistic sequence of operations — a human judgement no assertion makes

11.9 — previous-major lockfile

  • Legacy hub-prefixed sourceId read correctly, including multiple and mixed formats and many segments (real fs)
  • New format written on update, legacy preserved when untouched (real fs)
  • A lockfile captured from an actually shipped release rather than hand-written in a fixture

11.10 — stale cleanup

  • Stale entries with missing files removed; info message when there are none; user cancellation respected (nock + mock vscode)
  • Property-based cleanup coverage — test/commands/bundle-commands.cleanupStale.property.test.ts (mock vscode)
  • Uninstalling the last bundle deletes the lockfile and fires the event with null; other bundles preserved (nock)

TP-12 — Uninstall and Cleanup

#ScenarioExpected resultAutomation → where to focus
⭐ 12.1Uninstall a user-scope bundle in VS Code, then in KiroFiles removed from ~/.copilot/ and ~/.kiro/ respectively🟡 packages/app/test/install/uninstall-bundle.test.ts, uninstall-pipeline.test.ts. Not tested in Kiro
12.2Uninstall one of two bundles installed side by sideThe other's files untouched🟢 packages/app/test/registry/uninstall-installed-bundle.test.ts
⭐ 12.3Uninstall at each repository mode, in both hostsFiles removed from .github/ or .kiro/ as appropriate; matching lockfile entry goes too🟡 Covered for VS Code by test/e2e/lockfile-source-of-truth.test.ts; not tested in Kiro
12.4Hand-edit an installed file, then uninstallLocal-modification warning appears; choice honoured🟢 test/services/local-modification-warning-service.test.ts (+ .property.test.ts)
12.5Leave unrelated files alongside a bundle, then uninstallUnrelated files preserved🟢 test/e2e/uninstall-preserves-unrelated-files.test.ts
12.6Uninstall everything, then inspect the target directoriesNo orphaned directories or empty scaffolding🟡 Pipeline covered; leftover empty directories on real disk are the gap
⭐ 12.7Delete an installed bundle's files from disk by hand, then uninstall it from the tree viewUninstall is offered and cleans up the lockfile entry and registry record🔴 Known defect. registry-tree-provider.ts sets contextValue = 'installedBundle.filesMissing', which appears in no view/item/context when clause in package.json — every uninstall clause matches installed_bundle_* instead. The action is therefore unavailable and broken installs cannot be cleaned up from the UI. Reported from testing

TP-13 — Settings

#ScenarioExpected resultAutomation → where to focus
13.1All 9 promptregistry.* settings at defaults, main flows exercisedBehaviour matches the documented defaults🟡 test/config/package-configuration.test.ts asserts schema, defaults and enums for the 4 updateCheck.* settings only. The remaining 5 have no schema or default assertions
13.2installationScope set to user, workspace, project in turnDefault install target follows the setting🟢 test/services/user-scope-service.test.ts, packages/app/test/install/layout-resolver.test.ts
⭐ 13.3Set the settings in Kiro and confirm they are readSettings apply the same way; nothing depends on a VS Code-only config path🔴 No test covers Kiro — verify manually
13.4Turn enableLogging offOutput channel quiet; genuine errors still surfaced🟡 Logger behaviour covered in test/utils/**; "errors still reach the user" is a judgement call
13.5Turn autoCheckUpdates off and reloadNo update check on activation🔴 updateCheck.enabled scheduling is covered by test/services/update-scheduler.property.test.ts, but autoCheckUpdates appears in no behavioural test — only as a serialized field in packages/app/test/registry/registry-settings.test.ts
13.6Export Settings, then Import Settings into a clean profileFull configuration round-trips🟡 packages/app/test/registry/registry-settings.test.ts covers serialization properly (JSON/YAML, version checks, replace strategy). test/commands/settings-commands.test.ts only asserts the commands are callable. The clean-profile round-trip is manual
13.7Open SettingsExtension's settings scope opens directly🔴 No test references the openSettings command
13.8Compare reference/settings.md against package.jsonNames, types, defaults and enums match exactly🔴 No test compares docs against the manifest. Easy automation win

TP-14 — Authoring, Scaffolding and Publishing

Local authoring is well covered. Everything involving a real GitHub runner is not covered at all.

#ScenarioExpected resultAutomation → where to focus
⭐ 14.1Scaffold Project into an empty folderDocumented structure created, including the CI workflow🟢 test/commands/scaffold-command.test.ts (directory structure, example files), test/e2e/github-scaffold-integration.test.ts, packages/cli/test/commands/scaffolding.test.ts
14.2Scaffold Project in a workspace that already has contentExisting files untouched; nothing clobbered🟢 test/commands/scaffold-command.test.ts ("should not overwrite existing directory")
⭐ 14.3Add Resource for each of prompt, instruction, agent and skill, plus a README.mdEach created with valid frontmatter from the template🟢 packages/app/test/collection/generate-skill.test.ts, lib/test/skills.test.ts
14.4Create New CollectionValid deployment-manifest.yml with id, version and name🟢 lib/test/generate-manifest.test.ts, packages/core/test/domain/collection/manifest-validator.test.ts
⭐ 14.5Validate Collections against itPasses🟢 lib/test/validate.test.ts, lib/test/collections.test.ts
14.6Break the manifest, re-run Validate CollectionsErrors precise and located🟢 lib/test/validate.test.ts
14.7Validate APM Package on a sample packageReported against schemas/apm.schema.json🟢 packages/infra/test/adapters/apm-adapter.test.ts
⭐ 14.8Push to a real GitHub repository with the scaffolded runner configurationWorkflow runs and validation passes on the runner🔴 The scaffolded workflow is generated but never executed on a runner
⭐ 14.9Let the workflow finishA release is pushed to GitHub with the expected artifacts and correct version🔴 lib/test/publish-collections.test.ts is dry-run only. No real publish is ever tested
⭐ 14.10Change the collection and push againA new release with a correctly incremented version — not a re-tag, not a skipped bump🟡 Version maths covered by lib/test/hub-release-analyzer.test.ts and version-compute. Real second release is unverified
14.11List All CollectionsComplete listing🟢 lib/test/collections.test.ts, packages/cli/test/commands/collection-bundle.test.ts
14.12Open a collection, manifest and hub config in the editorBundled schemas give completion and inline validation🔴 Editor schema association is not tested
⭐ 14.13Run 14.1, 14.3, 14.5 and the publish flow through the CLISame result as the extension🟡 Per-command coverage exists. Cross-layer parity is manual

TP-15 — CLI (ai-primitives-hub)

The bar is parity: the same operation must produce the same on-disk result as the extension. Per-command behaviour is well covered; the parity claim itself is not covered by anything.

#ScenarioExpected resultAutomation → where to focus
15.1--help, --version, and a subcommand's --helpHelp renders correctly at every level🟢 packages/cli/test/framework/help-renderer.test.ts, golden.test.ts
⭐ 15.2init, status, doctor in a real projectEach reports the environment accurately🟡 packages/cli/test/commands/doctor-status-init-update.test.ts uses a test context. A real project is the gap
⭐ 15.3install, apply, update, uninstall for a bundleOn-disk result matches the extension for the same bundle🟡 packages/cli/test/commands/install.test.ts, uninstall.test.ts. Nothing diffs against the extension
⭐ 15.4source, hub, profile subcommandsParity with the equivalent extension commands🟡 packages/cli/test/commands/source.test.ts, hub.test.ts, profile.test.ts — each alone
⭐ 15.5target-types, then target-add for both vscode and kiroBoth listed as supported; both persisted and reflected in target-list🟢 packages/cli/test/commands/target.test.ts, packages/infra/test/stores/target-state-store.test.ts
⭐ 15.6Install the same collection with vscode and with kiro as the targetFiles land under .copilot/.github and .kiro respectively, matching what the extension produces in each host🟡 Transformers covered per target. The cross-layer match is manual
15.7target-remove for one of themRemoved without disturbing the other target's installed content🟢 packages/cli/test/commands/target.test.ts
15.8discover in a real projectRecommendations sensible for the detected context🟡 packages/cli/test/commands/discover.test.ts, packages/app/test/discovery/recommendation-engine.test.ts. Whether results are useful is a judgement
15.9collection-create/list/validate/affected, bundle-build, bundle-manifest, version-computeCorrect outputs on a sample collection🟢 packages/cli/test/commands/collection-bundle.test.ts
15.10Generators — skill-create, skill-new, skill-validate, agent-create, hook-create, prompt-create, instruction-create, plugin-create, plugins-listEach produces a valid artifact🟢 packages/cli/test/commands/scaffolding.test.ts, misc.test.ts
15.11Index pipeline — index-harvest, index-build, index-search, index-shortlist, index-stats, index-report, index-export, index-evalIndex round-trips; search returns expected hits🟢 packages/infra/test/search/** including eval-pattern and bench
15.12config-get, config-listOutput reflects real configuration🟡 packages/cli/test/framework/config.test.ts covers resolution precedence thoroughly (defaults, project, user/XDG, env coercion). The two commands' own output is not asserted
15.13completion for each supported shellInstalls and works🟡 packages/cli/test/commands/completion.test.ts covers generation. Installing into a real shell is manual
15.14SEA binary from pnpm -C packages/cli run build:sea, on a machine with no Node.jsRuns standalone🔴 build:sea never runs in CI. No test executes the binary

The CLI has no editor to detect, so it takes the host as an explicit target rather than inferring it. That difference is the point of 15.5–15.7: a host bug can exist in one delivery layer and not the other.

TP-16 — Collection Scripts (lib)

#ScenarioExpected resultAutomation → where to focus
16.1Install from a clean npx, run each of the 11 bins with --helpEvery bin present and self-documenting🔴 lib/test/cli.test.ts only tests argument-parsing helpers (parseSingleArg, parseMultiArg). No test invokes a bin or its --help
⭐ 16.2validate-collections and validate-skills on valid inputBoth pass🟢 lib/test/validate.test.ts, lib/test/skills.test.ts
16.3The same two on deliberately invalid inputFailures precise and located🟢 lib/test/validate.test.ts
⭐ 16.4build-collection-bundle, generate-manifest, compute-collection-version twice on the same inputDeterministic and reproducible🟢 lib/test/generate-manifest.test.ts, lib/test/bundle-id.test.ts
16.5detect-affected-collections against a real diffCorrect affected set🟡 lib/test/collections.test.ts. A real Git diff is the gap
16.6publish-collections in dry-runNothing published🟢 lib/test/publish-collections.test.ts
16.7list-collections, create-skill, hub-release-analyzer, hub-ownership-analyzerExpected report or artifact🟡 lib/test/hub-release-analyzer.test.ts covers one of the four; hub-ownership-analyzer has no test
⭐ 16.8The github-actions/validate-collections action on a sample repositoryPasses and fails as expected🔴 This action has no test suite at all, and consumers depend on it in their own CI

TP-17 — Upgrade and Migration from the Previous Major — cannot be waived

Run against real state, not a fixture. The migration mechanism is covered; real upgrade-in-place from a previously shipped version is not.

#ScenarioExpected resultAutomation → where to focus
⭐ 17.1Install the previous major, build real state — hubs, sources, profiles, favorites, bundles at user and repository scopeA representative starting point exists on disk🔴 No previous-major state fixture exists anywhere
⭐ 17.2Install this release over the top and activateMigrations run once and complete without error🟡 test/services/migration-registry.test.ts runs migrations against synthetic state. Real accumulated state is the gap
⭐ 17.3Inspect all the state from 17.1Everything survives intact; nothing silently dropped🔴 Nothing asserts survival of state written by a previous release
17.4Check the source-id normalization migrationLegacy ids normalized and every reference updated🟢 test/migrations/source-id-normalization-migration.test.ts
17.5Reload and activate againMigration does not re-run; provably idempotent🟢 test/services/migration-registry.test.ts
⭐ 17.6Run 17.1–17.5 in VS Code, then repeat the whole sequence in KiroMigration works in both hosts; Kiro state is not migrated into VS Code paths or vice versa🔴 Not tested in Kiro, and no cross-host migration test exists
⭐ 17.7On migrated state, confirm installed content is still where the current host expects itKiro content still under .kiro/, VS Code content still under .github/ and ~/.copilot/🟡 Path resolution covered by packages/infra/test/storage/xdg-app-storage.test.ts. Post-migration reality is manual
⭐ 17.8Exercise G1 and G3 against the migrated stateWorks on migrated data, not only on freshly created data🔴 All suites start from clean state

Coverage breakdown

The migration mechanism is solid. What is absent is any state that a shipped release actually produced — every fixture is hand-built, which is precisely the risk this plan exists to cover.

17.1 / 17.3 — real previous-major state survives

  • Any fixture representing state written by a previously shipped version
  • Hubs, sources, profiles and favorites asserted to survive an upgrade
  • Installed bundles at user and repository scope surviving together
  • A partially-migrated state (upgrade interrupted midway)

17.2 / 17.5 — migrations run once, and are idempotent

  • Migration executes on first run only; skipped when already completed or explicitly skipped (mock vscode)
  • Completion persisted with a timestamp; full migration state retrievable (mock vscode)
  • Errors from a migration propagate rather than being swallowed (mock vscode)
  • Second run of the source-id migration is a provable no-op (mock vscode)
  • Two migrations that must run in a specific order, and what happens if one fails midway through a batch

17.4 — source-id normalization

  • Legacy id migrated to the new id; already-new ids left alone; non-hub ids untouched (mock vscode)
  • Source cache files renamed as part of the migration (mock vscode)
  • Installation records referencing the old sourceId updated (mock vscode)
  • Lockfiles containing legacy ids still resolve — test/services/lockfile-manager.test.ts, test/e2e/lockfile-source-of-truth.test.ts (real fs / nock)

17.6 / 17.7 — per host, and content still where the host expects it

  • XDG config/cache/data split; state round-trips and persists across separate storage instances — packages/infra/test/storage/xdg-app-storage.test.ts (fake port)
  • Host-aware destinations after a scope switch (real fs — see TP-11)
  • Running the migration sequence in Kiro — not tested there
  • That Kiro state is not migrated into VS Code paths, or the reverse
  • Migration on a machine where XDG_* variables are set to non-default values

17.8 — golden path on migrated state

  • Any suite that starts from migrated rather than clean state. All of them begin clean

TP-18 — Publish and Distribution

Almost entirely manual by nature — it involves real registries and real releases.

#ScenarioExpected resultAutomation → where to focus
18.1Trigger the Publishing workflow on a pre-release tagEvery job completes green🟡 The workflow gates itself (tag format, audit, Trivy). A dry run against a real tag is the only real check
⭐ 18.2Review the VS Code Marketplace listingCorrect version, README, icon and categories🔴 No coverage
18.3Review the Open VSX listingSame🔴 No coverage
⭐ 18.4Install from each marketplace into a clean VS Code, and into a clean KiroWorks end to end in both; Kiro can install the published artifact, not only a local VSIX🔴 No coverage
18.5Confirm the rollback pathPrevious version still installable; procedure written down🔴 No coverage

TP-19 — Documentation and Release Notes

#ScenarioExpected resultAutomation → where to focus
19.1Compare reference/commands.md and reference/settings.md against package.jsonBoth match exactly🔴 No docs-vs-manifest check exists. Same easy automation win as 13.8
19.2Review user guide pages for every changed behaviourUpdated to match the shipped build🔴 Editorial judgement
19.3pnpm -C website run buildClean build; new pages registered in docs/README.md and website/sidebars.ts🟢 Enforced by the docs.yml workflow
19.4Read the release notes end to endEvery breaking change listed with a migration note🔴 Editorial judgement
19.5Check version references in README.mdUpdated by version:bump:major🟡 version:update rewrites them. Nothing verifies the result
19.6Regenerate helper skill references via copy-skill-referencesReflect the current docs/ tree🔴 Nothing detects a stale generated index

Where The Coverage Gaps Cluster

Pulled out of the tables above. Five themes account for nearly every 🔴 row, and each is a candidate for #370:

GapRows affectedWhy automation does not reach it today
Nothing is tested in Kiro (routing itself is covered)1.2, 6.5, 8.6, 9.2, 12.1, 12.3, 13.3, 17.6runTests() is called without a version, so only VS Code is launched
Host agents consuming our output7.8, 10.8Needs a live Copilot or Kiro agent, not files on disk
Real GitHub — runners, releases, private repos5.8, 14.8, 14.9, 18.2–18.4Suites use nock; publish-collections is dry-run only
Cross-layer parity (extension vs CLI)7.13, 14.13, 15.3, 15.4, 15.6Both layers tested in isolation; nothing diffs their output
Previous-major upgrade state17.1, 17.3, 17.8Every fixture is hand-built; none came from a shipped release. (Legacy sourceId formats are covered — see 11.9)
Command surface breadth1.4, 1.5, 13.1, 13.5, 13.7Only 6 of 66 commands and 4 of 9 settings are asserted anywhere

Action items, cheapest first

Each of these closes one or more boxes without new infrastructure:

#ActionCloses
1Extend test/config/package-configuration.test.ts to all 9 settings, plus a getCommands() sweep over all 66 commands1.4, 1.5, 13.1
2Compare reference/commands.md and reference/settings.md against package.json in a test13.8, 19.1
3Install the same bundle via extension and CLI, then diff the resulting tree7.13, 14.13, 15.3, 15.4, 15.6
4Assert a ~/.copilot/ destination for the general kind set — and fix test/services/skills-service.test.ts, which asserts a path against itself7.1
5One fixture collection carrying every primitive kind, installed in a single test7.6
6Lint the generated CI workflow in the scaffold test14.1
7Staleness check on the generated skill-reference index19.6
8Capture a lockfile and a state directory from a shipped release as fixtures11.9, 17.1, 17.3
9Point @vscode/test-electron at the compiled artifact rather than source1.1
10Add a scheduled job that pushes to a scratch repository and asserts a real release14.8, 14.9

Running the extension suites against a Kiro build, and asserting a real agent consumes our output, both need new infrastructure and are not on this list.

Two claims in earlier drafts of this page were wrong and are worth stating plainly, because the same mistake is easy to repeat: hub profile sync history and rollback are well covered (test/commands/hub-sync-history.test.ts), and test/commands/** covers far more of the command surface than the service-level suites suggest. Check test/commands/ before assuming a command is unautomated.

Recording A Run

Release sign-off lives on the golden path page. When you run plans from this page — because a PR touched a specific area — record which ones in the PR or release issue:

PlanReason it was runResultOwnerDate

Two plans here block a major release outright, because both touch state users already have on disk:

See Also