@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
@@ -19,6 +19,8 @@ const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs")
19
19
  // providing a deterministic failure path when git stalls (locked index, hung
20
20
  // remote, stalled NFS mount, etc.). Callers can override via deps.timeout.
21
21
  const DEFAULT_GIT_TIMEOUT_MS = 10000;
22
+ const WORKTREE_AGENT_BRANCH_RE = /^(worktree-)?agent-[A-Za-z0-9._/-]+$/;
23
+ const WORKTREE_AGENT_BRANCH_PATTERN = WORKTREE_AGENT_BRANCH_RE.source;
22
24
  /**
23
25
  * Execute a git command via the shell-projection seam, with a derived
24
26
  * `timedOut` field. Tests inject mocks via deps.execGit using the new
@@ -302,7 +304,7 @@ function normalizeCleanupManifestEntry(entry) {
302
304
  const expectedBase = typeof e.expected_base === 'string' ? e.expected_base : '';
303
305
  if (!worktreePath || !branch || !expectedBase)
304
306
  return null;
305
- if (!/^worktree-agent-[A-Za-z0-9._/-]+$/.test(branch))
307
+ if (!WORKTREE_AGENT_BRANCH_RE.test(branch))
306
308
  return null;
307
309
  const rawAllowedBases = Array.isArray(e.allowed_bases) ? e.allowed_bases : [];
308
310
  const allowedBases = Array.from(new Set([expectedBase, ...rawAllowedBases.filter((base) => typeof base === 'string' && base.length > 0)]));
@@ -449,20 +451,18 @@ function rescueSummaryArtifacts(worktreePath, repoRoot, deps) {
449
451
  // the executor's content could be lost. cat-file -e HEAD:<path> returns
450
452
  // exit 0 only when the object exists in the committed HEAD tree.
451
453
  //
452
- // Fail-closed on timeout/fatal git errors: if we cannot determine whether
453
- // the file is committed, do NOT rescue it (rescuing an actually-committed
454
- // file would re-create the untracked collision; the merge will surface the
455
- // issue). The cleanup will be blocked by merge_failed in the worst case,
456
- // which is the observable behaviour before this fix and is recoverable.
454
+ // #2556: rescue whenever the object is NOT confirmed committed (any non-zero
455
+ // exit). `git cat-file -e` returns 128 — NOT 1 — for an absent path (the
456
+ // normal uncommitted-SUMMARY state), so the previous `!== 1` check never
457
+ // rescued and the untracked file was silently discarded by `worktree remove
458
+ // --force`. Data safety wins: an un-rescued untracked SUMMARY is lost, while
459
+ // a spurious rescue is usually a no-op — the destination check below skips the
460
+ // copy when the main tree already holds identical content (which is also what
461
+ // guards the #706 merge collision). A divergent dest is overwritten, but only
462
+ // uncommitted main-tree content could be lost (committed content is git-recoverable).
457
463
  const catFileResult = execGit(['-C', worktreePath, 'cat-file', '-e', `HEAD:${relPath}`], { cwd: repoRoot });
458
- if (catFileResult.exitCode !== 1) {
459
- // Rescue only when cat-file definitively reports the object is absent (exit 1).
460
- // exit 0 → object exists (committed on HEAD) — merge will carry it, skip.
461
- // exit 128 → fatal git error (corrupt store, unborn HEAD, etc.) — uncertain,
462
- // fail-closed: do NOT rescue to avoid recreating the #706 collision.
463
- // timedOut / null / other → unreliable result — same fail-closed policy.
464
- // In all non-1 cases the merge will either succeed naturally (0) or surface
465
- // the problem safely (128/timeout), which is the recoverable pre-fix behaviour.
464
+ if (catFileResult.exitCode === 0) {
465
+ // exit 0 → the SUMMARY is committed on HEAD; the merge will carry it, so skip rescue.
466
466
  continue;
467
467
  }
468
468
  const dest = node_path_1.default.join(repoRoot, relPath);
@@ -751,7 +751,7 @@ function planWorktreeRecordAgent(manifestRaw, fields) {
751
751
  return {
752
752
  ok: false,
753
753
  reason: 'invalid_entry',
754
- hint: `Entry failed cleanup-manifest validation: --path/--branch/--base must be non-empty and --branch must match ^worktree-agent-[A-Za-z0-9._/-]+$ (got branch="${branch}"). Fix the field and re-run.`,
754
+ hint: `Entry failed cleanup-manifest validation: --path/--branch/--base must be non-empty and --branch must match ${WORKTREE_AGENT_BRANCH_PATTERN} (accepts both agent-<id> and worktree-agent-<id> namespaces; got branch="${branch}"). Fix the field and re-run.`,
755
755
  entry: null,
756
756
  manifest: null,
757
757
  };
@@ -893,6 +893,348 @@ function cmdWorktreeRecordAgent(cwd, args = [], deps = {}) {
893
893
  write(`${JSON.stringify({ ok: true, reason: 'ok', entry: plan.entry, manifest_path: resolved }, null, 2)}\n`);
894
894
  return { ok: true, reason: 'ok', entry: plan.entry, manifest_path: resolved };
895
895
  }
896
+ /**
897
+ * Pure planner for `worktree create`. Validates the four required fields
898
+ * (write-strict, same missing-field-hint style as `planWorktreeRecordAgent`),
899
+ * then runs the candidate entry through the SAME `normalizeCleanupManifestEntry`
900
+ * validation the cleanup-wave reader and record-agent use — so a worktree this
901
+ * verb creates is guaranteed manageable by cleanup-wave/reap-orphans, and an
902
+ * entry that would fail the reader's branch-namespace guard is rejected here,
903
+ * fail-closed, before any git command runs.
904
+ */
905
+ function planWorktreeCreate(fields) {
906
+ const agentId = (fields.agentId || '').trim();
907
+ const worktreePath = (fields.worktreePath || '').trim();
908
+ const branch = (fields.branch || '').trim();
909
+ const base = (fields.base || '').trim();
910
+ const missing = [];
911
+ if (!agentId)
912
+ missing.push('--agent-id');
913
+ if (!worktreePath)
914
+ missing.push('--path');
915
+ if (!branch)
916
+ missing.push('--branch');
917
+ if (!base)
918
+ missing.push('--base');
919
+ if (missing.length > 0) {
920
+ return {
921
+ ok: false,
922
+ reason: 'missing_field',
923
+ hint: `worktree create requires ${missing.join(', ')}. Re-run with all of --agent-id, --path, --branch, --base set to non-empty (non-whitespace) values.`,
924
+ entry: null,
925
+ };
926
+ }
927
+ const candidate = {
928
+ agent_id: agentId,
929
+ worktree_path: worktreePath,
930
+ branch,
931
+ expected_base: base,
932
+ };
933
+ const entry = normalizeCleanupManifestEntry(candidate);
934
+ if (!entry) {
935
+ return {
936
+ ok: false,
937
+ reason: 'invalid_entry',
938
+ hint: `Entry failed cleanup-manifest validation: --path/--branch/--base must be non-empty and --branch must match ${WORKTREE_AGENT_BRANCH_PATTERN} (accepts both agent-<id> and worktree-agent-<id> namespaces; got branch="${branch}"). Fix the field and re-run.`,
939
+ entry: null,
940
+ };
941
+ }
942
+ // #2584 FIX 4 — git argument-injection guard: a value starting with '-'
943
+ // could be parsed by git as a FLAG rather than a positional argument (e.g.
944
+ // base="--upload-pack=x", path="-f"). `git worktree add` / `git rev-parse`
945
+ // support for a `--` end-of-options separator is inconsistent across git
946
+ // versions, so rejecting a leading dash outright — not relying on `--` — is
947
+ // the portable fix.
948
+ if (branch.startsWith('-') || base.startsWith('-') || worktreePath.startsWith('-')) {
949
+ return {
950
+ ok: false,
951
+ reason: 'unsafe_leading_dash',
952
+ hint: `--branch/--base/--path must not start with "-" (a leading dash would be parsed by git as a flag, not a value). Got branch="${branch}" base="${base}" path="${worktreePath}".`,
953
+ entry: null,
954
+ };
955
+ }
956
+ // #2584 FIX 4 — path-traversal guard: reject a ".." path segment in --path.
957
+ // Absolute paths ARE allowed (the orchestrator legitimately uses them —
958
+ // Phase-3 root confinement is out of Phase-2 scope); only a literal ".."
959
+ // component is rejected. Split on BOTH separators so the guard is effective
960
+ // on a Windows-style path too.
961
+ if (worktreePath.split(/[/\\]/).includes('..')) {
962
+ return {
963
+ ok: false,
964
+ reason: 'unsafe_path_traversal',
965
+ hint: `--path must not contain a ".." path segment (got: "${worktreePath}").`,
966
+ entry: null,
967
+ };
968
+ }
969
+ return { ok: true, reason: 'ok', entry };
970
+ }
971
+ /**
972
+ * Best-effort bounded rollback of a partial/orphaned worktree (#2584 FIX 3,
973
+ * scope narrowed by FIX 5). Invoked ONLY when a `git worktree add` TIMED OUT
974
+ * mid-operation — a SIGTERM'd `add` can leave a `.git/worktrees/<name>` admin
975
+ * entry / directory on disk that got past validation into the file checkout,
976
+ * so the partial is genuinely THIS call's own creation and is safe to
977
+ * best-effort remove immediately. It is deliberately NOT invoked on a clean
978
+ * non-zero `add` exit (see FIX 5) — the most common such failure is a
979
+ * COLLISION (the path/branch is already a registered worktree), git fails
980
+ * FAST there having created nothing, and the branch namespace this verb
981
+ * writes into (`worktree-agent-*`/`agent-*`) is exactly the concurrent-
982
+ * executor namespace, so a colliding path is very plausibly a LIVE PEER
983
+ * executor whose uncommitted work `--force` would destroy. Also invoked from
984
+ * `cmdWorktreeCreate` when a successful `add` is followed by a manifest-write
985
+ * failure (#2584 FIX 1) — that path proves THIS call created the worktree, so
986
+ * removing it is safe. This is immediate best-effort hygiene, not the only
987
+ * safety net: `reapOrphanWorktrees` scans the `.git/worktrees/` admin
988
+ * directory directly (a genuine directory-scan backstop, not manifest-only),
989
+ * so any partial this call cannot reach is still eventually discovered and
990
+ * reaped there. The result is intentionally ignored and a throw is
991
+ * swallowed: this is best-effort cleanup, never a new source of truth, and
992
+ * must never mask or block the caller's own degraded-but-honest return.
993
+ */
994
+ function rollbackPartialWorktree(execGit, worktreePath, repoRoot) {
995
+ try {
996
+ execGit(['worktree', 'remove', '--force', worktreePath], { cwd: repoRoot });
997
+ }
998
+ catch {
999
+ // best-effort only — a throwing rollback must never mask the original failure.
1000
+ }
1001
+ }
1002
+ /**
1003
+ * Execute a `planWorktreeCreate` plan via bounded git. Fail-closed at every
1004
+ * step — a timeout or a non-zero exit degrades to a structured result rather
1005
+ * than throwing, and the base must resolve BEFORE any worktree is created (no
1006
+ * partial/orphaned worktree on a bad base). Returns `cwd` — the working
1007
+ * directory Phase 3's executor spawn will pass through.
1008
+ */
1009
+ function executeWorktreeCreatePlan(plan, repoRoot, deps = {}) {
1010
+ const execGit = deps.execGit || execGitDefault;
1011
+ if (!plan || !plan.ok || !plan.entry) {
1012
+ return {
1013
+ ok: false,
1014
+ reason: plan ? plan.reason : 'missing_plan',
1015
+ };
1016
+ }
1017
+ const { worktree_path: worktreePath, branch, expected_base: base } = plan.entry;
1018
+ const normalizedPath = (0, shell_command_projection_cjs_1.posixNormalize)(worktreePath);
1019
+ // 1. Verify the base resolves BEFORE creating anything (fail-closed). Nothing
1020
+ // has been created on this path yet, so there is nothing to roll back.
1021
+ const baseCheck = execGit(['rev-parse', '--verify', '--quiet', `${base}^{commit}`], { cwd: repoRoot });
1022
+ if (baseCheck.timedOut) {
1023
+ return { ok: false, reason: 'git_timeout', worktree_path: normalizedPath, branch, base };
1024
+ }
1025
+ if (!gitResultOk(baseCheck)) {
1026
+ return { ok: false, reason: 'base_unresolved', worktree_path: normalizedPath, branch, base, stderr: baseCheck.stderr || '' };
1027
+ }
1028
+ // 2. Create the worktree + branch together.
1029
+ const addResult = execGit(['worktree', 'add', '-b', branch, worktreePath, base], { cwd: repoRoot });
1030
+ if (addResult.timedOut) {
1031
+ // #2584 FIX 3: a SIGTERM'd `add` can leave a partial worktree on disk.
1032
+ rollbackPartialWorktree(execGit, worktreePath, repoRoot);
1033
+ return { ok: false, reason: 'git_timeout', worktree_path: normalizedPath, branch, base };
1034
+ }
1035
+ if (addResult.exitCode !== 0) {
1036
+ // #2584 FIX 5: deliberately NO rollback here. A clean non-zero exit is
1037
+ // most commonly a COLLISION (path/branch already a registered worktree),
1038
+ // and git fails FAST on that — it creates nothing. A colliding path in
1039
+ // this branch namespace is very plausibly a LIVE PEER executor;
1040
+ // `git worktree remove --force` on it would destroy real, uncommitted
1041
+ // work. The safe response to a clean failure is to fail loudly and leave
1042
+ // whatever is already on disk untouched.
1043
+ return { ok: false, reason: 'worktree_add_failed', worktree_path: normalizedPath, branch, base, stderr: addResult.stderr || '' };
1044
+ }
1045
+ return {
1046
+ ok: true,
1047
+ reason: 'created',
1048
+ worktree_path: normalizedPath,
1049
+ branch,
1050
+ base,
1051
+ cwd: normalizedPath,
1052
+ };
1053
+ }
1054
+ /**
1055
+ * CLI command: create a git worktree + branch off `--base`, then append the
1056
+ * validated manifest entry so the worktree is immediately manageable by
1057
+ * `worktree cleanup-wave` / `worktree reap-orphans`.
1058
+ *
1059
+ * Usage: worktree create --manifest <path> --agent-id <id> --path <worktree> --branch <branch> --base <sha>
1060
+ *
1061
+ * #2584 FIX 1 — ORDERING CONTRACT: every manifest read/parse/shape-validate/
1062
+ * plan step runs BEFORE the git side effect (step 5). The ONLY manifest
1063
+ * operation that can run AFTER `git worktree add` has succeeded is the final
1064
+ * guarded write (step 6), and a failure there triggers a best-effort rollback
1065
+ * of the just-created worktree — so a malformed/mis-shaped manifest, or a
1066
+ * `writeFile` IO error, can never leave a REAL worktree on disk with no
1067
+ * manifest entry (cleanup-wave/reap-orphans only discover worktrees via the
1068
+ * manifest, never a directory scan) or an uncaught throw.
1069
+ */
1070
+ function cmdWorktreeCreate(cwd, args = [], deps = {}) {
1071
+ const flag = (name) => {
1072
+ const i = args.indexOf(name);
1073
+ return i >= 0 && i + 1 < args.length ? args[i + 1] : '';
1074
+ };
1075
+ const write = deps.write || ((s) => process.stdout.write(s));
1076
+ const writeErr = deps.writeErr || ((s) => process.stderr.write(s));
1077
+ const manifestPath = flag('--manifest');
1078
+ if (!manifestPath) {
1079
+ writeErr('Usage: worktree create --manifest <path> --agent-id <id> --path <worktree> --branch <branch> --base <sha> [--root <dir>]\n');
1080
+ process.exitCode = 2;
1081
+ return { ok: false, reason: 'usage' };
1082
+ }
1083
+ // 1. Read the manifest (no side effect yet).
1084
+ const resolved = node_path_1.default.resolve(cwd, manifestPath);
1085
+ const readFile = deps.readFile || ((p) => node_fs_1.default.readFileSync(p, 'utf8'));
1086
+ let manifestRaw;
1087
+ try {
1088
+ manifestRaw = readFile(resolved);
1089
+ }
1090
+ catch (err) {
1091
+ const hint = `Manifest not found or unreadable at ${manifestPath}. The orchestrator must initialize it ({"orchestrator_root": "...", "worktrees": []}) before creating agent worktrees.`;
1092
+ writeErr(`[gsd] worktree.create: manifest_read_failed — ${hint}\n`);
1093
+ write(`${JSON.stringify({ ok: false, reason: 'manifest_read_failed', hint, error: err.message }, null, 2)}\n`);
1094
+ process.exitCode = 1;
1095
+ return { ok: false, reason: 'manifest_read_failed', hint };
1096
+ }
1097
+ // 2. Parse + shape-validate the manifest BEFORE any git command runs.
1098
+ // Mirrors planWorktreeRecordAgent's shell-acceptance rules (canonical
1099
+ // {worktrees:[]} object OR a bare top-level array).
1100
+ let parsed;
1101
+ try {
1102
+ parsed = JSON.parse(manifestRaw);
1103
+ }
1104
+ catch {
1105
+ const hint = 'Manifest is not valid JSON. The orchestrator must initialize it as {"orchestrator_root": "...", "worktrees": []} before creating agent worktrees.';
1106
+ writeErr(`[gsd] worktree.create: invalid_manifest_json — ${hint}\n`);
1107
+ write(`${JSON.stringify({ ok: false, reason: 'invalid_manifest_json', hint }, null, 2)}\n`);
1108
+ process.exitCode = 1;
1109
+ return { ok: false, reason: 'invalid_manifest_json', hint };
1110
+ }
1111
+ let worktrees;
1112
+ let writeBack;
1113
+ if (Array.isArray(parsed)) {
1114
+ worktrees = parsed;
1115
+ writeBack = worktrees;
1116
+ }
1117
+ else if (parsed && typeof parsed === 'object') {
1118
+ const container = parsed;
1119
+ if (container.worktrees === undefined)
1120
+ container.worktrees = [];
1121
+ if (!Array.isArray(container.worktrees)) {
1122
+ const hint = 'Manifest "worktrees" must be an array. Re-initialize as {"orchestrator_root": "...", "worktrees": []}.';
1123
+ writeErr(`[gsd] worktree.create: manifest_shape_invalid — ${hint}\n`);
1124
+ write(`${JSON.stringify({ ok: false, reason: 'manifest_shape_invalid', hint }, null, 2)}\n`);
1125
+ process.exitCode = 1;
1126
+ return { ok: false, reason: 'manifest_shape_invalid', hint };
1127
+ }
1128
+ worktrees = container.worktrees;
1129
+ writeBack = container;
1130
+ }
1131
+ else {
1132
+ const hint = 'Manifest must be a JSON object {"worktrees": []} or a top-level array.';
1133
+ writeErr(`[gsd] worktree.create: manifest_shape_invalid — ${hint}\n`);
1134
+ write(`${JSON.stringify({ ok: false, reason: 'manifest_shape_invalid', hint }, null, 2)}\n`);
1135
+ process.exitCode = 1;
1136
+ return { ok: false, reason: 'manifest_shape_invalid', hint };
1137
+ }
1138
+ // 3. Plan (pure, no I/O) — still before any git command.
1139
+ const plan = planWorktreeCreate({
1140
+ agentId: flag('--agent-id'),
1141
+ worktreePath: flag('--path'),
1142
+ branch: flag('--branch'),
1143
+ base: flag('--base'),
1144
+ });
1145
+ if (!plan.ok || !plan.entry) {
1146
+ writeErr(`[gsd] worktree.create: ${plan.reason} — ${plan.hint || ''}\n`);
1147
+ write(`${JSON.stringify({ ok: false, reason: plan.reason, hint: plan.hint }, null, 2)}\n`);
1148
+ process.exitCode = 1;
1149
+ return { ok: false, reason: plan.reason, hint: plan.hint };
1150
+ }
1151
+ // 3b. Optional root confinement (#2627, Phase 3 — the confinement Phase 2
1152
+ // deferred here from planWorktreeCreate's path-traversal guard).
1153
+ // planWorktreeCreate rejects a literal ".." SEGMENT, but a plain absolute
1154
+ // path outside the project contains no ".." and passes. Phase 3 makes the
1155
+ // orchestrator SPAWN executor processes into these paths, so an
1156
+ // unconfined --path is a write primitive aimed anywhere on the filesystem.
1157
+ //
1158
+ // The root is DECLARED by the caller (`--root`) rather than inferred: agent
1159
+ // worktrees legitimately live outside the orchestrator's own root (a lane
1160
+ // orchestrator creates siblings under the repo's .claude/worktrees/), so
1161
+ // there is no layout this module could derive without guessing. Absent
1162
+ // `--root` the behavior is exactly as shipped in Phase 2 — the
1163
+ // orchestrator-worktree scheduler path always passes it.
1164
+ //
1165
+ // Lexical by design: the worktree does not exist yet, so there is nothing
1166
+ // to realpath, and resolving only the root would not close a symlinked-leaf
1167
+ // hole. Pairs with the leading-dash and ".."-segment guards above.
1168
+ const rootFlag = flag('--root');
1169
+ if (rootFlag) {
1170
+ const absRoot = node_path_1.default.resolve(cwd, rootFlag);
1171
+ const absWorktree = node_path_1.default.resolve(cwd, plan.entry.worktree_path);
1172
+ const rel = node_path_1.default.relative(absRoot, absWorktree);
1173
+ // rel === '' → the worktree IS the root (would clobber the checkout)
1174
+ // rel === '..' / '../…' → escapes the root
1175
+ // path.isAbsolute(rel) → a different Windows drive or UNC root
1176
+ if (rel === '' || rel === '..' || rel.startsWith(`..${node_path_1.default.sep}`) || node_path_1.default.isAbsolute(rel)) {
1177
+ const hint = `--path must resolve INSIDE --root (root="${absRoot}", path="${absWorktree}"). A worktree outside the declared root is unreachable by manifest-scoped cleanup and would let a spawned executor write outside the project.`;
1178
+ writeErr(`[gsd] worktree.create: path_outside_root — ${hint}\n`);
1179
+ write(`${JSON.stringify({ ok: false, reason: 'path_outside_root', hint }, null, 2)}\n`);
1180
+ process.exitCode = 1;
1181
+ return { ok: false, reason: 'path_outside_root', hint };
1182
+ }
1183
+ }
1184
+ // 4. Compute the deduped final manifest STRING in memory now — the ONLY
1185
+ // manifest work left is the guarded write in step 6, after git succeeds.
1186
+ // #2584 FIX 2: the on-disk entry is the SAME minimal 4-field shape
1187
+ // `cmdWorktreeRecordAgent` writes — never the full normalized entry with
1188
+ // the derived `allowed_bases` — so the two verbs never write divergent
1189
+ // shapes into the same manifest (the reader re-derives `allowed_bases`
1190
+ // from `expected_base` on load, exactly as it does for record-agent).
1191
+ const recorded = {
1192
+ agent_id: plan.entry.agent_id,
1193
+ worktree_path: plan.entry.worktree_path,
1194
+ branch: plan.entry.branch,
1195
+ expected_base: plan.entry.expected_base,
1196
+ };
1197
+ const dedupeKey = `${recorded.worktree_path}\0${recorded.branch}`;
1198
+ const alreadyPresent = worktrees.some((existing) => {
1199
+ const normalized = normalizeCleanupManifestEntry(existing);
1200
+ return normalized !== null && `${normalized.worktree_path}\0${normalized.branch}` === dedupeKey;
1201
+ });
1202
+ if (!alreadyPresent) {
1203
+ worktrees.push(recorded);
1204
+ }
1205
+ const manifestToWrite = `${JSON.stringify(writeBack, null, 2)}\n`;
1206
+ // 5. NOW run the git side effect. Every manifest problem above is caught
1207
+ // before this point, so a malformed/mis-shaped manifest can never leave
1208
+ // an unmanifested worktree on disk. `executeWorktreeCreatePlan` itself
1209
+ // best-effort rolls back a partial worktree on its own add-timeout /
1210
+ // add-failed paths (#2584 FIX 3).
1211
+ const result = executeWorktreeCreatePlan(plan, cwd, deps);
1212
+ if (!result.ok) {
1213
+ writeErr(`[gsd] worktree.create: ${result.reason} — ${result.stderr || ''}\n`);
1214
+ write(`${JSON.stringify({ ok: false, reason: result.reason, stderr: result.stderr }, null, 2)}\n`);
1215
+ process.exitCode = 1;
1216
+ return { ok: false, reason: result.reason, stderr: result.stderr };
1217
+ }
1218
+ // 6. Write the pre-computed manifest string, guarded. A write failure here
1219
+ // means a REAL worktree now exists with NO manifest entry — roll it back
1220
+ // (best-effort) rather than leaving an orphan cleanup-wave/reap-orphans
1221
+ // can never reach (#2584 FIX 1). Never throws past this function.
1222
+ const writeFile = deps.writeFile || ((p, content) => node_fs_1.default.writeFileSync(p, content, 'utf8'));
1223
+ try {
1224
+ writeFile(resolved, manifestToWrite);
1225
+ }
1226
+ catch (err) {
1227
+ const execGit = deps.execGit || execGitDefault;
1228
+ rollbackPartialWorktree(execGit, plan.entry.worktree_path, cwd);
1229
+ const hint = `The worktree was created but the manifest write failed (${err.message}); rolled back the worktree via a best-effort 'git worktree remove --force'.`;
1230
+ writeErr(`[gsd] worktree.create: manifest_write_failed — ${hint}\n`);
1231
+ write(`${JSON.stringify({ ok: false, reason: 'manifest_write_failed', hint, error: err.message }, null, 2)}\n`);
1232
+ process.exitCode = 1;
1233
+ return { ok: false, reason: 'manifest_write_failed', hint };
1234
+ }
1235
+ write(`${JSON.stringify({ ok: true, reason: 'created', entry: recorded, cwd: result.cwd, manifest_path: resolved }, null, 2)}\n`);
1236
+ return { ok: true, reason: 'created', entry: recorded, cwd: result.cwd, manifest_path: resolved };
1237
+ }
896
1238
  /**
897
1239
  * Reap orphaned linked worktrees whose lock owner process is dead, whose
898
1240
  * branch tip is fully merged into the default branch, and whose lock file
@@ -1180,6 +1522,9 @@ module.exports = {
1180
1522
  cmdWorktreeCleanupWave,
1181
1523
  planWorktreeRecordAgent,
1182
1524
  cmdWorktreeRecordAgent,
1525
+ planWorktreeCreate,
1526
+ executeWorktreeCreatePlan,
1527
+ cmdWorktreeCreate,
1183
1528
  reapOrphanWorktrees,
1184
1529
  cmdWorktreeReapOrphans,
1185
1530
  resolveWorktreeRoot,
@@ -33,6 +33,7 @@
33
33
  "_auto_chain_active": false,
34
34
  "node_repair": true,
35
35
  "node_repair_budget": 2,
36
+ "smart_zone_tokens": 100000,
36
37
  "ui_phase": true,
37
38
  "ui_safety_gate": true,
38
39
  "text_mode": false,
@@ -19,6 +19,7 @@
19
19
  "workflow.auto_advance",
20
20
  "workflow.node_repair",
21
21
  "workflow.node_repair_budget",
22
+ "workflow.smart_zone_tokens",
22
23
  "workflow.human_verify_mode",
23
24
  "workflow.text_mode",
24
25
  "workflow.research_before_questions",
@@ -48,9 +49,6 @@
48
49
  "planning.commit_docs",
49
50
  "planning.search_gitignored",
50
51
  "planning.sub_repos",
51
- "review.ollama_host",
52
- "review.lm_studio_host",
53
- "review.llama_cpp_host",
54
52
  "review.default_reviewers",
55
53
  "review.max_prompt_tokens",
56
54
  "review.max_prompt_tokens_per_reviewer",
@@ -72,6 +70,7 @@
72
70
  "statusline.show_last_command",
73
71
  "statusline.context_position",
74
72
  "statusline.show_context_tokens",
73
+ "statusline.state_format",
75
74
  "statusline.show_git",
76
75
  "workflow.max_discuss_passes",
77
76
  "features.thinking_partner",
@@ -116,11 +115,6 @@
116
115
  "source": "^agent_skills\\.[a-zA-Z0-9_-]+$",
117
116
  "description": "agent_skills.<agent-type>"
118
117
  },
119
- {
120
- "topLevel": "review",
121
- "source": "^review\\.models\\.[a-zA-Z0-9_-]+$",
122
- "description": "review.models.<cli-name>"
123
- },
124
118
  {
125
119
  "topLevel": "features",
126
120
  "source": "^features\\.[a-zA-Z0-9_]+$",
@@ -148,8 +142,8 @@
148
142
  },
149
143
  {
150
144
  "topLevel": "dynamic_routing",
151
- "source": "^dynamic_routing\\.(enabled|escalate_on_failure|max_escalations|tier_models\\.(light|standard|heavy))$",
152
- "description": "dynamic_routing.<enabled|escalate_on_failure|max_escalations|tier_models.<light|standard|heavy>>"
145
+ "source": "^dynamic_routing\\.(enabled|escalate_on_failure|max_escalations|provider_escalation|tier_models\\.(light|standard|heavy))$",
146
+ "description": "dynamic_routing.<enabled|escalate_on_failure|max_escalations|provider_escalation|tier_models.<light|standard|heavy>>"
153
147
  },
154
148
  {
155
149
  "topLevel": "model_overrides",
@@ -176,11 +170,6 @@
176
170
  "source": "^fast_mode\\.agent_overrides\\.[a-zA-Z0-9_-]+$",
177
171
  "description": "fast_mode.agent_overrides.<agent-id>"
178
172
  },
179
- {
180
- "topLevel": "review",
181
- "source": "^review\\.max_prompt_tokens_per_reviewer\\.[a-zA-Z0-9_-]+$",
182
- "description": "review.max_prompt_tokens_per_reviewer.<reviewer-slug>"
183
- },
184
173
  {
185
174
  "topLevel": "review",
186
175
  "source": "^review\\.reviewer_instances\\.[a-zA-Z0-9_-]+\\.(cli|model|agent)$",
@@ -52,6 +52,11 @@
52
52
  "sonnet": null,
53
53
  "haiku": null
54
54
  },
55
+ "kimi-code": {
56
+ "opus": null,
57
+ "sonnet": null,
58
+ "haiku": null
59
+ },
55
60
  "cursor": {
56
61
  "opus": null,
57
62
  "sonnet": null,
@@ -63,6 +63,11 @@
63
63
  "kimi": [
64
64
  "kimi"
65
65
  ],
66
+ "kimi-code": [
67
+ "kimi-code",
68
+ "kimicode",
69
+ "kimi_code"
70
+ ],
66
71
  "codebuddy": [
67
72
  "codebuddy",
68
73
  "codebuddy-cli"
@@ -24,13 +24,26 @@ treated as an external-API integration when **either**:
24
24
  1. a `COVERAGE.md` matrix is present in the phase directory (the planner produced
25
25
  one at `plan:pre`), **or**
26
26
  2. the phase scope shows a strong external-API-integration signal (an integration
27
- verb co-occurring with an external-API noun, or an explicit `<Service>
28
- API|SDK|REST|GraphQL` surface) and no matrix yet exists.
29
-
30
- Non-API phases (refactors, bug fixes, internal-only work, features that merely
31
- *mention* an existing internal API) do **not** fire the gate — the trigger
32
- requires a compound signal, so a bare word like "api" in "the public API of
33
- UserController" is intentionally ignored.
27
+ verb and an external-API noun **in the same clause**, or an explicit
28
+ `<Service> API|SDK|REST|GraphQL` surface naming a real service) and no matrix
29
+ yet exists.
30
+
31
+ The detector is deliberately **fail-closed**: it leans toward firing, because a
32
+ false positive is dismissed by a one-line `COVERAGE.md` "no external API
33
+ integration" declaration, whereas a false *negative* silently lets a real
34
+ external-API phase past this blocking gate — strictly worse. So it suppresses
35
+ only prose that is unambiguously not external integration. A bare word like
36
+ "api" in "the public API of UserController" is ignored (no integration verb +
37
+ named service); the clause boundary is the whole relationship test, so an
38
+ integration verb and an API noun in **different** clauses do not pair. Since
39
+ #2365 the detector also excludes non-prose spans before matching: fenced code
40
+ blocks, inline `` `code` `` spans, and path-shaped tokens (a first-party
41
+ `src/app/api/profile/route.ts` route is a file path, not an external API, while
42
+ an external host like `api.stripe.com/v1` still counts). In the
43
+ `<Service> API` surface position it rejects capitalized sentence starters
44
+ ("The API"), locality/protocol descriptors ("Internal API", "REST API"),
45
+ compound modifiers ("Resolver-only API"), and first-party-qualified services
46
+ ("internal Payments API") — a real vendor name is none of these.
34
47
 
35
48
  ## The two touch points
36
49
 
@@ -70,6 +83,23 @@ must be non-empty and unique; every decision must be `INTEGRATE` or `OPT-OUT`;
70
83
  every `OPT-OUT` must have a reason. Violations block the seal with a precise
71
84
  error.
72
85
 
86
+ ### Declaring "no external API integration" (#2365)
87
+
88
+ A phase that integrates no external API/SDK/service — but was still asked for a
89
+ matrix (e.g. the detector over-fired, or a team wants the decision on record) —
90
+ declares it instead of fabricating a row:
91
+
92
+ ```markdown
93
+ No external API integration: UI-only phase, no third-party surface.
94
+ ```
95
+
96
+ The reason is **required**, exactly like an `OPT-OUT` reason — the declaration
97
+ is a reasoned decision, not a bypass. A `COVERAGE.md` containing both the
98
+ declaration and coverage rows is contradictory and blocks the seal. When the
99
+ detector still finds integration signals in the phase scope, the declaration
100
+ wins (it is the human overrule for a fallible detector) but the gate output
101
+ surfaces the overridden signals so the contradiction is visible, not silent.
102
+
73
103
  ## A second integration against the same need
74
104
 
75
105
  A second platform for an existing capability (e.g. adding YouTube alongside
@@ -474,7 +474,7 @@ npm run dev &
474
474
  DEV_SERVER_PID=$!
475
475
 
476
476
  # Wait for ready (max 30s) — uses fetch() for cross-platform compatibility
477
- timeout 30 bash -c 'until node -e "fetch(\"http://localhost:3000\").then(r=>{process.exit(r.ok?0:1)}).catch(()=>process.exit(1))" 2>/dev/null; do sleep 1; done'
477
+ gsd_run run-with-timeout 30 -- bash -c 'until node -e "fetch(\"http://localhost:3000\").then(r=>{process.exit(r.ok?0:1)}).catch(()=>process.exit(1))" 2>/dev/null; do sleep 1; done'
478
478
  ```
479
479
 
480
480
  **Port conflicts:** Kill stale process (`lsof -ti:3000 | xargs kill`) or use alternate port (`--port 3001`).
@@ -97,6 +97,19 @@ Checklist of frequent bug patterns to scan before forming hypotheses. Ordered by
97
97
  3. **Each checked pattern is a hypothesis candidate** — verify or eliminate with evidence
98
98
  4. **If no pattern matches**, proceed to open-ended investigation
99
99
 
100
+ ### Pattern categories → bug taxonomy (Phase 1.75)
101
+
102
+ The categories here feed bug-class classification (see `debugger-bug-taxonomy.md`):
103
+
104
+ | Pattern category | Typical bug_class |
105
+ |---|---|
106
+ | Null / Undefined, Off-by-One, State, Import, Type, Regex, Error Handling, Scope | Bohrbug (deterministic) |
107
+ | Async / Timing (intermittent, leaked timer, init order) | Heisenbug / Concurrency |
108
+ | Environment / Config (works-here-not-there) | Heisenbug / Mandelbug (or config-as-root-cause) |
109
+ | Data Shape / API Contract | Bohrbug (or Mandelbug if volume-dependent) |
110
+
111
+ The taxonomy routes the investigation technique (SBFL + bisect for Bohrbugs; record-replay/stability for Heisenbugs; atomicity/order/deadlock checklist for Concurrency).
112
+
100
113
  ### Symptom-to-Category Quick Map
101
114
 
102
115
  | Symptom | Check First |
@@ -83,3 +83,43 @@ Either list works — `enabledMcpjsonServers` is an explicit allow-list, `disabl
83
83
  ### Composition with model_profile
84
84
 
85
85
  Trimming MCPs and tuning `model_profile` are independent levers that **compound**. Disabling a 25k-token MCP saves 25k per turn whether you're running `quality` (opus everywhere) or `budget` (sonnet/haiku); the savings are additive, not in lieu of model tuning. Don't pick one — do both, and audit MCPs first because the per-turn savings show up immediately and stack across every subagent the orchestrator spawns.
86
+
87
+ ---
88
+
89
+ # Phase Sizing (gsd-planner)
90
+
91
+ ## Estimate Emission (#2631, ADR-2629)
92
+
93
+ Every plan carries an `estimate` block. It is the quantitative reason a phase must be sliced — tracer-first says *slice thin*, the estimate says *how thin, for this codebase*.
94
+
95
+ **Compute it:**
96
+ 1. Sum `estimateTokens`-scale cost across the plan: implementation + the files each task reads + verification output. Roughly chars/4 over what the executor will actually touch.
97
+ 2. Run `estimate-calibration` and **multiply your raw figure by its `factor`.** It is the measured estimate-vs-actual ratio for THIS project — a factor of 1 means there is not yet enough history to correct.
98
+ 3. `confidence` is **derived, not judged**: it is the `confidence` value from the same calibration query, keyed to the sample count (`low` <3, `med` 3–5, `high` ≥6). **Do not rate your own certainty.** Self-rated confidence was measured in this project and found weak (`references/honest-verifier.md:25-29`); every signal here routes on measured history instead.
99
+
100
+ **Over budget?** The plan-checker flags a plan whose estimate exceeds `workflow.smart_zone_tokens`. This is advisory — it never blocks. When flagged, re-slice: a tracer plus expansion slices, each inside the budget. Prefer more, smaller plans over one that spends the agent's best early-context tokens and finishes degraded.
101
+
102
+ ## Context Budget Rules
103
+
104
+ Plans should complete within ~50% context (not 80%). No context anxiety, quality maintained start to finish, room for unexpected complexity.
105
+
106
+ **Each plan: 2-3 tasks maximum.**
107
+
108
+ | Context Weight | Tasks/Plan | Context/Task | Total |
109
+ |----------------|------------|--------------|-------|
110
+ | Light (CRUD, config) | 3 | ~10-15% | ~30-45% |
111
+ | Medium (auth, payments) | 2 | ~20-30% | ~40-50% |
112
+ | Heavy (migrations, multi-subsystem) | 1-2 | ~30-40% | ~30-50% |
113
+
114
+ ## Split Signals
115
+
116
+ **ALWAYS split if:**
117
+ - More than 3 tasks
118
+ - Multiple subsystems (DB + API + UI = separate plans)
119
+ - Any task with >5 file modifications
120
+ - Checkpoint + implementation in same plan
121
+ - Discovery + implementation in same plan
122
+
123
+ **CONSIDER splitting:** >5 files total, natural semantic boundaries, context cost estimate exceeds 40% for a single plan. See `<planner_authority_limits>` for prohibited split reasons.
124
+
125
+ See @~/.claude/gsd-core/references/planner-guidance.md for Granularity Calibration table (Coarse/Standard/Fine plans-per-phase).