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:
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
322
tests/fixtures/golden-install-parity/pi.json
vendored
Normal file
322
tests/fixtures/golden-install-parity/pi.json
vendored
Normal 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"
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
28
tests/fixtures/pi-host-plugin.cjs
vendored
28
tests/fixtures/pi-host-plugin.cjs
vendored
@@ -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';
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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') },
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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)', () => {
|
||||
|
||||
@@ -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)', () => {
|
||||
|
||||
@@ -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
172
tests/pi-upgrades.test.cjs
Normal 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');
|
||||
});
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user