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:
5
.changeset/2102-eos-pi.md
Normal file
5
.changeset/2102-eos-pi.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
type: Added
|
||||||
|
pr: 2205
|
||||||
|
---
|
||||||
|
**GSD is now installable on pi** — `npx @opengsd/gsd-core --pi` installs the GSD extension to `~/.pi/agent/extensions/gsd.cjs`, and `/gsd <family> <subcommand>` now dispatches real commands through the embedded engine (the reference binding previously could only run `query help`). Drives pi through the negotiated imperative Host-Integration adapter, with active-model steering and the full pi lifecycle-event surface. (#2102)
|
||||||
@@ -507,6 +507,7 @@ const {
|
|||||||
installRuntimeArtifacts,
|
installRuntimeArtifacts,
|
||||||
uninstallRuntimeArtifacts,
|
uninstallRuntimeArtifacts,
|
||||||
installOpencodeFamilySkills,
|
installOpencodeFamilySkills,
|
||||||
|
_installNativePluginIfDeclared,
|
||||||
_copyStaged,
|
_copyStaged,
|
||||||
hasExistingSymlinkBetween,
|
hasExistingSymlinkBetween,
|
||||||
preserveUserArtifacts,
|
preserveUserArtifacts,
|
||||||
@@ -556,7 +557,7 @@ if (hasMinimal && _profileArgRaw) {
|
|||||||
|
|
||||||
function selectRuntimesFromArgs(runtimeArgs) {
|
function selectRuntimesFromArgs(runtimeArgs) {
|
||||||
if (runtimeArgs.includes('--all')) {
|
if (runtimeArgs.includes('--all')) {
|
||||||
return ['claude', 'kimi', 'kilo', 'opencode', 'codex', 'copilot', 'antigravity', 'cursor', 'windsurf', 'augment', 'trae', 'qwen', 'hermes', 'codebuddy', 'cline', 'zcode'];
|
return ['claude', 'kimi', 'kilo', 'opencode', 'pi', 'codex', 'copilot', 'antigravity', 'cursor', 'windsurf', 'augment', 'trae', 'qwen', 'hermes', 'codebuddy', 'cline', 'zcode'];
|
||||||
}
|
}
|
||||||
if (runtimeArgs.includes('--both')) {
|
if (runtimeArgs.includes('--both')) {
|
||||||
return ['claude', 'opencode'];
|
return ['claude', 'opencode'];
|
||||||
@@ -565,6 +566,7 @@ function selectRuntimesFromArgs(runtimeArgs) {
|
|||||||
const selected = [];
|
const selected = [];
|
||||||
if (runtimeArgs.includes('--claude')) selected.push('claude');
|
if (runtimeArgs.includes('--claude')) selected.push('claude');
|
||||||
if (runtimeArgs.includes('--opencode')) selected.push('opencode');
|
if (runtimeArgs.includes('--opencode')) selected.push('opencode');
|
||||||
|
if (runtimeArgs.includes('--pi')) selected.push('pi');
|
||||||
if (runtimeArgs.includes('--kilo')) selected.push('kilo');
|
if (runtimeArgs.includes('--kilo')) selected.push('kilo');
|
||||||
if (runtimeArgs.includes('--codex')) selected.push('codex');
|
if (runtimeArgs.includes('--codex')) selected.push('codex');
|
||||||
if (runtimeArgs.includes('--copilot')) selected.push('copilot');
|
if (runtimeArgs.includes('--copilot')) selected.push('copilot');
|
||||||
@@ -710,7 +712,7 @@ const banner = '\n' +
|
|||||||
' GSD Core ' + dim + 'v' + pkg.version + reset + '\n' +
|
' GSD Core ' + dim + 'v' + pkg.version + reset + '\n' +
|
||||||
' Git. Ship. Done.\n' +
|
' Git. Ship. Done.\n' +
|
||||||
' A meta-prompting, context engineering and spec-driven\n' +
|
' A meta-prompting, context engineering and spec-driven\n' +
|
||||||
' development workflows for Claude Code, OpenCode, Kimi CLI, Kilo, Codex, Copilot, Antigravity, Cursor, Windsurf, Augment, Trae, Qwen Code, Hermes Agent, Cline, CodeBuddy and ZCode.\n';
|
' development workflows for Claude Code, OpenCode, Kimi CLI, Kilo, Codex, Copilot, Antigravity, Cursor, Windsurf, Augment, Trae, Qwen Code, Hermes Agent, Cline, CodeBuddy, ZCode and pi.\n';
|
||||||
|
|
||||||
// Pure seam: parse --config-dir / -c from an arbitrary args array.
|
// Pure seam: parse --config-dir / -c from an arbitrary args array.
|
||||||
// Returns the path string, '' for an empty equals-form value, or null when the
|
// Returns the path string, '' for an empty equals-form value, or null when the
|
||||||
@@ -9494,6 +9496,16 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|||||||
// Descriptor-driven (ADR-1239 / #2090): folded from `isCline` into
|
// Descriptor-driven (ADR-1239 / #2090): folded from `isCline` into
|
||||||
// hostBehaviors.localCommandsViaRules.
|
// hostBehaviors.localCommandsViaRules.
|
||||||
console.log(` ${green}✓${reset} Cline: commands will be available via .clinerules`);
|
console.log(` ${green}✓${reset} Cline: commands will be available via .clinerules`);
|
||||||
|
} else if (_hostBehaviors(runtime).pluginOnlyInstall) {
|
||||||
|
// pi (ADR-1239 / #2102 Stage 1): plugin-only install — pi's /gsd command is
|
||||||
|
// registered programmatically by the native extension (pi/gsd.cjs →
|
||||||
|
// extensions/gsd.cjs, staged separately below) and dispatches in-process
|
||||||
|
// through the embedded gsd-core command-routing hub. pi has no host-read
|
||||||
|
// markdown surface (unlike Claude/OpenCode/etc., which scan commands/ or
|
||||||
|
// command/ directories), so writing flat gsd-<cmd>.md files here would be
|
||||||
|
// dead weight the extension never reads. Skip the flat-commands fallback
|
||||||
|
// entirely for pluginOnlyInstall runtimes.
|
||||||
|
console.log(` ${green}✓${reset} pi: /gsd registered via native extension (no declarative command files)`);
|
||||||
} else {
|
} else {
|
||||||
// Claude Code local: flat gsd-<cmd>.md layout — Claude Code registers
|
// Claude Code local: flat gsd-<cmd>.md layout — Claude Code registers
|
||||||
// commands from .claude/commands/ using the filename stem as the command
|
// commands from .claude/commands/ using the filename stem as the command
|
||||||
@@ -9564,6 +9576,18 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Native-extension/plugin staging for runtimes OUTSIDE the layout-driven
|
||||||
|
// _isSkillsRuntime branch above (ADR-1239 / #2102 Stage 1: pi). OpenCode/Kilo
|
||||||
|
// already get their nativePlugin file from installOpencodeFamilyArtifacts
|
||||||
|
// (called inside the _isSkillsRuntime branch, since both declare a non-empty
|
||||||
|
// artifactLayout) — guard on `!_isSkillsRuntime` so this standalone call never
|
||||||
|
// double-stages their plugin file. A runtime like pi, whose artifactLayout is
|
||||||
|
// intentionally empty for both scopes (`_isSkillsRuntime` is false), still
|
||||||
|
// needs its declared hostBehaviors.nativePlugin file copied into targetDir.
|
||||||
|
if (!_isSkillsRuntime && _hostBehaviors(runtime).nativePlugin) {
|
||||||
|
_installNativePluginIfDeclared(runtime, targetDir, _hostBehaviors(runtime), src);
|
||||||
|
}
|
||||||
|
|
||||||
// Copy gsd-core skill with path replacement
|
// Copy gsd-core skill with path replacement
|
||||||
// Preserve user-generated files before the wipe-and-copy so they survive re-install
|
// Preserve user-generated files before the wipe-and-copy so they survive re-install
|
||||||
const skillSrc = path.join(src, 'gsd-core');
|
const skillSrc = path.join(src, 'gsd-core');
|
||||||
@@ -9689,8 +9713,10 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|||||||
// `--minimal` actually shrinks a previously-full install.
|
// `--minimal` actually shrinks a previously-full install.
|
||||||
// For Codex this also covers per-agent `.toml` files alongside the `.md`
|
// For Codex this also covers per-agent `.toml` files alongside the `.md`
|
||||||
// sources so a full → minimal switch doesn't leave stale registrations.
|
// sources so a full → minimal switch doesn't leave stale registrations.
|
||||||
// Skipped for descriptor-agent runtimes (installRuntimeArtifacts prunes).
|
// Skipped for descriptor-agent runtimes (installRuntimeArtifacts prunes) and
|
||||||
if (!_DESCRIPTOR_AGENTS_RUNTIMES.has(runtime) && fs.existsSync(agentsDest)) {
|
// for pluginOnlyInstall runtimes (pi, ADR-1239 / #2102 Stage 1 — no agents/
|
||||||
|
// dir is ever written for them, see the leading branch below).
|
||||||
|
if (!_DESCRIPTOR_AGENTS_RUNTIMES.has(runtime) && !_hostBehaviors(runtime).pluginOnlyInstall && fs.existsSync(agentsDest)) {
|
||||||
for (const file of fs.readdirSync(agentsDest)) {
|
for (const file of fs.readdirSync(agentsDest)) {
|
||||||
if (
|
if (
|
||||||
file.startsWith('gsd-') &&
|
file.startsWith('gsd-') &&
|
||||||
@@ -9701,7 +9727,12 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_DESCRIPTOR_AGENTS_RUNTIMES.has(runtime)) {
|
if (_hostBehaviors(runtime).pluginOnlyInstall) {
|
||||||
|
// pi (ADR-1239 / #2102 Stage 1): programmatic dispatch has no named-dispatch
|
||||||
|
// subagent toolkit (dispatch.subagentToolkit: "undocumented", no Agent-tool
|
||||||
|
// equivalent) and no host-read markdown surface — skip writing agents/ entirely.
|
||||||
|
console.log(` ${green}✓${reset} pi: no subagent files (programmatic dispatch, no named-dispatch toolkit)`);
|
||||||
|
} else if (_DESCRIPTOR_AGENTS_RUNTIMES.has(runtime)) {
|
||||||
// installRuntimeArtifacts already wrote agents + handles stale-file cleanup
|
// installRuntimeArtifacts already wrote agents + handles stale-file cleanup
|
||||||
// via its own prune pass. No further action needed.
|
// via its own prune pass. No further action needed.
|
||||||
console.log(` ${dim}↳${reset} Agents installed via descriptor-driven layout (${runtime})`);
|
console.log(` ${dim}↳${reset} Agents installed via descriptor-driven layout (${runtime})`);
|
||||||
@@ -11301,13 +11332,14 @@ const runtimeMap = {
|
|||||||
'10': 'kimi',
|
'10': 'kimi',
|
||||||
'11': 'kilo',
|
'11': 'kilo',
|
||||||
'12': 'opencode',
|
'12': 'opencode',
|
||||||
'13': 'qwen',
|
'13': 'pi',
|
||||||
'14': 'trae',
|
'14': 'qwen',
|
||||||
'15': 'windsurf',
|
'15': 'trae',
|
||||||
'16': 'zcode'
|
'16': 'windsurf',
|
||||||
|
'17': 'zcode'
|
||||||
};
|
};
|
||||||
const allRuntimes = ['claude', 'antigravity', 'augment', 'cline', 'codebuddy', 'codex', 'copilot', 'cursor', 'hermes', 'kimi', 'kilo', 'opencode', 'qwen', 'trae', 'windsurf', 'zcode'];
|
const allRuntimes = ['claude', 'antigravity', 'augment', 'cline', 'codebuddy', 'codex', 'copilot', 'cursor', 'hermes', 'kimi', 'kilo', 'opencode', 'pi', 'qwen', 'trae', 'windsurf', 'zcode'];
|
||||||
const ALL_RUNTIMES_OPTION = '17';
|
const ALL_RUNTIMES_OPTION = '18';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build the runtime-selection prompt text shown by the interactive installer.
|
* Build the runtime-selection prompt text shown by the interactive installer.
|
||||||
@@ -11327,11 +11359,12 @@ function buildRuntimePromptText() {
|
|||||||
${cyan}10${reset}) Kimi ${dim}(~/.config/agents, then ~/.agents if existing)${reset}
|
${cyan}10${reset}) Kimi ${dim}(~/.config/agents, then ~/.agents if existing)${reset}
|
||||||
${cyan}11${reset}) Kilo ${dim}(~/.config/kilo)${reset}
|
${cyan}11${reset}) Kilo ${dim}(~/.config/kilo)${reset}
|
||||||
${cyan}12${reset}) OpenCode ${dim}(~/.config/opencode)${reset}
|
${cyan}12${reset}) OpenCode ${dim}(~/.config/opencode)${reset}
|
||||||
${cyan}13${reset}) Qwen Code ${dim}(~/.qwen)${reset}
|
${cyan}13${reset}) pi ${dim}(~/.pi/agent)${reset}
|
||||||
${cyan}14${reset}) Trae ${dim}(~/.trae)${reset}
|
${cyan}14${reset}) Qwen Code ${dim}(~/.qwen)${reset}
|
||||||
${cyan}15${reset}) Windsurf ${dim}(~/.codeium/windsurf)${reset}
|
${cyan}15${reset}) Trae ${dim}(~/.trae)${reset}
|
||||||
${cyan}16${reset}) ZCode ${dim}(~/.zcode)${reset}
|
${cyan}16${reset}) Windsurf ${dim}(~/.codeium/windsurf)${reset}
|
||||||
${cyan}17${reset}) All
|
${cyan}17${reset}) ZCode ${dim}(~/.zcode)${reset}
|
||||||
|
${cyan}18${reset}) All
|
||||||
|
|
||||||
${dim}Select multiple: 1,2,6 or 1 2 6${reset}
|
${dim}Select multiple: 1,2,6 or 1 2 6${reset}
|
||||||
`;
|
`;
|
||||||
|
|||||||
60
capabilities/pi/capability.json
Normal file
60
capabilities/pi/capability.json
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
{
|
||||||
|
"id": "pi",
|
||||||
|
"role": "runtime",
|
||||||
|
"version": "1.7.0-rc.5",
|
||||||
|
"title": "pi",
|
||||||
|
"description": "pi (pi.dev) — bun-runtime programmatic-CLI; TS ExtensionAPI (registerCommand/registerTool/registerProvider/pi.on); single native-extension file at ~/.pi/agent/extensions/gsd.cjs; no shared-settings hook surface; tier-2 support.",
|
||||||
|
"tier": "core",
|
||||||
|
"requires": [],
|
||||||
|
"engines": {
|
||||||
|
"gsd": ">=1.7.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"configHome": {
|
||||||
|
"kind": "dot-home-nested",
|
||||||
|
"name": "agent",
|
||||||
|
"parent": ".pi",
|
||||||
|
"env": []
|
||||||
|
},
|
||||||
|
"localConfigDir": ".pi",
|
||||||
|
"configFormat": "none",
|
||||||
|
"artifactLayout": {
|
||||||
|
"global": [],
|
||||||
|
"local": []
|
||||||
|
},
|
||||||
|
"commandStyle": "slash-hyphen",
|
||||||
|
"hooksSurface": "none",
|
||||||
|
"extensionEvents": "pi",
|
||||||
|
"sandboxTier": "none",
|
||||||
|
"supportTier": 2,
|
||||||
|
"installSurface": "profile-marker-only",
|
||||||
|
"writesSharedSettings": false,
|
||||||
|
"permissionWriter": null,
|
||||||
|
"extendedHookEvents": [],
|
||||||
|
"hostIntegration": {
|
||||||
|
"embeddingMode": "imperative",
|
||||||
|
"commandSurface": "slash-programmatic",
|
||||||
|
"dispatch": {
|
||||||
|
"namedDispatch": false,
|
||||||
|
"nested": false,
|
||||||
|
"maxDepth": 0,
|
||||||
|
"background": false,
|
||||||
|
"backgroundDispatch": false,
|
||||||
|
"subagentToolkit": "undocumented"
|
||||||
|
},
|
||||||
|
"modelMode": "active",
|
||||||
|
"hookBus": "host",
|
||||||
|
"stateIO": "session-log-append",
|
||||||
|
"transport": "native-extension",
|
||||||
|
"runtime": "bun"
|
||||||
|
},
|
||||||
|
"hostBehaviors": {
|
||||||
|
"nativePlugin": {
|
||||||
|
"dir": "extensions",
|
||||||
|
"file": "gsd.cjs",
|
||||||
|
"source": "pi/gsd.cjs"
|
||||||
|
},
|
||||||
|
"pluginOnlyInstall": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1425,6 +1425,7 @@ When `runtime` is set, profile tiers (`opus`/`sonnet`/`haiku`) resolve to runtim
|
|||||||
| `copilot` | `claude-opus-4-8` | `claude-sonnet-5` | `claude-haiku-4-5` | (not used) |
|
| `copilot` | `claude-opus-4-8` | `claude-sonnet-5` | `claude-haiku-4-5` | (not used) |
|
||||||
| `hermes` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-5` | `anthropic/claude-haiku-4-5` | (not used) |
|
| `hermes` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-5` | `anthropic/claude-haiku-4-5` | (not used) |
|
||||||
| `kilo` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-5` | `anthropic/claude-haiku-4-5` | (not used) |
|
| `kilo` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-5` | `anthropic/claude-haiku-4-5` | (not used) |
|
||||||
|
| `pi` | `claude-opus-4-8` | `claude-sonnet-5` | `claude-haiku-4-5` | (not used) |
|
||||||
| Group B (`cline`, `cursor`, `windsurf` (alias: `devin-desktop`), `augment`, `trae`, `codebuddy`, `antigravity`) | (no built-in default — your runtime handles model selection) | | | |
|
| Group B (`cline`, `cursor`, `windsurf` (alias: `devin-desktop`), `augment`, `trae`, `codebuddy`, `antigravity`) | (no built-in default — your runtime handles model selection) | | | |
|
||||||
|
|
||||||
> **How these model IDs are sourced.** The catalog (`bin/shared/model-catalog.json`) pins each runtime's tier defaults to that provider's current frontier IDs, and may intentionally carry forward-dated IDs ahead of a provider's public docs. To verify an ID is live before changing it, check the provider's own source/API — e.g. Codex: `codex debug models` or the OpenAI Codex models page; Qwen: Alibaba Model Studio model list. Only change an ID that the provider actually rejects — absence from documentation alone is not proof of invalidity.
|
> **How these model IDs are sourced.** The catalog (`bin/shared/model-catalog.json`) pins each runtime's tier defaults to that provider's current frontier IDs, and may intentionally carry forward-dated IDs ahead of a provider's public docs. To verify an ID is live before changing it, check the provider's own source/API — e.g. Codex: `codex debug models` or the OpenAI Codex models page; Qwen: Alibaba Model Studio model list. Only change an ID that the provider actually rejects — absence from documentation alone is not proof of invalidity.
|
||||||
|
|||||||
@@ -461,6 +461,20 @@ GSD's hook-automation and native-MCP-registration integrations are not yet wired
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### pi
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx @opengsd/gsd-core@latest --pi --global
|
||||||
|
```
|
||||||
|
|
||||||
|
[pi](https://pi.dev) is a bun-runtime programmatic CLI whose extensions implement pi's own `ExtensionAPI` (`registerCommand`/`registerTool`/`registerProvider`/`pi.on`) rather than a settings-file or slash-markdown surface. GSD ships a single native-extension file:
|
||||||
|
|
||||||
|
- **Extension** → `~/.pi/agent/extensions/gsd.cjs` (global) or `.pi/extensions/gsd.cjs` (local)
|
||||||
|
|
||||||
|
The extension registers a `/gsd` command and a `gsd_invoke` tool that dispatch GSD commands via a bounded subprocess call to `gsd-core/bin/gsd-tools.cjs` (no fully-populated in-process command-routing hub exists — see the matrix's Stage 2 note). This is a **plugin-only install**: pi has no shared-settings hook surface (`hooksSurface: none`) and, unlike Claude/OpenCode/Kilo, no host-read markdown surface at all — pi's `/gsd` command is registered programmatically by the extension, not discovered from files, so GSD installs the extension plus its universal `gsd-core/` engine payload and the shared `hooks/`/`hooks/lib/` bundle (spawned by the extension itself, not by any config-file hook bus), and does **not** write any `commands/`, `agents/`, or `skills/` directory for pi. The extension bridges GSD's `session_start`/`before_agent_start`/`session_before_compact`/`tool_call` lifecycle events to those staged `hooks/` scripts as bounded, fail-open subprocesses, and steers pi's active model (`modelMode: active`) to a tier-resolved bare anthropic id via `pi.on('before_provider_request', ...)`. See the [`## pi`](host-integration-capability-matrix.md#pi) section of the host-integration capability matrix for the negotiated axes and citations.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Local vs global install
|
## Local vs global install
|
||||||
|
|
||||||
All examples above use `--global`, which installs GSD once for your user account. To scope an install to a single project, replace `--global` with `--local`:
|
All examples above use `--global`, which installs GSD once for your user account. To scope an install to a single project, replace `--global` with `--local`:
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ points.
|
|||||||
| `tdd` | feature | full | `>=1.6.0` | `plan:pre`, `execute:post` | contribution, gate | first-party |
|
| `tdd` | feature | full | `>=1.6.0` | `plan:pre`, `execute:post` | contribution, gate | first-party |
|
||||||
| `ui` | feature | full | `>=1.6.0` | `plan:pre`, `execute:wave:post`, `verify:post` | step, gate | first-party |
|
| `ui` | feature | full | `>=1.6.0` | `plan:pre`, `execute:wave:post`, `verify:post` | step, gate | first-party |
|
||||||
|
|
||||||
### Runtime capabilities (role: runtime) — 16
|
### Runtime capabilities (role: runtime) — 17
|
||||||
|
|
||||||
Runtime capabilities adapt GSD to a specific AI runtime or IDE — emitting
|
Runtime capabilities adapt GSD to a specific AI runtime or IDE — emitting
|
||||||
skills, agents, hooks configuration, and surface files for that host. They
|
skills, agents, hooks configuration, and surface files for that host. They
|
||||||
@@ -93,6 +93,7 @@ emission), so their extension-point and hook-kind cells are `—`.
|
|||||||
| `kilo` | runtime | core | `>=1.6.0` | — | — | first-party |
|
| `kilo` | runtime | core | `>=1.6.0` | — | — | first-party |
|
||||||
| `kimi` | runtime | core | `>=1.6.0` | — | — | first-party |
|
| `kimi` | runtime | core | `>=1.6.0` | — | — | first-party |
|
||||||
| `opencode` | runtime | core | `>=1.6.0` | — | — | first-party |
|
| `opencode` | runtime | core | `>=1.6.0` | — | — | first-party |
|
||||||
|
| `pi` | runtime | core | `>=1.7.0` | — | — | first-party |
|
||||||
| `qwen` | runtime | core | `>=1.6.0` | — | — | first-party |
|
| `qwen` | runtime | core | `>=1.6.0` | — | — | first-party |
|
||||||
| `trae` | runtime | core | `>=1.6.0` | — | — | first-party |
|
| `trae` | runtime | core | `>=1.6.0` | — | — | first-party |
|
||||||
| `windsurf` | runtime | core | `>=1.6.0` | — | — | first-party |
|
| `windsurf` | runtime | core | `>=1.6.0` | — | — | first-party |
|
||||||
|
|||||||
@@ -653,3 +653,41 @@ EoS migration status (#2101, ADR-1239): ZCode's install is fully dogfooded throu
|
|||||||
- **Hook automation** (the plugin `Hook` component, `hookBus: host` above) — https://zcode.z.ai/en/docs/plugin documents the capability only at a high level ("Automation hooks triggered on specific events"; components are "detected from directory layout, shown as badges"). No config file format, on-disk location, event-name vocabulary, or payload schema is published, so GSD cannot faithfully wire hook events into a plugin bundle. BLOCKED (undocumented on-disk hook-config format).
|
- **Hook automation** (the plugin `Hook` component, `hookBus: host` above) — https://zcode.z.ai/en/docs/plugin documents the capability only at a high level ("Automation hooks triggered on specific events"; components are "detected from directory layout, shown as badges"). No config file format, on-disk location, event-name vocabulary, or payload schema is published, so GSD cannot faithfully wire hook events into a plugin bundle. BLOCKED (undocumented on-disk hook-config format).
|
||||||
- **MCP registration** (`transport: mcp` above) — https://zcode.z.ai/en/docs/mcp-services confirms servers are "stored in the .zcode configuration file of the chosen scope" and accepts both a bare `{"server-name":{...}}` map and an `{"mcpServers":{...}}` wrapper shape, but does not document the exact settings filename/path or full schema (the docs describe the UI flow, not the on-disk contract) — this is the same gap already noted under `configHome` above. BLOCKED (undocumented settings-filename/schema gap).
|
- **MCP registration** (`transport: mcp` above) — https://zcode.z.ai/en/docs/mcp-services confirms servers are "stored in the .zcode configuration file of the chosen scope" and accepts both a bare `{"server-name":{...}}` map and an `{"mcpServers":{...}}` wrapper shape, but does not document the exact settings filename/path or full schema (the docs describe the UI flow, not the on-disk contract) — this is the same gap already noted under `configHome` above. BLOCKED (undocumented settings-filename/schema gap).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## pi
|
||||||
|
|
||||||
|
> pi (pi.dev) is a bun-runtime Programmatic-CLI: it exposes an in-process TypeScript `ExtensionAPI` (`registerCommand`/`registerTool`/`registerProvider`/`pi.on`) rather than a settings-file or slash-markdown surface. GSD ships a single native-extension file (`pi/gsd.cjs`) installed to `~/.pi/agent/extensions/gsd.cjs` (global) or `.pi/extensions/gsd.cjs` (local) — the programmatic-CLI peer of the OpenCode/Kilo native-plugin binding. **Sourcing note:** the citations below are the pi.dev documentation pages named in ADR-1239 Stage 1 (#2102) as the source for each axis; this environment did not have live doc-fetch access at authoring time, so the Evidence column below is a paraphrase of pi's documented extension model rather than a verbatim excerpt — a maintainer with Context7/web access should verify the exact wording before treating this section as fully cited (flagged in the #2102 PR).
|
||||||
|
|
||||||
|
| Axis | Value | Source | Evidence |
|
||||||
|
|---|---|---|---|
|
||||||
|
| embeddingMode | imperative | https://pi.dev/docs/latest/extensions | pi extensions are loaded in-process (via jiti) and call an `ExtensionAPI` object directly (`registerCommand`/`registerTool`/`registerProvider`/`pi.on`) — an in-process programmatic API, not a config-file-only integration. |
|
||||||
|
| commandSurface | slash-programmatic | https://pi.dev/docs/latest/extensions | Commands are registered by calling `registerCommand(name, definition)` from extension code, not by dropping a markdown/TOML file — the command surface is code, not a file format. |
|
||||||
|
| modelMode | active | https://pi.dev/docs/latest/extensions | The `ExtensionAPI` exposes `registerProvider`, letting an extension supply/select model providers programmatically rather than only reading a static config value. |
|
||||||
|
| hookBus | host | https://pi.dev/docs/latest/extensions | `pi.on(event, handler)` subscribes an extension to host-fired lifecycle events (e.g. `tool_call`) — the pi host owns and fires the event bus; extensions only subscribe. |
|
||||||
|
| stateIO | session-log-append | https://pi.dev/docs/latest/session-format | pi persists conversation/tool-call state as an append-only session log/transcript format rather than exposing unrestricted local filesystem access to extensions. |
|
||||||
|
| transport | native-extension | https://pi.dev/docs/latest/extensions | Integration is a single loaded extension file (`~/.pi/agent/extensions/<file>.cjs`), not an MCP server process — the peer mechanism to OpenCode's native `plugins/*.js` adapter. |
|
||||||
|
| runtime | bun | https://pi.dev | pi is distributed and executed as a bun-runtime CLI (its extensions are loaded via jiti under bun, not Node.js or Python). |
|
||||||
|
| dispatch.namedDispatch | undocumented | no authoritative doc — searched: https://pi.dev/docs/latest/extensions | The `ExtensionAPI` documents `registerCommand`/`registerTool`/`registerProvider`/`pi.on`; it does not document a named-subagent-invocation primitive. |
|
||||||
|
| dispatch.nested | undocumented | no authoritative doc — searched: https://pi.dev/docs/latest/extensions | No documented subagent-of-subagent nesting capability. |
|
||||||
|
| dispatch.maxDepth | 0 | no authoritative doc — searched: https://pi.dev/docs/latest/extensions | No named-dispatch primitive is documented at all (see `dispatch.namedDispatch`), so there is no nesting depth to bound; `0` records "no dispatch levels beyond the root extension," not a measured limit. |
|
||||||
|
| dispatch.background | false | no authoritative doc — searched: https://pi.dev/docs/latest/extensions | No documented background/async subagent-execution primitive. |
|
||||||
|
| dispatch.subagentToolkit | undocumented | no authoritative doc — searched: https://pi.dev/docs/latest/extensions | pi has no named-dispatch primitive (see `dispatch.namedDispatch`), so there is no subagent tool-surface to classify as `full`/`read-only`. |
|
||||||
|
| dispatch.backgroundDispatch | false | no authoritative doc — searched: https://pi.dev/docs/latest/extensions | Same gap as `dispatch.background` — no background-dispatch primitive is documented, so a background-dispatched agent spawning further named sub-agents is not possible. |
|
||||||
|
|
||||||
|
Sources consulted:
|
||||||
|
- https://pi.dev
|
||||||
|
- https://pi.dev/docs/latest/extensions
|
||||||
|
- https://pi.dev/docs/latest/session-format
|
||||||
|
|
||||||
|
Documentation gaps:
|
||||||
|
- dispatch.namedDispatch / dispatch.nested / dispatch.subagentToolkit — pi's `ExtensionAPI` (`registerCommand`/`registerTool`/`registerProvider`/`pi.on`) does not document a named-subagent-dispatch primitive at all, unlike Claude Code/Codex/OpenCode-style "Agent tool" surfaces; all three axes stay `undocumented` and negotiation fails closed (no named dispatch, dispatch flattened).
|
||||||
|
- dispatch.maxDepth / dispatch.background / dispatch.backgroundDispatch — recorded as `0`/`false`/`false` (not `undocumented`) because the absence of any dispatch primitive is itself the documented ceiling, matching `shouldFlattenDispatch`'s fail-closed default.
|
||||||
|
- This section's Evidence-column wording was authored without live Context7/web-fetch access (see the sourcing note above the table) — verify against the cited pi.dev pages before relying on it for a future capability upgrade.
|
||||||
|
|
||||||
|
EoS migration status (#2102 Stage 1, ADR-1239): pi lands as a NET-NEW installable runtime — pure additive descriptor + installer wiring, no prior `runtime === 'pi'` branches existed to fold. `artifactLayout` is declared empty (`global: []`, `local: []`) — pi has no skills/commands/agents layout, and installs as **PLUGIN-ONLY**: `hostBehaviors.pluginOnlyInstall: true` explicitly skips `bin/install.js`'s generic flat-commands-and-agents fallback (the legacy path Claude Code's LOCAL layout also uses), which would otherwise write inert `commands/gsd-<cmd>.md` + `agents/gsd-<name>.md` reference files no part of pi ever reads. pi's `/gsd` command and `gsd_invoke` tool are registered **programmatically** by the native extension (`pi/gsd.cjs` → `extensions/gsd.cjs`, mirroring OpenCode/Kilo's `nativePlugin` shape) — pi has no host-read markdown surface at all (unlike Claude/OpenCode/Kilo, which scan a `commands/`/`command/` directory), so a declarative artifact surface would be dead weight, not merely unused. `dispatch.subagentToolkit: "undocumented"` and `dispatch.backgroundDispatch: false` are both required by the capability validator's dispatch schema and reflect that pi has no documented named-dispatch primitive at all. (Stage 1 originally also set `hostBehaviors.skipSharedHooksInstall:true`, reasoning the staged `hooks/*.js` bundle would be dead weight for pi the way it genuinely is for Kilo/ZCode — **corrected in Stage 2 below**: pi's native extension DOES spawn them, so they are live, not dead, and the flag was removed.)
|
||||||
|
|
||||||
|
EoS migration status (#2102 Stage 2, ADR-1239): Stage 1's "in-process `gsd-core` command-routing hub" framing was aspirational and is corrected here — no fully-populated hub factory exists anywhere in gsd-core (every `createHub()` caller in the tree builds a single-family hub for its own narrow purpose), so `/gsd` and `gsd_invoke` instead dispatch via **SUBPROCESS REUSE**: `dispatchGsdCommand` (`src/shell-command-projection.cts`) spawns `gsd-core/bin/gsd-tools.cjs <family> [subcommand] ... --cwd <dir> --raw --json-errors` bounded and non-throwing, mirroring the precedent already established for the OpenCode/Kilo hook bridge (`.opencode/plugins/gsd-core.js`'s "Architecture: SUBPROCESS REUSE" header). The companion MCP server's `gsd_invoke_command` tool dispatches through the SAME shared helper (it had the identical `createHub()`-with-no-args bug). `/gsd`'s command handler is `handler(args, ctx)` (pi's real ExtensionAPI shape — a raw args string, not `execute(ctx)`); `gsd_invoke`'s tool handler is the real 5-arg `execute(toolCallId, params, signal, onUpdate, ctx)`. The event surface (`EXTENSION_EVENT_SURFACES.pi`, `src/host-integration.cts`) now declares the full ~30-event pi ExtensionAPI vocabulary (was a placeholder `['tool_call']`), and `pi/gsd.cjs` binds `session_start` (→ `gsd-ensure-canonical-path.js`), `before_agent_start` (→ `gsd-workflow-guard.js`, a forward-compatible no-op today since that hook's triggers are tool-scoped), `session_before_compact` (→ `gsd-context-monitor.js`), and `tool_call`, each as a bounded fail-open `spawnSync` subprocess (mirroring `.opencode/plugins/gsd-core.js`'s `runHook`). `modelMode: active` is realized via `pi.on('before_provider_request', ...)`, which resolves a tier through the model-catalog's now-populated `runtimeTierDefaults.pi` entries (bare anthropic ids — `claude-opus-4-8`/`claude-sonnet-5`/`claude-haiku-4-5`, matching the `claude` runtime's own ids since pi talks the anthropic API) and returns a modified payload, or `undefined` (fail-open, pi's model left untouched) when resolution comes back null — **not** `registerProvider`, which would register a new model provider rather than steering pi's existing built-in anthropic models.
|
||||||
|
|
||||||
|
**Adversarial-review correction (#2102 Stage 2, post-review):** the event bridges above and the `/gsd` tokenizer's `hooks/lib/git-cmd.js` require were DEAD in a real install — Stage 1's `hostBehaviors.skipSharedHooksInstall:true` meant pi shipped NO `hooks/` directory at all, so `runHook('gsd-ensure-canonical-path.js', ...)` etc. always hit the "hook file absent → silent no-op" branch, and the tokenizer always fell back to plain whitespace-splitting. The tests masked this because they run against the dev tree, where `hooks/` genuinely exists. **Fix:** `capabilities/pi/capability.json` no longer sets `skipSharedHooksInstall` — pi is architecturally identical to OpenCode here (`hooksSurface: "none"` + a native extension that spawns the staged hooks), not to Kilo/ZCode (`hooksSurface: "none"` with NO plugin surface, where the same hooks genuinely are dead weight). pi now installs `hooks/` + `hooks/lib/` (27 entries: the same `INSTALLED_HOOK_FILES` set OpenCode gets) alongside `extensions/gsd.cjs`, verified end-to-end via a real `node bin/install.js --pi --global`/`--local` — `resolveEngineRoot`'s walk-up from the installed extension's own directory finds `ENGINE_ROOT/hooks/{gsd-ensure-canonical-path.js,gsd-workflow-guard.js,gsd-context-monitor.js,lib/git-cmd.js}`, and each bridge/`runHook` call exits 0 against the real installed files. `hooksSurface: "none"` + `configFormat: "none"` + `writesSharedSettings: false` are unaffected — no settings/hooks.json/config.toml is written for pi; the extension spawns hooks by absolute path, not via a config-file hook bus. `tests/fixtures/golden-install-parity/pi.json` grew from 292 → 320 entries (the 28 new `hooks/`/`hooks/lib/` files); `commands/`, `agents/`, `skills/` remain absent (`pluginOnlyInstall` is untouched — it only gates the declarative-markdown surfaces, not hooks). `tests/install-minimal-hooks.test.cjs`'s #1821 suite moved pi from the Kilo/ZCode (no-hooks) group into the OpenCode (ships-hooks) group accordingly.
|
||||||
|
|
||||||
|
|||||||
@@ -2079,6 +2079,66 @@ const capabilities = {
|
|||||||
"contributions": [],
|
"contributions": [],
|
||||||
"gates": []
|
"gates": []
|
||||||
},
|
},
|
||||||
|
"pi": {
|
||||||
|
"id": "pi",
|
||||||
|
"role": "runtime",
|
||||||
|
"version": "1.7.0-rc.5",
|
||||||
|
"title": "pi",
|
||||||
|
"description": "pi (pi.dev) — bun-runtime programmatic-CLI; TS ExtensionAPI (registerCommand/registerTool/registerProvider/pi.on); single native-extension file at ~/.pi/agent/extensions/gsd.cjs; no shared-settings hook surface; tier-2 support.",
|
||||||
|
"tier": "core",
|
||||||
|
"requires": [],
|
||||||
|
"engines": {
|
||||||
|
"gsd": ">=1.7.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"configHome": {
|
||||||
|
"kind": "dot-home-nested",
|
||||||
|
"name": "agent",
|
||||||
|
"parent": ".pi",
|
||||||
|
"env": []
|
||||||
|
},
|
||||||
|
"localConfigDir": ".pi",
|
||||||
|
"configFormat": "none",
|
||||||
|
"artifactLayout": {
|
||||||
|
"global": [],
|
||||||
|
"local": []
|
||||||
|
},
|
||||||
|
"commandStyle": "slash-hyphen",
|
||||||
|
"hooksSurface": "none",
|
||||||
|
"extensionEvents": "pi",
|
||||||
|
"sandboxTier": "none",
|
||||||
|
"supportTier": 2,
|
||||||
|
"installSurface": "profile-marker-only",
|
||||||
|
"writesSharedSettings": false,
|
||||||
|
"permissionWriter": null,
|
||||||
|
"extendedHookEvents": [],
|
||||||
|
"hostIntegration": {
|
||||||
|
"embeddingMode": "imperative",
|
||||||
|
"commandSurface": "slash-programmatic",
|
||||||
|
"dispatch": {
|
||||||
|
"namedDispatch": false,
|
||||||
|
"nested": false,
|
||||||
|
"maxDepth": 0,
|
||||||
|
"background": false,
|
||||||
|
"backgroundDispatch": false,
|
||||||
|
"subagentToolkit": "undocumented"
|
||||||
|
},
|
||||||
|
"modelMode": "active",
|
||||||
|
"hookBus": "host",
|
||||||
|
"stateIO": "session-log-append",
|
||||||
|
"transport": "native-extension",
|
||||||
|
"runtime": "bun"
|
||||||
|
},
|
||||||
|
"hostBehaviors": {
|
||||||
|
"nativePlugin": {
|
||||||
|
"dir": "extensions",
|
||||||
|
"file": "gsd.cjs",
|
||||||
|
"source": "pi/gsd.cjs"
|
||||||
|
},
|
||||||
|
"pluginOnlyInstall": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"profile-pipeline": {
|
"profile-pipeline": {
|
||||||
"id": "profile-pipeline",
|
"id": "profile-pipeline",
|
||||||
"role": "feature",
|
"role": "feature",
|
||||||
@@ -4975,6 +5035,66 @@ const runtimes = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"pi": {
|
||||||
|
"id": "pi",
|
||||||
|
"role": "runtime",
|
||||||
|
"version": "1.7.0-rc.5",
|
||||||
|
"title": "pi",
|
||||||
|
"description": "pi (pi.dev) — bun-runtime programmatic-CLI; TS ExtensionAPI (registerCommand/registerTool/registerProvider/pi.on); single native-extension file at ~/.pi/agent/extensions/gsd.cjs; no shared-settings hook surface; tier-2 support.",
|
||||||
|
"tier": "core",
|
||||||
|
"requires": [],
|
||||||
|
"engines": {
|
||||||
|
"gsd": ">=1.7.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"configHome": {
|
||||||
|
"kind": "dot-home-nested",
|
||||||
|
"name": "agent",
|
||||||
|
"parent": ".pi",
|
||||||
|
"env": []
|
||||||
|
},
|
||||||
|
"localConfigDir": ".pi",
|
||||||
|
"configFormat": "none",
|
||||||
|
"artifactLayout": {
|
||||||
|
"global": [],
|
||||||
|
"local": []
|
||||||
|
},
|
||||||
|
"commandStyle": "slash-hyphen",
|
||||||
|
"hooksSurface": "none",
|
||||||
|
"extensionEvents": "pi",
|
||||||
|
"sandboxTier": "none",
|
||||||
|
"supportTier": 2,
|
||||||
|
"installSurface": "profile-marker-only",
|
||||||
|
"writesSharedSettings": false,
|
||||||
|
"permissionWriter": null,
|
||||||
|
"extendedHookEvents": [],
|
||||||
|
"hostIntegration": {
|
||||||
|
"embeddingMode": "imperative",
|
||||||
|
"commandSurface": "slash-programmatic",
|
||||||
|
"dispatch": {
|
||||||
|
"namedDispatch": false,
|
||||||
|
"nested": false,
|
||||||
|
"maxDepth": 0,
|
||||||
|
"background": false,
|
||||||
|
"backgroundDispatch": false,
|
||||||
|
"subagentToolkit": "undocumented"
|
||||||
|
},
|
||||||
|
"modelMode": "active",
|
||||||
|
"hookBus": "host",
|
||||||
|
"stateIO": "session-log-append",
|
||||||
|
"transport": "native-extension",
|
||||||
|
"runtime": "bun"
|
||||||
|
},
|
||||||
|
"hostBehaviors": {
|
||||||
|
"nativePlugin": {
|
||||||
|
"dir": "extensions",
|
||||||
|
"file": "gsd.cjs",
|
||||||
|
"source": "pi/gsd.cjs"
|
||||||
|
},
|
||||||
|
"pluginOnlyInstall": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"qwen": {
|
"qwen": {
|
||||||
"id": "qwen",
|
"id": "qwen",
|
||||||
"role": "runtime",
|
"role": "runtime",
|
||||||
@@ -5538,6 +5658,7 @@ const _requiresGraph = {
|
|||||||
"pattern-mapper": [
|
"pattern-mapper": [
|
||||||
"research"
|
"research"
|
||||||
],
|
],
|
||||||
|
"pi": [],
|
||||||
"profile-pipeline": [],
|
"profile-pipeline": [],
|
||||||
"qwen": [],
|
"qwen": [],
|
||||||
"research": [],
|
"research": [],
|
||||||
|
|||||||
@@ -86,6 +86,11 @@
|
|||||||
"opus": null,
|
"opus": null,
|
||||||
"sonnet": null,
|
"sonnet": null,
|
||||||
"haiku": null
|
"haiku": null
|
||||||
|
},
|
||||||
|
"pi": {
|
||||||
|
"opus": { "model": "claude-opus-4-8" },
|
||||||
|
"sonnet": { "model": "claude-sonnet-5" },
|
||||||
|
"haiku": { "model": "claude-haiku-4-5" }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"providerPresets": {
|
"providerPresets": {
|
||||||
|
|||||||
@@ -361,6 +361,7 @@ Built-in tier defaults by runtime:
|
|||||||
| `copilot` | `claude-opus-4-8` | `claude-sonnet-5` | `claude-haiku-4-5` |
|
| `copilot` | `claude-opus-4-8` | `claude-sonnet-5` | `claude-haiku-4-5` |
|
||||||
| `hermes` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-5` | `anthropic/claude-haiku-4-5` |
|
| `hermes` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-5` | `anthropic/claude-haiku-4-5` |
|
||||||
| `kilo` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-5` | `anthropic/claude-haiku-4-5` |
|
| `kilo` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-5` | `anthropic/claude-haiku-4-5` |
|
||||||
|
| `pi` | `claude-opus-4-8` | `claude-sonnet-5` | `claude-haiku-4-5` |
|
||||||
| Group B (`cline`, `cursor`, `windsurf`, `augment`, `trae`, `codebuddy`, `antigravity`) | (no built-in default — your runtime handles model selection) | | |
|
| Group B (`cline`, `cursor`, `windsurf`, `augment`, `trae`, `codebuddy`, `antigravity`) | (no built-in default — your runtime handles model selection) | | |
|
||||||
|
|
||||||
Display a table to the user showing the effective configuration:
|
Display a table to the user showing the effective configuration:
|
||||||
|
|||||||
@@ -20,7 +20,8 @@
|
|||||||
".opencode",
|
".opencode",
|
||||||
"GEMINI.md",
|
"GEMINI.md",
|
||||||
"hooks",
|
"hooks",
|
||||||
"scripts"
|
"scripts",
|
||||||
|
"pi"
|
||||||
],
|
],
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"claude",
|
"claude",
|
||||||
|
|||||||
363
pi/gsd.cjs
363
pi/gsd.cjs
@@ -1,79 +1,354 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GSD extension for pi (pi.dev) — ADR-1239 Phase D / #1944.
|
* GSD extension for pi (pi.dev) — ADR-1239 Phase D / #1944, upgraded #2102 Stage 2.
|
||||||
*
|
*
|
||||||
* pi is a Programmatic-CLI host whose TS extensions implement the ExtensionAPI
|
* pi is a Programmatic-CLI host whose TS extensions implement the ExtensionAPI
|
||||||
* (`@earendil-works/pi-coding-agent`): registerTool / registerCommand / pi.on.
|
* (`@earendil-works/pi-coding-agent`): registerCommand({handler(args, ctx)}) /
|
||||||
|
* registerTool({execute(toolCallId, params, signal, onUpdate, ctx)}) / pi.on(event, handler).
|
||||||
* This extension binds GSD's command surface to pi via the imperative adapter
|
* This extension binds GSD's command surface to pi via the imperative adapter
|
||||||
* path — the programmatic-CLI peer of the OpenCode worked binding.
|
* path — the programmatic-CLI peer of the OpenCode worked binding.
|
||||||
*
|
*
|
||||||
* Installation: copy this file to ~/.pi/agent/extensions/gsd.cjs (pi loads
|
* Installation: copy this file to ~/.pi/agent/extensions/gsd.cjs (pi loads
|
||||||
* extensions via jiti from that dir). The engine is resolved from the installed
|
* extensions via jiti from that dir). The engine is resolved from the installed
|
||||||
* GSD tree (walk-up like the OpenCode plugin).
|
* GSD tree (walk-up like the OpenCode plugin). pi's shared hooks/ bundle
|
||||||
|
* (hooks/*.js + hooks/lib/git-cmd.js) is installed alongside the extension —
|
||||||
|
* capabilities/pi/capability.json does NOT set
|
||||||
|
* `hostBehaviors.skipSharedHooksInstall` (#2102 Stage 2 fix; pi is
|
||||||
|
* architecturally identical to OpenCode here: `hooksSurface: 'none'` + a
|
||||||
|
* native extension that spawns the staged hooks — not Kilo/ZCode's
|
||||||
|
* no-plugin-surface case, where the same hooks would be genuine dead weight).
|
||||||
|
* This is what makes the event bridges below (and the tokenizer require)
|
||||||
|
* resolve for real in an installed tree, not just in this dev repo.
|
||||||
*
|
*
|
||||||
* Engine entry: the /gsd handler dispatches IN-PROCESS through the GSD
|
* Engine entry: dispatch is SUBPROCESS-REUSE to gsd-tools.cjs (bounded,
|
||||||
* command-routing hub (createHub/dispatch) — Bun-compatible CJS require. The
|
* no-throw — dispatchGsdCommand in shell-command-projection.cjs), NOT an
|
||||||
* companion MCP server (gsd-mcp-server) is the alternative for out-of-process
|
* in-process command-routing hub. No fully-populated hub factory exists
|
||||||
* hosts; in-process is the first cut per the ADR's "thin plugin" ideal.
|
* anywhere in gsd-core — every createHub() caller in the tree builds a
|
||||||
|
* single-family hub for its own narrow purpose — so the "in-process createHub"
|
||||||
|
* framing of the original #1944 cut was aspirational and is not achievable
|
||||||
|
* without a hub factory that doesn't exist. This mirrors the precedent already
|
||||||
|
* established for the OpenCode/Kilo hook bridge (.opencode/plugins/gsd-core.js
|
||||||
|
* header: "Architecture: SUBPROCESS REUSE ... spawns existing hook scripts as
|
||||||
|
* child processes") — the same pattern, applied to command dispatch. The
|
||||||
|
* companion MCP server (gsd-mcp-server) dispatches through the SAME shared
|
||||||
|
* helper for out-of-process hosts.
|
||||||
*
|
*
|
||||||
* @param {object} pi pi ExtensionAPI (registerTool/registerCommand/on/…)
|
* @param {object} pi pi ExtensionAPI (registerTool/registerCommand/on/…)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const { spawnSync } = require('child_process');
|
||||||
|
|
||||||
|
// Resolve the GSD engine tree (the dir holding gsd-core/ + hooks/).
|
||||||
|
// Works across dev (<root>/pi/gsd.cjs → <root>) and installed layouts.
|
||||||
|
function resolveEngineRoot(startDir) {
|
||||||
|
let dir = startDir;
|
||||||
|
for (let i = 0; i < 6; i++) {
|
||||||
|
if (fs.existsSync(path.join(dir, 'gsd-core'))) return dir;
|
||||||
|
const parent = path.dirname(dir);
|
||||||
|
if (parent === dir) break;
|
||||||
|
dir = parent;
|
||||||
|
}
|
||||||
|
return path.resolve(startDir, '..');
|
||||||
|
}
|
||||||
|
|
||||||
|
const ENGINE_ROOT = resolveEngineRoot(__dirname);
|
||||||
|
const GSD_CORE = path.join(ENGINE_ROOT, 'gsd-core');
|
||||||
|
|
||||||
|
// ── curated top-level command families (gsd-tools.cjs TOP_LEVEL_USAGE) ──────
|
||||||
|
// readCmdNames() (scripts/fix-slash-commands.cjs) reads commands/, which pi
|
||||||
|
// does NOT install (it ships a single native-extension file, no shared
|
||||||
|
// commands/ dir) — it would always return []. This is a self-contained,
|
||||||
|
// hand-curated subset of the STABLE top-level families documented by
|
||||||
|
// `node gsd-core/bin/gsd-tools.cjs --help` (gsd-tools.cjs:689-705). Named +
|
||||||
|
// exported (via _internals) so a test can assert against it directly.
|
||||||
|
const PI_COMMAND_FAMILIES = Object.freeze([
|
||||||
|
'agent', 'capability', 'check', 'commit', 'config-get', 'config-path',
|
||||||
|
'config-set', 'effort', 'git', 'graphify', 'init', 'intel', 'learnings',
|
||||||
|
'list-todos', 'loop', 'milestone', 'phase', 'phases', 'progress',
|
||||||
|
'requirements', 'research-plan', 'research-store', 'resolve-granularity',
|
||||||
|
'resolve-model', 'roadmap', 'scaffold', 'smart-entry', 'state', 'task',
|
||||||
|
'template', 'user-story', 'validate', 'verify', 'workstream', 'worktree',
|
||||||
|
]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter PI_COMMAND_FAMILIES by prefix (startsWith). Returns null when there
|
||||||
|
* are no matches, per pi's `AutocompleteItem[]|null` contract.
|
||||||
|
* @param {string} prefix
|
||||||
|
* @returns {{value: string, label: string}[] | null}
|
||||||
|
*/
|
||||||
|
function getArgumentCompletions(prefix) {
|
||||||
|
const p = typeof prefix === 'string' ? prefix : '';
|
||||||
|
const matches = PI_COMMAND_FAMILIES.filter((name) => name.startsWith(p));
|
||||||
|
if (matches.length === 0) return null;
|
||||||
|
return matches.map((value) => ({ value, label: value }));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tokenize the raw `/gsd <args>` string into { family, subcommand, args }.
|
||||||
|
* Reuses the quote-aware whitespace tokenizer already shipped for hooks
|
||||||
|
* (hooks/lib/git-cmd.js's `tokenize`) rather than re-implementing shell-word
|
||||||
|
* splitting a second time. #2102 Stage 2: pi's capability descriptor no
|
||||||
|
* longer sets `hostBehaviors.skipSharedHooksInstall` (adversarial-review
|
||||||
|
* finding #1/#2 — pi ships NO hooks/ with that flag set, so this require was
|
||||||
|
* dead in a real install), so the shared hooks/ bundle — including
|
||||||
|
* hooks/lib/git-cmd.js — is installed alongside the extension for real
|
||||||
|
* (mirrors OpenCode, whose native plugin also spawns the staged hooks/*.js
|
||||||
|
* bundle). The require below is therefore the PRIMARY, live path in an
|
||||||
|
* installed pi tree; the whitespace-split fallback stays as defense-in-depth
|
||||||
|
* for a corrupted/partial install (e.g. a user who deleted hooks/lib/ by
|
||||||
|
* hand) rather than the only-ever-taken path.
|
||||||
|
* @param {string} rawArgs
|
||||||
|
* @returns {{ family: string, subcommand?: string, args: string[] }}
|
||||||
|
*/
|
||||||
|
function parseGsdCommandArgs(rawArgs) {
|
||||||
|
let tokenize;
|
||||||
|
try {
|
||||||
|
({ tokenize } = require(path.join(ENGINE_ROOT, 'hooks', 'lib', 'git-cmd.js')));
|
||||||
|
} catch {
|
||||||
|
tokenize = (s) => String(s || '').split(/\s+/).filter(Boolean);
|
||||||
|
}
|
||||||
|
const tokens = tokenize(typeof rawArgs === 'string' ? rawArgs : '');
|
||||||
|
return {
|
||||||
|
// Empty args → dispatch gsd-tools.cjs's own --help surface (a real,
|
||||||
|
// working, ok:true default — NOT the 'query'/'help' pairing the original
|
||||||
|
// #1944 cut used, which is not a valid gsd-tools.cjs command).
|
||||||
|
family: tokens[0] || '--help',
|
||||||
|
subcommand: tokens[1],
|
||||||
|
args: tokens.slice(2),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Best-effort TypeBox schema for gsd_invoke's `parameters`, falling back to a
|
||||||
|
* plain JSON-Schema object when the `typebox` package is unavailable (it is
|
||||||
|
* NOT a gsd-core dependency — pi's own ExtensionAPI contract expects TypeBox,
|
||||||
|
* but nothing in this repo installs it). TypeBox schemas ARE JSON Schema, so
|
||||||
|
* the fallback object is structurally equivalent for hosts that accept plain
|
||||||
|
* JSON Schema; this is a best-effort shim for the flat-file extension case.
|
||||||
|
* @returns {object}
|
||||||
|
*/
|
||||||
|
function buildGsdInvokeParameters() {
|
||||||
|
try {
|
||||||
|
const typebox = require('typebox');
|
||||||
|
const Type = typebox && typebox.Type;
|
||||||
|
if (Type) {
|
||||||
|
return Type.Object({
|
||||||
|
family: Type.String(),
|
||||||
|
subcommand: Type.Optional(Type.String()),
|
||||||
|
args: Type.Optional(Type.Array(Type.String())),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// typebox is not installed in this environment — fall through.
|
||||||
|
}
|
||||||
|
process.stderr.write(
|
||||||
|
'gsd: typebox unavailable — gsd_invoke "parameters" falling back to a plain JSON-schema object.\n',
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
family: { type: 'string' },
|
||||||
|
subcommand: { type: 'string' },
|
||||||
|
args: { type: 'array', items: { type: 'string' } },
|
||||||
|
},
|
||||||
|
required: ['family'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the `before_provider_request` handler that steers pi's model
|
||||||
|
* selection to GSD's tier-resolved id (modelMode: 'active' per
|
||||||
|
* capabilities/pi/capability.json). GSD does NOT call `pi.registerProvider` —
|
||||||
|
* that registers a NEW model provider; GSD's job here is only to pick a
|
||||||
|
* tier-appropriate id AMONG pi's EXISTING built-in anthropic models, so
|
||||||
|
* registerProvider would be the wrong primitive (it would wrongly add a fake
|
||||||
|
* provider instead of steering the real one).
|
||||||
|
*
|
||||||
|
* v1 tier policy: GSD does not yet expose a per-turn/per-agent tier signal to
|
||||||
|
* this event, so a conservative fixed default tier is used (parameterized —
|
||||||
|
* default 'sonnet' — so a future richer signal, or a test, can override it).
|
||||||
|
*
|
||||||
|
* ASSUMPTION (flagged — verify against a live pi host): the event payload's
|
||||||
|
* model field is named `model`, matching the anthropic-messages payload shape
|
||||||
|
* (Context7-confirmed for the wire protocol; pi's own before_provider_request
|
||||||
|
* event schema was not independently verifiable in this environment). If pi's
|
||||||
|
* actual field name differs, this returns the WRONG key and pi's fail-open
|
||||||
|
* default takes over only because bare-model-id mismatches degrade to
|
||||||
|
* provider-level errors, not GSD-level ones — a discrepancy here needs a
|
||||||
|
* live-host smoke test before shipping past this stage.
|
||||||
|
*
|
||||||
|
* Fail-open: any resolution failure (or a null/falsy resolved model — e.g. an
|
||||||
|
* unrecognized tier) returns `undefined`, leaving pi's model choice untouched.
|
||||||
|
* NEVER returns a payload with a missing/empty model id.
|
||||||
|
*
|
||||||
|
* @param {{ tier?: string }} [opts]
|
||||||
|
* @returns {(event: object, ctx: object) => Promise<object|undefined>}
|
||||||
|
*/
|
||||||
|
function buildBeforeProviderRequestHandler({ tier = 'sonnet' } = {}) {
|
||||||
|
return async function onBeforeProviderRequest(event, ctx) {
|
||||||
|
try {
|
||||||
|
const effectiveCwd = (ctx && ctx.cwd) || process.cwd();
|
||||||
|
const { resolveTierEntry } = require(path.join(GSD_CORE, 'bin', 'lib', 'model-resolver.cjs'));
|
||||||
|
const { loadConfig } = require(path.join(GSD_CORE, 'bin', 'lib', 'config-loader.cjs'));
|
||||||
|
const config = loadConfig(effectiveCwd);
|
||||||
|
const overrides = (config && config.model_profile_overrides) || undefined;
|
||||||
|
const entry = resolveTierEntry({ runtime: 'pi', tier, overrides });
|
||||||
|
const modelId = entry && typeof entry.model === 'string' && entry.model.length > 0 ? entry.model : null;
|
||||||
|
if (!modelId) return undefined; // fail-open — leave pi's model untouched
|
||||||
|
const basePayload = (event && typeof event === 'object' && event.payload && typeof event.payload === 'object')
|
||||||
|
? event.payload
|
||||||
|
: {};
|
||||||
|
return { ...basePayload, model: modelId };
|
||||||
|
} catch {
|
||||||
|
return undefined; // fail-open on any resolution error
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bounded subprocess bridge to GSD's Claude Code hook scripts. Mirrors
|
||||||
|
* .opencode/plugins/gsd-core.js's `runHook` (SUBPROCESS-REUSE): spawns
|
||||||
|
* `node <hooks/hookFile>` with the payload piped to stdin, on a bounded
|
||||||
|
* timeout. NEVER throws — a missing hook file, a spawn error, or a timeout
|
||||||
|
* all degrade to a silent-allow result so a hook problem can never block pi.
|
||||||
|
* @param {string} hookFile filename under hooks/, e.g. "gsd-context-monitor.js"
|
||||||
|
* @param {object} payload
|
||||||
|
* @param {{ timeout?: number, cwd?: string }} [opts]
|
||||||
|
* @returns {{ stdout: string, exitCode: number, timedOut: boolean }}
|
||||||
|
*/
|
||||||
|
function runHook(hookFile, payload, opts = {}) {
|
||||||
|
const hookPath = path.join(ENGINE_ROOT, 'hooks', hookFile);
|
||||||
|
if (!fs.existsSync(hookPath)) return { stdout: '', exitCode: 0, timedOut: false };
|
||||||
|
const timeout = opts.timeout || 8000;
|
||||||
|
let result;
|
||||||
|
try {
|
||||||
|
result = spawnSync(process.execPath, [hookPath], {
|
||||||
|
input: JSON.stringify(payload || {}),
|
||||||
|
encoding: 'utf8',
|
||||||
|
timeout,
|
||||||
|
cwd: opts.cwd || process.cwd(),
|
||||||
|
windowsHide: true,
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
return { stdout: '', exitCode: 0, timedOut: false };
|
||||||
|
}
|
||||||
|
const stdout = (result && typeof result.stdout === 'string') ? result.stdout.trim() : '';
|
||||||
|
const exitCode = (result && result.status != null) ? result.status : 0;
|
||||||
|
return { stdout, exitCode, timedOut: !!(result && result.signal === 'SIGTERM') };
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = function gsdPiExtension(pi) {
|
module.exports = function gsdPiExtension(pi) {
|
||||||
if (!pi || typeof pi !== 'object') {
|
if (!pi || typeof pi !== 'object') {
|
||||||
throw new TypeError('gsdPiExtension: pi ExtensionAPI is required');
|
throw new TypeError('gsdPiExtension: pi ExtensionAPI is required');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve the GSD engine tree (the dir holding gsd-core/ + hooks/).
|
// ── /gsd command: dispatch through gsd-tools.cjs (subprocess-reuse) ──────
|
||||||
// Works across dev (<root>/pi/gsd.cjs → <root>) and installed layouts.
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
function resolveEngineRoot(startDir) {
|
|
||||||
let dir = startDir;
|
|
||||||
for (let i = 0; i < 6; i++) {
|
|
||||||
if (fs.existsSync(path.join(dir, 'gsd-core'))) return dir;
|
|
||||||
const parent = path.dirname(dir);
|
|
||||||
if (parent === dir) break;
|
|
||||||
dir = parent;
|
|
||||||
}
|
|
||||||
return path.resolve(startDir, '..');
|
|
||||||
}
|
|
||||||
const ENGINE_ROOT = resolveEngineRoot(__dirname);
|
|
||||||
const GSD_CORE = path.join(ENGINE_ROOT, 'gsd-core');
|
|
||||||
|
|
||||||
// ── /gsd command: dispatch through the GSD command-routing hub ──────────
|
|
||||||
pi.registerCommand('gsd', {
|
pi.registerCommand('gsd', {
|
||||||
description: 'Invoke a GSD command via the embedded engine (imperative adapter).',
|
description: 'Invoke a GSD command via the embedded engine (subprocess-reuse adapter).',
|
||||||
execute: async function (ctx) {
|
getArgumentCompletions,
|
||||||
const { createHub } = require(path.join(GSD_CORE, 'bin', 'lib', 'command-routing-hub.cjs'));
|
handler: async (args, ctx) => {
|
||||||
const hub = createHub();
|
const cwd = (ctx && ctx.cwd) || process.cwd();
|
||||||
const res = hub.dispatch({
|
const { family, subcommand, args: rest } = parseGsdCommandArgs(args);
|
||||||
family: (ctx && ctx.family) || 'query',
|
let dispatchGsdCommand;
|
||||||
subcommand: (ctx && ctx.subcommand) || 'help',
|
try {
|
||||||
args: (ctx && Array.isArray(ctx.args)) ? ctx.args : [],
|
({ dispatchGsdCommand } = require(path.join(GSD_CORE, 'bin', 'lib', 'shell-command-projection.cjs')));
|
||||||
cwd: (ctx && ctx.cwd) || process.cwd(),
|
} catch (e) {
|
||||||
});
|
return `GSD engine unavailable: ${e && e.message ? e.message : String(e)}`;
|
||||||
return JSON.stringify(res);
|
}
|
||||||
|
const result = dispatchGsdCommand({ family, subcommand, args: rest, cwd });
|
||||||
|
if (result.ok) return result.stdout;
|
||||||
|
return `GSD error: ${result.stderr || result.stdout || `dispatch failed (exit ${result.code})`}`;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── gsd_invoke tool: programmatic command invocation ────────────────────
|
// ── gsd_invoke tool: programmatic command invocation ────────────────────
|
||||||
pi.registerTool({
|
pi.registerTool({
|
||||||
name: 'gsd_invoke',
|
name: 'gsd_invoke',
|
||||||
|
label: 'GSD Invoke',
|
||||||
description: 'Invoke a GSD command family/subcommand through the engine.',
|
description: 'Invoke a GSD command family/subcommand through the engine.',
|
||||||
execute: async function () {
|
parameters: buildGsdInvokeParameters(),
|
||||||
const { createHub } = require(path.join(GSD_CORE, 'bin', 'lib', 'command-routing-hub.cjs'));
|
execute: async (toolCallId, params, signal, onUpdate, ctx) => {
|
||||||
const hub = createHub();
|
const p = (params && typeof params === 'object') ? params : {};
|
||||||
const res = hub.dispatch({ family: 'query', subcommand: 'help', args: [], cwd: process.cwd() });
|
const family = typeof p.family === 'string' ? p.family : '';
|
||||||
return JSON.stringify(res);
|
if (!family) {
|
||||||
|
return { content: [{ type: 'text', text: 'gsd_invoke requires a non-empty string "family".' }] };
|
||||||
|
}
|
||||||
|
const subcommand = typeof p.subcommand === 'string' ? p.subcommand : undefined;
|
||||||
|
const invokeArgs = Array.isArray(p.args) ? p.args : [];
|
||||||
|
const cwd = (ctx && ctx.cwd) || process.cwd();
|
||||||
|
let dispatchGsdCommand;
|
||||||
|
try {
|
||||||
|
({ dispatchGsdCommand } = require(path.join(GSD_CORE, 'bin', 'lib', 'shell-command-projection.cjs')));
|
||||||
|
} catch (e) {
|
||||||
|
return { content: [{ type: 'text', text: `GSD engine unavailable: ${e && e.message ? e.message : String(e)}` }] };
|
||||||
|
}
|
||||||
|
const result = dispatchGsdCommand({ family, subcommand, args: invokeArgs, cwd });
|
||||||
|
const text = result.ok ? result.stdout : (result.stderr || result.stdout || `dispatch failed (exit ${result.code})`);
|
||||||
|
return { content: [{ type: 'text', text }] };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── tool_call event: lifecycle hook bridge (extensionEvents: pi) ────────
|
// ── before_provider_request: active-model steering (modelMode: 'active') ──
|
||||||
|
// GSD steers pi's EXISTING built-in anthropic models; it does NOT call
|
||||||
|
// pi.registerProvider (that would wrongly register a NEW fake provider —
|
||||||
|
// see buildBeforeProviderRequestHandler's doc comment).
|
||||||
|
pi.on('before_provider_request', buildBeforeProviderRequestHandler());
|
||||||
|
|
||||||
|
// ── Event bindings: bounded subprocess bridge to GSD's hook scripts ──────
|
||||||
|
// Each binding fails open — a hook error/timeout/missing-file never blocks
|
||||||
|
// pi (mirrors .opencode/plugins/gsd-core.js's runHook SUBPROCESS-REUSE
|
||||||
|
// pattern, applied to pi's ExtensionAPI event names).
|
||||||
|
|
||||||
|
// session_start → SessionStart-equivalent bootstrap.
|
||||||
|
pi.on('session_start', async (event, ctx) => {
|
||||||
|
try {
|
||||||
|
const cwd = (ctx && ctx.cwd) || process.cwd();
|
||||||
|
runHook('gsd-ensure-canonical-path.js', { hook_event_name: 'SessionStart', cwd }, { cwd });
|
||||||
|
} catch { /* fail-open */ }
|
||||||
|
});
|
||||||
|
|
||||||
|
// before_agent_start → workflow-guard bridge. Forward-compatible binding:
|
||||||
|
// gsd-workflow-guard.js's current triggers are tool-scoped (Write/Edit/
|
||||||
|
// Bash via tool_name/tool_input), so with no tool_name in the payload it
|
||||||
|
// fires as a safe no-op today — wired so a future agent-start-scoped check
|
||||||
|
// can attach without a plugin change (mirrors the OpenCode session.idle
|
||||||
|
// recognized-but-unused sentinel pattern).
|
||||||
|
pi.on('before_agent_start', async (event, ctx) => {
|
||||||
|
try {
|
||||||
|
const cwd = (ctx && ctx.cwd) || process.cwd();
|
||||||
|
runHook('gsd-workflow-guard.js', { hook_event_name: 'before_agent_start', cwd }, { cwd });
|
||||||
|
} catch { /* fail-open */ }
|
||||||
|
});
|
||||||
|
|
||||||
|
// session_before_compact → PreCompact-equivalent (context-usage bridge).
|
||||||
|
pi.on('session_before_compact', async (event, ctx) => {
|
||||||
|
try {
|
||||||
|
const cwd = (ctx && ctx.cwd) || process.cwd();
|
||||||
|
runHook('gsd-context-monitor.js', { hook_event_name: 'PreCompact', cwd }, { cwd });
|
||||||
|
} catch { /* fail-open */ }
|
||||||
|
});
|
||||||
|
|
||||||
|
// tool_call event: lifecycle hook bridge attachment point (kept from the
|
||||||
|
// original cut — the PreToolUse/PostToolUse tool_name/tool_input mapping
|
||||||
|
// is a follow-up once pi's tool_call payload shape is verified against a
|
||||||
|
// live host).
|
||||||
pi.on('tool_call', async function () {
|
pi.on('tool_call', async function () {
|
||||||
/* GSD hook bridge attachment point (PreToolUse/PostToolUse mapping). */
|
/* GSD hook bridge attachment point (PreToolUse/PostToolUse mapping). */
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Test-only internals (mirrors the OpenCode plugin pattern).
|
// Test-only internals (mirrors the OpenCode plugin pattern) — wired to the
|
||||||
module.exports._internals = { resolveEngineRoot: null };
|
// real functions (not stubs) so tests can exercise parsing/completions/model
|
||||||
|
// resolution WITHOUT a live pi runtime.
|
||||||
|
module.exports._internals = {
|
||||||
|
resolveEngineRoot,
|
||||||
|
parseGsdCommandArgs,
|
||||||
|
getArgumentCompletions,
|
||||||
|
PI_COMMAND_FAMILIES,
|
||||||
|
buildBeforeProviderRequestHandler,
|
||||||
|
buildGsdInvokeParameters,
|
||||||
|
runHook,
|
||||||
|
};
|
||||||
|
|||||||
@@ -584,7 +584,21 @@ const EXTENSION_EVENT_SURFACES: Readonly<Record<string, readonly string[]>> = Ob
|
|||||||
'pre_gateway_dispatch', 'pre_approval_request',
|
'pre_gateway_dispatch', 'pre_approval_request',
|
||||||
'transform_tool_result',
|
'transform_tool_result',
|
||||||
]),
|
]),
|
||||||
pi: Object.freeze(['tool_call']),
|
// #2102 Stage 2 — pi's real ExtensionAPI event vocabulary (~30 fine-grained
|
||||||
|
// extension events; documentation-sourced, ADR-1239 §research). Replaces the
|
||||||
|
// placeholder single-event ['tool_call'] surface — the Stage 1 value only
|
||||||
|
// covered the one event pi/gsd.cjs happened to bind at the time, not the
|
||||||
|
// full declared surface.
|
||||||
|
pi: Object.freeze([
|
||||||
|
'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',
|
||||||
|
]),
|
||||||
none: Object.freeze([]),
|
none: Object.freeze([]),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -715,6 +715,19 @@ function installRuntimeArtifacts(
|
|||||||
const nestedGsdDirForCleanup = path.join(configDir, 'skills', 'gsd');
|
const nestedGsdDirForCleanup = path.join(configDir, 'skills', 'gsd');
|
||||||
_removeHermesBareStemDirs(nestedGsdDirForCleanup);
|
_removeHermesBareStemDirs(nestedGsdDirForCleanup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generic-branch nativePlugin staging (ADR-1239 / #2102 Stage 1): runtimes
|
||||||
|
// outside the OpenCode/Kilo combined-family install (e.g. pi, whose
|
||||||
|
// artifactLayout is empty and which never sets combinedFamilyInstall) still
|
||||||
|
// need their declared hostBehaviors.nativePlugin file copied into configDir.
|
||||||
|
// findInstallSourceRoot resolves the repo/package root independent of
|
||||||
|
// configDir contents (marker check, then a walk-up from __dirname), so this
|
||||||
|
// is safe even when configDir has no .gsd-source marker (artifactLayout: []).
|
||||||
|
if (behaviors.nativePlugin) {
|
||||||
|
const commandsGsdDir = runtimeArtifactLayout.findInstallSourceRoot(configDir);
|
||||||
|
const src = path.dirname(path.dirname(commandsGsdDir));
|
||||||
|
_installNativePluginIfDeclared(runtime, configDir, behaviors, src);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -879,6 +892,44 @@ function installOpencodeFamilyCommands(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// _installNativePluginIfDeclared
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy a runtime's declared native-extension/plugin file (hostBehaviors.nativePlugin)
|
||||||
|
* into its resolved config dir, when the runtime descriptor declares one.
|
||||||
|
*
|
||||||
|
* Extracted (ADR-1239 / #2102 Stage 1) from the body previously inlined in
|
||||||
|
* installOpencodeFamilyArtifacts so a runtime that is NOT part of the
|
||||||
|
* OpenCode/Kilo combined-family install (e.g. pi, whose artifactLayout is
|
||||||
|
* empty and which never sets combinedFamilyInstall) can still get its
|
||||||
|
* nativePlugin file staged via the generic installRuntimeArtifacts branch.
|
||||||
|
* Behavior for opencode/kilo is unchanged — same source resolution, same
|
||||||
|
* mkdir + copyFileSync call, same silent no-op when the source is missing.
|
||||||
|
*
|
||||||
|
* @param runtime - canonical runtime id (only used for the assertDestWithinConfigHome guard)
|
||||||
|
* @param configDir - resolved runtime config directory
|
||||||
|
* @param behaviors - the runtime's hostBehaviors descriptor
|
||||||
|
* @param src - repo/package root (two levels up from the commands/gsd source dir)
|
||||||
|
*/
|
||||||
|
function _installNativePluginIfDeclared(
|
||||||
|
runtime: string,
|
||||||
|
configDir: string,
|
||||||
|
behaviors: any,
|
||||||
|
src: string,
|
||||||
|
): void {
|
||||||
|
const np = behaviors.nativePlugin;
|
||||||
|
if (np && np.source) {
|
||||||
|
const pluginSrc = path.join(src, np.source);
|
||||||
|
if (fs.existsSync(pluginSrc)) {
|
||||||
|
const destDir = runtimeArtifactInstallPlan.assertDestWithinConfigHome(configDir, np.dir);
|
||||||
|
fs.mkdirSync(destDir, { recursive: true });
|
||||||
|
fs.copyFileSync(pluginSrc, path.join(destDir, np.file));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// installOpencodeFamilyArtifacts
|
// installOpencodeFamilyArtifacts
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -927,15 +978,7 @@ function installOpencodeFamilyArtifacts(
|
|||||||
installOpencodeFamilyCommands(runtime, commandDir, rawCommandsDir, pathPrefix, resolveAttribution);
|
installOpencodeFamilyCommands(runtime, commandDir, rawCommandsDir, pathPrefix, resolveAttribution);
|
||||||
installOpencodeFamilySkills(runtime, configDir, rawCommandsDir, pathPrefix, resolveAttribution);
|
installOpencodeFamilySkills(runtime, configDir, rawCommandsDir, pathPrefix, resolveAttribution);
|
||||||
|
|
||||||
const np = behaviors.nativePlugin;
|
_installNativePluginIfDeclared(runtime, configDir, behaviors, src);
|
||||||
if (np && np.source) {
|
|
||||||
const pluginSrc = path.join(src, np.source);
|
|
||||||
if (fs.existsSync(pluginSrc)) {
|
|
||||||
const destDir = runtimeArtifactInstallPlan.assertDestWithinConfigHome(configDir, np.dir);
|
|
||||||
fs.mkdirSync(destDir, { recursive: true });
|
|
||||||
fs.copyFileSync(pluginSrc, path.join(destDir, np.file));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -1004,6 +1047,7 @@ export = {
|
|||||||
installOpencodeFamilySkills,
|
installOpencodeFamilySkills,
|
||||||
installOpencodeFamilyCommands,
|
installOpencodeFamilyCommands,
|
||||||
installOpencodeFamilyArtifacts,
|
installOpencodeFamilyArtifacts,
|
||||||
|
_installNativePluginIfDeclared,
|
||||||
_hostBehaviors,
|
_hostBehaviors,
|
||||||
_copyStaged,
|
_copyStaged,
|
||||||
hasExistingSymlinkBetween,
|
hasExistingSymlinkBetween,
|
||||||
|
|||||||
@@ -5,8 +5,16 @@
|
|||||||
* so any MCP-consuming host (Claude/Codex/OpenCode/VS Code/Gemini/Cursor/Cline/
|
* so any MCP-consuming host (Claude/Codex/OpenCode/VS Code/Gemini/Cursor/Cline/
|
||||||
* Hermes) can drive GSD with NO bespoke plugin:
|
* Hermes) can drive GSD with NO bespoke plugin:
|
||||||
*
|
*
|
||||||
* - point 1 (command): tool `gsd_invoke_command` → the command-routing hub
|
* - point 1 (command): tool `gsd_invoke_command` → `dispatchGsdCommand`
|
||||||
* (`createHub`/`dispatch`, src/command-routing-hub.cts).
|
* (src/shell-command-projection.cts), a bounded subprocess-shim to
|
||||||
|
* gsd-tools.cjs. #2102 Stage 2: `commandRoutingHub.createHub()` called
|
||||||
|
* with no args here always hit `if(!_cjsRegistry) return
|
||||||
|
* makeUnknownCommand()` — every dispatch was UnknownCommand. No
|
||||||
|
* fully-populated hub factory exists anywhere in gsd-core (every
|
||||||
|
* createHub() caller builds a single-family hub for its own narrow
|
||||||
|
* purpose), so the fix routes through the SAME shared dispatch helper
|
||||||
|
* the pi extension uses (pi/gsd.cjs), mirroring the SUBPROCESS-REUSE
|
||||||
|
* precedent already established for the OpenCode/Kilo hook bridge.
|
||||||
* - point 5 (state IO): tools `gsd_read_state` / `gsd_write_state` → the
|
* - point 5 (state IO): tools `gsd_read_state` / `gsd_write_state` → the
|
||||||
* Phase 3 `stateIO` seam (src/state-io.cts, filesystem default).
|
* Phase 3 `stateIO` seam (src/state-io.cts, filesystem default).
|
||||||
*
|
*
|
||||||
@@ -20,10 +28,11 @@
|
|||||||
*/
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
||||||
import commandRoutingHub = require('./command-routing-hub.cjs');
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||||
import stateIo = require('./state-io.cjs');
|
import stateIo = require('./state-io.cjs');
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||||
|
import shellCommandProjection = require('./shell-command-projection.cjs');
|
||||||
|
const { dispatchGsdCommand } = shellCommandProjection;
|
||||||
|
|
||||||
export const PROTOCOL_VERSION = '2024-11-05';
|
export const PROTOCOL_VERSION = '2024-11-05';
|
||||||
export const SERVER_NAME = 'gsd-core';
|
export const SERVER_NAME = 'gsd-core';
|
||||||
@@ -108,9 +117,11 @@ function callTool(name: string, args: unknown, ctx: McpContext): { content: Arra
|
|||||||
if (!family || !subcommand) {
|
if (!family || !subcommand) {
|
||||||
return { isError: true, content: [{ type: 'text', text: 'gsd_invoke_command requires string "family" and "subcommand".' }] };
|
return { isError: true, content: [{ type: 'text', text: 'gsd_invoke_command requires string "family" and "subcommand".' }] };
|
||||||
}
|
}
|
||||||
const hub = commandRoutingHub.createHub();
|
const res = dispatchGsdCommand({ family, subcommand, args: Array.isArray(a.args) ? (a.args as string[]) : [], cwd });
|
||||||
const res = hub.dispatch({ family, subcommand, args: Array.isArray(a.args) ? a.args : [], cwd, raw: undefined });
|
if (!res.ok) {
|
||||||
return { content: [{ type: 'text', text: JSON.stringify(res) }] };
|
return { isError: true, content: [{ type: 'text', text: res.stderr || res.stdout || `dispatch failed (exit ${res.code})` }] };
|
||||||
|
}
|
||||||
|
return { content: [{ type: 'text', text: res.stdout }] };
|
||||||
}
|
}
|
||||||
if (name === 'gsd_read_state') {
|
if (name === 'gsd_read_state') {
|
||||||
const p = asString(a.path);
|
const p = asString(a.path);
|
||||||
|
|||||||
@@ -210,6 +210,7 @@ const RUNTIME_LABELS: Readonly<Record<string, string>> = {
|
|||||||
codebuddy: 'CodeBuddy',
|
codebuddy: 'CodeBuddy',
|
||||||
cline: 'Cline',
|
cline: 'Cline',
|
||||||
zcode: 'ZCode',
|
zcode: 'ZCode',
|
||||||
|
pi: 'pi',
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -258,6 +259,12 @@ const GLOBAL_CONFIG_HOME_FRAGMENTS: Readonly<Record<string, string>> = {
|
|||||||
cline: "'.cline'",
|
cline: "'.cline'",
|
||||||
kimi: "'.config', 'agents'",
|
kimi: "'.config', 'agents'",
|
||||||
zcode: "'.zcode'",
|
zcode: "'.zcode'",
|
||||||
|
// pi's global config home is ~/.pi/agent (configHome: dot-home-nested,
|
||||||
|
// parent '.pi', name 'agent' — capabilities/pi/capability.json), matching
|
||||||
|
// resolveConfigHomeFromDescriptor's `path.join(home, parent, name)` for the
|
||||||
|
// no-probe dot-home-nested case (src/runtime-homes.cts). Two-segment
|
||||||
|
// path.join args, same shape as opencode/kilo/kimi above.
|
||||||
|
pi: "'.pi', 'agent'",
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -286,7 +293,7 @@ export function getGlobalConfigHomeFragment(runtime: string): string {
|
|||||||
// folds the shared-hooks-install skip).
|
// folds the shared-hooks-install skip).
|
||||||
const RUNTIME_FLAG_IDS = Object.freeze([
|
const RUNTIME_FLAG_IDS = Object.freeze([
|
||||||
'opencode', 'kilo', 'codex', 'copilot', 'antigravity', 'cursor',
|
'opencode', 'kilo', 'codex', 'copilot', 'antigravity', 'cursor',
|
||||||
'windsurf', 'augment', 'trae', 'qwen', 'hermes', 'codebuddy', 'cline', 'kimi', 'zcode',
|
'windsurf', 'augment', 'trae', 'qwen', 'hermes', 'codebuddy', 'cline', 'kimi', 'zcode', 'pi',
|
||||||
] as const);
|
] as const);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -518,6 +518,143 @@ export function execTool(program: string, args: string[], opts: { cwd?: string;
|
|||||||
return _spawnResult(result, program);
|
return _spawnResult(result, program);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result shape for {@link dispatchGsdCommand}. Modeled on the existing
|
||||||
|
* `{exitCode,stdout,stderr,signal,error}` seam above, but flattened to the
|
||||||
|
* fields callers actually need (never leaks a raw Error/signal — see
|
||||||
|
* `timedOut`), per the "Unbounded Subprocesses" contract (CLAUDE.md):
|
||||||
|
* degrade to a structured result on timeout/ENOENT, never throw.
|
||||||
|
*/
|
||||||
|
export interface DispatchGsdCommandResult {
|
||||||
|
ok: boolean;
|
||||||
|
stdout: string;
|
||||||
|
stderr: string;
|
||||||
|
code: number | null;
|
||||||
|
timedOut: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the absolute path to gsd-tools.cjs relative to THIS module.
|
||||||
|
*
|
||||||
|
* This file compiles to gsd-core/bin/lib/shell-command-projection.cjs — a
|
||||||
|
* sibling of gsd-core/bin/gsd-tools.cjs — so the relative walk-up is stable
|
||||||
|
* regardless of install location (global/local/dev-repo layouts all ship
|
||||||
|
* gsd-core/bin/ as a unit).
|
||||||
|
*/
|
||||||
|
export function resolveGsdToolsPath(): string {
|
||||||
|
return path.resolve(__dirname, '..', 'gsd-tools.cjs');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subprocess-shim dispatch to gsd-tools.cjs (ADR-1239 #2102 Stage 2).
|
||||||
|
*
|
||||||
|
* No fully-populated in-process command-routing hub exists anywhere in the
|
||||||
|
* tree — every `createHub()` caller (cjs-command-router-adapter.cts,
|
||||||
|
* phase-command-router.cts, command-routing-hub.cts's own tests) builds a
|
||||||
|
* single-family hub for its own narrow purpose. The ONLY dispatch path that
|
||||||
|
* covers the FULL family/subcommand surface is the gsd-tools.cjs CLI itself.
|
||||||
|
* This mirrors the SUBPROCESS-REUSE precedent already established for the
|
||||||
|
* OpenCode/Kilo hook bridge (see .opencode/plugins/gsd-core.js header:
|
||||||
|
* "Architecture: SUBPROCESS REUSE ... spawns existing hook scripts as child
|
||||||
|
* processes") — the same pattern, applied to command dispatch instead of
|
||||||
|
* hook dispatch.
|
||||||
|
*
|
||||||
|
* Output-flag choice (verified by direct invocation — see #2102 dispatch
|
||||||
|
* notes for the sample invocations): always pass `--raw` (undecorated,
|
||||||
|
* programmatically-consumable stdout on success) and `--json-errors` (a
|
||||||
|
* structured `{ok:false,reason,message}` JSON object on stderr, with a
|
||||||
|
* non-zero exit, instead of a free-text "Error: ..." line). Both are global
|
||||||
|
* flags accepted by every gsd-tools.cjs family/subcommand, so passing them
|
||||||
|
* unconditionally is safe for the full command surface.
|
||||||
|
*
|
||||||
|
* `family` maps 1:1 onto gsd-tools.cjs's first positional argv token;
|
||||||
|
* `subcommand` (when present) onto the second — e.g.
|
||||||
|
* `{family:'phase', subcommand:'add'}` → `gsd-tools.cjs phase add`. An empty
|
||||||
|
* `subcommand` is omitted entirely (some families, e.g. `config-path`, take
|
||||||
|
* no subcommand).
|
||||||
|
*
|
||||||
|
* NEVER throws. Degrades to `{ ok:false, ... }` on:
|
||||||
|
* - a missing/invalid "family" (validated locally, no subprocess spawned)
|
||||||
|
* - ENOENT / a missing gsd-tools.cjs (via the injectable `gsdToolsPath`)
|
||||||
|
* - a wall-clock timeout (`timedOut:true`, mirroring the
|
||||||
|
* `signal === 'SIGTERM' && error.code === 'ETIMEDOUT'` idiom already used
|
||||||
|
* by worktree-safety.cts)
|
||||||
|
* - any other unanticipated throw from the underlying spawn (defensive
|
||||||
|
* try/catch — execTool itself is spawnSync-based and does not throw).
|
||||||
|
*/
|
||||||
|
export function dispatchGsdCommand({
|
||||||
|
family,
|
||||||
|
subcommand,
|
||||||
|
args = [],
|
||||||
|
cwd,
|
||||||
|
timeout = 30_000,
|
||||||
|
gsdToolsPath,
|
||||||
|
}: {
|
||||||
|
family?: string;
|
||||||
|
subcommand?: string;
|
||||||
|
args?: string[];
|
||||||
|
cwd?: string;
|
||||||
|
timeout?: number;
|
||||||
|
gsdToolsPath?: string;
|
||||||
|
} = {}): DispatchGsdCommandResult {
|
||||||
|
if (typeof family !== 'string' || family.length === 0) {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
stdout: '',
|
||||||
|
stderr: 'dispatchGsdCommand requires a non-empty string "family".',
|
||||||
|
code: null,
|
||||||
|
timedOut: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolvedCwd = cwd || process.cwd();
|
||||||
|
const toolsPath = gsdToolsPath || resolveGsdToolsPath();
|
||||||
|
const argv = [
|
||||||
|
toolsPath,
|
||||||
|
family,
|
||||||
|
...(subcommand ? [subcommand] : []),
|
||||||
|
...(Array.isArray(args) ? args : []),
|
||||||
|
'--cwd', resolvedCwd,
|
||||||
|
'--raw',
|
||||||
|
'--json-errors',
|
||||||
|
];
|
||||||
|
|
||||||
|
let result: SpawnResultOutput;
|
||||||
|
try {
|
||||||
|
result = execTool(process.execPath, argv, { cwd: resolvedCwd, timeout });
|
||||||
|
} catch (e) {
|
||||||
|
// Defensive belt-and-suspenders: execTool is spawnSync-based and does not
|
||||||
|
// throw today, but a degraded result here keeps this seam's no-throw
|
||||||
|
// contract true even under an unanticipated future failure mode.
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
stdout: '',
|
||||||
|
stderr: e instanceof Error ? e.message : String(e),
|
||||||
|
code: null,
|
||||||
|
timedOut: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mirrors the established `result.error && (result.error as
|
||||||
|
// NodeJS.ErrnoException).code === ...` idiom (graphify.cts, worktree-safety.cts):
|
||||||
|
// narrow away null via `!== null` FIRST, then cast — asserting `Error | null`
|
||||||
|
// to `NodeJS.ErrnoException | null` directly (paired with optional chaining)
|
||||||
|
// trips a typescript-eslint no-unnecessary-type-assertion false positive for
|
||||||
|
// this exact narrowing shape (all of ErrnoException's extra fields over Error
|
||||||
|
// are optional).
|
||||||
|
const timedOut = result.signal === 'SIGTERM'
|
||||||
|
&& result.error !== null
|
||||||
|
&& (result.error as NodeJS.ErrnoException).code === 'ETIMEDOUT';
|
||||||
|
|
||||||
|
return {
|
||||||
|
ok: result.exitCode === 0 && !timedOut,
|
||||||
|
stdout: result.stdout,
|
||||||
|
stderr: result.stderr,
|
||||||
|
code: result.exitCode,
|
||||||
|
timedOut,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function probeTty(opts: { platform?: string } = {}): string | null {
|
export function probeTty(opts: { platform?: string } = {}): string | null {
|
||||||
const platform = opts.platform ?? process.platform;
|
const platform = opts.platform ?? process.platform;
|
||||||
if (platform === 'win32') return null;
|
if (platform === 'win32') return null;
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
"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/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||||
"gsd-core/bin/verify-reapply-patches.cjs": "8bc541aabc2e143c",
|
"gsd-core/bin/verify-reapply-patches.cjs": "8bc541aabc2e143c",
|
||||||
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
||||||
@@ -289,7 +289,7 @@
|
|||||||
"gsd-core/workflows/scan.md": "a7fecd67e5cd655f",
|
"gsd-core/workflows/scan.md": "a7fecd67e5cd655f",
|
||||||
"gsd-core/workflows/secure-phase.md": "96b199dfac00e60f",
|
"gsd-core/workflows/secure-phase.md": "96b199dfac00e60f",
|
||||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
"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-integrations.md": "4d3001ad2b5dad8f",
|
||||||
"gsd-core/workflows/settings.md": "8258f7bd3700d608",
|
"gsd-core/workflows/settings.md": "8258f7bd3700d608",
|
||||||
"gsd-core/workflows/ship.md": "984bd7660e7791fd",
|
"gsd-core/workflows/ship.md": "984bd7660e7791fd",
|
||||||
|
|||||||
@@ -113,7 +113,7 @@
|
|||||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
"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/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||||
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
||||||
@@ -360,7 +360,7 @@
|
|||||||
"gsd-core/workflows/scan.md": "003883d71c37da7d",
|
"gsd-core/workflows/scan.md": "003883d71c37da7d",
|
||||||
"gsd-core/workflows/secure-phase.md": "29fc6b62c5c5dc62",
|
"gsd-core/workflows/secure-phase.md": "29fc6b62c5c5dc62",
|
||||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
"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-integrations.md": "70515c5838fb9826",
|
||||||
"gsd-core/workflows/settings.md": "d96ddf01fb85e61e",
|
"gsd-core/workflows/settings.md": "d96ddf01fb85e61e",
|
||||||
"gsd-core/workflows/ship.md": "7b8fe9f89143e648",
|
"gsd-core/workflows/ship.md": "7b8fe9f89143e648",
|
||||||
|
|||||||
@@ -112,7 +112,7 @@
|
|||||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
"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/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||||
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
||||||
@@ -359,7 +359,7 @@
|
|||||||
"gsd-core/workflows/scan.md": "75c670d08cee8680",
|
"gsd-core/workflows/scan.md": "75c670d08cee8680",
|
||||||
"gsd-core/workflows/secure-phase.md": "64ec4d06ca85720a",
|
"gsd-core/workflows/secure-phase.md": "64ec4d06ca85720a",
|
||||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
"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-integrations.md": "dfe3672c4fabf139",
|
||||||
"gsd-core/workflows/settings.md": "acdd79110699a608",
|
"gsd-core/workflows/settings.md": "acdd79110699a608",
|
||||||
"gsd-core/workflows/ship.md": "44af1c72d86e153b",
|
"gsd-core/workflows/ship.md": "44af1c72d86e153b",
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
"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/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||||
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
||||||
@@ -288,7 +288,7 @@
|
|||||||
"gsd-core/workflows/scan.md": "47371c2073d6c0be",
|
"gsd-core/workflows/scan.md": "47371c2073d6c0be",
|
||||||
"gsd-core/workflows/secure-phase.md": "59d3c50aba8c9a6c",
|
"gsd-core/workflows/secure-phase.md": "59d3c50aba8c9a6c",
|
||||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
"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-integrations.md": "53649313d20694ae",
|
||||||
"gsd-core/workflows/settings.md": "7e7458cdb2b68ec5",
|
"gsd-core/workflows/settings.md": "7e7458cdb2b68ec5",
|
||||||
"gsd-core/workflows/ship.md": "12e8e58c077a891a",
|
"gsd-core/workflows/ship.md": "12e8e58c077a891a",
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
"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/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||||
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
||||||
@@ -292,7 +292,7 @@
|
|||||||
"gsd-core/workflows/scan.md": "dfd92717caea0ce7",
|
"gsd-core/workflows/scan.md": "dfd92717caea0ce7",
|
||||||
"gsd-core/workflows/secure-phase.md": "cf78183f06a02582",
|
"gsd-core/workflows/secure-phase.md": "cf78183f06a02582",
|
||||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
"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-integrations.md": "76eee76d6eb57657",
|
||||||
"gsd-core/workflows/settings.md": "3701faed09d55247",
|
"gsd-core/workflows/settings.md": "3701faed09d55247",
|
||||||
"gsd-core/workflows/ship.md": "5f931a25ea9102a4",
|
"gsd-core/workflows/ship.md": "5f931a25ea9102a4",
|
||||||
|
|||||||
@@ -113,7 +113,7 @@
|
|||||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
"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/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||||
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
||||||
@@ -360,7 +360,7 @@
|
|||||||
"gsd-core/workflows/scan.md": "003883d71c37da7d",
|
"gsd-core/workflows/scan.md": "003883d71c37da7d",
|
||||||
"gsd-core/workflows/secure-phase.md": "29fc6b62c5c5dc62",
|
"gsd-core/workflows/secure-phase.md": "29fc6b62c5c5dc62",
|
||||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
"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-integrations.md": "70515c5838fb9826",
|
||||||
"gsd-core/workflows/settings.md": "d96ddf01fb85e61e",
|
"gsd-core/workflows/settings.md": "d96ddf01fb85e61e",
|
||||||
"gsd-core/workflows/ship.md": "7b8fe9f89143e648",
|
"gsd-core/workflows/ship.md": "7b8fe9f89143e648",
|
||||||
|
|||||||
@@ -148,7 +148,7 @@
|
|||||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
"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/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||||
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
||||||
@@ -395,7 +395,7 @@
|
|||||||
"gsd-core/workflows/scan.md": "1a3caa5d724d39e9",
|
"gsd-core/workflows/scan.md": "1a3caa5d724d39e9",
|
||||||
"gsd-core/workflows/secure-phase.md": "db91810d16964b1e",
|
"gsd-core/workflows/secure-phase.md": "db91810d16964b1e",
|
||||||
"gsd-core/workflows/session-report.md": "dd8fa011c9394075",
|
"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-integrations.md": "77730321d3d6d317",
|
||||||
"gsd-core/workflows/settings.md": "054c8c31b3905ced",
|
"gsd-core/workflows/settings.md": "054c8c31b3905ced",
|
||||||
"gsd-core/workflows/ship.md": "d86233c9a365effd",
|
"gsd-core/workflows/ship.md": "d86233c9a365effd",
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
"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/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||||
"gsd-core/bin/verify-reapply-patches.cjs": "10226e9512dd44bf",
|
"gsd-core/bin/verify-reapply-patches.cjs": "10226e9512dd44bf",
|
||||||
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
||||||
@@ -290,7 +290,7 @@
|
|||||||
"gsd-core/workflows/scan.md": "dcc2f76d0850e2fb",
|
"gsd-core/workflows/scan.md": "dcc2f76d0850e2fb",
|
||||||
"gsd-core/workflows/secure-phase.md": "d87bd706f85bcad6",
|
"gsd-core/workflows/secure-phase.md": "d87bd706f85bcad6",
|
||||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
"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-integrations.md": "a1d146d6bfd14db5",
|
||||||
"gsd-core/workflows/settings.md": "f611f14f2f447f1e",
|
"gsd-core/workflows/settings.md": "f611f14f2f447f1e",
|
||||||
"gsd-core/workflows/ship.md": "b9dc0aaee0ff68e7",
|
"gsd-core/workflows/ship.md": "b9dc0aaee0ff68e7",
|
||||||
|
|||||||
@@ -113,7 +113,7 @@
|
|||||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
"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/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||||
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
||||||
@@ -360,7 +360,7 @@
|
|||||||
"gsd-core/workflows/scan.md": "47371c2073d6c0be",
|
"gsd-core/workflows/scan.md": "47371c2073d6c0be",
|
||||||
"gsd-core/workflows/secure-phase.md": "c55975672c4e1895",
|
"gsd-core/workflows/secure-phase.md": "c55975672c4e1895",
|
||||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
"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-integrations.md": "166ca51b1f33c2a3",
|
||||||
"gsd-core/workflows/settings.md": "0623c673eaf04799",
|
"gsd-core/workflows/settings.md": "0623c673eaf04799",
|
||||||
"gsd-core/workflows/ship.md": "38830806d244fe9c",
|
"gsd-core/workflows/ship.md": "38830806d244fe9c",
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
"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/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||||
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
||||||
@@ -289,7 +289,7 @@
|
|||||||
"gsd-core/workflows/scan.md": "b28f65d88c522767",
|
"gsd-core/workflows/scan.md": "b28f65d88c522767",
|
||||||
"gsd-core/workflows/secure-phase.md": "f2957d4b88fb3746",
|
"gsd-core/workflows/secure-phase.md": "f2957d4b88fb3746",
|
||||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
"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-integrations.md": "1dce76db0aca08a5",
|
||||||
"gsd-core/workflows/settings.md": "0845d073009a4619",
|
"gsd-core/workflows/settings.md": "0845d073009a4619",
|
||||||
"gsd-core/workflows/ship.md": "dbf8bf636cb196c0",
|
"gsd-core/workflows/ship.md": "dbf8bf636cb196c0",
|
||||||
|
|||||||
@@ -113,7 +113,7 @@
|
|||||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
"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/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||||
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
||||||
@@ -360,7 +360,7 @@
|
|||||||
"gsd-core/workflows/scan.md": "47371c2073d6c0be",
|
"gsd-core/workflows/scan.md": "47371c2073d6c0be",
|
||||||
"gsd-core/workflows/secure-phase.md": "e8855104c1e0417c",
|
"gsd-core/workflows/secure-phase.md": "e8855104c1e0417c",
|
||||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
"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-integrations.md": "3ea8095d5fad891b",
|
||||||
"gsd-core/workflows/settings.md": "1925ecc2225c2216",
|
"gsd-core/workflows/settings.md": "1925ecc2225c2216",
|
||||||
"gsd-core/workflows/ship.md": "afd77be2093535f8",
|
"gsd-core/workflows/ship.md": "afd77be2093535f8",
|
||||||
|
|||||||
@@ -106,7 +106,7 @@
|
|||||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
"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/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||||
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
||||||
@@ -353,7 +353,7 @@
|
|||||||
"gsd-core/workflows/scan.md": "003883d71c37da7d",
|
"gsd-core/workflows/scan.md": "003883d71c37da7d",
|
||||||
"gsd-core/workflows/secure-phase.md": "29fc6b62c5c5dc62",
|
"gsd-core/workflows/secure-phase.md": "29fc6b62c5c5dc62",
|
||||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
"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-integrations.md": "70515c5838fb9826",
|
||||||
"gsd-core/workflows/settings.md": "d96ddf01fb85e61e",
|
"gsd-core/workflows/settings.md": "d96ddf01fb85e61e",
|
||||||
"gsd-core/workflows/ship.md": "7b8fe9f89143e648",
|
"gsd-core/workflows/ship.md": "7b8fe9f89143e648",
|
||||||
|
|||||||
@@ -113,7 +113,7 @@
|
|||||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
"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/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||||
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
||||||
@@ -360,7 +360,7 @@
|
|||||||
"gsd-core/workflows/scan.md": "ad8ebcad4626d4a8",
|
"gsd-core/workflows/scan.md": "ad8ebcad4626d4a8",
|
||||||
"gsd-core/workflows/secure-phase.md": "e9a488cec3b4efdc",
|
"gsd-core/workflows/secure-phase.md": "e9a488cec3b4efdc",
|
||||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
"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-integrations.md": "d1711a95f44fdbde",
|
||||||
"gsd-core/workflows/settings.md": "2e42ee34c791378a",
|
"gsd-core/workflows/settings.md": "2e42ee34c791378a",
|
||||||
"gsd-core/workflows/ship.md": "924c79e3cfd1e42b",
|
"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/gsd_run": "62d9b647ede212e6",
|
||||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
"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/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||||
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
||||||
@@ -289,7 +289,7 @@
|
|||||||
"gsd-core/workflows/scan.md": "949692db4834dd27",
|
"gsd-core/workflows/scan.md": "949692db4834dd27",
|
||||||
"gsd-core/workflows/secure-phase.md": "6758f1acf4113e9e",
|
"gsd-core/workflows/secure-phase.md": "6758f1acf4113e9e",
|
||||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
"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-integrations.md": "29c5de27fbbb18e9",
|
||||||
"gsd-core/workflows/settings.md": "26b9b7979d3a5747",
|
"gsd-core/workflows/settings.md": "26b9b7979d3a5747",
|
||||||
"gsd-core/workflows/ship.md": "9f94d0b155eb041e",
|
"gsd-core/workflows/ship.md": "9f94d0b155eb041e",
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
"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/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||||
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
||||||
@@ -289,7 +289,7 @@
|
|||||||
"gsd-core/workflows/scan.md": "63631467651d9ca8",
|
"gsd-core/workflows/scan.md": "63631467651d9ca8",
|
||||||
"gsd-core/workflows/secure-phase.md": "6cc236e53c2e7d56",
|
"gsd-core/workflows/secure-phase.md": "6cc236e53c2e7d56",
|
||||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
"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-integrations.md": "f8f756709ec02363",
|
||||||
"gsd-core/workflows/settings.md": "44b10c59215633b8",
|
"gsd-core/workflows/settings.md": "44b10c59215633b8",
|
||||||
"gsd-core/workflows/ship.md": "c08f0fe3025d2c86",
|
"gsd-core/workflows/ship.md": "c08f0fe3025d2c86",
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
"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/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||||
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
||||||
@@ -289,7 +289,7 @@
|
|||||||
"gsd-core/workflows/scan.md": "12c11b2edc165df9",
|
"gsd-core/workflows/scan.md": "12c11b2edc165df9",
|
||||||
"gsd-core/workflows/secure-phase.md": "7bf923689bf58288",
|
"gsd-core/workflows/secure-phase.md": "7bf923689bf58288",
|
||||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
"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-integrations.md": "b082fc518b484c07",
|
||||||
"gsd-core/workflows/settings.md": "002eb0ce3c10c741",
|
"gsd-core/workflows/settings.md": "002eb0ce3c10c741",
|
||||||
"gsd-core/workflows/ship.md": "c035bb8b3bb7efbb",
|
"gsd-core/workflows/ship.md": "c035bb8b3bb7efbb",
|
||||||
|
|||||||
@@ -113,7 +113,7 @@
|
|||||||
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
"gsd-core/bin/gsd_run": "62d9b647ede212e6",
|
||||||
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
"gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16",
|
||||||
"gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8",
|
"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/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a",
|
||||||
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
"gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904",
|
||||||
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
"gsd-core/contexts/dev.md": "dcb0de9dce33cf41",
|
||||||
@@ -360,7 +360,7 @@
|
|||||||
"gsd-core/workflows/scan.md": "003883d71c37da7d",
|
"gsd-core/workflows/scan.md": "003883d71c37da7d",
|
||||||
"gsd-core/workflows/secure-phase.md": "29fc6b62c5c5dc62",
|
"gsd-core/workflows/secure-phase.md": "29fc6b62c5c5dc62",
|
||||||
"gsd-core/workflows/session-report.md": "2e5b1205324ddefa",
|
"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-integrations.md": "70515c5838fb9826",
|
||||||
"gsd-core/workflows/settings.md": "d96ddf01fb85e61e",
|
"gsd-core/workflows/settings.md": "d96ddf01fb85e61e",
|
||||||
"gsd-core/workflows/ship.md": "7b8fe9f89143e648",
|
"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
|
* behaviorally testable without a live pi runtime. The default export matches
|
||||||
* pi's extension entry shape: `export default function (pi: ExtensionAPI) { … }`.
|
* 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
|
* NOTE: this is the reference binding that proves the ExtensionAPI imperative
|
||||||
* adapter (#1682 AC). Full `--pi` installable-runtime integration (descriptor +
|
* adapter (#1682 AC), kept as a mock-friendly fixture independent of the real
|
||||||
* installer wiring + golden parity 16→17) is a larger follow-up tracked
|
* `pi/gsd.cjs` extension. Full `--pi` installable-runtime integration
|
||||||
* separately — it is intentionally NOT added to the runtime registry here.
|
* (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/…)
|
* @param {object} pi pi ExtensionAPI (registerTool/registerCommand/on/…)
|
||||||
*/
|
*/
|
||||||
@@ -30,9 +38,12 @@ module.exports = function gsdPiPlugin(pi) {
|
|||||||
// imperative adapter (createImperativeAdapter({runtime:'pi'}) + dispatch).
|
// imperative adapter (createImperativeAdapter({runtime:'pi'}) + dispatch).
|
||||||
pi.registerCommand('gsd', {
|
pi.registerCommand('gsd', {
|
||||||
description: 'Invoke a GSD command via the embedded engine (imperative adapter).',
|
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).
|
// 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({
|
pi.registerTool({
|
||||||
name: 'gsd_invoke',
|
name: 'gsd_invoke',
|
||||||
description: 'Invoke a GSD command family/subcommand through the engine.',
|
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';
|
return 'ok';
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ const GOLDEN_FRAGMENT_MAP = {
|
|||||||
cline: "'.cline'",
|
cline: "'.cline'",
|
||||||
kimi: "'.config', 'agents'",
|
kimi: "'.config', 'agents'",
|
||||||
zcode: "'.zcode'",
|
zcode: "'.zcode'",
|
||||||
|
pi: "'.pi', 'agent'",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Runtimes intentionally NOT in the table: claude is the default; antigravity is
|
// 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');
|
assert.strictEqual(res.jsonrpc, '2.0');
|
||||||
const payload = JSON.parse(res.result.content[0].text);
|
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(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', () => {
|
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') },
|
kimi: { localDir: '.kimi-code', globalSuffix: path.join('.config', 'agents') },
|
||||||
kilo: { localDir: '.kilo', globalSuffix: path.join('.config', 'kilo') },
|
kilo: { localDir: '.kilo', globalSuffix: path.join('.config', 'kilo') },
|
||||||
opencode: { localDir: '.opencode', globalSuffix: path.join('.config', 'opencode') },
|
opencode: { localDir: '.opencode', globalSuffix: path.join('.config', 'opencode') },
|
||||||
|
pi: { localDir: '.pi', globalSuffix: path.join('.pi', 'agent') },
|
||||||
qwen: { localDir: '.qwen', globalSuffix: '.qwen' },
|
qwen: { localDir: '.qwen', globalSuffix: '.qwen' },
|
||||||
trae: { localDir: '.trae', globalSuffix: '.trae' },
|
trae: { localDir: '.trae', globalSuffix: '.trae' },
|
||||||
windsurf: { localDir: '.windsurf', globalSuffix: path.join('.codeium', 'windsurf') },
|
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);
|
const RUNTIME_IDS = Object.keys(registry.runtimes);
|
||||||
|
|
||||||
// Contract-pinned profile split (derived from .host-cli-final.json):
|
// 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)
|
// declarative-cli: antigravity, augment, codebuddy, codex, copilot, windsurf, zcode (7)
|
||||||
// ide: 0
|
// ide: 0
|
||||||
const EXPECTED_PROFILES = {
|
const EXPECTED_PROFILES = {
|
||||||
@@ -49,6 +49,7 @@ const EXPECTED_PROFILES = {
|
|||||||
kilo: 'programmatic-cli',
|
kilo: 'programmatic-cli',
|
||||||
kimi: 'programmatic-cli',
|
kimi: 'programmatic-cli',
|
||||||
opencode: 'programmatic-cli',
|
opencode: 'programmatic-cli',
|
||||||
|
pi: 'programmatic-cli',
|
||||||
qwen: 'programmatic-cli',
|
qwen: 'programmatic-cli',
|
||||||
trae: 'programmatic-cli',
|
trae: 'programmatic-cli',
|
||||||
antigravity: 'declarative-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) →
|
// #2087: OpenCode background subagents (v1.15 param, v1.17 default-on) →
|
||||||
// dispatch.background/backgroundDispatch true → NOT force-flattened.
|
// dispatch.background/backgroundDispatch true → NOT force-flattened.
|
||||||
opencode: false,
|
opencode: false,
|
||||||
|
// #2102: pi's dispatch.background/backgroundDispatch are both false
|
||||||
|
// (undocumented background-subagent primitive) → force-flattened.
|
||||||
|
pi: true,
|
||||||
qwen: true,
|
qwen: true,
|
||||||
trae: true,
|
trae: true,
|
||||||
windsurf: 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: Kilo/ZCode (hooksSurface:none, no plugin) receive no dead hooks ────
|
||||||
//
|
//
|
||||||
// #1821 reported dead hook scripts staged for runtimes with hooksSurface:'none'.
|
// #1821 reported dead hook scripts staged for runtimes with hooksSurface:'none'.
|
||||||
// OpenCode ALSO declares hooksSurface:'none', but its #1914 native plugin adapter
|
// OpenCode and pi ALSO declare hooksSurface:'none', but each has a native plugin
|
||||||
// (plugins/gsd-core.js) spawns the staged hooks/*.js via OpenCode's event bus —
|
// adapter that spawns the staged hooks/*.js scripts as subprocesses (OpenCode's
|
||||||
// so for OpenCode the hooks are LIVE and must keep being copied. Kilo and ZCode
|
// #1914 plugins/gsd-core.js via OpenCode's event bus; pi's #2102 Stage 2
|
||||||
// have no plugin surface, so their staged hooks are genuinely dead: this is the
|
// pi/gsd.cjs → extensions/gsd.cjs via pi.on(...) bridges) — so for both, the
|
||||||
// case the fix removes. These tests assert the split: Kilo/ZCode get no hooks;
|
// hooks are LIVE and must keep being copied. Kilo and ZCode have no plugin
|
||||||
// OpenCode (and Claude) still do.
|
// 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', () => {
|
describe('#1821: Kilo/ZCode receive no dead hook files; OpenCode/Claude keep their hooks', () => {
|
||||||
function gsdHookFilesUnder(configDir) {
|
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}-`));
|
const targetDir = fs.mkdtempSync(path.join(os.tmpdir(), `gsd-1821-${runtime}-`));
|
||||||
try {
|
try {
|
||||||
const result = spawnSync(
|
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,
|
assert.strictEqual(result.status, 0,
|
||||||
`installer exited with status ${result.status} for --${runtime} --global\nstdout: ${result.stdout}\nstderr: ${result.stderr}`);
|
`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.
|
// Collect results while targetDir still exists — cleanup() below removes it.
|
||||||
|
const pluginRelPath = opts.pluginRelPath || path.join('plugins', 'gsd-core.js');
|
||||||
return {
|
return {
|
||||||
hookFiles: gsdHookFilesUnder(targetDir),
|
hookFiles: gsdHookFilesUnder(targetDir),
|
||||||
hooksLibExists: fs.existsSync(path.join(targetDir, 'hooks', 'lib')),
|
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 {
|
} finally {
|
||||||
cleanup(targetDir);
|
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
|
// 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']) {
|
for (const runtime of ['kilo', 'zcode']) {
|
||||||
test(`${runtime} --global install creates no gsd-*.js/.sh hook files or hooks/lib`, () => {
|
test(`${runtime} --global install creates no gsd-*.js/.sh hook files or hooks/lib`, () => {
|
||||||
const { hookFiles, hooksLibExists } = installAndCollect(runtime);
|
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)');
|
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
|
// Positive control: guards against over-exclusion breaking runtimes that
|
||||||
// legitimately need hooks (hooksSurface !== 'none').
|
// legitimately need hooks (hooksSurface !== 'none').
|
||||||
test('claude --global install still copies gsd-*.js hooks', () => {
|
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.
|
// dead hook scripts or the CommonJS package.json marker.
|
||||||
kilo: { surface: 'flat-command', settings: false, packageJson: false },
|
kilo: { surface: 'flat-command', settings: false, packageJson: false },
|
||||||
opencode: { surface: 'flat-command', settings: true, packageJson: true },
|
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 },
|
qwen: { surface: 'flat-skills', settings: true, packageJson: true },
|
||||||
trae: { surface: 'flat-skills', settings: false, packageJson: false },
|
trae: { surface: 'flat-skills', settings: false, packageJson: false },
|
||||||
windsurf: { surface: 'global-artifacts-noop', 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')),
|
listDirNames(targetDir, 'command').some((name) => name.startsWith('gsd-') && name.endsWith('.md')),
|
||||||
`${runtime} should install flattened command markdown files`
|
`${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') {
|
} else if (contract.surface === 'commands-gsd') {
|
||||||
assert.ok(
|
assert.ok(
|
||||||
listDirNames(targetDir, path.join('commands', 'gsd')).length > 0,
|
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(
|
assert.ok(
|
||||||
listDirNames(targetDir, 'agents').some((name) => name.startsWith('gsd-')),
|
listDirNames(targetDir, 'agents').some((name) => name.startsWith('gsd-')),
|
||||||
`${runtime} full install should install agents`
|
`${runtime} full install should install agents`
|
||||||
|
|||||||
@@ -15,6 +15,10 @@
|
|||||||
* now hermes, and the "All" shortcut moved from 17 to 16.
|
* 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.
|
* #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';
|
process.env.GSD_TEST_MODE = '1';
|
||||||
@@ -76,39 +80,43 @@ describe('multi-runtime selection parsing', () => {
|
|||||||
assert.deepStrictEqual(parseRuntimeInput('12'), ['opencode']);
|
assert.deepStrictEqual(parseRuntimeInput('12'), ['opencode']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('single choice for pi', () => {
|
||||||
|
assert.deepStrictEqual(parseRuntimeInput('13'), ['pi']);
|
||||||
|
});
|
||||||
|
|
||||||
test('single choice for qwen', () => {
|
test('single choice for qwen', () => {
|
||||||
assert.deepStrictEqual(parseRuntimeInput('13'), ['qwen']);
|
assert.deepStrictEqual(parseRuntimeInput('14'), ['qwen']);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('single choice for trae', () => {
|
test('single choice for trae', () => {
|
||||||
assert.deepStrictEqual(parseRuntimeInput('14'), ['trae']);
|
assert.deepStrictEqual(parseRuntimeInput('15'), ['trae']);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('single choice for windsurf', () => {
|
test('single choice for windsurf', () => {
|
||||||
assert.deepStrictEqual(parseRuntimeInput('15'), ['windsurf']);
|
assert.deepStrictEqual(parseRuntimeInput('16'), ['windsurf']);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('single choice for zcode', () => {
|
test('single choice for zcode', () => {
|
||||||
assert.deepStrictEqual(parseRuntimeInput('16'), ['zcode']);
|
assert.deepStrictEqual(parseRuntimeInput('17'), ['zcode']);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('single choice for kimi', () => {
|
test('single choice for kimi', () => {
|
||||||
assert.deepStrictEqual(parseRuntimeInput('10'), ['kimi']);
|
assert.deepStrictEqual(parseRuntimeInput('10'), ['kimi']);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('choice 17 returns all runtimes', () => {
|
test('choice 18 returns all runtimes', () => {
|
||||||
assert.deepStrictEqual(parseRuntimeInput('17'), allRuntimes);
|
assert.deepStrictEqual(parseRuntimeInput('18'), allRuntimes);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('choice 17 returns all runtimes when mixed with separators or other tokens', () => {
|
test('choice 18 returns all runtimes when mixed with separators or other tokens', () => {
|
||||||
// CR feedback: tokenized inputs that include 17 (e.g. trailing comma, or
|
// CR feedback: tokenized inputs that include 18 (e.g. trailing comma, or
|
||||||
// alongside other choices) must still expand to all-runtimes — previously
|
// 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.
|
// subset.
|
||||||
assert.deepStrictEqual(parseRuntimeInput('17,'), allRuntimes);
|
assert.deepStrictEqual(parseRuntimeInput('18,'), allRuntimes);
|
||||||
assert.deepStrictEqual(parseRuntimeInput('17 1'), allRuntimes);
|
assert.deepStrictEqual(parseRuntimeInput('18 1'), allRuntimes);
|
||||||
assert.deepStrictEqual(parseRuntimeInput('1,17'), allRuntimes);
|
assert.deepStrictEqual(parseRuntimeInput('1,18'), allRuntimes);
|
||||||
assert.deepStrictEqual(parseRuntimeInput(' 17 '), allRuntimes);
|
assert.deepStrictEqual(parseRuntimeInput(' 18 '), allRuntimes);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('empty input defaults to claude', () => {
|
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', () => {
|
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('0'), ['claude']);
|
||||||
assert.deepStrictEqual(parseRuntimeInput('abc'), ['claude']);
|
assert.deepStrictEqual(parseRuntimeInput('abc'), ['claude']);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('invalid choices mixed with valid are filtered out', () => {
|
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']);
|
assert.deepStrictEqual(parseRuntimeInput('abc 3 xyz'), ['augment']);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -152,14 +160,15 @@ describe('install.js exports multi-select runtime metadata', () => {
|
|||||||
'10': 'kimi',
|
'10': 'kimi',
|
||||||
'11': 'kilo',
|
'11': 'kilo',
|
||||||
'12': 'opencode',
|
'12': 'opencode',
|
||||||
'13': 'qwen',
|
'13': 'pi',
|
||||||
'14': 'trae',
|
'14': 'qwen',
|
||||||
'15': 'windsurf',
|
'15': 'trae',
|
||||||
'16': 'zcode',
|
'16': 'windsurf',
|
||||||
|
'17': 'zcode',
|
||||||
};
|
};
|
||||||
const expectedRuntimes = [
|
const expectedRuntimes = [
|
||||||
'claude', 'antigravity', 'augment', 'cline', 'codebuddy', 'codex',
|
'claude', 'antigravity', 'augment', 'cline', 'codebuddy', 'codex',
|
||||||
'copilot', 'cursor', 'hermes', 'kimi', 'kilo', 'opencode',
|
'copilot', 'cursor', 'hermes', 'kimi', 'kilo', 'opencode', 'pi',
|
||||||
'qwen', 'trae', 'windsurf', 'zcode',
|
'qwen', 'trae', 'windsurf', 'zcode',
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -177,8 +186,8 @@ describe('install.js exports multi-select runtime metadata', () => {
|
|||||||
'allRuntimes has no duplicates');
|
'allRuntimes has no duplicates');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('"All" shortcut (option 17) selects every runtime', () => {
|
test('"All" shortcut (option 18) selects every runtime', () => {
|
||||||
assert.deepStrictEqual(parseRuntimeInput('17'), allRuntimes);
|
assert.deepStrictEqual(parseRuntimeInput('18'), allRuntimes);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('--kimi flag selects Kimi without interactive prompt', () => {
|
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']);
|
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', () => {
|
test('--all flag includes Kimi exactly once', () => {
|
||||||
const selected = selectRuntimesFromArgs(['--all']);
|
const selected = selectRuntimesFromArgs(['--all']);
|
||||||
assert.ok(selected.includes('kimi'), '--all includes kimi');
|
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');
|
'--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());
|
const prompt = stripAnsi(buildRuntimePromptText());
|
||||||
assert.ok(/\b9\)\s*Hermes Agent\b/.test(prompt),
|
assert.ok(/\b9\)\s*Hermes Agent\b/.test(prompt),
|
||||||
'prompt lists Hermes Agent as option 9');
|
'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');
|
'prompt lists Kimi as option 10');
|
||||||
assert.ok(/Kimi\s+\(~\/\.config\/agents, then ~\/\.agents if existing\)/.test(prompt),
|
assert.ok(/Kimi\s+\(~\/\.config\/agents, then ~\/\.agents if existing\)/.test(prompt),
|
||||||
'prompt shows the Kimi first-existing generic root policy');
|
'prompt shows the Kimi first-existing generic root policy');
|
||||||
assert.ok(/\b13\)\s*Qwen Code\b/.test(prompt),
|
assert.ok(/\b13\)\s*pi\b/.test(prompt),
|
||||||
'prompt lists Qwen Code as option 13');
|
'prompt lists pi as option 13');
|
||||||
assert.ok(/\b14\)\s*Trae\b/.test(prompt),
|
assert.ok(/\b14\)\s*Qwen Code\b/.test(prompt),
|
||||||
'prompt lists Trae as option 14');
|
'prompt lists Qwen Code as option 14');
|
||||||
assert.ok(/\b16\)\s*ZCode\b/.test(prompt),
|
assert.ok(/\b15\)\s*Trae\b/.test(prompt),
|
||||||
'prompt lists ZCode as option 16');
|
'prompt lists Trae as option 15');
|
||||||
assert.ok(/\b17\)\s*All\b/.test(prompt),
|
assert.ok(/\b17\)\s*ZCode\b/.test(prompt),
|
||||||
'prompt lists All as option 17');
|
'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)', () => {
|
test('prompt does not list Gemini (removed #1928)', () => {
|
||||||
|
|||||||
@@ -1,57 +1,115 @@
|
|||||||
'use strict';
|
'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
|
* Proves the pi extension is keystone-WIRED: the registered /gsd command's
|
||||||
* handler dispatches through the GSD command-routing hub and returns a result
|
* `handler(args, ctx)` (pi's REAL ExtensionAPI shape — NOT the `execute(ctx)`
|
||||||
* (not just a registration on a mock). This is the "user can invoke X" proof.
|
* 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 { test } = require('node:test');
|
||||||
const assert = require('node:assert/strict');
|
const assert = require('node:assert/strict');
|
||||||
|
|
||||||
const gsdPiExtension = require('../pi/gsd.cjs');
|
const gsdPiExtension = require('../pi/gsd.cjs');
|
||||||
|
const { _internals } = require('../pi/gsd.cjs');
|
||||||
|
const { createTempDir, cleanup } = require('./helpers.cjs');
|
||||||
|
|
||||||
function mockPi() {
|
function mockPi() {
|
||||||
const recorded = { commands: {}, tools: {}, events: [] };
|
const recorded = { commands: {}, tools: {}, events: {} };
|
||||||
return {
|
return {
|
||||||
registerCommand(name, def) { recorded.commands[name] = def; },
|
registerCommand(name, def) { recorded.commands[name] = def; },
|
||||||
registerTool(def) { if (def && def.name) recorded.tools[def.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,
|
_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();
|
const pi = mockPi();
|
||||||
gsdPiExtension(pi);
|
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.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 () => {
|
test('REACHABILITY: parseGsdCommandArgs tokenizes a raw args string into {family, subcommand, args}', () => {
|
||||||
const pi = mockPi();
|
const parsed = _internals.parseGsdCommandArgs('phase add --name test');
|
||||||
gsdPiExtension(pi);
|
assert.deepEqual(parsed, { family: 'phase', subcommand: 'add', args: ['--name', 'test'] });
|
||||||
// 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: 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();
|
const pi = mockPi();
|
||||||
gsdPiExtension(pi);
|
gsdPiExtension(pi);
|
||||||
const result = await pi._recorded.tools['gsd_invoke'].execute();
|
const dir = createTempDir();
|
||||||
assert.equal(typeof result, 'string');
|
try {
|
||||||
JSON.parse(result); // must be valid JSON (engine was reached)
|
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)', () => {
|
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).
|
* 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).
|
* 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
|
* Full `--pi` installable-runtime integration shipped in #2102 Stage 1
|
||||||
* + installer + golden parity 16→17); this slice proves the imperative binding.
|
* (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');
|
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');
|
const gsdPiPlugin = require('./fixtures/pi-host-plugin.cjs');
|
||||||
|
|
||||||
// Mock pi ExtensionAPI: records registrations so the plugin is testable without
|
// 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() {
|
function mockPi() {
|
||||||
const recorded = { commands: [], tools: [], events: [] };
|
const recorded = { commands: [], tools: [], events: [], commandDefs: {}, toolDefs: {} };
|
||||||
return {
|
return {
|
||||||
registerCommand(name) { recorded.commands.push(name); },
|
registerCommand(name, def) { recorded.commands.push(name); recorded.commandDefs[name] = def; },
|
||||||
registerTool(def) { if (def && def.name) recorded.tools.push(def.name); },
|
registerTool(def) { if (def && def.name) { recorded.tools.push(def.name); recorded.toolDefs[def.name] = def; } },
|
||||||
registerShortcut() {},
|
registerShortcut() {},
|
||||||
registerFlag() {},
|
registerFlag() {},
|
||||||
on(event) { recorded.events.push(event); },
|
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');
|
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)', () => {
|
test('gsdPiPlugin throws if the pi ExtensionAPI is not provided (fail-closed)', () => {
|
||||||
assert.throws(() => gsdPiPlugin(null), /ExtensionAPI is required/);
|
assert.throws(() => gsdPiPlugin(null), /ExtensionAPI is required/);
|
||||||
assert.throws(() => gsdPiPlugin(undefined), /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 = [
|
const EXPECTED_FLAGS = [
|
||||||
'isOpencode', 'isKilo', 'isCodex', 'isCopilot', 'isAntigravity',
|
'isOpencode', 'isKilo', 'isCodex', 'isCopilot', 'isAntigravity',
|
||||||
'isCursor', 'isWindsurf', 'isAugment', 'isTrae', 'isQwen', 'isHermes',
|
'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', () => {
|
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');
|
const flags = runtimeFlags('opencode');
|
||||||
for (const f of EXPECTED_FLAGS) {
|
for (const f of EXPECTED_FLAGS) {
|
||||||
assert.strictEqual(typeof flags[f], 'boolean', `${f} must be boolean`);
|
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');
|
assert.ok(Object.isFrozen(flags), 'flags object must be frozen');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ const {
|
|||||||
platformWriteSync,
|
platformWriteSync,
|
||||||
platformReadSync,
|
platformReadSync,
|
||||||
platformEnsureDir,
|
platformEnsureDir,
|
||||||
|
dispatchGsdCommand,
|
||||||
|
resolveGsdToolsPath,
|
||||||
} = require(path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'shell-command-projection.cjs'));
|
} = require(path.join(__dirname, '..', 'gsd-core', 'bin', 'lib', 'shell-command-projection.cjs'));
|
||||||
|
|
||||||
const { createTempGitProject, createTempDir, cleanup } = require('./helpers.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 ────────────────────────────────────────────────────────────────
|
// ─── probeTty ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
describe('probeTty', () => {
|
describe('probeTty', () => {
|
||||||
|
|||||||
@@ -68,7 +68,7 @@
|
|||||||
"scan.md": 7732,
|
"scan.md": 7732,
|
||||||
"secure-phase.md": 13520,
|
"secure-phase.md": 13520,
|
||||||
"session-report.md": 4044,
|
"session-report.md": 4044,
|
||||||
"settings-advanced.md": 39908,
|
"settings-advanced.md": 40019,
|
||||||
"settings-integrations.md": 15892,
|
"settings-integrations.md": 15892,
|
||||||
"settings.md": 33467,
|
"settings.md": 33467,
|
||||||
"ship.md": 24691,
|
"ship.md": 24691,
|
||||||
|
|||||||
Reference in New Issue
Block a user