@opengsd/gsd-core 1.7.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (261) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.opencode/plugins/gsd-core.js +45 -1
  4. package/README.md +2 -0
  5. package/agents/gsd-code-fixer.md +1 -1
  6. package/agents/gsd-codebase-mapper.md +1 -1
  7. package/agents/gsd-debug-session-manager.md +78 -4
  8. package/agents/gsd-debugger.md +87 -29
  9. package/agents/gsd-executor.md +49 -9
  10. package/agents/gsd-intel-updater.md +3 -3
  11. package/agents/gsd-phase-researcher.md +4 -2
  12. package/agents/gsd-plan-checker.md +20 -0
  13. package/agents/gsd-planner.md +44 -59
  14. package/agents/gsd-project-researcher.md +2 -2
  15. package/agents/gsd-ui-auditor.md +0 -40
  16. package/agents/gsd-verifier.md +2 -2
  17. package/bin/install.js +1338 -135
  18. package/commands/gsd/ai-integration-phase.md +1 -1
  19. package/commands/gsd/mempalace-capture.md +9 -5
  20. package/commands/gsd/new-milestone.md +1 -1
  21. package/commands/gsd/plan-phase.md +5 -3
  22. package/commands/gsd/plan-review-convergence.md +7 -2
  23. package/gsd-core/bin/gsd-tools.cjs +2690 -2472
  24. package/gsd-core/bin/lib/adapter-imperative.cjs +8 -1
  25. package/gsd-core/bin/lib/agent-command-router.cjs +20 -5
  26. package/gsd-core/bin/lib/api-coverage.cjs +360 -53
  27. package/gsd-core/bin/lib/audit.cjs +8 -8
  28. package/gsd-core/bin/lib/broken-windows.cjs +716 -0
  29. package/gsd-core/bin/lib/capability-command-router.cjs +733 -0
  30. package/gsd-core/bin/lib/capability-consent.cjs +40 -1
  31. package/gsd-core/bin/lib/capability-lifecycle.cjs +58 -0
  32. package/gsd-core/bin/lib/capability-loader.cjs +23 -1
  33. package/gsd-core/bin/lib/capability-registry.cjs +1450 -160
  34. package/gsd-core/bin/lib/capability-trust.cjs +468 -33
  35. package/gsd-core/bin/lib/capability-validator.cjs +882 -6
  36. package/gsd-core/bin/lib/capability-writer.cjs +6 -1
  37. package/gsd-core/bin/lib/check-command-router.cjs +140 -27
  38. package/gsd-core/bin/lib/cjs-command-router-adapter.cjs +15 -0
  39. package/gsd-core/bin/lib/claude-orchestration-command-router.cjs +209 -31
  40. package/gsd-core/bin/lib/claude-orchestration.cjs +203 -25
  41. package/gsd-core/bin/lib/command-aliases.cjs +14 -0
  42. package/gsd-core/bin/lib/commands.cjs +326 -21
  43. package/gsd-core/bin/lib/config-loader.cjs +214 -30
  44. package/gsd-core/bin/lib/config.cjs +158 -22
  45. package/gsd-core/bin/lib/core-utils.cjs +6 -1
  46. package/gsd-core/bin/lib/decisions.cjs +32 -8
  47. package/gsd-core/bin/lib/docs.cjs +6 -0
  48. package/gsd-core/bin/lib/estimate-cli.cjs +336 -0
  49. package/gsd-core/bin/lib/external-descriptor-trust.cjs +14 -2
  50. package/gsd-core/bin/lib/frontmatter.cjs +125 -15
  51. package/gsd-core/bin/lib/gap-checker.cjs +17 -2
  52. package/gsd-core/bin/lib/host-integration.cjs +215 -8
  53. package/gsd-core/bin/lib/init.cjs +155 -66
  54. package/gsd-core/bin/lib/install-engine.cjs +299 -23
  55. package/gsd-core/bin/lib/install-profiles.cjs +239 -1
  56. package/gsd-core/bin/lib/installer-migrations/005-opencode-baseline-commands-dir.cjs +146 -0
  57. package/gsd-core/bin/lib/installer-migrations/006-pi-extension-cjs-to-js.cjs +91 -0
  58. package/gsd-core/bin/lib/installer-migrations.cjs +44 -5
  59. package/gsd-core/bin/lib/markdown-sectionizer.cjs +107 -0
  60. package/gsd-core/bin/lib/milestone.cjs +248 -14
  61. package/gsd-core/bin/lib/model-catalog.cjs +69 -4
  62. package/gsd-core/bin/lib/model-resolver.cjs +189 -7
  63. package/gsd-core/bin/lib/observability/logger.cjs +7 -2
  64. package/gsd-core/bin/lib/onboard-projection.cjs +11 -8
  65. package/gsd-core/bin/lib/phase-command-router.cjs +10 -1
  66. package/gsd-core/bin/lib/phase-estimation.cjs +398 -0
  67. package/gsd-core/bin/lib/phase-id.cjs +304 -9
  68. package/gsd-core/bin/lib/phase.cjs +258 -17
  69. package/gsd-core/bin/lib/plan-drift-guard.cjs +1 -1
  70. package/gsd-core/bin/lib/plan-scan.cjs +70 -2
  71. package/gsd-core/bin/lib/planning-workspace.cjs +9 -2
  72. package/gsd-core/bin/lib/profile-output.cjs +34 -8
  73. package/gsd-core/bin/lib/review-lane-descriptor.cjs +927 -0
  74. package/gsd-core/bin/lib/review-lane-invocation.cjs +348 -0
  75. package/gsd-core/bin/lib/review-lane-runner.cjs +594 -0
  76. package/gsd-core/bin/lib/review-reviewer-selection.cjs +114 -32
  77. package/gsd-core/bin/lib/roadmap-parser.cjs +61 -10
  78. package/gsd-core/bin/lib/roadmap.cjs +23 -7
  79. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +38 -5
  80. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +23 -9
  81. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +156 -0
  82. package/gsd-core/bin/lib/runtime-name-policy.cjs +15 -2
  83. package/gsd-core/bin/lib/smart-entry.cjs +70 -5
  84. package/gsd-core/bin/lib/state-document.cjs +171 -24
  85. package/gsd-core/bin/lib/state-transition.cjs +50 -11
  86. package/gsd-core/bin/lib/state.cjs +206 -32
  87. package/gsd-core/bin/lib/surface.cjs +51 -9
  88. package/gsd-core/bin/lib/uat-predicate.cjs +6 -4
  89. package/gsd-core/bin/lib/uat.cjs +428 -11
  90. package/gsd-core/bin/lib/ui-consideration-probe.cjs +2 -2
  91. package/gsd-core/bin/lib/unusable-input.cjs +216 -0
  92. package/gsd-core/bin/lib/validate.cjs +44 -8
  93. package/gsd-core/bin/lib/verification.cjs +163 -31
  94. package/gsd-core/bin/lib/verify.cjs +348 -42
  95. package/gsd-core/bin/lib/worktree-safety.cjs +360 -15
  96. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
  97. package/gsd-core/bin/shared/config-schema.manifest.json +4 -15
  98. package/gsd-core/bin/shared/model-catalog.json +5 -0
  99. package/gsd-core/bin/shared/runtime-aliases.manifest.json +5 -0
  100. package/gsd-core/references/api-coverage.md +37 -7
  101. package/gsd-core/references/checkpoints.md +1 -1
  102. package/gsd-core/references/common-bug-patterns.md +13 -0
  103. package/gsd-core/references/context-budget.md +40 -0
  104. package/gsd-core/references/debugger-bug-taxonomy.md +111 -0
  105. package/gsd-core/references/debugger-fix-acceptance.md +157 -0
  106. package/gsd-core/references/debugger-philosophy.md +1 -0
  107. package/gsd-core/references/debugger-prevention.md +98 -0
  108. package/gsd-core/references/debugger-rca-branching.md +98 -0
  109. package/gsd-core/references/debugger-repro-hardening.md +130 -0
  110. package/gsd-core/references/debugger-sbfl.md +110 -0
  111. package/gsd-core/references/debugger-semantic-recall.md +81 -0
  112. package/gsd-core/references/execute-phase-quota-recovery.md +55 -0
  113. package/gsd-core/references/execute-phase-requirement-revert.md +8 -0
  114. package/gsd-core/references/execute-phase-response-language.md +7 -0
  115. package/gsd-core/references/gate-prompts.md +6 -3
  116. package/gsd-core/references/model-profile-resolution.md +64 -13
  117. package/gsd-core/references/offer-next.md +88 -0
  118. package/gsd-core/references/planner-antipatterns.md +6 -0
  119. package/gsd-core/references/planner-mvp-mode.md +12 -13
  120. package/gsd-core/references/planner-preconditions.md +156 -0
  121. package/gsd-core/references/planner-reversibility.md +132 -0
  122. package/gsd-core/references/planning-config.md +2 -1
  123. package/gsd-core/references/reviewer-instances.md +28 -19
  124. package/gsd-core/references/runtime-aware-dispatch.md +42 -0
  125. package/gsd-core/references/skeleton-template.md +1 -1
  126. package/gsd-core/references/thinking-models-planning.md +3 -1
  127. package/gsd-core/references/ui-consideration-probe.md +2 -2
  128. package/gsd-core/references/worktree-branch-check.md +4 -4
  129. package/gsd-core/templates/DEBUG.md +5 -3
  130. package/gsd-core/templates/summary-minimal.md +4 -0
  131. package/gsd-core/templates/summary-standard.md +4 -0
  132. package/gsd-core/templates/summary.md +7 -0
  133. package/gsd-core/workflows/add-phase.md +2 -0
  134. package/gsd-core/workflows/add-tests.md +3 -1
  135. package/gsd-core/workflows/add-todo.md +32 -1
  136. package/gsd-core/workflows/ai-integration-phase.md +8 -6
  137. package/gsd-core/workflows/audit-fix.md +6 -2
  138. package/gsd-core/workflows/audit-milestone.md +8 -0
  139. package/gsd-core/workflows/autonomous.md +19 -15
  140. package/gsd-core/workflows/check-todos.md +5 -3
  141. package/gsd-core/workflows/cleanup.md +7 -1
  142. package/gsd-core/workflows/code-review-fix.md +14 -6
  143. package/gsd-core/workflows/code-review.md +93 -24
  144. package/gsd-core/workflows/complete-milestone.md +3 -0
  145. package/gsd-core/workflows/debug.md +35 -7
  146. package/gsd-core/workflows/diagnose-issues.md +5 -1
  147. package/gsd-core/workflows/discovery-phase.md +7 -0
  148. package/gsd-core/workflows/discuss-phase/modes/advisor.md +2 -4
  149. package/gsd-core/workflows/discuss-phase/modes/auto.md +0 -6
  150. package/gsd-core/workflows/discuss-phase/templates/context.md +16 -2
  151. package/gsd-core/workflows/discuss-phase-assumptions.md +18 -9
  152. package/gsd-core/workflows/discuss-phase.md +2 -2
  153. package/gsd-core/workflows/do.md +7 -1
  154. package/gsd-core/workflows/docs-update.md +9 -0
  155. package/gsd-core/workflows/eval-review.md +4 -1
  156. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +4 -0
  157. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +160 -0
  158. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +4 -4
  159. package/gsd-core/workflows/execute-phase/steps/regression-gate.md +2 -2
  160. package/gsd-core/workflows/execute-phase.md +110 -149
  161. package/gsd-core/workflows/execute-plan.md +20 -8
  162. package/gsd-core/workflows/explore.md +4 -0
  163. package/gsd-core/workflows/extract-learnings.md +21 -0
  164. package/gsd-core/workflows/graduation.md +3 -0
  165. package/gsd-core/workflows/health.md +7 -1
  166. package/gsd-core/workflows/help/modes/full.md +9 -5
  167. package/gsd-core/workflows/import.md +11 -2
  168. package/gsd-core/workflows/inbox.md +7 -0
  169. package/gsd-core/workflows/ingest-docs.md +19 -10
  170. package/gsd-core/workflows/manager.md +3 -1
  171. package/gsd-core/workflows/map-codebase.md +17 -10
  172. package/gsd-core/workflows/mvp-phase.md +3 -0
  173. package/gsd-core/workflows/new-milestone.md +79 -23
  174. package/gsd-core/workflows/new-project.md +28 -19
  175. package/gsd-core/workflows/new-workspace.md +3 -1
  176. package/gsd-core/workflows/next.md +5 -2
  177. package/gsd-core/workflows/onboard.md +3 -0
  178. package/gsd-core/workflows/plan-phase.md +56 -51
  179. package/gsd-core/workflows/plan-review-convergence.md +61 -12
  180. package/gsd-core/workflows/plant-seed.md +3 -0
  181. package/gsd-core/workflows/profile-user.md +7 -1
  182. package/gsd-core/workflows/progress.md +31 -3
  183. package/gsd-core/workflows/quick.md +33 -10
  184. package/gsd-core/workflows/remove-workspace.md +3 -0
  185. package/gsd-core/workflows/review.md +172 -585
  186. package/gsd-core/workflows/scan.md +10 -2
  187. package/gsd-core/workflows/secure-phase.md +13 -2
  188. package/gsd-core/workflows/settings-integrations.md +3 -0
  189. package/gsd-core/workflows/settings.md +3 -0
  190. package/gsd-core/workflows/ship.md +88 -11
  191. package/gsd-core/workflows/sketch.md +3 -0
  192. package/gsd-core/workflows/smart-entry.md +4 -1
  193. package/gsd-core/workflows/spike.md +7 -1
  194. package/gsd-core/workflows/ui-phase.md +11 -2
  195. package/gsd-core/workflows/ui-review.md +11 -1
  196. package/gsd-core/workflows/undo.md +7 -0
  197. package/gsd-core/workflows/update.md +106 -5
  198. package/gsd-core/workflows/validate-phase.md +13 -2
  199. package/gsd-core/workflows/verify-phase.md +2 -2
  200. package/gsd-core/workflows/verify-work.md +15 -4
  201. package/hooks/dist/gsd-context-monitor.js +27 -9
  202. package/hooks/dist/gsd-cursor-session-start.js +6 -2
  203. package/hooks/dist/gsd-cursor-stop.js +6 -2
  204. package/hooks/dist/gsd-cursor-subagent-start.js +6 -2
  205. package/hooks/dist/gsd-graphify-update.sh +9 -0
  206. package/hooks/dist/gsd-phase-boundary.sh +14 -2
  207. package/hooks/dist/gsd-prompt-guard.js +101 -2
  208. package/hooks/dist/gsd-read-guard.js +100 -2
  209. package/hooks/dist/gsd-read-injection-scanner.js +109 -2
  210. package/hooks/dist/gsd-statusline.js +97 -9
  211. package/hooks/dist/gsd-workflow-guard.js +110 -6
  212. package/hooks/dist/gsd-worktree-path-guard.js +132 -8
  213. package/hooks/dist/lib/cursor-workspace.js +74 -0
  214. package/hooks/gsd-context-monitor.js +27 -9
  215. package/hooks/gsd-cursor-session-start.js +6 -2
  216. package/hooks/gsd-cursor-stop.js +6 -2
  217. package/hooks/gsd-cursor-subagent-start.js +6 -2
  218. package/hooks/gsd-graphify-update.sh +9 -0
  219. package/hooks/gsd-phase-boundary.sh +14 -2
  220. package/hooks/gsd-prompt-guard.js +101 -2
  221. package/hooks/gsd-read-guard.js +100 -2
  222. package/hooks/gsd-read-injection-scanner.js +109 -2
  223. package/hooks/gsd-statusline.js +97 -9
  224. package/hooks/gsd-workflow-guard.js +110 -6
  225. package/hooks/gsd-worktree-path-guard.js +132 -8
  226. package/hooks/lib/cursor-workspace.js +74 -0
  227. package/package.json +10 -8
  228. package/pi/gsd.cjs +34 -3
  229. package/scripts/changeset/lint.cjs +1 -0
  230. package/scripts/changeset/parse.cjs +26 -0
  231. package/scripts/check-coverage-gate.cjs +51 -0
  232. package/scripts/check-glossary-refs.cjs +244 -0
  233. package/scripts/ci-rebase-check.cjs +48 -4
  234. package/scripts/ci-test-scope.cjs +67 -17
  235. package/scripts/gen-adr-index.cjs +528 -0
  236. package/scripts/gen-capability-matrix.cjs +26 -2
  237. package/scripts/gen-capability-registry.cjs +132 -34
  238. package/scripts/gen-emitted-baseline.cjs +145 -0
  239. package/scripts/gen-test-timings.cjs +201 -0
  240. package/scripts/lint-compiled-artifact-sync.cjs +146 -0
  241. package/scripts/lint-emitted-drift-ack.cjs +149 -0
  242. package/scripts/lint-fix-has-regression-test.cjs +131 -0
  243. package/scripts/lint-portable-timeout.cjs +140 -0
  244. package/scripts/lint-resolution-provenance.cjs +9 -0
  245. package/scripts/lint-test-file-count.allowlist.json +1 -0
  246. package/scripts/mutation-matrix.cjs +4 -0
  247. package/scripts/prompt-injection-scan.sh +6 -0
  248. package/scripts/registry-schema.cjs +57 -8
  249. package/scripts/release-notes/conventional-title.cjs +19 -1
  250. package/scripts/release-notes/format-github-release-notes.cjs +7 -3
  251. package/scripts/release-tarball-smoke.cjs +18 -11
  252. package/scripts/run-tests.cjs +420 -58
  253. package/scripts/workflow-size.cjs +16 -8
  254. package/skills/gsd-ai-integration-phase/SKILL.md +1 -1
  255. package/skills/gsd-mempalace-capture/SKILL.md +9 -5
  256. package/skills/gsd-new-milestone/SKILL.md +1 -1
  257. package/skills/gsd-plan-phase/SKILL.md +5 -3
  258. package/skills/gsd-plan-review-convergence/SKILL.md +7 -2
  259. package/vscode/package.json +1 -1
  260. package/scripts/gen-golden-install-parity-zcode.cjs +0 -77
  261. package/scripts/update-size-baseline.cjs +0 -68
@@ -298,7 +298,16 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
298
298
  const mismatchMsg = '## Post-Planning Gap Analysis\n\nextracted 0 of N — possible format mismatch in CONTEXT.md decisions block.\n';
299
299
  // If there are also requirement items, include them in the return with the
300
300
  // mismatch summary appended, so the caller still sees requirement coverage.
301
- if (items.length > 0) {
301
+ // #2334 HIGH 1: gate on `ghostReqIds.length > 0` too — identical defect to
302
+ // the one fixed at #2316-6b (~34 lines below, at the `items.length === 0`
303
+ // early return): a phase whose EVERY cited REQ-ID is unregistered has
304
+ // `items.length === 0` (all its requirement items were filtered out at
305
+ // ~line 297) but still has real ghost rows to report. Without this guard,
306
+ // a single malformed `<decisions>` line in CONTEXT.md made an all-ghost
307
+ // phase's ghost rows silently vanish (this could-not-parse branch fell
308
+ // through to the bare `mismatchMsg`-only return below, dropping ghost
309
+ // rows that the general path further down correctly surfaces).
310
+ if (items.length > 0 || ghostReqIds.length > 0) {
302
311
  const rows = sortRows([
303
312
  ...detectCoverage(items, planText),
304
313
  ...ghostReqIds.map(id => ({ source: 'REQUIREMENTS.md', item: id, status: 'Missing from REQUIREMENTS.md' })),
@@ -325,7 +334,13 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
325
334
  };
326
335
  }
327
336
  // #1365: if no items at all, surface a clean no-check message.
328
- if (items.length === 0) {
337
+ // #2316-6b: this must NOT fire when `ghostReqIds` is non-empty — a phase
338
+ // whose EVERY cited REQ-ID is unregistered has `items.length === 0` (all
339
+ // its requirement items were filtered out at ~line 297) but still has real
340
+ // ghost rows to report below. Without this guard, an all-orphan phase
341
+ // reported LESS than a partially-orphan one (which falls through to the
342
+ // general path further down and correctly surfaces its ghost rows).
343
+ if (items.length === 0 && ghostReqIds.length === 0) {
329
344
  return {
330
345
  enabled: true,
331
346
  rows: [],
@@ -37,7 +37,32 @@ const HOST_INTEGRATION_AXES = Object.freeze({
37
37
  stateIO: Object.freeze(['filesystem', 'sandboxed-storage', 'session-log-append']),
38
38
  transport: Object.freeze(['mcp', 'native-extension']),
39
39
  runtime: Object.freeze(['node', 'bun', 'sandboxed-web', 'python', 'go', 'rust', 'electron', 'other']),
40
- subagentToolkit: Object.freeze(['full', 'read-only']),
40
+ subagentToolkit: Object.freeze(['full', 'read-only', 'built-in-only']),
41
+ // ADR-1239 amendment (#2481): how reasoning effort reaches this host.
42
+ // `argv` — deliverable as an argument on the host's own invocation.
43
+ // `none` — the host exposes no reasoning-effort mechanism.
44
+ // `undocumented` is NOT a member here; it is the corpus-wide sentinel above.
45
+ // A config-file-only surface deliberately has NO vocabulary member: the only
46
+ // host that ever had one (Gemini CLI's thinkingConfig) was removed as a sunset
47
+ // runtime in #1928/#1996, and neither its successor Antigravity CLI nor ZCode
48
+ // documents a reasoning setting. Adding a member with no host would be a guess.
49
+ effortSurface: Object.freeze(['argv', 'none']),
50
+ // ADR-1239 Codex-binding amendment (#2584): a `dispatch` sub-field — not a new
51
+ // axis — declaring how a host isolates concurrent same-wave executors.
52
+ // `harness-worktree` — the host's own harness creates + binds a git worktree
53
+ // per executor; GSD passes the host's own isolation flag and calls no git
54
+ // itself (host-driven fan-out).
55
+ // `orchestrator-worktree` — GSD itself process-spawns each executor with an
56
+ // explicit working directory into a worktree GSD created, validated, and
57
+ // merges (GSD-driven fan-out; concurrency is OS-level, not the host's).
58
+ // `none` — no isolation primitive; same-wave plans run inline/sequentially
59
+ // (the #853 flatten rule).
60
+ // `undocumented` is NOT a member here; it is the corpus-wide sentinel above.
61
+ // Mechanism-specific ("worktree"), not abstract — same "name only what a
62
+ // host actually has" rule that kept effortSurface from guessing a
63
+ // config-file member above. A future non-worktree isolation mechanism adds a
64
+ // `*-container` member then, evidence-backed.
65
+ isolation: Object.freeze(['harness-worktree', 'orchestrator-worktree', 'none']),
41
66
  });
42
67
  const INTERFACE_POINTS = Object.freeze(['command', 'dispatch', 'model', 'hooks', 'state', 'artifact']);
43
68
  // ---------------------------------------------------------------------------
@@ -47,43 +72,47 @@ const INTERFACE_POINTS = Object.freeze(['command', 'dispatch', 'model', 'hooks',
47
72
  const SAFE_DEFAULTS = {
48
73
  embeddingMode: 'declarative',
49
74
  commandSurface: 'prose-only',
50
- dispatch: { namedDispatch: false, nested: false, maxDepth: 0, background: false, subagentToolkit: 'read-only', backgroundDispatch: false },
75
+ dispatch: { namedDispatch: false, nested: false, maxDepth: 0, background: false, subagentToolkit: 'read-only', backgroundDispatch: false, isolation: 'none' },
51
76
  modelMode: 'passive',
52
77
  hookBus: 'none',
53
78
  stateIO: 'session-log-append',
54
79
  transport: 'mcp',
55
80
  runtime: 'node',
81
+ effortSurface: 'none',
56
82
  };
57
83
  const PROFILE_BASELINES = Object.freeze({
58
84
  'programmatic-cli': Object.freeze({
59
85
  embeddingMode: 'imperative',
60
86
  commandSurface: 'slash-file',
61
- dispatch: Object.freeze({ namedDispatch: true, nested: true, maxDepth: -1, background: true, subagentToolkit: 'full', backgroundDispatch: true }),
87
+ dispatch: Object.freeze({ namedDispatch: true, nested: true, maxDepth: -1, background: true, subagentToolkit: 'full', backgroundDispatch: true, isolation: 'none' }),
62
88
  modelMode: 'passive',
63
89
  hookBus: 'host',
64
90
  stateIO: 'filesystem',
65
91
  transport: 'mcp',
66
92
  runtime: 'node',
93
+ effortSurface: 'none',
67
94
  }),
68
95
  'declarative-cli': Object.freeze({
69
96
  embeddingMode: 'declarative',
70
97
  commandSurface: 'slash-file',
71
- dispatch: Object.freeze({ namedDispatch: true, nested: false, maxDepth: 1, background: false, subagentToolkit: 'full', backgroundDispatch: false }),
98
+ dispatch: Object.freeze({ namedDispatch: true, nested: false, maxDepth: 1, background: false, subagentToolkit: 'full', backgroundDispatch: false, isolation: 'none' }),
72
99
  modelMode: 'passive',
73
100
  hookBus: 'host',
74
101
  stateIO: 'filesystem',
75
102
  transport: 'mcp',
76
103
  runtime: 'node',
104
+ effortSurface: 'none',
77
105
  }),
78
106
  'ide': Object.freeze({
79
107
  embeddingMode: 'imperative',
80
108
  commandSurface: 'palette',
81
- dispatch: Object.freeze({ namedDispatch: true, nested: true, maxDepth: 5, background: true, subagentToolkit: 'full', backgroundDispatch: true }),
109
+ dispatch: Object.freeze({ namedDispatch: true, nested: true, maxDepth: 5, background: true, subagentToolkit: 'full', backgroundDispatch: true, isolation: 'none' }),
82
110
  modelMode: 'active',
83
111
  hookBus: 'engine',
84
112
  stateIO: 'sandboxed-storage',
85
113
  transport: 'mcp',
86
114
  runtime: 'sandboxed-web',
115
+ effortSurface: 'none',
87
116
  }),
88
117
  });
89
118
  // ---------------------------------------------------------------------------
@@ -136,6 +165,13 @@ function degradationFor(point, axes) {
136
165
  return { level: 'degraded', fallback: 'flat dispatch — waves run inline' };
137
166
  }
138
167
  case 'model': {
168
+ // NOTE (#2481): the effortSurface axis is deliberately NOT folded into this
169
+ // level. `modelMode` has graded interface point 3 since Phase A, and every
170
+ // existing consumer reads it as "can GSD drive model selection". Widening it
171
+ // to also mean "…and deliver effort" silently redefines an established
172
+ // contract — an `active` host with no declared effort surface would flip
173
+ // from `full` to `absent`. Effort is negotiated on its own axis and read
174
+ // from `effective.effortSurface` by the consumers that care.
139
175
  const mm = axes.modelMode;
140
176
  if (mm === 'active')
141
177
  return { level: 'full', fallback: '' };
@@ -199,12 +235,13 @@ const DEFAULT_ENGINE = {
199
235
  axes: {
200
236
  embeddingMode: 'imperative',
201
237
  commandSurface: 'slash-file',
202
- dispatch: { namedDispatch: true, nested: true, maxDepth: -1, background: true, subagentToolkit: 'full', backgroundDispatch: true },
238
+ dispatch: { namedDispatch: true, nested: true, maxDepth: -1, background: true, subagentToolkit: 'full', backgroundDispatch: true, isolation: 'none' },
203
239
  modelMode: 'active',
204
240
  hookBus: 'host',
205
241
  stateIO: 'filesystem',
206
242
  transport: 'mcp',
207
243
  runtime: 'node',
244
+ effortSurface: 'argv',
208
245
  },
209
246
  known: HOST_INTEGRATION_AXES,
210
247
  };
@@ -264,6 +301,15 @@ function negotiateHostCapabilities(host, engine = DEFAULT_ENGINE) {
264
301
  if (hostVal === 'active' && engineVal === 'passive')
265
302
  return 'passive';
266
303
  }
304
+ // For effortSurface: 'argv' > 'none'. An engine that cannot deliver the
305
+ // host's richer channel caps the result to what it can drive.
306
+ if (axis === 'effortSurface') {
307
+ const RANK = { argv: 1, none: 0 };
308
+ const hr = RANK[hostVal] ?? 0;
309
+ const er = RANK[engineVal] ?? 0;
310
+ if (hr > er)
311
+ return engineVal;
312
+ }
267
313
  return hostVal;
268
314
  }
269
315
  // Negotiate all scalar axes
@@ -274,6 +320,7 @@ function negotiateHostCapabilities(host, engine = DEFAULT_ENGINE) {
274
320
  const effectiveStateIO = negotiateScalar('stateIO');
275
321
  const effectiveTransport = negotiateScalar('transport');
276
322
  const effectiveRuntime = negotiateScalar('runtime');
323
+ const effectiveEffortSurface = negotiateScalar('effortSurface');
277
324
  // ---------------------------------------------------------------------------
278
325
  // Dispatch struct negotiation
279
326
  // ---------------------------------------------------------------------------
@@ -287,6 +334,7 @@ function negotiateHostCapabilities(host, engine = DEFAULT_ENGINE) {
287
334
  let effectiveBackgroundDispatch;
288
335
  let effectiveSubagentToolkit;
289
336
  let effectiveMaxDepth;
337
+ let effectiveIsolation;
290
338
  if (hostDispatch === null) {
291
339
  // Host didn't declare dispatch at all — fail-closed to most-restrictive values
292
340
  warnings.push(`host did not declare 'dispatch'`);
@@ -296,6 +344,7 @@ function negotiateHostCapabilities(host, engine = DEFAULT_ENGINE) {
296
344
  effectiveBackgroundDispatch = false;
297
345
  effectiveSubagentToolkit = 'read-only';
298
346
  effectiveMaxDepth = 0;
347
+ effectiveIsolation = 'none';
299
348
  }
300
349
  else {
301
350
  // N1: observability warnings for 'undocumented' sentinel on dispatch fields
@@ -314,6 +363,16 @@ function negotiateHostCapabilities(host, engine = DEFAULT_ENGINE) {
314
363
  if (hostDispatch.backgroundDispatch === 'undocumented') {
315
364
  warnings.push(`dispatch.backgroundDispatch is undocumented — degraded closed`);
316
365
  }
366
+ if (hostDispatch.isolation === 'undocumented') {
367
+ warnings.push(`dispatch.isolation is undocumented — degraded closed (none)`);
368
+ }
369
+ if (hostDispatch.maxDepth === UNDOCUMENTED) {
370
+ // #2603: maxDepth was the one dispatch sub-axis with no sentinel-specific
371
+ // warning, so a descriptor carrying the documented fail-closed sentinel was
372
+ // reported as `missing or not a number` — indistinguishable from a genuinely
373
+ // malformed descriptor. Six shipped runtimes use the sentinel here.
374
+ warnings.push(`dispatch.maxDepth is undocumented — degraded closed (0)`);
375
+ }
317
376
  effectiveNamedDispatch = (hostDispatch.namedDispatch === true) && engineDispatch.namedDispatch;
318
377
  effectiveNested = (hostDispatch.nested === true) && engineDispatch.nested;
319
378
  effectiveBackground = (hostDispatch.background === true) && engineDispatch.background;
@@ -323,10 +382,22 @@ function negotiateHostCapabilities(host, engine = DEFAULT_ENGINE) {
323
382
  const hostToolkit = hostDispatch.subagentToolkit === 'full' ? 'full' : 'read-only';
324
383
  const engineToolkit = engineDispatch.subagentToolkit === 'read-only' ? 'read-only' : 'full';
325
384
  effectiveSubagentToolkit = (hostToolkit === 'read-only' || engineToolkit === 'read-only') ? 'read-only' : 'full';
326
- // maxDepth: missing/non-number/non-finite → 0 + warning
385
+ // isolation: effective = the host's declared value only if it is a known
386
+ // valid vocabulary member; otherwise 'none'. NOT host && engine gated —
387
+ // GSD owns the vocabulary, so "engine-known" == "in the valid set" (this
388
+ // still satisfies effective ⊆ host-declared ∩ engine-known).
389
+ const hostIso = hostDispatch.isolation;
390
+ effectiveIsolation = (typeof hostIso === 'string' && HOST_INTEGRATION_AXES.isolation.includes(hostIso))
391
+ ? hostIso
392
+ : 'none';
393
+ // maxDepth: missing/non-number/non-finite → 0 + warning. The documented
394
+ // 'undocumented' sentinel also degrades to 0, but is reported by the
395
+ // sentinel-specific warning above rather than as a malformed value (#2603).
327
396
  let hostMaxDepth;
328
397
  if (typeof hostDispatch.maxDepth !== 'number' || !Number.isFinite(hostDispatch.maxDepth)) {
329
- warnings.push(`host dispatch.maxDepth is missing or not a number — treating as 0`);
398
+ if (hostDispatch.maxDepth !== UNDOCUMENTED) {
399
+ warnings.push(`host dispatch.maxDepth is missing or not a number — treating as 0`);
400
+ }
330
401
  hostMaxDepth = 0;
331
402
  }
332
403
  else {
@@ -352,6 +423,7 @@ function negotiateHostCapabilities(host, engine = DEFAULT_ENGINE) {
352
423
  background: effectiveBackground,
353
424
  subagentToolkit: effectiveSubagentToolkit,
354
425
  backgroundDispatch: effectiveBackgroundDispatch,
426
+ isolation: effectiveIsolation,
355
427
  };
356
428
  // ---------------------------------------------------------------------------
357
429
  // Assemble effective axes
@@ -365,6 +437,7 @@ function negotiateHostCapabilities(host, engine = DEFAULT_ENGINE) {
365
437
  stateIO: effectiveStateIO,
366
438
  transport: effectiveTransport,
367
439
  runtime: effectiveRuntime,
440
+ effortSurface: effectiveEffortSurface,
368
441
  };
369
442
  // ---------------------------------------------------------------------------
370
443
  // Compute points (fresh objects — mutation-safe)
@@ -395,6 +468,63 @@ function shouldFlattenDispatch(dispatch) {
395
468
  return !canBackground;
396
469
  }
397
470
  // ---------------------------------------------------------------------------
471
+ // resolveDispatchType — ADR-1239 / epic #2505 Phase 4 (Option A)
472
+ //
473
+ // Maps a requested GSD subagent name (e.g. "gsd-planner") to the type an
474
+ // Agent() call should actually use on the CURRENT runtime. On runtimes whose
475
+ // descriptor declares `hostIntegration.dispatch.namedDispatch: true` (Claude,
476
+ // OpenCode, Cursor, …), the requested name is returned unchanged — those hosts
477
+ // can dispatch GSD's named subagents directly. On runtimes with
478
+ // `namedDispatch: false` (kimi-code — only three built-in subagents
479
+ // `coder`/`explore`/`plan`, per moonshotai.github.io/kimi-code/en/customization/
480
+ // agents), the name is mapped to the closest built-in by role-suffix
481
+ // heuristic. The persona rides the existing `${AGENT_SKILLS_*}` prompt
482
+ // injection (Phase 3 / #2510) regardless of the resolved type, so the
483
+ // dispatcher does not need to know the persona — only the toolkit tier.
484
+ //
485
+ // This is Option A of the Phase 4 design (per-workflow runtime detection via
486
+ // `gsd_run query resolve-dispatch-type`), not Option B (PreToolUse mutation) —
487
+ // Kimi Code's documented hook API supports only allow/deny on PreToolUse, not
488
+ // tool_input rewriting, so a hook-based remap is infeasible (see #2508).
489
+ //
490
+ // Fail-closed: unknown dispatch shape or missing namedDispatch axis ⇒ return
491
+ // the requested name unchanged (named-dispatch is the GSD default; degrading
492
+ // to it on unknown runtimes preserves behavior for every runtime already in
493
+ // the field).
494
+ // ---------------------------------------------------------------------------
495
+ // Role-suffix → built-in mapping. Order matters: the first match wins.
496
+ // `plan`-tier agents plan/design without touching files; `explore`-tier agents
497
+ // are read-only; everything else (executors, writers, fixers, debuggers) maps
498
+ // to `coder` (the general-purpose built-in with the full tool set).
499
+ const DISPATCH_TYPE_SUFFIX_MAP = Object.freeze([
500
+ [/-?(planner|roadmapper|selector|spec)$/i, 'plan'],
501
+ [/-?(researcher|mapper|checker|verifier|auditor|analyzer|synthesizer|profiler|curator|classifier|reviewer)$/i, 'explore'],
502
+ ]);
503
+ // Names that are already generic (not gsd-*) and should map to the
504
+ // general-purpose built-in on built-in-only runtimes.
505
+ const GENERIC_NAMES_TO_CODER = Object.freeze(new Set([
506
+ 'general-purpose', 'general', 'default', 'sonnet', 'opus', 'haiku',
507
+ ]));
508
+ function resolveDispatchType(requested, dispatch) {
509
+ if (typeof requested !== 'string' || requested.length === 0)
510
+ return 'coder';
511
+ // Built-in-only runtime (EXPLICIT namedDispatch: false, e.g. kimi-code):
512
+ // map to coder/explore/plan by suffix heuristic.
513
+ if (dispatch && typeof dispatch === 'object' && dispatch.namedDispatch === false) {
514
+ if (GENERIC_NAMES_TO_CODER.has(requested))
515
+ return 'coder';
516
+ for (const [pattern, builtin] of DISPATCH_TYPE_SUFFIX_MAP) {
517
+ if (pattern.test(requested))
518
+ return builtin;
519
+ }
520
+ return 'coder';
521
+ }
522
+ // Named-dispatch runtime (namedDispatch: true OR unknown/absent): use the
523
+ // requested name unchanged. Absent namedDispatch degrades to named-dispatch
524
+ // (the GSD default) so every runtime already in the field keeps working.
525
+ return requested;
526
+ }
527
+ // ---------------------------------------------------------------------------
398
528
  // Managed-hook event surface per hookEvents dialect (ADR-1239 / ADR-1016)
399
529
  // ---------------------------------------------------------------------------
400
530
  // Host-fireable MANAGED-hook events per `hookEvents` dialect. `hookEvents` is the
@@ -492,6 +622,81 @@ function extensionEventSurfaceFor(extensionEvents) {
492
622
  return null;
493
623
  return EXTENSION_EVENT_SURFACES[extensionEvents] || null;
494
624
  }
625
+ /**
626
+ * Resolve an `orchestratorExec` descriptor + target cwd (+ optional executor
627
+ * prompt) into a concrete argv/cwd shape for a process-spawn primitive.
628
+ *
629
+ * Fail-closed: never throws, always returns a discriminated result. When
630
+ * `cwdFlag` is a non-empty string, `[cwdFlag, cwd]` is appended to `args`
631
+ * exactly once (e.g. codex: `exec --cd <cwd>`); when `cwdFlag` is `null` or
632
+ * absent (e.g. kimi-code, which binds via the spawned process's own cwd —
633
+ * "process-cwd" case), no flag is appended and `cwd` is returned for the
634
+ * caller to bind via the subprocess's own working-directory option.
635
+ *
636
+ * Prompt passing (Phase 3, #2627) is descriptor data for the same reason the
637
+ * cwd flag is: the confirmed `orchestrator-worktree` hosts disagree on the
638
+ * shape. `codex exec "<prompt>"` and `opencode run "<prompt>"` take it
639
+ * positionally; `kimi --print --prompt "<p>"` and Kimi Code's `kimi -p "<p>"`
640
+ * take a flag. Encoding that as `promptFlag` keeps the scheduler free of the
641
+ * per-host branch ADR-1239 exists to remove. Omit `prompt` entirely and the
642
+ * resolution is byte-identical to Phase 2's (the unconsumed-resolver shape).
643
+ *
644
+ * Argv order is base args → cwd flag → prompt, so the prompt stays the final
645
+ * positional token for the hosts that read it that way.
646
+ */
647
+ function resolveOrchestratorExec(orchestratorExec, cwd, prompt) {
648
+ if (!orchestratorExec || typeof orchestratorExec !== 'object' || Array.isArray(orchestratorExec)) {
649
+ return { ok: false, reason: 'missing_command' };
650
+ }
651
+ const oe = orchestratorExec;
652
+ if (typeof oe.command !== 'string' || oe.command.length === 0) {
653
+ return { ok: false, reason: 'missing_command' };
654
+ }
655
+ if (typeof cwd !== 'string' || cwd.length === 0) {
656
+ return { ok: false, reason: 'invalid_cwd' };
657
+ }
658
+ if (oe.args !== undefined && (!Array.isArray(oe.args) || !oe.args.every((a) => typeof a === 'string'))) {
659
+ return { ok: false, reason: 'invalid_args' };
660
+ }
661
+ if (oe.cwdFlag !== undefined && oe.cwdFlag !== null && typeof oe.cwdFlag !== 'string') {
662
+ return { ok: false, reason: 'invalid_cwd_flag' };
663
+ }
664
+ if (oe.promptFlag !== undefined && oe.promptFlag !== null && typeof oe.promptFlag !== 'string') {
665
+ return { ok: false, reason: 'invalid_prompt_flag' };
666
+ }
667
+ // An executor spawned with no instruction is a hang, not a degraded run —
668
+ // fail closed rather than launching a prompt-less process.
669
+ if (prompt !== undefined && (typeof prompt !== 'string' || prompt.length === 0)) {
670
+ return { ok: false, reason: 'invalid_prompt' };
671
+ }
672
+ // Leading-dash guard, mirroring worktree-safety.cts's `unsafe_leading_dash`
673
+ // check on git arguments. A positional prompt (or a cwd) beginning with '-'
674
+ // is parsed by the spawned CLI as a FLAG, not a value — the same failure the
675
+ // git path already rejects, and for the same reason: `--` end-of-options
676
+ // support is inconsistent across these CLIs, so rejecting outright is the
677
+ // portable fix rather than relying on a separator. Applied to the resolver
678
+ // (not just its current caller) because this is a general descriptor->argv
679
+ // seam: a future caller must not have to rediscover the hazard.
680
+ if (typeof prompt === 'string' && prompt.startsWith('-')) {
681
+ return { ok: false, reason: 'unsafe_leading_dash_prompt' };
682
+ }
683
+ if (cwd.startsWith('-')) {
684
+ return { ok: false, reason: 'unsafe_leading_dash_cwd' };
685
+ }
686
+ const baseArgs = Array.isArray(oe.args) ? [...oe.args] : [];
687
+ const args = typeof oe.cwdFlag === 'string' && oe.cwdFlag.length > 0
688
+ ? [...baseArgs, oe.cwdFlag, cwd]
689
+ : baseArgs;
690
+ if (typeof prompt === 'string') {
691
+ if (typeof oe.promptFlag === 'string' && oe.promptFlag.length > 0) {
692
+ args.push(oe.promptFlag, prompt);
693
+ }
694
+ else {
695
+ args.push(prompt);
696
+ }
697
+ }
698
+ return { ok: true, command: oe.command, args, cwd };
699
+ }
495
700
  module.exports = {
496
701
  PROTOCOL_VERSION,
497
702
  UNDOCUMENTED,
@@ -505,6 +710,8 @@ module.exports = {
505
710
  profileOf,
506
711
  negotiateHostCapabilities,
507
712
  shouldFlattenDispatch,
713
+ resolveDispatchType,
508
714
  hookEventSurfaceFor,
509
715
  extensionEventSurfaceFor,
716
+ resolveOrchestratorExec,
510
717
  };