feat(#2102): make pi a first-class installable runtime + fix its dispatch (ADR-1239)

Net-new EoS/pi installable runtime — purely additive (no prior runtime==='pi'
branches). pi is a bun-runtime programmatic-CLI whose /gsd command is registered
by a native ExtensionAPI extension and dispatches through the embedded engine.

Stage 1 (install plumbing):
- capabilities/pi/capability.json: full hostIntegration descriptor (imperative /
  slash-programmatic / active-model / native-extension / bun) + hostBehaviors
  {nativePlugin, pluginOnlyInstall}.
- --pi flag + interactive-menu renumber (All 17->18); pi added to RUNTIME_FLAG_IDS,
  RUNTIME_LABELS, RUNTIME_META, allRuntimes/runtimeMap, model-catalog defaults.
- Install mirrors OpenCode: pi installs the gsd.cjs extension + the shared engine
  payload (gsd-core + scripts + config markers) + the shared hooks bundle (spawned
  by the extension at lifecycle events, like OpenCode's plugin). pluginOnlyInstall
  EXCLUDES declarative command/agent/skill markdown, which pi has no host-read
  surface for (its /gsd is programmatic). _installNativePluginIfDeclared (extracted
  from the opencode-family path) copies pi/gsd.cjs -> ~/.pi/agent/extensions/gsd.cjs
  (global) / .pi/extensions/ (local). pi added to package.json files.
- Golden: new pi.json (320 files: extension + engine + 27-file hooks bundle, no
  markdown); the 16 other fixtures + claude-local change only by the shared
  model-catalog hash line.

Stage 2 (real dispatch + upgrades):
- Shared dispatchGsdCommand() (shell-command-projection): bounded, no-throw
  subprocess-shim to gsd-tools.cjs (the only full-surface dispatch path; no
  in-process full-hub factory exists). Fixes pi/gsd.cjs's createHub()-no-args bug
  (every dispatch was UnknownCommand) AND the identical bug in mcp-server.cts's
  gsd_invoke_command, which a vacuous unknown-family-only test had masked (now has
  a real dispatch regression test).
- pi/gsd.cjs: /gsd handler now (args, ctx) - tokenizes (quote-aware, via the
  shipped hooks/lib/git-cmd.js) + dispatches real family/subcommand (not hardcoded
  query/help); gsd_invoke gets a TypeBox (JSON-schema-fallback) parameters schema +
  consumes params; getArgumentCompletions; before_provider_request active-model
  steering (fail-open on null resolution); functional session_start /
  before_agent_start / session_before_compact hook bridges (spawn the shipped GSD
  hook scripts).
- EXTENSION_EVENT_SURFACES.pi expanded from ['tool_call'] to the full 30-event
  vocabulary.

Docs (host-integration matrix + how-to) + changeset (Added).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-07-11 19:34:23 -04:00
parent 246f566ee1
commit 79d7657eff
49 changed files with 1771 additions and 198 deletions

View File

@@ -42,7 +42,7 @@
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
"gsd-core/bin/shared/model-catalog.json": "d0b59409a46a456a",
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "8bc541aabc2e143c",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
@@ -289,7 +289,7 @@
"gsd-core/workflows/scan.md": "a7fecd67e5cd655f",
"gsd-core/workflows/secure-phase.md": "96b199dfac00e60f",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
"gsd-core/workflows/settings-advanced.md": "289b3d653d18e284",
"gsd-core/workflows/settings-advanced.md": "6d25100a9de15b31",
"gsd-core/workflows/settings-integrations.md": "4d3001ad2b5dad8f",
"gsd-core/workflows/settings.md": "8258f7bd3700d608",
"gsd-core/workflows/ship.md": "984bd7660e7791fd",

View File

@@ -113,7 +113,7 @@
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
"gsd-core/bin/shared/model-catalog.json": "d0b59409a46a456a",
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
@@ -360,7 +360,7 @@
"gsd-core/workflows/scan.md": "003883d71c37da7d",
"gsd-core/workflows/secure-phase.md": "29fc6b62c5c5dc62",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
"gsd-core/workflows/settings-advanced.md": "b6fa466a953dd3a2",
"gsd-core/workflows/settings-advanced.md": "414db4dbea97ba44",
"gsd-core/workflows/settings-integrations.md": "70515c5838fb9826",
"gsd-core/workflows/settings.md": "d96ddf01fb85e61e",
"gsd-core/workflows/ship.md": "7b8fe9f89143e648",

View File

@@ -112,7 +112,7 @@
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
"gsd-core/bin/shared/model-catalog.json": "d0b59409a46a456a",
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
@@ -359,7 +359,7 @@
"gsd-core/workflows/scan.md": "75c670d08cee8680",
"gsd-core/workflows/secure-phase.md": "64ec4d06ca85720a",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
"gsd-core/workflows/settings-advanced.md": "a44896a18f1f2edc",
"gsd-core/workflows/settings-advanced.md": "94d61da368e9f85b",
"gsd-core/workflows/settings-integrations.md": "dfe3672c4fabf139",
"gsd-core/workflows/settings.md": "acdd79110699a608",
"gsd-core/workflows/ship.md": "44af1c72d86e153b",

View File

@@ -41,7 +41,7 @@
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
"gsd-core/bin/shared/model-catalog.json": "d0b59409a46a456a",
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
@@ -288,7 +288,7 @@
"gsd-core/workflows/scan.md": "47371c2073d6c0be",
"gsd-core/workflows/secure-phase.md": "59d3c50aba8c9a6c",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
"gsd-core/workflows/settings-advanced.md": "fa4a285b6bcbc384",
"gsd-core/workflows/settings-advanced.md": "339def28c34b0797",
"gsd-core/workflows/settings-integrations.md": "53649313d20694ae",
"gsd-core/workflows/settings.md": "7e7458cdb2b68ec5",
"gsd-core/workflows/ship.md": "12e8e58c077a891a",

View File

@@ -45,7 +45,7 @@
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
"gsd-core/bin/shared/model-catalog.json": "d0b59409a46a456a",
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
@@ -292,7 +292,7 @@
"gsd-core/workflows/scan.md": "dfd92717caea0ce7",
"gsd-core/workflows/secure-phase.md": "cf78183f06a02582",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
"gsd-core/workflows/settings-advanced.md": "7147ba291d13e007",
"gsd-core/workflows/settings-advanced.md": "69f3a19bf2c61160",
"gsd-core/workflows/settings-integrations.md": "76eee76d6eb57657",
"gsd-core/workflows/settings.md": "3701faed09d55247",
"gsd-core/workflows/ship.md": "5f931a25ea9102a4",

View File

@@ -113,7 +113,7 @@
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
"gsd-core/bin/shared/model-catalog.json": "d0b59409a46a456a",
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
@@ -360,7 +360,7 @@
"gsd-core/workflows/scan.md": "003883d71c37da7d",
"gsd-core/workflows/secure-phase.md": "29fc6b62c5c5dc62",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
"gsd-core/workflows/settings-advanced.md": "b6fa466a953dd3a2",
"gsd-core/workflows/settings-advanced.md": "414db4dbea97ba44",
"gsd-core/workflows/settings-integrations.md": "70515c5838fb9826",
"gsd-core/workflows/settings.md": "d96ddf01fb85e61e",
"gsd-core/workflows/ship.md": "7b8fe9f89143e648",

View File

@@ -148,7 +148,7 @@
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
"gsd-core/bin/shared/model-catalog.json": "d0b59409a46a456a",
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
@@ -395,7 +395,7 @@
"gsd-core/workflows/scan.md": "1a3caa5d724d39e9",
"gsd-core/workflows/secure-phase.md": "db91810d16964b1e",
"gsd-core/workflows/session-report.md": "dd8fa011c9394075",
"gsd-core/workflows/settings-advanced.md": "38566aab0c529f2c",
"gsd-core/workflows/settings-advanced.md": "2431433811616f76",
"gsd-core/workflows/settings-integrations.md": "77730321d3d6d317",
"gsd-core/workflows/settings.md": "054c8c31b3905ced",
"gsd-core/workflows/ship.md": "d86233c9a365effd",

View File

@@ -43,7 +43,7 @@
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
"gsd-core/bin/shared/model-catalog.json": "d0b59409a46a456a",
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "10226e9512dd44bf",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
@@ -290,7 +290,7 @@
"gsd-core/workflows/scan.md": "dcc2f76d0850e2fb",
"gsd-core/workflows/secure-phase.md": "d87bd706f85bcad6",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
"gsd-core/workflows/settings-advanced.md": "8c88bbcb8fa0fa39",
"gsd-core/workflows/settings-advanced.md": "230a658de9c017a6",
"gsd-core/workflows/settings-integrations.md": "a1d146d6bfd14db5",
"gsd-core/workflows/settings.md": "f611f14f2f447f1e",
"gsd-core/workflows/ship.md": "b9dc0aaee0ff68e7",

View File

@@ -113,7 +113,7 @@
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
"gsd-core/bin/shared/model-catalog.json": "d0b59409a46a456a",
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
@@ -360,7 +360,7 @@
"gsd-core/workflows/scan.md": "47371c2073d6c0be",
"gsd-core/workflows/secure-phase.md": "c55975672c4e1895",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
"gsd-core/workflows/settings-advanced.md": "93a20fc634b05964",
"gsd-core/workflows/settings-advanced.md": "bf8ea69c8f7ae019",
"gsd-core/workflows/settings-integrations.md": "166ca51b1f33c2a3",
"gsd-core/workflows/settings.md": "0623c673eaf04799",
"gsd-core/workflows/ship.md": "38830806d244fe9c",

View File

@@ -42,7 +42,7 @@
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
"gsd-core/bin/shared/model-catalog.json": "d0b59409a46a456a",
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
@@ -289,7 +289,7 @@
"gsd-core/workflows/scan.md": "b28f65d88c522767",
"gsd-core/workflows/secure-phase.md": "f2957d4b88fb3746",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
"gsd-core/workflows/settings-advanced.md": "7374ef968d5280f0",
"gsd-core/workflows/settings-advanced.md": "49be159144d7f426",
"gsd-core/workflows/settings-integrations.md": "1dce76db0aca08a5",
"gsd-core/workflows/settings.md": "0845d073009a4619",
"gsd-core/workflows/ship.md": "dbf8bf636cb196c0",

View File

@@ -113,7 +113,7 @@
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
"gsd-core/bin/shared/model-catalog.json": "d0b59409a46a456a",
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
@@ -360,7 +360,7 @@
"gsd-core/workflows/scan.md": "47371c2073d6c0be",
"gsd-core/workflows/secure-phase.md": "e8855104c1e0417c",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
"gsd-core/workflows/settings-advanced.md": "e00cd1a5d3fbba9c",
"gsd-core/workflows/settings-advanced.md": "edd858cd6cfddaf1",
"gsd-core/workflows/settings-integrations.md": "3ea8095d5fad891b",
"gsd-core/workflows/settings.md": "1925ecc2225c2216",
"gsd-core/workflows/ship.md": "afd77be2093535f8",

View File

@@ -106,7 +106,7 @@
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
"gsd-core/bin/shared/model-catalog.json": "d0b59409a46a456a",
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
@@ -353,7 +353,7 @@
"gsd-core/workflows/scan.md": "003883d71c37da7d",
"gsd-core/workflows/secure-phase.md": "29fc6b62c5c5dc62",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
"gsd-core/workflows/settings-advanced.md": "b6fa466a953dd3a2",
"gsd-core/workflows/settings-advanced.md": "414db4dbea97ba44",
"gsd-core/workflows/settings-integrations.md": "70515c5838fb9826",
"gsd-core/workflows/settings.md": "d96ddf01fb85e61e",
"gsd-core/workflows/ship.md": "7b8fe9f89143e648",

View File

@@ -113,7 +113,7 @@
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
"gsd-core/bin/shared/model-catalog.json": "d0b59409a46a456a",
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
@@ -360,7 +360,7 @@
"gsd-core/workflows/scan.md": "ad8ebcad4626d4a8",
"gsd-core/workflows/secure-phase.md": "e9a488cec3b4efdc",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
"gsd-core/workflows/settings-advanced.md": "92524bd2a53b43f5",
"gsd-core/workflows/settings-advanced.md": "252b0d3edc315339",
"gsd-core/workflows/settings-integrations.md": "d1711a95f44fdbde",
"gsd-core/workflows/settings.md": "2e42ee34c791378a",
"gsd-core/workflows/ship.md": "924c79e3cfd1e42b",

View File

@@ -0,0 +1,322 @@
{
".gsd-profile": "0e716a5fef4e6dc1",
".gsd/defaults.json": "560664b045e645cb",
"extensions/gsd.cjs": "619cec0af9cfdadf",
"gsd-core/VERSION": "ef0deccd81a6723c",
"gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74",
"gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62",
"gsd-core/bin/gsd-tools.cjs": "6a7616125440c8b1",
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
"gsd-core/contexts/research.md": "b3285d8e7209cc3b",
"gsd-core/contexts/review.md": "dc578fdd74bbea11",
"gsd-core/references/agent-contracts.md": "ff65e633c656c0d2",
"gsd-core/references/agent-skills-bootstrap.md": "5ab875054b1adda9",
"gsd-core/references/ai-evals.md": "b5afa786b938671e",
"gsd-core/references/ai-frameworks.md": "f827de93dde124eb",
"gsd-core/references/api-coverage.md": "66264d41dfd9154a",
"gsd-core/references/artifact-types.md": "a6d2e1f9453ffbf5",
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
"gsd-core/references/checkpoints.md": "6aa620c6ca38bdf0",
"gsd-core/references/common-bug-patterns.md": "780145be56352626",
"gsd-core/references/context-budget.md": "f1ce57bf418824af",
"gsd-core/references/continuation-format.md": "ce73e57d053ab8a7",
"gsd-core/references/debugger-philosophy.md": "0466f95a3d6bfcd3",
"gsd-core/references/decimal-phase-calculation.md": "46b5ba045852c474",
"gsd-core/references/doc-conflict-engine.md": "67d019d23e17f934",
"gsd-core/references/domain-probes.md": "62d23ed1992c48a9",
"gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json": "72d1e29cedc854ec",
"gsd-core/references/edge-probe-fixtures/01-round-half-even/requirements.json": "fbc1b355d8625eeb",
"gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json": "fad67dcc8294f6da",
"gsd-core/references/edge-probe-fixtures/02-merge-intervals/requirements.json": "30a78ee9ce3473ea",
"gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json": "66dd60957fee45f0",
"gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/requirements.json": "47fca61f076835fa",
"gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json": "72d1e29cedc854ec",
"gsd-core/references/edge-probe-fixtures/04-money-rounding/requirements.json": "80f04f5c04fb24cf",
"gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json": "fad67dcc8294f6da",
"gsd-core/references/edge-probe-fixtures/05-list-dedupe/requirements.json": "d38147adb0e5b342",
"gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json": "bc552c01939bf4f8",
"gsd-core/references/edge-probe-fixtures/06-resolved-mixed/requirements.json": "30a78ee9ce3473ea",
"gsd-core/references/edge-probe-fixtures/06-resolved-mixed/resolutions.json": "688ec62c13e08afe",
"gsd-core/references/edge-probe.md": "5687eba25a078561",
"gsd-core/references/execute-mvp-tdd.md": "a98a270a7ab126bc",
"gsd-core/references/execute-phase-between-wave-reset.md": "3ad96ca0f7fee37e",
"gsd-core/references/execute-phase-context-guard.md": "982006c1f3364242",
"gsd-core/references/execute-phase-wave-guard.md": "de9ac22cead4cfd8",
"gsd-core/references/executor-examples.md": "ba59243ed45c8ab1",
"gsd-core/references/few-shot-examples/plan-checker.md": "2574808188ac9de4",
"gsd-core/references/few-shot-examples/verifier.md": "5badee4560b14ae8",
"gsd-core/references/gate-prompts.md": "e69f5993ab944d80",
"gsd-core/references/gates.md": "bd79c7f90c8cb8d7",
"gsd-core/references/git-integration.md": "77bf9dff38b2c9d4",
"gsd-core/references/git-planning-commit.md": "f897a15ebfc3f5a7",
"gsd-core/references/gsd-run-resolver.md": "e71eb728b84641b1",
"gsd-core/references/honest-verifier.md": "8815c9fc18c35719",
"gsd-core/references/ios-scaffold.md": "5ef0cb7e0fac891f",
"gsd-core/references/loop-hook-dispatch.md": "32e5dfb4dba76987",
"gsd-core/references/mandatory-initial-read.md": "fe59abce693717cf",
"gsd-core/references/model-profile-resolution.md": "f32bb05102839767",
"gsd-core/references/model-profiles.md": "e067ad3df6770db1",
"gsd-core/references/mvp-concepts.md": "72f7e8b1f8ae7118",
"gsd-core/references/phase-argument-parsing.md": "e5bbb985f3bc3e34",
"gsd-core/references/planner-antipatterns.md": "7ed54ec1e2cc54ac",
"gsd-core/references/planner-chunked.md": "79fe674221e738e6",
"gsd-core/references/planner-gap-closure.md": "76bee257911413e7",
"gsd-core/references/planner-graphify-auto-update.md": "6aeffd9097e25e22",
"gsd-core/references/planner-guidance.md": "96486cac2f7885e6",
"gsd-core/references/planner-human-verify-mode.md": "56d05e841630b3f4",
"gsd-core/references/planner-interface-context.md": "b28fa3da6ae739a8",
"gsd-core/references/planner-load-graph-context.md": "ca7a7af3f35ae61b",
"gsd-core/references/planner-mvp-mode.md": "ec33050db81101a8",
"gsd-core/references/planner-reviews.md": "dda0193a0fbd4947",
"gsd-core/references/planner-revision.md": "86ba8a511f081f05",
"gsd-core/references/planner-source-audit.md": "7de5bdb07232ce0b",
"gsd-core/references/planning-config.md": "ac409835e8260a3e",
"gsd-core/references/prohibition-probe-fixtures/01-streak-reminder/expected.json": "f10df472f2846cc6",
"gsd-core/references/prohibition-probe-fixtures/02-clean-utility/expected.json": "31e8a781eeffe020",
"gsd-core/references/prohibition-probe-fixtures/03-multi-prohibition/expected.json": "70a532a7cc1b6ae8",
"gsd-core/references/prohibition-probe.md": "605dc3f5a118ff3b",
"gsd-core/references/project-skills-discovery.md": "c155e03dce8dc3c2",
"gsd-core/references/questioning.md": "a8c988cab05f4651",
"gsd-core/references/research-documentation-lookup.md": "c070007d1d72ab71",
"gsd-core/references/research-philosophy.md": "62930e66cc979c1a",
"gsd-core/references/research-verification-protocol.md": "9c38c9d9a687e679",
"gsd-core/references/reviewer-instances.md": "385501e4f9bbb31d",
"gsd-core/references/revision-loop.md": "e55ff32dd98c63df",
"gsd-core/references/scout-codebase.md": "ba266ecc18fbf172",
"gsd-core/references/security-asvs-levels.md": "4774fac3b94b6ca8",
"gsd-core/references/skeleton-template.md": "528691d1f0efa878",
"gsd-core/references/sketch-interactivity.md": "7d982fe877e1e1cc",
"gsd-core/references/sketch-theme-system.md": "33e2e96e450456f8",
"gsd-core/references/sketch-tooling.md": "df6c4f24c1c27611",
"gsd-core/references/sketch-variant-patterns.md": "66c197aa4fb52810",
"gsd-core/references/specless-probe-fallback.md": "5e400dc05a15e972",
"gsd-core/references/spidr-splitting.md": "074ac154c0e4f906",
"gsd-core/references/tdd.md": "e4708ede157478b6",
"gsd-core/references/thinking-models-debug.md": "2da61022b16c4e7c",
"gsd-core/references/thinking-models-execution.md": "dcc650a8b5f3e049",
"gsd-core/references/thinking-models-planning.md": "7e19462313fa028f",
"gsd-core/references/thinking-models-research.md": "5f6bf3f3b889c6e4",
"gsd-core/references/thinking-models-verification.md": "a71a933d51ca3d8d",
"gsd-core/references/thinking-partner.md": "41069529ef776e39",
"gsd-core/references/ui-brand.md": "48717bcfcd63bd27",
"gsd-core/references/ui-consideration-probe.md": "7e019dfaae47f4c4",
"gsd-core/references/universal-anti-patterns.md": "6a1245050b21df01",
"gsd-core/references/untrusted-input-boundary.md": "d33b80d4d348599a",
"gsd-core/references/user-profiling.md": "b50416fe57c1b321",
"gsd-core/references/user-story-template.md": "0cc50e06a144ff8a",
"gsd-core/references/verification-overrides.md": "8213de9bd62283b6",
"gsd-core/references/verification-patterns.md": "72f4d4814fdae9a8",
"gsd-core/references/verify-mvp-mode.md": "534bdc7f2432903a",
"gsd-core/references/workstream-flag.md": "ca99ca79e716f0f5",
"gsd-core/references/worktree-branch-check.md": "21d9c31bf6542b93",
"gsd-core/references/worktree-path-safety.md": "3c8d74756f9b16a8",
"gsd-core/templates/AI-SPEC.md": "efa1f8354bd3a24b",
"gsd-core/templates/DEBUG.md": "a13470b82b1935e7",
"gsd-core/templates/README.md": "93d3426fc64e2c12",
"gsd-core/templates/SECURITY.md": "b628f7f1c6d2328f",
"gsd-core/templates/UAT.md": "9e296471b97ebcec",
"gsd-core/templates/UI-SPEC.md": "7dd5c7cdc7ece0ec",
"gsd-core/templates/VALIDATION.md": "f53e0ca061d3528e",
"gsd-core/templates/claude-md.md": "d1d333e4b963c0d2",
"gsd-core/templates/codebase/architecture.md": "6be88214162fdd89",
"gsd-core/templates/codebase/concerns.md": "efa26d1fb5132f25",
"gsd-core/templates/codebase/conventions.md": "c2e07698dad6b364",
"gsd-core/templates/codebase/integrations.md": "39bd23c71eedd564",
"gsd-core/templates/codebase/stack.md": "116e7e67dd87ddec",
"gsd-core/templates/codebase/structure.md": "222997133232a6f5",
"gsd-core/templates/codebase/testing.md": "76abff7f2050c9ea",
"gsd-core/templates/config.json": "a4b783ef759a0f37",
"gsd-core/templates/context.md": "69b01e7909ea3f66",
"gsd-core/templates/continue-here.md": "f522a51b6895fba8",
"gsd-core/templates/copilot-instructions.md": "aea34bc52ff548ea",
"gsd-core/templates/debug-subagent-prompt.md": "920656683dedb869",
"gsd-core/templates/dev-preferences.md": "88d0a65ec0993a3a",
"gsd-core/templates/discovery.md": "9a0e0935cc825dbc",
"gsd-core/templates/discussion-log.md": "cac1b48ec0f4dcb8",
"gsd-core/templates/milestone-archive.md": "591b6decdc0c0e51",
"gsd-core/templates/milestone.md": "74d2f750ae9f4a9c",
"gsd-core/templates/phase-prompt.md": "b811bf951092df2f",
"gsd-core/templates/planner-subagent-prompt.md": "ebf29dbb27042370",
"gsd-core/templates/project.md": "4f311fb1b05b823b",
"gsd-core/templates/requirements.md": "a44de4c2f146e473",
"gsd-core/templates/research-project/ARCHITECTURE.md": "746b9ef791d758b0",
"gsd-core/templates/research-project/FEATURES.md": "f2b800de5df91b0f",
"gsd-core/templates/research-project/PITFALLS.md": "3ef75fa768422eec",
"gsd-core/templates/research-project/STACK.md": "82c85799ac4dd344",
"gsd-core/templates/research-project/SUMMARY.md": "dceb2f346388839d",
"gsd-core/templates/research.md": "88ce0920417091d0",
"gsd-core/templates/retrospective.md": "03981e30dd760103",
"gsd-core/templates/roadmap.md": "e4e35a9eb5dd4d4f",
"gsd-core/templates/spec.md": "26d55bce940f0288",
"gsd-core/templates/state.md": "4d123aa6cea167fe",
"gsd-core/templates/summary-complex.md": "a5e40574fd8894dc",
"gsd-core/templates/summary-minimal.md": "7d09b5e709e2e67c",
"gsd-core/templates/summary-standard.md": "e8d9cf4a8377cdff",
"gsd-core/templates/summary.md": "23c40f6503b3ea98",
"gsd-core/templates/user-profile.md": "20749f23e4c413fc",
"gsd-core/templates/user-setup.md": "78b7d718b6e8d67c",
"gsd-core/templates/verification-report.md": "dd5faa6254183731",
"gsd-core/workflows/_runtime-launcher.snippet.sh": "bf2dd5d1debd5335",
"gsd-core/workflows/add-backlog.md": "1bc7377b105194fc",
"gsd-core/workflows/add-phase.md": "46e0551ffdd8ce1a",
"gsd-core/workflows/add-tests.md": "2c1da65d41dc12d2",
"gsd-core/workflows/add-todo.md": "cc0efe270004c8fb",
"gsd-core/workflows/ai-integration-phase.md": "40c217869a06981f",
"gsd-core/workflows/analyze-dependencies.md": "52942af10f140717",
"gsd-core/workflows/audit-fix.md": "eedb2da4bffb7575",
"gsd-core/workflows/audit-milestone.md": "6866ce260980c21e",
"gsd-core/workflows/audit-uat.md": "fcfbec501620b564",
"gsd-core/workflows/autonomous.md": "7e5683728ef33707",
"gsd-core/workflows/check-todos.md": "bdeaf43f9c61e0cc",
"gsd-core/workflows/cleanup.md": "0daa2f2720c111f1",
"gsd-core/workflows/code-review-fix.md": "2e113d1f4350a075",
"gsd-core/workflows/code-review.md": "334c90c401f291f8",
"gsd-core/workflows/complete-milestone.md": "c1f91b77f4ace7f2",
"gsd-core/workflows/debug.md": "849d7e5b9c12dcae",
"gsd-core/workflows/diagnose-issues.md": "d6d978fddfd5da8d",
"gsd-core/workflows/discovery-phase.md": "3ba7cfb89fb1e761",
"gsd-core/workflows/discuss-phase-assumptions.md": "f5b765d33eba4f88",
"gsd-core/workflows/discuss-phase-power.md": "0841f7dc6e9a054a",
"gsd-core/workflows/discuss-phase.md": "acfe2baa4c8bebbe",
"gsd-core/workflows/discuss-phase/modes/advisor.md": "db488d74d080c653",
"gsd-core/workflows/discuss-phase/modes/all.md": "fa70d79066562e54",
"gsd-core/workflows/discuss-phase/modes/analyze.md": "da0788f3be7f8105",
"gsd-core/workflows/discuss-phase/modes/auto.md": "d06f0bea2ba0b240",
"gsd-core/workflows/discuss-phase/modes/batch.md": "6946597770e2d448",
"gsd-core/workflows/discuss-phase/modes/chain.md": "5502a67de7776853",
"gsd-core/workflows/discuss-phase/modes/default.md": "67d1b67f61f03966",
"gsd-core/workflows/discuss-phase/modes/power.md": "fdc7a728eaaa2261",
"gsd-core/workflows/discuss-phase/modes/text.md": "da6d45207da4a988",
"gsd-core/workflows/discuss-phase/templates/checkpoint.json": "e3bc3dca49db59eb",
"gsd-core/workflows/discuss-phase/templates/context.md": "6cd929e989fe2b0f",
"gsd-core/workflows/discuss-phase/templates/discussion-log.md": "1bbd7703f11128e1",
"gsd-core/workflows/do.md": "7512dd1892f118a1",
"gsd-core/workflows/docs-update.md": "f35922d15b7061c9",
"gsd-core/workflows/edit-phase.md": "966a3eadd1bebc04",
"gsd-core/workflows/eval-review.md": "f898936e2cfe4130",
"gsd-core/workflows/execute-phase.md": "8f6c2443655aceb1",
"gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "4e265392b3f2ba0e",
"gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028",
"gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "811b6d8489571581",
"gsd-core/workflows/execute-phase/steps/regression-gate.md": "8ccc16a6c7cf6000",
"gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md": "be84efbd71e1513e",
"gsd-core/workflows/execute-plan.md": "ff172c3540b52e9d",
"gsd-core/workflows/explore.md": "6c04f2e658d93261",
"gsd-core/workflows/extract-learnings.md": "b6f01ca3d8f58de4",
"gsd-core/workflows/fast.md": "94136fb570d20a9d",
"gsd-core/workflows/forensics.md": "0d500a3f5ab26913",
"gsd-core/workflows/graduation.md": "47f1594c88c08501",
"gsd-core/workflows/health.md": "107e3c72e76d9535",
"gsd-core/workflows/help.md": "5d040504b9ab35e3",
"gsd-core/workflows/help/modes/brief.md": "924860e1f07defb0",
"gsd-core/workflows/help/modes/default.md": "08a02976c0c5cc50",
"gsd-core/workflows/help/modes/full.md": "87951776f730390d",
"gsd-core/workflows/help/modes/topic.md": "5c160093f3cbf35d",
"gsd-core/workflows/import.md": "3d3fa603ceb8bc9f",
"gsd-core/workflows/inbox.md": "437f981ef9ae7b26",
"gsd-core/workflows/ingest-docs.md": "c859921c811ac11b",
"gsd-core/workflows/insert-phase.md": "08dca838d831bb5b",
"gsd-core/workflows/list-phase-assumptions.md": "53dd4b69536c1bc3",
"gsd-core/workflows/list-seeds.md": "b687f2a843032d65",
"gsd-core/workflows/list-workspaces.md": "7f94e18b5c549453",
"gsd-core/workflows/manager.md": "3ba7a3e8213c4b23",
"gsd-core/workflows/map-codebase.md": "83d750aade709983",
"gsd-core/workflows/milestone-summary.md": "e0cdfddbd39a9043",
"gsd-core/workflows/mvp-phase.md": "344dd300e0cc1e74",
"gsd-core/workflows/new-milestone.md": "a616efb0f82d1a11",
"gsd-core/workflows/new-project.md": "4c32c69910d55ec0",
"gsd-core/workflows/new-workspace.md": "017688423110ed66",
"gsd-core/workflows/next.md": "13fb800f2472d970",
"gsd-core/workflows/node-repair.md": "07a1628e5a1ff96b",
"gsd-core/workflows/note.md": "5a99eb396c744619",
"gsd-core/workflows/onboard.md": "6f9e6c0b484271a9",
"gsd-core/workflows/pause-work.md": "f2b33bba5593d422",
"gsd-core/workflows/plan-milestone-gaps.md": "852f6d7c0c4299dc",
"gsd-core/workflows/plan-phase.md": "5a841e26f1d6f9ae",
"gsd-core/workflows/plan-phase/steps/closed-phase-gate.md": "b36f77ac7344a072",
"gsd-core/workflows/plan-phase/steps/prd-express-path.md": "197c0590326371b2",
"gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md": "49f58c3f75be3eb5",
"gsd-core/workflows/plan-review-convergence.md": "c635ecb8590a724e",
"gsd-core/workflows/plant-seed.md": "10b92ae08a6fdede",
"gsd-core/workflows/pr-branch.md": "c87db7ac8c28be1b",
"gsd-core/workflows/profile-user.md": "1bac7f69142801ef",
"gsd-core/workflows/progress.md": "893aa3c36983f74b",
"gsd-core/workflows/quick.md": "4471707540842a41",
"gsd-core/workflows/reapply-patches.md": "39050f72601aec89",
"gsd-core/workflows/remove-phase.md": "df9a45f0b1880999",
"gsd-core/workflows/remove-workspace.md": "a7ca66db6b7c132c",
"gsd-core/workflows/resume-project.md": "f28da1200e4545f4",
"gsd-core/workflows/review.md": "93fe46bd0b5dd3d1",
"gsd-core/workflows/scan.md": "003883d71c37da7d",
"gsd-core/workflows/secure-phase.md": "29fc6b62c5c5dc62",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
"gsd-core/workflows/settings-advanced.md": "414db4dbea97ba44",
"gsd-core/workflows/settings-integrations.md": "70515c5838fb9826",
"gsd-core/workflows/settings.md": "d96ddf01fb85e61e",
"gsd-core/workflows/ship.md": "7b8fe9f89143e648",
"gsd-core/workflows/sketch-wrap-up.md": "838c701bd072ae73",
"gsd-core/workflows/sketch.md": "c7725562b3efd311",
"gsd-core/workflows/smart-entry.md": "449238eb94abe187",
"gsd-core/workflows/spec-phase.md": "8c480bd91f3cef6f",
"gsd-core/workflows/spike-wrap-up.md": "0b24057c340a8a17",
"gsd-core/workflows/spike.md": "9134cdc3b75282c9",
"gsd-core/workflows/stats.md": "76a42cbeaf6007c2",
"gsd-core/workflows/sync-skills.md": "b505e6f8331c0918",
"gsd-core/workflows/thread.md": "927e7eeefd2fcf5c",
"gsd-core/workflows/transition.md": "cb8ec5affb7ebba1",
"gsd-core/workflows/ui-phase.md": "d77fa19403a4684b",
"gsd-core/workflows/ui-review.md": "7acfc485526d064b",
"gsd-core/workflows/ultraplan-phase.md": "0fb8291153e3937d",
"gsd-core/workflows/undo.md": "96d2775f008b3a85",
"gsd-core/workflows/update.md": "7e69d278375a2493",
"gsd-core/workflows/validate-phase.md": "2c6d7671fcaabcaa",
"gsd-core/workflows/verify-phase.md": "22f18492581f1da5",
"gsd-core/workflows/verify-work.md": "63b3f680d8f0a6f3",
"hooks/gsd-check-update-worker.js": "55376b5b9335a580",
"hooks/gsd-check-update.js": "a89562537a41f83d",
"hooks/gsd-config-reload.js": "96546e0e8bb47904",
"hooks/gsd-context-monitor.js": "1c48eb0f38a24318",
"hooks/gsd-cursor-post-tool.js": "9168e0a09de1972a",
"hooks/gsd-cursor-pre-tool.js": "873998b25e308c29",
"hooks/gsd-cursor-session-start.js": "9b2e6f4f0c405375",
"hooks/gsd-cursor-stop.js": "bfaaf60f419e3238",
"hooks/gsd-cursor-subagent-start.js": "06d77fde5c1372b6",
"hooks/gsd-cursor-subagent-stop.js": "4bbf22917da4d389",
"hooks/gsd-ensure-canonical-path.js": "62d0819a51b55fc4",
"hooks/gsd-graphify-update.sh": "e4c6e14fe6ad64ff",
"hooks/gsd-phase-boundary.sh": "32739d5fbe0d0a1c",
"hooks/gsd-prompt-guard.js": "a749b8cb2c5248de",
"hooks/gsd-read-guard.js": "9e423cd03e2d1b16",
"hooks/gsd-read-injection-scanner.js": "f454242c010804cf",
"hooks/gsd-session-state.sh": "e54379ba86bf1b6d",
"hooks/gsd-statusline.js": "5539e1ae859b987e",
"hooks/gsd-update-banner.js": "55143a25f978f301",
"hooks/gsd-validate-commit.sh": "bf5dd61d33cb3a38",
"hooks/gsd-windsurf-pre-command.js": "948be1c6d14c79cd",
"hooks/gsd-windsurf-pre-write.js": "92d4dbfbc36ab0cf",
"hooks/gsd-workflow-guard.js": "91ae24a15d2bca6f",
"hooks/gsd-worktree-path-guard.js": "2a2a7515c01ef998",
"hooks/lib/git-cmd.js": "268ba15992ca0b23",
"hooks/lib/gsd-graphify-rebuild.sh": "66af89601074d2a9",
"hooks/managed-hooks-registry.cjs": "ac720a2b548ba200",
"package.json": "dbf8353f77358bc1",
"scripts/changeset/README.md": "86ff89331dfd94b2",
"scripts/changeset/cli.cjs": "68f92a344b199271",
"scripts/changeset/github-release-notes.cjs": "795677f0c009b132",
"scripts/changeset/lint.cjs": "0066faed159154f0",
"scripts/changeset/new.cjs": "4991e21fd17f5541",
"scripts/changeset/parse.cjs": "f9a949cbcab56445",
"scripts/changeset/render.cjs": "e47bc3e1587c3cae",
"scripts/changeset/serialize.cjs": "ac0b8fe6f87cdb0e",
"scripts/fix-slash-commands.cjs": "0519742531ff3529",
"scripts/gen-capability-registry.cjs": "c52201ff4d1c2cd7",
"scripts/gen-loop-host-contract.cjs": "c7f15237234811a0",
"scripts/lib/allowlist-ratchet.cjs": "ffaceaac3efc2660",
"scripts/lib/cli-exit.cjs": "612d0c372c75b7e7"
}

View File

@@ -42,7 +42,7 @@
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
"gsd-core/bin/shared/model-catalog.json": "d0b59409a46a456a",
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
@@ -289,7 +289,7 @@
"gsd-core/workflows/scan.md": "949692db4834dd27",
"gsd-core/workflows/secure-phase.md": "6758f1acf4113e9e",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
"gsd-core/workflows/settings-advanced.md": "d80d7acc4058cb40",
"gsd-core/workflows/settings-advanced.md": "5e05212fb5cba531",
"gsd-core/workflows/settings-integrations.md": "29c5de27fbbb18e9",
"gsd-core/workflows/settings.md": "26b9b7979d3a5747",
"gsd-core/workflows/ship.md": "9f94d0b155eb041e",

View File

@@ -42,7 +42,7 @@
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
"gsd-core/bin/shared/model-catalog.json": "d0b59409a46a456a",
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
@@ -289,7 +289,7 @@
"gsd-core/workflows/scan.md": "63631467651d9ca8",
"gsd-core/workflows/secure-phase.md": "6cc236e53c2e7d56",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
"gsd-core/workflows/settings-advanced.md": "90d913043cd11502",
"gsd-core/workflows/settings-advanced.md": "39e66386f6c48025",
"gsd-core/workflows/settings-integrations.md": "f8f756709ec02363",
"gsd-core/workflows/settings.md": "44b10c59215633b8",
"gsd-core/workflows/ship.md": "c08f0fe3025d2c86",

View File

@@ -42,7 +42,7 @@
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
"gsd-core/bin/shared/model-catalog.json": "d0b59409a46a456a",
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
@@ -289,7 +289,7 @@
"gsd-core/workflows/scan.md": "12c11b2edc165df9",
"gsd-core/workflows/secure-phase.md": "7bf923689bf58288",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
"gsd-core/workflows/settings-advanced.md": "18eff9c17e0b1948",
"gsd-core/workflows/settings-advanced.md": "2f86ec7b998f9485",
"gsd-core/workflows/settings-integrations.md": "b082fc518b484c07",
"gsd-core/workflows/settings.md": "002eb0ce3c10c741",
"gsd-core/workflows/ship.md": "c035bb8b3bb7efbb",

View File

@@ -113,7 +113,7 @@
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
"gsd-core/bin/shared/model-catalog.json": "d0b59409a46a456a",
"gsd-core/bin/shared/model-catalog.json": "b55176ca044728d3",
"gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
@@ -360,7 +360,7 @@
"gsd-core/workflows/scan.md": "003883d71c37da7d",
"gsd-core/workflows/secure-phase.md": "29fc6b62c5c5dc62",
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
"gsd-core/workflows/settings-advanced.md": "b6fa466a953dd3a2",
"gsd-core/workflows/settings-advanced.md": "414db4dbea97ba44",
"gsd-core/workflows/settings-integrations.md": "70515c5838fb9826",
"gsd-core/workflows/settings.md": "d96ddf01fb85e61e",
"gsd-core/workflows/ship.md": "7b8fe9f89143e648",

View File

@@ -14,10 +14,18 @@
* behaviorally testable without a live pi runtime. The default export matches
* pi's extension entry shape: `export default function (pi: ExtensionAPI) { … }`.
*
* #2102 Stage 2: registerCommand takes `handler(args, ctx)` (args is the raw
* string after the command) — NOT `execute(ctx)`, which the original #1682
* cut used. gsd_invoke's `execute` takes pi's real 5-arg tool-execute
* signature `(toolCallId, params, signal, onUpdate, ctx)`. See pi/gsd.cjs for
* the full production binding this reference fixture mirrors.
*
* NOTE: this is the reference binding that proves the ExtensionAPI imperative
* adapter (#1682 AC). Full `--pi` installable-runtime integration (descriptor +
* installer wiring + golden parity 16→17) is a larger follow-up tracked
* separately — it is intentionally NOT added to the runtime registry here.
* adapter (#1682 AC), kept as a mock-friendly fixture independent of the real
* `pi/gsd.cjs` extension. Full `--pi` installable-runtime integration
* (descriptor + installer wiring + golden parity 16→17) shipped in #2102
* Stage 1 — see capabilities/pi/capability.json and
* tests/fixtures/golden-install-parity/pi.json.
*
* @param {object} pi pi ExtensionAPI (registerTool/registerCommand/on/…)
*/
@@ -30,9 +38,12 @@ module.exports = function gsdPiPlugin(pi) {
// imperative adapter (createImperativeAdapter({runtime:'pi'}) + dispatch).
pi.registerCommand('gsd', {
description: 'Invoke a GSD command via the embedded engine (imperative adapter).',
execute: async function /* ctx */ () {
handler: async function (args, ctx) {
void args;
void ctx;
// Engine dispatch is wired by the host at load (createImperativeAdapter).
// Kept declarative in the reference; the real binding dispatches the hub.
// Kept declarative in the reference; the real binding (pi/gsd.cjs)
// dispatches through gsd-tools.cjs via dispatchGsdCommand.
},
});
@@ -41,7 +52,12 @@ module.exports = function gsdPiPlugin(pi) {
pi.registerTool({
name: 'gsd_invoke',
description: 'Invoke a GSD command family/subcommand through the engine.',
execute: async function () {
execute: async function (toolCallId, params, signal, onUpdate, ctx) {
void toolCallId;
void params;
void signal;
void onUpdate;
void ctx;
return 'ok';
},
});

View File

@@ -47,6 +47,7 @@ const GOLDEN_FRAGMENT_MAP = {
cline: "'.cline'",
kimi: "'.config', 'agents'",
zcode: "'.zcode'",
pi: "'.pi', 'agent'",
};
// Runtimes intentionally NOT in the table: claude is the default; antigravity is

View File

@@ -54,6 +54,36 @@ test('tools/call gsd_invoke_command: dispatches to the command hub (point 1); un
assert.strictEqual(res.jsonrpc, '2.0');
const payload = JSON.parse(res.result.content[0].text);
assert.strictEqual(payload.ok, false, 'an unknown command dispatches to the hub and returns ok:false');
assert.strictEqual(res.result.isError, true, 'unknown family surfaces as isError:true (#2102)');
});
// REGRESSION #2102: gsd_invoke_command previously called
// `commandRoutingHub.createHub()` with NO arguments, which always hits
// `if (!_cjsRegistry) return makeUnknownCommand()` — every dispatch, valid
// family or not, returned UnknownCommand. The test above (family:
// 'no-such-family') could not catch this: an UnknownCommand result is
// EXACTLY what an unknown family is supposed to return, whether or not
// dispatch actually worked — it is vacuous by construction. This test proves
// the fix: a VALID read-only family/subcommand must reach gsd-tools.cjs for
// real and come back with actual data (not a crash, not UnknownCommand).
test('tools/call gsd_invoke_command: REGRESSION #2102 — a valid read-only family dispatches for real (not the createHub()-with-no-args UnknownCommand bug)', () => {
const dir = createTempDir();
try {
const res = handleMessage(
{ jsonrpc: '2.0', id: 9, method: 'tools/call', params: { name: 'gsd_invoke_command', arguments: { family: 'progress', subcommand: 'json' } } },
{ cwd: dir },
);
assert.strictEqual(res.jsonrpc, '2.0');
assert.notStrictEqual(res.result.isError, true, 'a valid family must not surface as an error');
const text = res.result.content[0].text;
const parsed = JSON.parse(text);
// Fail-first proof: under the bug, this would be
// { ok: false, kind: 'UnknownCommand', command: 'progress json' } instead
// of the real progress payload — `percent` would not exist.
assert.strictEqual(typeof parsed.percent, 'number', 'the real "progress json" command ran (the engine was reached)');
} finally {
cleanup(dir);
}
});
test('tools/call: unknown tool name surfaces a tool error (isError), not a JSON-RPC protocol error', () => {

View File

@@ -57,6 +57,7 @@ const RUNTIME_META = {
kimi: { localDir: '.kimi-code', globalSuffix: path.join('.config', 'agents') },
kilo: { localDir: '.kilo', globalSuffix: path.join('.config', 'kilo') },
opencode: { localDir: '.opencode', globalSuffix: path.join('.config', 'opencode') },
pi: { localDir: '.pi', globalSuffix: path.join('.pi', 'agent') },
qwen: { localDir: '.qwen', globalSuffix: '.qwen' },
trae: { localDir: '.trae', globalSuffix: '.trae' },
windsurf: { localDir: '.windsurf', globalSuffix: path.join('.codeium', 'windsurf') },

View File

@@ -38,7 +38,7 @@ const DISPATCH_KEYS = ['namedDispatch', 'nested', 'maxDepth', 'background', 'sub
const RUNTIME_IDS = Object.keys(registry.runtimes);
// Contract-pinned profile split (derived from .host-cli-final.json):
// programmatic-cli: claude, cline, cursor, hermes, kilo, kimi, opencode, qwen, trae (9)
// programmatic-cli: claude, cline, cursor, hermes, kilo, kimi, opencode, pi, qwen, trae (10)
// declarative-cli: antigravity, augment, codebuddy, codex, copilot, windsurf, zcode (7)
// ide: 0
const EXPECTED_PROFILES = {
@@ -49,6 +49,7 @@ const EXPECTED_PROFILES = {
kilo: 'programmatic-cli',
kimi: 'programmatic-cli',
opencode: 'programmatic-cli',
pi: 'programmatic-cli',
qwen: 'programmatic-cli',
trae: 'programmatic-cli',
antigravity: 'declarative-cli',
@@ -283,6 +284,9 @@ describe('ADR-1239 Phase A: hostIntegration descriptors', () => {
// #2087: OpenCode background subagents (v1.15 param, v1.17 default-on) →
// dispatch.background/backgroundDispatch true → NOT force-flattened.
opencode: false,
// #2102: pi's dispatch.background/backgroundDispatch are both false
// (undocumented background-subagent primitive) → force-flattened.
pi: true,
qwen: true,
trae: true,
windsurf: true,

View File

@@ -674,12 +674,14 @@ describe('#1755: .sh hooks are copied and executable after install', () => {
// ─── #1821: Kilo/ZCode (hooksSurface:none, no plugin) receive no dead hooks ────
//
// #1821 reported dead hook scripts staged for runtimes with hooksSurface:'none'.
// OpenCode ALSO declares hooksSurface:'none', but its #1914 native plugin adapter
// (plugins/gsd-core.js) spawns the staged hooks/*.js via OpenCode's event bus —
// so for OpenCode the hooks are LIVE and must keep being copied. Kilo and ZCode
// have no plugin surface, so their staged hooks are genuinely dead: this is the
// case the fix removes. These tests assert the split: Kilo/ZCode get no hooks;
// OpenCode (and Claude) still do.
// OpenCode and pi ALSO declare hooksSurface:'none', but each has a native plugin
// adapter that spawns the staged hooks/*.js scripts as subprocesses (OpenCode's
// #1914 plugins/gsd-core.js via OpenCode's event bus; pi's #2102 Stage 2
// pi/gsd.cjs → extensions/gsd.cjs via pi.on(...) bridges) — so for both, the
// hooks are LIVE and must keep being copied. Kilo and ZCode have no plugin
// surface at all, so their staged hooks are genuinely dead: this is the case
// the fix removes. These tests assert the split: Kilo/ZCode get no hooks;
// OpenCode/pi (and Claude) still do.
describe('#1821: Kilo/ZCode receive no dead hook files; OpenCode/Claude keep their hooks', () => {
function gsdHookFilesUnder(configDir) {
@@ -691,7 +693,7 @@ describe('#1821: Kilo/ZCode receive no dead hook files; OpenCode/Claude keep the
});
}
function installAndCollect(runtime) {
function installAndCollect(runtime, opts = {}) {
const targetDir = fs.mkdtempSync(path.join(os.tmpdir(), `gsd-1821-${runtime}-`));
try {
const result = spawnSync(
@@ -702,10 +704,12 @@ describe('#1821: Kilo/ZCode receive no dead hook files; OpenCode/Claude keep the
assert.strictEqual(result.status, 0,
`installer exited with status ${result.status} for --${runtime} --global\nstdout: ${result.stdout}\nstderr: ${result.stderr}`);
// Collect results while targetDir still exists — cleanup() below removes it.
const pluginRelPath = opts.pluginRelPath || path.join('plugins', 'gsd-core.js');
return {
hookFiles: gsdHookFilesUnder(targetDir),
hooksLibExists: fs.existsSync(path.join(targetDir, 'hooks', 'lib')),
pluginExists: fs.existsSync(path.join(targetDir, 'plugins', 'gsd-core.js')),
gitCmdExists: fs.existsSync(path.join(targetDir, 'hooks', 'lib', 'git-cmd.js')),
pluginExists: fs.existsSync(path.join(targetDir, pluginRelPath)),
};
} finally {
cleanup(targetDir);
@@ -713,7 +717,8 @@ describe('#1821: Kilo/ZCode receive no dead hook files; OpenCode/Claude keep the
}
// Kilo and ZCode both declare hooksSurface:'none' with no plugin surface, so
// their staged hooks are dead weight (#1821).
// their staged hooks are genuinely dead weight (#1821) — this is the case
// the fix removes.
for (const runtime of ['kilo', 'zcode']) {
test(`${runtime} --global install creates no gsd-*.js/.sh hook files or hooks/lib`, () => {
const { hookFiles, hooksLibExists } = installAndCollect(runtime);
@@ -735,6 +740,31 @@ describe('#1821: Kilo/ZCode receive no dead hook files; OpenCode/Claude keep the
assert.ok(pluginExists, 'opencode install must install plugins/gsd-core.js (#1914 hook bridge)');
});
// pi ALSO declares hooksSurface:'none', but — like OpenCode — it is NOT a
// dead-weight case: pi's native extension (pi/gsd.cjs → extensions/gsd.cjs)
// spawns the staged hooks/*.js scripts as bounded subprocesses (session_start
// → gsd-ensure-canonical-path.js, before_agent_start → gsd-workflow-guard.js,
// session_before_compact → gsd-context-monitor.js — #2102 Stage 2), and its
// /gsd command handler tokenizes raw args via the shared hooks/lib/git-cmd.js
// tokenizer. hostBehaviors.skipSharedHooksInstall is therefore NOT set for
// pi (unlike Kilo/ZCode/Cursor/Cline/Trae/Copilot/Windsurf/Kimi) — pi is in
// the OpenCode group, not the Kilo/ZCode group.
test('pi --global install still copies hooks (spawned by the native extension) + hooks/lib/git-cmd.js + the extension itself', () => {
const { hookFiles, hooksLibExists, gitCmdExists, pluginExists } = installAndCollect('pi', {
pluginRelPath: path.join('extensions', 'gsd.cjs'),
});
const basenames = hookFiles.map((f) => path.basename(f));
for (const expected of ['gsd-ensure-canonical-path.js', 'gsd-workflow-guard.js', 'gsd-context-monitor.js']) {
assert.ok(
basenames.includes(expected),
`pi install must copy ${expected} (spawned by pi/gsd.cjs's event bridges), found: ${basenames.join(', ')}`,
);
}
assert.ok(hooksLibExists, 'pi install must create hooks/lib/');
assert.ok(gitCmdExists, 'pi install must copy hooks/lib/git-cmd.js (the /gsd command tokenizer)');
assert.ok(pluginExists, 'pi install must install extensions/gsd.cjs (the native-extension hook bridge)');
});
// Positive control: guards against over-exclusion breaking runtimes that
// legitimately need hooks (hooksSurface !== 'none').
test('claude --global install still copies gsd-*.js hooks', () => {

View File

@@ -39,6 +39,20 @@ const RUNTIME_INSTALL_CONTRACTS = {
// dead hook scripts or the CommonJS package.json marker.
kilo: { surface: 'flat-command', settings: false, packageJson: false },
opencode: { surface: 'flat-command', settings: true, packageJson: true },
// #2102 Stage 1/2: pi is a PLUGIN-ONLY install (hostBehaviors.pluginOnlyInstall)
// for commands/agents/skills — NO commands/, agents/, or skills/ dir. pi's
// /gsd command is registered programmatically by the native extension
// (extensions/gsd.cjs) and dispatches via a bounded subprocess to
// gsd-tools.cjs; it has no host-read markdown surface. Stage 2 (adversarial-
// review fix): pi's native extension DOES spawn the shared hooks/*.js bundle
// as bounded subprocesses (session_start/before_agent_start/session_before_
// compact bridges) and its /gsd tokenizer requires hooks/lib/git-cmd.js, so
// `hostBehaviors.skipSharedHooksInstall` was removed — pi now receives
// hooks/ + hooks/lib/ + the {"type":"commonjs"} package.json marker, exactly
// like OpenCode (architecturally identical: hooksSurface:'none' + a native
// plugin that spawns the staged hooks), NOT like Kilo/ZCode (no plugin
// surface, where the same hooks are genuinely dead weight).
pi: { surface: 'plugin-only', settings: false, packageJson: true },
qwen: { surface: 'flat-skills', settings: true, packageJson: true },
trae: { surface: 'flat-skills', settings: false, packageJson: false },
windsurf: { surface: 'global-artifacts-noop', settings: false, packageJson: false },
@@ -262,6 +276,43 @@ function assertFreshInstallContract(runtime, targetDir) {
listDirNames(targetDir, 'command').some((name) => name.startsWith('gsd-') && name.endsWith('.md')),
`${runtime} should install flattened command markdown files`
);
} else if (contract.surface === 'plugin-only') {
// #2102 Stage 1/2: pi — PLUGIN-ONLY install for commands/agents/skills
// (hostBehaviors.pluginOnlyInstall). pi's /gsd command is registered
// programmatically by the native extension and dispatches via a bounded
// subprocess to gsd-tools.cjs — pi has no host-read markdown surface, so
// NO commands/, agents/, or skills/ dir is written. The extension DOES
// spawn the shared hooks/*.js bundle as bounded subprocesses (Stage 2
// adversarial-review fix — hooksSurface:'none' no longer implies
// skipSharedHooksInstall for pi, mirroring OpenCode), so hooks/ + the
// git-cmd.js tokenizer helper ARE part of the artifact surface now.
assert.ok(
fs.existsSync(path.join(targetDir, 'extensions', 'gsd.cjs')),
`${runtime} should install the native extension file at extensions/gsd.cjs`
);
assert.ok(
fs.existsSync(path.join(targetDir, 'hooks', 'gsd-ensure-canonical-path.js')),
`${runtime} should install the shared hooks/ bundle (spawned by the native extension's event bridges)`
);
assert.ok(
fs.existsSync(path.join(targetDir, 'hooks', 'lib', 'git-cmd.js')),
`${runtime} should install hooks/lib/git-cmd.js (the /gsd command tokenizer)`
);
assert.equal(
fs.existsSync(path.join(targetDir, 'commands')),
false,
`${runtime} should NOT install a commands/ dir (plugin-only, no host-read markdown surface)`
);
assert.equal(
fs.existsSync(path.join(targetDir, 'agents')),
false,
`${runtime} should NOT install an agents/ dir (plugin-only, no named-dispatch toolkit)`
);
assert.equal(
fs.existsSync(path.join(targetDir, 'skills')),
false,
`${runtime} should NOT install a skills/ dir (plugin-only)`
);
} else if (contract.surface === 'commands-gsd') {
assert.ok(
listDirNames(targetDir, path.join('commands', 'gsd')).length > 0,
@@ -301,7 +352,7 @@ function assertFreshInstallContract(runtime, targetDir) {
);
}
if (contract.surface !== 'kimi-skills-agents' && contract.surface !== 'global-artifacts-noop') {
if (contract.surface !== 'kimi-skills-agents' && contract.surface !== 'global-artifacts-noop' && contract.surface !== 'plugin-only') {
assert.ok(
listDirNames(targetDir, 'agents').some((name) => name.startsWith('gsd-')),
`${runtime} full install should install agents`

View File

@@ -15,6 +15,10 @@
* now hermes, and the "All" shortcut moved from 17 to 16.
*
* #1925: ZCode (Z.ai) added as option 16; the "All" shortcut moved from 16 to 17.
*
* #2102: pi added as option 13 (alphabetical slot between opencode and qwen) —
* qwen/trae/windsurf/zcode each shift up one slot (14/15/16/17), and the "All"
* shortcut moves from 17 to 18.
*/
process.env.GSD_TEST_MODE = '1';
@@ -76,39 +80,43 @@ describe('multi-runtime selection parsing', () => {
assert.deepStrictEqual(parseRuntimeInput('12'), ['opencode']);
});
test('single choice for pi', () => {
assert.deepStrictEqual(parseRuntimeInput('13'), ['pi']);
});
test('single choice for qwen', () => {
assert.deepStrictEqual(parseRuntimeInput('13'), ['qwen']);
assert.deepStrictEqual(parseRuntimeInput('14'), ['qwen']);
});
test('single choice for trae', () => {
assert.deepStrictEqual(parseRuntimeInput('14'), ['trae']);
assert.deepStrictEqual(parseRuntimeInput('15'), ['trae']);
});
test('single choice for windsurf', () => {
assert.deepStrictEqual(parseRuntimeInput('15'), ['windsurf']);
assert.deepStrictEqual(parseRuntimeInput('16'), ['windsurf']);
});
test('single choice for zcode', () => {
assert.deepStrictEqual(parseRuntimeInput('16'), ['zcode']);
assert.deepStrictEqual(parseRuntimeInput('17'), ['zcode']);
});
test('single choice for kimi', () => {
assert.deepStrictEqual(parseRuntimeInput('10'), ['kimi']);
});
test('choice 17 returns all runtimes', () => {
assert.deepStrictEqual(parseRuntimeInput('17'), allRuntimes);
test('choice 18 returns all runtimes', () => {
assert.deepStrictEqual(parseRuntimeInput('18'), allRuntimes);
});
test('choice 17 returns all runtimes when mixed with separators or other tokens', () => {
// CR feedback: tokenized inputs that include 17 (e.g. trailing comma, or
test('choice 18 returns all runtimes when mixed with separators or other tokens', () => {
// CR feedback: tokenized inputs that include 18 (e.g. trailing comma, or
// alongside other choices) must still expand to all-runtimes — previously
// only the bare all-runtimes option matched, so "17," or "17 1" silently installed a
// only the bare all-runtimes option matched, so "18," or "18 1" silently installed a
// subset.
assert.deepStrictEqual(parseRuntimeInput('17,'), allRuntimes);
assert.deepStrictEqual(parseRuntimeInput('17 1'), allRuntimes);
assert.deepStrictEqual(parseRuntimeInput('1,17'), allRuntimes);
assert.deepStrictEqual(parseRuntimeInput(' 17 '), allRuntimes);
assert.deepStrictEqual(parseRuntimeInput('18,'), allRuntimes);
assert.deepStrictEqual(parseRuntimeInput('18 1'), allRuntimes);
assert.deepStrictEqual(parseRuntimeInput('1,18'), allRuntimes);
assert.deepStrictEqual(parseRuntimeInput(' 18 '), allRuntimes);
});
test('empty input defaults to claude', () => {
@@ -117,13 +125,13 @@ describe('multi-runtime selection parsing', () => {
});
test('invalid choices are ignored, falls back to claude if all invalid', () => {
assert.deepStrictEqual(parseRuntimeInput('18'), ['claude']);
assert.deepStrictEqual(parseRuntimeInput('19'), ['claude']);
assert.deepStrictEqual(parseRuntimeInput('0'), ['claude']);
assert.deepStrictEqual(parseRuntimeInput('abc'), ['claude']);
});
test('invalid choices mixed with valid are filtered out', () => {
assert.deepStrictEqual(parseRuntimeInput('1,18,7'), ['claude', 'copilot']);
assert.deepStrictEqual(parseRuntimeInput('1,19,7'), ['claude', 'copilot']);
assert.deepStrictEqual(parseRuntimeInput('abc 3 xyz'), ['augment']);
});
@@ -152,14 +160,15 @@ describe('install.js exports multi-select runtime metadata', () => {
'10': 'kimi',
'11': 'kilo',
'12': 'opencode',
'13': 'qwen',
'14': 'trae',
'15': 'windsurf',
'16': 'zcode',
'13': 'pi',
'14': 'qwen',
'15': 'trae',
'16': 'windsurf',
'17': 'zcode',
};
const expectedRuntimes = [
'claude', 'antigravity', 'augment', 'cline', 'codebuddy', 'codex',
'copilot', 'cursor', 'hermes', 'kimi', 'kilo', 'opencode',
'copilot', 'cursor', 'hermes', 'kimi', 'kilo', 'opencode', 'pi',
'qwen', 'trae', 'windsurf', 'zcode',
];
@@ -177,8 +186,8 @@ describe('install.js exports multi-select runtime metadata', () => {
'allRuntimes has no duplicates');
});
test('"All" shortcut (option 17) selects every runtime', () => {
assert.deepStrictEqual(parseRuntimeInput('17'), allRuntimes);
test('"All" shortcut (option 18) selects every runtime', () => {
assert.deepStrictEqual(parseRuntimeInput('18'), allRuntimes);
});
test('--kimi flag selects Kimi without interactive prompt', () => {
@@ -189,6 +198,10 @@ describe('install.js exports multi-select runtime metadata', () => {
assert.deepStrictEqual(selectRuntimesFromArgs(['--zcode']), ['zcode']);
});
test('--pi flag selects pi without interactive prompt', () => {
assert.deepStrictEqual(selectRuntimesFromArgs(['--pi']), ['pi']);
});
test('--all flag includes Kimi exactly once', () => {
const selected = selectRuntimesFromArgs(['--all']);
assert.ok(selected.includes('kimi'), '--all includes kimi');
@@ -203,7 +216,14 @@ describe('install.js exports multi-select runtime metadata', () => {
'--all includes zcode exactly once');
});
test('prompt lists ZCode (16), and All (17)', () => {
test('--all flag includes pi exactly once', () => {
const selected = selectRuntimesFromArgs(['--all']);
assert.ok(selected.includes('pi'), '--all includes pi');
assert.strictEqual(selected.filter((runtime) => runtime === 'pi').length, 1,
'--all includes pi exactly once');
});
test('prompt lists pi (13), ZCode (17), and All (18)', () => {
const prompt = stripAnsi(buildRuntimePromptText());
assert.ok(/\b9\)\s*Hermes Agent\b/.test(prompt),
'prompt lists Hermes Agent as option 9');
@@ -211,14 +231,16 @@ describe('install.js exports multi-select runtime metadata', () => {
'prompt lists Kimi as option 10');
assert.ok(/Kimi\s+\(~\/\.config\/agents, then ~\/\.agents if existing\)/.test(prompt),
'prompt shows the Kimi first-existing generic root policy');
assert.ok(/\b13\)\s*Qwen Code\b/.test(prompt),
'prompt lists Qwen Code as option 13');
assert.ok(/\b14\)\s*Trae\b/.test(prompt),
'prompt lists Trae as option 14');
assert.ok(/\b16\)\s*ZCode\b/.test(prompt),
'prompt lists ZCode as option 16');
assert.ok(/\b17\)\s*All\b/.test(prompt),
'prompt lists All as option 17');
assert.ok(/\b13\)\s*pi\b/.test(prompt),
'prompt lists pi as option 13');
assert.ok(/\b14\)\s*Qwen Code\b/.test(prompt),
'prompt lists Qwen Code as option 14');
assert.ok(/\b15\)\s*Trae\b/.test(prompt),
'prompt lists Trae as option 15');
assert.ok(/\b17\)\s*ZCode\b/.test(prompt),
'prompt lists ZCode as option 17');
assert.ok(/\b18\)\s*All\b/.test(prompt),
'prompt lists All as option 18');
});
test('prompt does not list Gemini (removed #1928)', () => {

View File

@@ -1,57 +1,115 @@
'use strict';
/**
* pi extension reachability test — ADR-1239 Phase D / #1944.
* pi extension reachability test — ADR-1239 Phase D / #1944, upgraded #2102.
*
* Proves the pi extension is keystone-WIRED: the registered /gsd command
* handler dispatches through the GSD command-routing hub and returns a result
* (not just a registration on a mock). This is the "user can invoke X" proof.
* Proves the pi extension is keystone-WIRED: the registered /gsd command's
* `handler(args, ctx)` (pi's REAL ExtensionAPI shape — NOT the `execute(ctx)`
* shape the original #1944 cut used) dispatches through gsd-tools.cjs
* (subprocess-reuse — dispatchGsdCommand) and returns real output, not just a
* registration on a mock. This is the "user can invoke X" proof.
*
* Dispatch is exercised with a real read-only family/subcommand
* (progress/json) against a real temp project, matching the sibling
* tests/vscode-extension-reachability.test.cjs pattern — no fake dispatcher
* injected, because the whole point of "reachability" is that the real
* engine is reached.
*/
const { test } = require('node:test');
const assert = require('node:assert/strict');
const gsdPiExtension = require('../pi/gsd.cjs');
const { _internals } = require('../pi/gsd.cjs');
const { createTempDir, cleanup } = require('./helpers.cjs');
function mockPi() {
const recorded = { commands: {}, tools: {}, events: [] };
const recorded = { commands: {}, tools: {}, events: {} };
return {
registerCommand(name, def) { recorded.commands[name] = def; },
registerTool(def) { if (def && def.name) recorded.tools[def.name] = def; },
on(event) { recorded.events.push(event); },
on(event, handler) { (recorded.events[event] = recorded.events[event] || []).push(handler); },
_recorded: recorded,
};
}
test('the pi extension registers /gsd + gsd_invoke + tool_call via ExtensionAPI', () => {
test('the pi extension registers /gsd (with getArgumentCompletions + handler) + gsd_invoke + the event surface via ExtensionAPI', () => {
const pi = mockPi();
gsdPiExtension(pi);
assert.ok(pi._recorded.commands['gsd'], 'registers /gsd command');
const gsdCommand = pi._recorded.commands['gsd'];
assert.ok(gsdCommand, 'registers /gsd command');
assert.equal(typeof gsdCommand.handler, 'function', '/gsd registers a handler(args, ctx) — pi\'s REAL ExtensionAPI shape, not execute(ctx)');
assert.equal(typeof gsdCommand.getArgumentCompletions, 'function', '/gsd registers getArgumentCompletions');
assert.ok(pi._recorded.tools['gsd_invoke'], 'registers gsd_invoke tool');
assert.ok(pi._recorded.events.includes('tool_call'), 'subscribes to tool_call');
assert.equal(typeof pi._recorded.tools['gsd_invoke'].execute, 'function');
assert.ok(pi._recorded.events['tool_call'], 'subscribes to tool_call');
assert.ok(pi._recorded.events['before_provider_request'], 'subscribes to before_provider_request');
});
test('REACHABILITY: the /gsd handler dispatches through the engine hub (keystone wired)', async () => {
const pi = mockPi();
gsdPiExtension(pi);
// Invoke the registered /gsd handler — it must dispatch through createHub
// and return a JSON result (not throw). This is the keystone-wired proof.
const result = await pi._recorded.commands['gsd'].execute({
family: 'query',
subcommand: 'help',
});
assert.equal(typeof result, 'string', '/gsd handler returns a string result');
const parsed = JSON.parse(result);
assert.ok(parsed !== null && typeof parsed === 'object',
'/gsd dispatch produced a result object (the engine was reached)');
test('REACHABILITY: parseGsdCommandArgs tokenizes a raw args string into {family, subcommand, args}', () => {
const parsed = _internals.parseGsdCommandArgs('phase add --name test');
assert.deepEqual(parsed, { family: 'phase', subcommand: 'add', args: ['--name', 'test'] });
});
test('REACHABILITY: the gsd_invoke tool dispatches through the engine hub', async () => {
test('REACHABILITY: empty args dispatch a working default (gsd-tools.cjs --help), not the broken "query help"', () => {
const parsed = _internals.parseGsdCommandArgs('');
assert.equal(parsed.family, '--help');
assert.equal(parsed.subcommand, undefined);
});
test('REACHABILITY: the /gsd handler dispatches a real family through gsd-tools.cjs and returns real output (keystone wired)', async () => {
const pi = mockPi();
gsdPiExtension(pi);
const result = await pi._recorded.tools['gsd_invoke'].execute();
assert.equal(typeof result, 'string');
JSON.parse(result); // must be valid JSON (engine was reached)
const dir = createTempDir();
try {
const result = await pi._recorded.commands['gsd'].handler('progress json', { cwd: dir });
assert.equal(typeof result, 'string', '/gsd handler returns a string result');
const parsed = JSON.parse(result);
assert.equal(typeof parsed.percent, 'number', '/gsd dispatch reached gsd-tools.cjs for real (the engine was reached)');
} finally {
cleanup(dir);
}
});
test('REACHABILITY: an unknown family surfaces a clear GSD error string, not a throw', async () => {
const pi = mockPi();
gsdPiExtension(pi);
const dir = createTempDir();
try {
const result = await pi._recorded.commands['gsd'].handler('no-such-family-8675309', { cwd: dir });
assert.equal(typeof result, 'string');
assert.match(result, /GSD error:/);
assert.match(result, /no-such-family-8675309|Unknown command/);
} finally {
cleanup(dir);
}
});
test('REACHABILITY: the gsd_invoke tool dispatches through the engine and returns real content', async () => {
const pi = mockPi();
gsdPiExtension(pi);
const dir = createTempDir();
try {
const result = await pi._recorded.tools['gsd_invoke'].execute(
'call-1',
{ family: 'progress', subcommand: 'json' },
null,
null,
{ cwd: dir },
);
assert.ok(result && Array.isArray(result.content), 'gsd_invoke returns {content:[...]}');
const parsed = JSON.parse(result.content[0].text);
assert.equal(typeof parsed.percent, 'number', 'gsd_invoke dispatch reached gsd-tools.cjs for real');
} finally {
cleanup(dir);
}
});
test('gsd_invoke rejects a missing "family" without dispatching', async () => {
const pi = mockPi();
gsdPiExtension(pi);
const result = await pi._recorded.tools['gsd_invoke'].execute('call-2', {}, null, null, {});
assert.match(result.content[0].text, /requires a non-empty string "family"/);
});
test('gsdPiExtension throws without pi ExtensionAPI (fail-closed)', () => {

View File

@@ -10,8 +10,10 @@
* 2. pi's axes (imperative + bun) classify as 'programmatic-cli' (the reference profile).
* 3. the reference pi host-plugin binds GSD via ExtensionAPI (registers command + tool + event).
*
* Full `--pi` installable-runtime integration is a larger follow-up (descriptor
* + installer + golden parity 16→17); this slice proves the imperative binding.
* Full `--pi` installable-runtime integration shipped in #2102 Stage 1
* (capabilities/pi/capability.json + bin/install.js wiring + golden parity
* 16→17, see tests/fixtures/golden-install-parity/pi.json); this slice
* continues to prove the imperative ExtensionAPI binding in isolation.
*/
const { test } = require('node:test');
@@ -22,12 +24,13 @@ const { profileOf } = require('../gsd-core/bin/lib/host-integration.cjs');
const gsdPiPlugin = require('./fixtures/pi-host-plugin.cjs');
// Mock pi ExtensionAPI: records registrations so the plugin is testable without
// a live pi runtime.
// a live pi runtime. Records the full command/tool definitions (not just
// names) so #2102's handler/execute SHAPE can be asserted, not just presence.
function mockPi() {
const recorded = { commands: [], tools: [], events: [] };
const recorded = { commands: [], tools: [], events: [], commandDefs: {}, toolDefs: {} };
return {
registerCommand(name) { recorded.commands.push(name); },
registerTool(def) { if (def && def.name) recorded.tools.push(def.name); },
registerCommand(name, def) { recorded.commands.push(name); recorded.commandDefs[name] = def; },
registerTool(def) { if (def && def.name) { recorded.tools.push(def.name); recorded.toolDefs[def.name] = def; } },
registerShortcut() {},
registerFlag() {},
on(event) { recorded.events.push(event); },
@@ -58,6 +61,27 @@ test('the pi reference host-plugin binds GSD via the ExtensionAPI (command + too
assert.ok(pi._recorded.events.includes('tool_call'), 'subscribes to the tool_call event');
});
// #2102 Stage 2: pi's REAL ExtensionAPI shape is `handler(args, ctx)` for
// registerCommand (NOT `execute(ctx)`, which the original #1682 cut used) and
// a 5-arg `execute(toolCallId, params, signal, onUpdate, ctx)` for
// registerTool. Locks the shape so a future drift back to the wrong contract
// is a visible test failure.
test('the /gsd command registers a handler(args, ctx) — not execute(ctx)', () => {
const pi = mockPi();
gsdPiPlugin(pi);
const def = pi._recorded.commandDefs['gsd'];
assert.equal(typeof def.handler, 'function', 'registerCommand takes handler(args, ctx), pi\'s real ExtensionAPI shape');
assert.equal(def.execute, undefined, 'must not use the wrong execute(ctx) shape');
});
test('the gsd_invoke tool registers a 5-arg execute(toolCallId, params, signal, onUpdate, ctx)', () => {
const pi = mockPi();
gsdPiPlugin(pi);
const def = pi._recorded.toolDefs['gsd_invoke'];
assert.equal(typeof def.execute, 'function');
assert.equal(def.execute.length, 5, 'gsd_invoke.execute must declare pi\'s real 5-arg tool-execute signature');
});
test('gsdPiPlugin throws if the pi ExtensionAPI is not provided (fail-closed)', () => {
assert.throws(() => gsdPiPlugin(null), /ExtensionAPI is required/);
assert.throws(() => gsdPiPlugin(undefined), /ExtensionAPI is required/);

172
tests/pi-upgrades.test.cjs Normal file
View File

@@ -0,0 +1,172 @@
'use strict';
/**
* pi upgrades — ADR-1239 Phase D / #2102 Stage 2 (EoS/pi).
*
* Mirrors tests/opencode-imperative-reference.test.cjs's structure (Host-
* Integration axes classification/negotiation + the Context7-verified
* upgrades) for pi's three additive upgrades:
* 1. EXTENSION_EVENT_SURFACES.pi — the full ~30-event ExtensionAPI surface
* (was a single-event ['tool_call'] placeholder).
* 2. Event bindings — pi/gsd.cjs actually binds session_start,
* before_agent_start, session_before_compact (+ tool_call) via pi.on(),
* not just declaring the surface in host-integration.cts.
* 3. Active-model steering — before_provider_request resolves GSD's
* tier→model via the model-catalog's pi entries (populated this stage)
* and returns a bare anthropic model id pi's built-in models accept;
* fails open (returns undefined) when resolution comes back null.
*
* Plus the command-surface completions (getArgumentCompletions) and the
* standard fail-closed negotiation guarantee.
*/
const { test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const {
extensionEventSurfaceFor,
negotiateHostCapabilities,
UNDOCUMENTED,
} = require('../gsd-core/bin/lib/host-integration.cjs');
const { RUNTIME_PROFILE_MAP } = require('../gsd-core/bin/lib/model-catalog.cjs');
const gsdPiExtension = require('../pi/gsd.cjs');
const { _internals } = require('../pi/gsd.cjs');
const PI_CAP = JSON.parse(
fs.readFileSync(path.join(__dirname, '..', 'capabilities', 'pi', 'capability.json'), 'utf8'),
);
const PI_AXES = PI_CAP.runtime.hostIntegration;
function mockPi() {
const recorded = { commands: {}, tools: {}, events: {} };
return {
registerCommand(name, def) { recorded.commands[name] = def; },
registerTool(def) { if (def && def.name) recorded.tools[def.name] = def; },
registerProvider() {
throw new Error('gsdPiExtension must NOT call registerProvider — GSD steers pi\'s existing anthropic models, it does not add a new provider');
},
on(event, handler) { (recorded.events[event] = recorded.events[event] || []).push(handler); },
_recorded: recorded,
};
}
// -- (1) EXTENSION_EVENT_SURFACES.pi has all 30 events -----------------------
const EXPECTED_PI_EVENTS = [
'session_start', 'project_trust', 'resources_discover', 'input',
'before_agent_start', 'agent_start', 'message_start', 'message_update',
'message_end', 'turn_start', 'context', 'before_provider_request',
'after_provider_response', 'tool_execution_start', 'tool_execution_update',
'tool_execution_end', 'tool_call', 'tool_result', 'turn_end', 'agent_end',
'session_before_switch', 'session_shutdown', 'session_before_fork',
'session_info_changed', 'session_before_compact', 'session_compact',
'session_before_tree', 'session_tree', 'thinking_level_select', 'model_select',
];
test('pi extension-event surface declares all 30 documented ExtensionAPI events (#2102)', () => {
const surface = extensionEventSurfaceFor('pi');
assert.ok(surface, 'pi is a consumed extensionEvents dialect');
assert.equal(surface.length, 30, `expected exactly 30 events, got ${surface.length}`);
for (const ev of EXPECTED_PI_EVENTS) {
assert.ok(surface.includes(ev), `expected pi extension-event surface to include "${ev}"`);
}
assert.deepEqual([...surface].sort(), [...EXPECTED_PI_EVENTS].sort());
});
// -- (2) the binding actually binds session_start/before_agent_start/ -------
// session_before_compact (not just declared in host-integration.cts)
test('gsdPiExtension binds session_start, before_agent_start, session_before_compact, tool_call, before_provider_request', () => {
const pi = mockPi();
gsdPiExtension(pi);
for (const ev of ['session_start', 'before_agent_start', 'session_before_compact', 'tool_call', 'before_provider_request']) {
assert.ok(Array.isArray(pi._recorded.events[ev]) && pi._recorded.events[ev].length > 0,
`expected gsdPiExtension to bind pi.on("${ev}", ...)`);
}
});
test('gsdPiExtension does NOT call registerProvider (GSD steers pi\'s existing anthropic models, not a new provider)', () => {
const pi = mockPi();
// If gsdPiExtension called registerProvider, mockPi's registerProvider throws.
assert.doesNotThrow(() => gsdPiExtension(pi));
});
// Finding #3 (adversarial review): the tests below previously only exercised
// buildBeforeProviderRequestHandler() directly (the builder), never the
// handler ACTUALLY REGISTERED via pi.on('before_provider_request', ...) in
// gsdPiExtension. This ties the bound handler (default tier = 'sonnet') to
// the real model-catalog steering end-to-end.
test('the ACTUALLY-REGISTERED before_provider_request handler steers to the default-tier model-catalog pi id', async () => {
const pi = mockPi();
gsdPiExtension(pi);
const boundHandler = pi._recorded.events['before_provider_request'][0];
assert.equal(typeof boundHandler, 'function');
const out = await boundHandler({ payload: {} }, { cwd: __dirname });
assert.ok(out, 'expected a modified payload, not undefined');
assert.equal(out.model, RUNTIME_PROFILE_MAP.pi.sonnet.model, 'the bound handler steers to the default (sonnet) tier\'s model-catalog pi id');
assert.equal(out.model, 'claude-sonnet-5');
});
// -- (3) before_provider_request: active-model steering ----------------------
test('before_provider_request resolves a tier that maps to a model → returns a payload with the bare anthropic model id (model-catalog pi ids)', async () => {
const handler = _internals.buildBeforeProviderRequestHandler({ tier: 'sonnet' });
const result = await handler({ payload: { existing: 'field' } }, { cwd: __dirname });
assert.ok(result, 'expected a modified payload, not undefined');
assert.equal(result.existing, 'field', 'original payload fields are preserved');
assert.equal(result.model, RUNTIME_PROFILE_MAP.pi.sonnet.model, 'model id matches the model-catalog pi entry');
assert.equal(result.model, 'claude-sonnet-5');
});
test('before_provider_request resolves opus/haiku tiers to their model-catalog pi ids too', async () => {
const opusHandler = _internals.buildBeforeProviderRequestHandler({ tier: 'opus' });
const opusResult = await opusHandler({ payload: {} }, { cwd: __dirname });
assert.equal(opusResult.model, RUNTIME_PROFILE_MAP.pi.opus.model);
const haikuHandler = _internals.buildBeforeProviderRequestHandler({ tier: 'haiku' });
const haikuResult = await haikuHandler({ payload: {} }, { cwd: __dirname });
assert.equal(haikuResult.model, RUNTIME_PROFILE_MAP.pi.haiku.model);
});
test('before_provider_request given a tier that resolves to null returns undefined (fail-open, never a wrong/empty id)', async () => {
const handler = _internals.buildBeforeProviderRequestHandler({ tier: 'not-a-real-tier-8675309' });
const result = await handler({ payload: { existing: 'field' } }, { cwd: __dirname });
assert.equal(result, undefined);
});
// -- getArgumentCompletions returns family suggestions -----------------------
test('getArgumentCompletions filters PI_COMMAND_FAMILIES by prefix and returns null when empty', () => {
const matches = _internals.getArgumentCompletions('mi');
assert.ok(Array.isArray(matches) && matches.length > 0);
assert.ok(matches.some((m) => m.value === 'milestone'));
for (const m of matches) {
assert.equal(typeof m.value, 'string');
assert.equal(typeof m.label, 'string');
}
const all = _internals.getArgumentCompletions('');
assert.ok(Array.isArray(all) && all.length > 0);
assert.deepEqual(all.map((m) => m.value), [..._internals.PI_COMMAND_FAMILIES]);
const none = _internals.getArgumentCompletions('zzz-no-such-family-8675309');
assert.equal(none, null);
});
// -- fail-closed negotiation for pi -------------------------------------------
test('negotiateHostCapabilities never throws for pi, even on an undeclared/corrupted axis', () => {
assert.doesNotThrow(() => negotiateHostCapabilities({}));
assert.doesNotThrow(() => negotiateHostCapabilities({ ...PI_AXES, embeddingMode: UNDOCUMENTED }));
assert.doesNotThrow(() => negotiateHostCapabilities({ ...PI_AXES, embeddingMode: 'future-unknown-axis-value' }));
assert.doesNotThrow(() => negotiateHostCapabilities({ ...PI_AXES, dispatch: undefined }));
});
test('pi axes negotiate modelMode:"active" (the active-model steering axis)', () => {
const result = negotiateHostCapabilities(PI_AXES);
assert.equal(result.effective.modelMode, 'active');
});

View File

@@ -14,7 +14,7 @@ const registry = require('../gsd-core/bin/lib/capability-registry.cjs');
const EXPECTED_FLAGS = [
'isOpencode', 'isKilo', 'isCodex', 'isCopilot', 'isAntigravity',
'isCursor', 'isWindsurf', 'isAugment', 'isTrae', 'isQwen', 'isHermes',
'isCodebuddy', 'isCline', 'isKimi', 'isZcode',
'isCodebuddy', 'isCline', 'isKimi', 'isZcode', 'isPi',
];
test('runtimeFlags: every known non-claude runtime sets exactly its own flag true', () => {
@@ -35,12 +35,12 @@ test('runtimeFlags: claude / unknown / empty → all flags false (fail-closed)',
}
});
test('runtimeFlags: all 15 flags present + boolean + the object is frozen', () => {
test('runtimeFlags: all 16 flags present + boolean + the object is frozen', () => {
const flags = runtimeFlags('opencode');
for (const f of EXPECTED_FLAGS) {
assert.strictEqual(typeof flags[f], 'boolean', `${f} must be boolean`);
}
assert.deepStrictEqual(Object.keys(flags).sort(), [...EXPECTED_FLAGS].sort(), 'exactly the 15 flags');
assert.deepStrictEqual(Object.keys(flags).sort(), [...EXPECTED_FLAGS].sort(), 'exactly the 16 flags');
assert.ok(Object.isFrozen(flags), 'flags object must be frozen');
});

View File

@@ -14,6 +14,8 @@ const {
platformWriteSync,
platformReadSync,
platformEnsureDir,
dispatchGsdCommand,
resolveGsdToolsPath,
} = require(path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'shell-command-projection.cjs'));
const { createTempGitProject, createTempDir, cleanup } = require('./helpers.cjs');
@@ -99,6 +101,78 @@ describe('execTool', () => {
});
});
// ─── dispatchGsdCommand (#2102 Stage 2 — subprocess-shim dispatch to gsd-tools.cjs) ──
//
// The command-routing hub (`createHub()`) has no fully-populated factory
// anywhere in the tree — every caller builds a single-family hub — so the
// only dispatch path covering the FULL family/subcommand surface is the
// gsd-tools.cjs CLI itself. This is the shared helper pi/gsd.cjs and the
// companion MCP server both dispatch through.
describe('dispatchGsdCommand', () => {
let tmpDir;
beforeEach(() => { tmpDir = createTempDir(); });
afterEach(() => { cleanup(tmpDir); });
test('resolveGsdToolsPath resolves to the real gsd-tools.cjs on disk', () => {
const toolsPath = resolveGsdToolsPath();
assert.ok(fs.existsSync(toolsPath), `expected gsd-tools.cjs to exist at ${toolsPath}`);
assert.equal(path.basename(toolsPath), 'gsd-tools.cjs');
});
test('a valid read-only family/subcommand dispatches for real and returns ok:true + non-empty stdout', () => {
const result = dispatchGsdCommand({ family: 'progress', subcommand: 'json', cwd: tmpDir });
assert.equal(result.ok, true, `expected ok:true, got: ${JSON.stringify(result)}`);
assert.equal(typeof result.stdout, 'string');
assert.ok(result.stdout.length > 0, 'stdout must be non-empty');
const parsed = JSON.parse(result.stdout);
assert.equal(typeof parsed.percent, 'number', 'the real progress command ran (proves the engine was reached)');
assert.equal(result.code, 0);
assert.equal(result.timedOut, false);
});
test('an unknown family returns ok:false without throwing', () => {
assert.doesNotThrow(() => {
const result = dispatchGsdCommand({ family: 'no-such-family-8675309', cwd: tmpDir });
assert.equal(result.ok, false);
assert.notEqual(result.code, 0);
assert.equal(typeof result.stderr, 'string');
assert.ok(result.stderr.length > 0);
// --json-errors gives a structured, parseable error envelope.
const parsedErr = JSON.parse(result.stderr);
assert.equal(parsedErr.ok, false);
});
});
test('a missing/bogus gsd-tools.cjs path degrades to ok:false without throwing', () => {
assert.doesNotThrow(() => {
const result = dispatchGsdCommand({
family: 'progress',
cwd: tmpDir,
gsdToolsPath: path.join(tmpDir, 'definitely-not-a-real-gsd-tools-8675309.cjs'),
});
assert.equal(result.ok, false);
assert.equal(result.timedOut, false);
});
});
test('a missing/empty "family" is rejected locally without spawning a subprocess', () => {
const result = dispatchGsdCommand({ cwd: tmpDir });
assert.equal(result.ok, false);
assert.equal(result.code, null);
assert.match(result.stderr, /requires a non-empty string "family"/);
});
test('a wall-clock timeout is reported via timedOut:true, ok:false — never throws', () => {
assert.doesNotThrow(() => {
const result = dispatchGsdCommand({ family: 'progress', subcommand: 'json', cwd: tmpDir, timeout: 1 });
assert.equal(result.ok, false);
assert.equal(result.timedOut, true);
});
});
});
// ─── probeTty ────────────────────────────────────────────────────────────────
describe('probeTty', () => {

View File

@@ -68,7 +68,7 @@
"scan.md": 7732,
"secure-phase.md": 13520,
"session-report.md": 4044,
"settings-advanced.md": 39908,
"settings-advanced.md": 40019,
"settings-integrations.md": 15892,
"settings.md": 33467,
"ship.md": 24691,