@opengsd/gsd-core 1.10.0 → 1.11.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 (328) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/agents/gsd-debug-session-manager.md +11 -0
  4. package/agents/gsd-doc-synthesizer.md +2 -4
  5. package/agents/gsd-executor.md +5 -5
  6. package/agents/gsd-mempalace-curator.md +5 -2
  7. package/agents/gsd-phase-researcher.md +20 -1
  8. package/agents/gsd-plan-checker.md +37 -0
  9. package/agents/gsd-planner.md +44 -46
  10. package/agents/gsd-user-profiler.md +3 -0
  11. package/agents/gsd-verifier.md +12 -3
  12. package/bin/install.js +841 -971
  13. package/bin/lib/ui-safety-gate.cjs +2 -0
  14. package/commands/gsd/code-review.md +1 -1
  15. package/commands/gsd/execute-phase.md +1 -1
  16. package/commands/gsd/map-codebase.md +1 -1
  17. package/commands/gsd/mempalace-capture.md +1 -1
  18. package/commands/gsd/mempalace-recall.md +1 -1
  19. package/commands/gsd/new-milestone.md +1 -1
  20. package/commands/gsd/quick.md +1 -1
  21. package/commands/gsd/review-backlog.md +2 -1
  22. package/commands/gsd/verify-work.md +1 -1
  23. package/gsd-core/bin/gsd-tools.cjs +469 -88
  24. package/gsd-core/bin/lib/active-workstream-store.cjs +138 -22
  25. package/gsd-core/bin/lib/agent-install-check.cjs +230 -32
  26. package/gsd-core/bin/lib/api-coverage.cjs +3 -5
  27. package/gsd-core/bin/lib/artifacts.cjs +3 -0
  28. package/gsd-core/bin/lib/assumption-delta.cjs +2 -4
  29. package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
  30. package/gsd-core/bin/lib/audit.cjs +876 -240
  31. package/gsd-core/bin/lib/broken-windows.cjs +1 -1
  32. package/gsd-core/bin/lib/capability-consent.cjs +149 -15
  33. package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
  34. package/gsd-core/bin/lib/capability-registry.cjs +575 -101
  35. package/gsd-core/bin/lib/capability-source.cjs +92 -0
  36. package/gsd-core/bin/lib/capability-trust.cjs +444 -25
  37. package/gsd-core/bin/lib/capability-validator.cjs +495 -22
  38. package/gsd-core/bin/lib/capability-writer.cjs +3 -2
  39. package/gsd-core/bin/lib/check-command-router.cjs +71 -37
  40. package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
  41. package/gsd-core/bin/lib/codex-agent-toml.cjs +329 -0
  42. package/gsd-core/bin/lib/command-aliases.cjs +22 -0
  43. package/gsd-core/bin/lib/command-roster.cjs +44 -1
  44. package/gsd-core/bin/lib/commands.cjs +651 -86
  45. package/gsd-core/bin/lib/commonjs-marker.cjs +12 -6
  46. package/gsd-core/bin/lib/complexity-trigger.cjs +1172 -0
  47. package/gsd-core/bin/lib/config-loader.cjs +75 -0
  48. package/gsd-core/bin/lib/config.cjs +10 -1
  49. package/gsd-core/bin/lib/core-utils.cjs +127 -29
  50. package/gsd-core/bin/lib/decisions.cjs +23 -0
  51. package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
  52. package/gsd-core/bin/lib/frontmatter.cjs +155 -20
  53. package/gsd-core/bin/lib/gap-checker.cjs +68 -7
  54. package/gsd-core/bin/lib/git-base-branch.cjs +102 -0
  55. package/gsd-core/bin/lib/gsd2-import.cjs +10 -1
  56. package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
  57. package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
  58. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +145 -0
  59. package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
  60. package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
  61. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
  62. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +265 -0
  63. package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
  64. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
  65. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +173 -0
  66. package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
  67. package/gsd-core/bin/lib/health-diagnostic.cjs +431 -0
  68. package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
  69. package/gsd-core/bin/lib/init.cjs +321 -129
  70. package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
  71. package/gsd-core/bin/lib/install-engine.cjs +745 -258
  72. package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
  73. package/gsd-core/bin/lib/install-model-override-resolver.cjs +203 -0
  74. package/gsd-core/bin/lib/install-profiles.cjs +134 -57
  75. package/gsd-core/bin/lib/install-scope.cjs +270 -0
  76. package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
  77. package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
  78. package/gsd-core/bin/lib/installer-migrations.cjs +138 -31
  79. package/gsd-core/bin/lib/io.cjs +10 -0
  80. package/gsd-core/bin/lib/markdown-sectionizer.cjs +2 -1
  81. package/gsd-core/bin/lib/markdown-table.cjs +133 -20
  82. package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
  83. package/gsd-core/bin/lib/milestone.cjs +754 -70
  84. package/gsd-core/bin/lib/model-catalog.cjs +59 -1
  85. package/gsd-core/bin/lib/model-resolver.cjs +183 -40
  86. package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
  87. package/gsd-core/bin/lib/pattern.cjs +122 -0
  88. package/gsd-core/bin/lib/phase-estimation.cjs +1 -1
  89. package/gsd-core/bin/lib/phase-id.cjs +444 -36
  90. package/gsd-core/bin/lib/phase-lifecycle.cjs +28 -3
  91. package/gsd-core/bin/lib/phase-locator.cjs +125 -18
  92. package/gsd-core/bin/lib/phase.cjs +646 -143
  93. package/gsd-core/bin/lib/plan-dependency-graph.cjs +72 -1
  94. package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
  95. package/gsd-core/bin/lib/plan-scan.cjs +86 -2
  96. package/gsd-core/bin/lib/planning-scope.cjs +31 -0
  97. package/gsd-core/bin/lib/planning-snapshot.cjs +890 -0
  98. package/gsd-core/bin/lib/planning-workspace.cjs +56 -6
  99. package/gsd-core/bin/lib/probe-core.cjs +1 -1
  100. package/gsd-core/bin/lib/profile-output.cjs +1 -1
  101. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +740 -0
  102. package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +11 -6
  103. package/gsd-core/bin/lib/review-lane-descriptor.cjs +13 -4
  104. package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
  105. package/gsd-core/bin/lib/review-lane-runner.cjs +421 -66
  106. package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
  107. package/gsd-core/bin/lib/roadmap-command-router.cjs +34 -0
  108. package/gsd-core/bin/lib/roadmap-parser.cjs +943 -184
  109. package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
  110. package/gsd-core/bin/lib/roadmap.cjs +385 -94
  111. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +608 -46
  112. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
  113. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +426 -55
  114. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
  115. package/gsd-core/bin/lib/runtime-homes.cjs +69 -3
  116. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +115 -3
  117. package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
  118. package/gsd-core/bin/lib/runtime-slash.cjs +27 -9
  119. package/gsd-core/bin/lib/security.cjs +104 -5
  120. package/gsd-core/bin/lib/shell-command-projection.cjs +275 -3
  121. package/gsd-core/bin/lib/smart-entry.cjs +142 -22
  122. package/gsd-core/bin/lib/state-command-router.cjs +5 -1
  123. package/gsd-core/bin/lib/state-document.cjs +152 -8
  124. package/gsd-core/bin/lib/state-transition.cjs +371 -117
  125. package/gsd-core/bin/lib/state.cjs +1794 -357
  126. package/gsd-core/bin/lib/surface.cjs +23 -9
  127. package/gsd-core/bin/lib/text-lines.cjs +80 -0
  128. package/gsd-core/bin/lib/token-scanner.cjs +76 -0
  129. package/gsd-core/bin/lib/uat-predicate.cjs +9 -3
  130. package/gsd-core/bin/lib/uat.cjs +399 -56
  131. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
  132. package/gsd-core/bin/lib/ui-safety-gate.cjs +14 -5
  133. package/gsd-core/bin/lib/unusable-input.cjs +24 -0
  134. package/gsd-core/bin/lib/update-context.cjs +8 -2
  135. package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
  136. package/gsd-core/bin/lib/validate.cjs +20 -6
  137. package/gsd-core/bin/lib/vendor/README.md +37 -0
  138. package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
  139. package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
  140. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  141. package/gsd-core/bin/lib/verification.cjs +258 -8
  142. package/gsd-core/bin/lib/verify.cjs +368 -888
  143. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +53 -32
  144. package/gsd-core/bin/lib/workstream-inventory.cjs +63 -10
  145. package/gsd-core/bin/lib/workstream.cjs +2 -2
  146. package/gsd-core/bin/lib/worktree-safety.cjs +176 -9
  147. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
  148. package/gsd-core/bin/shared/config-schema.manifest.json +7 -1
  149. package/gsd-core/references/agent-contracts.md +43 -26
  150. package/gsd-core/references/checkpoints.md +2 -2
  151. package/gsd-core/references/context-budget.md +1 -1
  152. package/gsd-core/references/dispatch-isolation-gate.md +138 -0
  153. package/gsd-core/references/doc-conflict-engine.md +1 -1
  154. package/gsd-core/references/execute-mvp-tdd.md +3 -3
  155. package/gsd-core/references/execute-phase-between-wave-reset.md +6 -2
  156. package/gsd-core/references/execute-phase-context-guard.md +1 -1
  157. package/gsd-core/references/execute-phase-response-language.md +1 -1
  158. package/gsd-core/references/execute-phase-wave-guard.md +6 -2
  159. package/gsd-core/references/gate-prompts.md +1 -1
  160. package/gsd-core/references/git-planning-commit.md +2 -1
  161. package/gsd-core/references/loop-hook-dispatch.md +39 -2
  162. package/gsd-core/references/model-profiles.md +12 -4
  163. package/gsd-core/references/mvp-concepts.md +9 -9
  164. package/gsd-core/references/planner-guidance.md +3 -9
  165. package/gsd-core/references/planner-preconditions.md +1 -1
  166. package/gsd-core/references/planner-reviews.md +1 -1
  167. package/gsd-core/references/planning-config.md +8 -6
  168. package/gsd-core/references/revision-loop.md +1 -1
  169. package/gsd-core/references/specless-probe-fallback.md +1 -1
  170. package/gsd-core/references/universal-anti-patterns.md +3 -3
  171. package/gsd-core/references/verifier-phase-gates.md +192 -0
  172. package/gsd-core/references/verify-mvp-mode.md +1 -1
  173. package/gsd-core/references/workstream-flag.md +22 -6
  174. package/gsd-core/templates/discussion-log.md +1 -1
  175. package/gsd-core/templates/phase-prompt.md +2 -4
  176. package/gsd-core/templates/state.md +4 -4
  177. package/gsd-core/templates/verification-report.md +9 -1
  178. package/gsd-core/workflows/ai-integration-phase.md +9 -11
  179. package/gsd-core/workflows/autonomous.md +1 -1
  180. package/gsd-core/workflows/cleanup.md +62 -3
  181. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +13 -3
  182. package/gsd-core/workflows/code-review-fix.md +37 -10
  183. package/gsd-core/workflows/code-review.md +38 -12
  184. package/gsd-core/workflows/complete-milestone.md +141 -18
  185. package/gsd-core/workflows/debug.md +7 -5
  186. package/gsd-core/workflows/diagnose-issues.md +35 -9
  187. package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -1
  188. package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
  189. package/gsd-core/workflows/discuss-phase-assumptions.md +2 -1
  190. package/gsd-core/workflows/edit-phase.md +26 -1
  191. package/gsd-core/workflows/eval-review.md +3 -5
  192. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +31 -6
  193. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
  194. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +2 -0
  195. package/gsd-core/workflows/execute-phase.md +38 -50
  196. package/gsd-core/workflows/execute-plan.md +36 -4
  197. package/gsd-core/workflows/explore.md +131 -4
  198. package/gsd-core/workflows/fast.md +10 -2
  199. package/gsd-core/workflows/health.md +73 -4
  200. package/gsd-core/workflows/import.md +4 -4
  201. package/gsd-core/workflows/ingest-docs.md +5 -5
  202. package/gsd-core/workflows/mvp-phase.md +6 -3
  203. package/gsd-core/workflows/new-milestone.md +14 -9
  204. package/gsd-core/workflows/new-project.md +14 -14
  205. package/gsd-core/workflows/next.md +12 -0
  206. package/gsd-core/workflows/plan-phase.md +41 -17
  207. package/gsd-core/workflows/plan-review-convergence.md +50 -2
  208. package/gsd-core/workflows/progress.md +34 -6
  209. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +4 -4
  210. package/gsd-core/workflows/quick/steps/quick-verification.md +27 -6
  211. package/gsd-core/workflows/quick/steps/research-phase.md +2 -2
  212. package/gsd-core/workflows/quick.md +35 -15
  213. package/gsd-core/workflows/review.md +26 -5
  214. package/gsd-core/workflows/secure-phase.md +1 -1
  215. package/gsd-core/workflows/session-report.md +2 -1
  216. package/gsd-core/workflows/settings.md +66 -2
  217. package/gsd-core/workflows/ship.md +104 -44
  218. package/gsd-core/workflows/spec-phase.md +30 -12
  219. package/gsd-core/workflows/sync-skills.md +63 -8
  220. package/gsd-core/workflows/transition.md +46 -11
  221. package/gsd-core/workflows/ui-phase.md +5 -5
  222. package/gsd-core/workflows/ui-review.md +2 -2
  223. package/gsd-core/workflows/update.md +1 -1
  224. package/gsd-core/workflows/validate-phase.md +1 -1
  225. package/gsd-core/workflows/verify-work.md +9 -7
  226. package/hooks/dist/gsd-agent-isolation-guard.js +103 -14
  227. package/hooks/dist/gsd-check-update-worker.js +56 -13
  228. package/hooks/dist/gsd-check-update.js +19 -1
  229. package/hooks/dist/gsd-cursor-pre-tool.js +0 -3
  230. package/hooks/dist/gsd-cursor-subagent-start.js +77 -2
  231. package/hooks/dist/gsd-cursor-subagent-stop.js +3 -2
  232. package/hooks/dist/gsd-prompt-guard.js +21 -20
  233. package/hooks/dist/gsd-read-injection-scanner.js +38 -24
  234. package/hooks/dist/gsd-statusline.js +18 -0
  235. package/hooks/dist/gsd-update-banner.js +22 -1
  236. package/hooks/dist/gsd-workflow-guard.js +134 -36
  237. package/hooks/dist/lib/git-cmd.js +92 -59
  238. package/hooks/dist/lib/injection-patterns.js +45 -0
  239. package/hooks/dist/lib/isolation-deny-reason.js +39 -0
  240. package/hooks/dist/lib/isolation-sentinel.js +9 -0
  241. package/hooks/gsd-agent-isolation-guard.js +103 -14
  242. package/hooks/gsd-check-update-worker.js +56 -13
  243. package/hooks/gsd-check-update.js +19 -1
  244. package/hooks/gsd-cursor-pre-tool.js +0 -3
  245. package/hooks/gsd-cursor-subagent-start.js +77 -2
  246. package/hooks/gsd-cursor-subagent-stop.js +3 -2
  247. package/hooks/gsd-prompt-guard.js +21 -20
  248. package/hooks/gsd-read-injection-scanner.js +38 -24
  249. package/hooks/gsd-statusline.js +18 -0
  250. package/hooks/gsd-update-banner.js +22 -1
  251. package/hooks/gsd-workflow-guard.js +134 -36
  252. package/hooks/lib/git-cmd.js +92 -59
  253. package/hooks/lib/injection-patterns.js +45 -0
  254. package/hooks/lib/isolation-deny-reason.js +39 -0
  255. package/hooks/lib/isolation-sentinel.js +9 -0
  256. package/package.json +21 -9
  257. package/pi/gsd.cjs +19 -5
  258. package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
  259. package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
  260. package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
  261. package/scripts/changeset/lint.cjs +60 -5
  262. package/scripts/check-alias-drift.cjs +7 -43
  263. package/scripts/check-contract-drift.cjs +297 -0
  264. package/scripts/ci-test-scope.cjs +19 -2
  265. package/scripts/command-contract-helpers.cjs +903 -1
  266. package/scripts/gen-adr-index.cjs +728 -38
  267. package/scripts/gen-capability-registry.cjs +3 -15
  268. package/scripts/gen-context-index.cjs +2 -11
  269. package/scripts/gen-health-docs.cjs +390 -0
  270. package/scripts/gen-inventory-manifest.cjs +50 -4
  271. package/scripts/gen-loop-host-contract.cjs +4 -24
  272. package/scripts/gen-registry.cjs +3 -14
  273. package/scripts/lib/alias-drift-families.cjs +46 -0
  274. package/scripts/lib/drift-scan.cjs +278 -0
  275. package/scripts/lint-allow-test-rule-refs.allowlist.json +1 -26
  276. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
  277. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
  278. package/scripts/lint-canary-version-leak.cjs +73 -0
  279. package/scripts/lint-command-contract.cjs +96 -13
  280. package/scripts/lint-completion-predicate-drift.cjs +933 -0
  281. package/scripts/lint-completion-ratio-drift.cjs +214 -0
  282. package/scripts/lint-default-flip-documentation.cjs +193 -0
  283. package/scripts/lint-eslint-glob-coverage.allowlist.json +34 -0
  284. package/scripts/lint-eslint-glob-coverage.cjs +340 -0
  285. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
  286. package/scripts/lint-health-diagnostic-rule-table.cjs +404 -0
  287. package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
  288. package/scripts/lint-milestone-window-drift.cjs +468 -0
  289. package/scripts/lint-phase-enumeration-drift.cjs +479 -0
  290. package/scripts/lint-plan-count-drift.cjs +318 -0
  291. package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
  292. package/scripts/lint-planning-prompt-drift.cjs +434 -0
  293. package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
  294. package/scripts/lint-regression-test-names.cjs +15 -13
  295. package/scripts/lint-removed-but-needed.cjs +320 -0
  296. package/scripts/lint-state-field-drift.cjs +805 -0
  297. package/scripts/lint-state-write-path-drift.cjs +1045 -0
  298. package/scripts/lint-test-file-count.allowlist.json +21 -10
  299. package/scripts/lint-unreachable-guard-drift.cjs +843 -0
  300. package/scripts/lint-vendored-deps.cjs +124 -0
  301. package/scripts/pr-changed-files.cjs +63 -0
  302. package/scripts/pr-template-policy.cjs +14 -4
  303. package/scripts/prompt-injection-scan.sh +25 -0
  304. package/scripts/require-issue-link-policy.cjs +192 -0
  305. package/scripts/state-write-path-drift-baseline.json +19 -0
  306. package/scripts/sync-runtime-launcher.cjs +2 -4
  307. package/skills/gsd-autonomous/SKILL.md +0 -1
  308. package/skills/gsd-code-review/SKILL.md +1 -1
  309. package/skills/gsd-execute-phase/SKILL.md +1 -2
  310. package/skills/gsd-map-codebase/SKILL.md +1 -1
  311. package/skills/gsd-mempalace-capture/SKILL.md +1 -1
  312. package/skills/gsd-mempalace-recall/SKILL.md +1 -1
  313. package/skills/gsd-new-milestone/SKILL.md +1 -1
  314. package/skills/gsd-next/SKILL.md +0 -1
  315. package/skills/gsd-plan-phase/SKILL.md +0 -1
  316. package/skills/gsd-progress/SKILL.md +0 -1
  317. package/skills/gsd-quick/SKILL.md +1 -1
  318. package/skills/gsd-review-backlog/SKILL.md +2 -1
  319. package/skills/gsd-stats/SKILL.md +0 -1
  320. package/skills/gsd-verify-work/SKILL.md +1 -1
  321. package/vscode/package.json +1 -1
  322. package/gsd-core/workflows/discovery-phase.md +0 -298
  323. package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
  324. package/gsd-core/workflows/verify-phase.md +0 -574
  325. package/scripts/affected-tests-lib.cjs +0 -554
  326. package/scripts/lint-allow-test-rule-refs.cjs +0 -162
  327. package/scripts/run-affected-tests.cjs +0 -7
  328. package/scripts/run-tests.cjs +0 -1051
@@ -4,9 +4,15 @@
4
4
  // Detects when Claude attempts file edits outside a GSD workflow context
5
5
  // (no active /gsd- skill or Task subagent) and injects an advisory warning.
6
6
  //
7
- // This is a SOFT guard — it advises, not blocks. The edit still proceeds.
8
- // The warning nudges Claude to use /gsd:quick or /gsd:fast instead of
9
- // making direct edits that bypass state tracking.
7
+ // This is a SOFT guard for edits — it advises, not blocks. The edit still
8
+ // proceeds. The warning nudges Claude to use /gsd:quick or /gsd:fast instead
9
+ // of making direct edits that bypass state tracking.
10
+ //
11
+ // ONE hard block lives here: `git add -f` on an agent/worktree-agent branch
12
+ // (WORKTREE_AGENT_FORCE_ADD_FORBIDDEN) — and that block leg fails CLOSED on
13
+ // internal error (#3504): when the guard is enabled and the blocking context
14
+ // holds, a thrown error exits 2 (block), not 0. The advisory legs keep the
15
+ // fail-open posture — a broken advisory must never wedge every tool call.
10
16
  //
11
17
  // Enable via config: hooks.workflow_guard: true (default: false)
12
18
  // Only triggers on Write/Edit tool calls to non-.planning/ files.
@@ -14,40 +20,46 @@
14
20
  const fs = require('fs');
15
21
  const path = require('path');
16
22
  const { spawnSync } = require('child_process');
17
- const { tokenize } = require('./lib/git-cmd.js');
23
+ const { tokenize, skipToSubcommand } = require('./lib/git-cmd.js');
18
24
 
19
25
  function forceGitAddCwds(command, defaultCwd) {
20
26
  const tokens = tokenize(command || '');
21
27
  const separators = new Set(['&&', '||', ';', '|']);
22
28
  const cwdList = [];
23
- for (let i = 0; i < tokens.length; i++) {
24
- if (path.basename(tokens[i]) !== 'git') continue;
29
+ // #3504: per-segment walk driven by git-cmd.js's canonical skipToSubcommand.
30
+ // The previous inline walk knew six global flags and silently missed the
31
+ // rest — `git -c core.hooksPath=/tmp/x add -f x`, `git --no-optional-locks
32
+ // add -f x`, `--literal-pathspecs`, `--namespace=…` and friends all fell
33
+ // through to a silent exit 0, a no-crash bypass the fail-closed catch is
34
+ // structurally blind to (it only fires on throws). Sharing the classifier
35
+ // makes the block's flag knowledge exactly the classifier's.
36
+ const segments = [];
37
+ let start = 0;
38
+ for (let i = 0; i <= tokens.length; i++) {
39
+ if (i === tokens.length || separators.has(tokens[i])) {
40
+ if (i > start) segments.push(tokens.slice(start, i));
41
+ start = i + 1;
42
+ }
43
+ }
44
+ for (const seg of segments) {
45
+ const subIdx = skipToSubcommand(seg);
46
+ if (subIdx === -1 || subIdx >= seg.length || seg[subIdx] !== 'add') continue;
25
47
 
26
- let j = i + 1;
48
+ // Resolve a `-C <dir>` (separate-arg form) preceding the subcommand so
49
+ // the branch is probed at the repository the add targets.
27
50
  let gitCwd = defaultCwd;
28
- while (j < tokens.length) {
29
- const token = tokens[j];
30
- const flagName = token.includes('=') ? token.slice(0, token.indexOf('=')) : token;
31
- if (token === '-C' && tokens[j + 1]) {
32
- gitCwd = path.resolve(gitCwd, tokens[j + 1]);
33
- j += 2;
34
- continue;
35
- }
36
- if (['-C', '--git-dir', '--work-tree'].includes(flagName) && !token.includes('=')) {
37
- j += 2;
38
- continue;
39
- }
40
- if (['--git-dir', '--work-tree', '--no-pager', '-p', '-P'].includes(flagName)) {
41
- j++;
51
+ for (let k = 0; k < subIdx; ) {
52
+ if (seg[k] === '-C' && k + 1 < subIdx) {
53
+ gitCwd = path.resolve(gitCwd, seg[k + 1]);
54
+ k += 2;
42
55
  continue;
43
56
  }
44
- break;
57
+ k++;
45
58
  }
46
59
 
47
- if (tokens[j] !== 'add') continue;
48
- for (let k = j + 1; k < tokens.length && !separators.has(tokens[k]); k++) {
49
- if (tokens[k] === '--') break;
50
- if (tokens[k] === '--force' || tokens[k] === '-f' || /^-[A-Za-z]*f[A-Za-z]*$/.test(tokens[k])) {
60
+ for (let k = subIdx + 1; k < seg.length; k++) {
61
+ if (seg[k] === '--') break;
62
+ if (seg[k] === '--force' || seg[k] === '-f' || /^-[A-Za-z]*f[A-Za-z]*$/.test(seg[k])) {
51
63
  cwdList.push(gitCwd);
52
64
  break;
53
65
  }
@@ -62,11 +74,86 @@ function currentBranch(cwd) {
62
74
  encoding: 'utf8',
63
75
  stdio: ['ignore', 'pipe', 'ignore'],
64
76
  windowsHide: true,
77
+ // #3504: bounded — this ran unbounded before, an indefinite hang under a
78
+ // wedged git would hang every PreToolUse call. Host wiring allows a 5s
79
+ // budget for the whole hook, so the probe gets 2s of it; a timeout
80
+ // returns '' (branch unknown), which both the block decision and the
81
+ // fail-closed re-check treat as "cannot establish agent branch".
82
+ timeout: 2000,
83
+ killSignal: 'SIGTERM',
65
84
  });
66
85
  if (result.status !== 0) return '';
67
86
  return result.stdout.trim();
68
87
  }
69
88
 
89
+ // Agent-branch predicate, shared by the happy-path detector and the fail-closed
90
+ // re-check so the block's scope cannot drift between the two paths (#3504).
91
+ function isAgentBranch(branch) {
92
+ return /^(worktree-)?agent-/.test(branch);
93
+ }
94
+
95
+ // Typed cwd read, shared by both paths for the same reason: a non-string cwd
96
+ // degrades to process.cwd() instead of throwing inside path.join().
97
+ function payloadCwd(data) {
98
+ return typeof data?.cwd === 'string' && data.cwd ? data.cwd : process.cwd();
99
+ }
100
+
101
+ // The force-add block payload, emitted by the happy-path detector and the
102
+ // fail-closed catch — one source so the two exits cannot drift. `origin`
103
+ // distinguishes them structurally ('force-add-detected' vs 'fail-closed') so a
104
+ // consumer — or the model reading Kimi's stderr feedback — is never told a
105
+ // force-add was detected when the call was in fact blocked unanalyzed.
106
+ function emitForceAddBlock(origin) {
107
+ const failClosed = origin === 'fail-closed';
108
+ const reason = failClosed
109
+ ? 'workflow guard internal error on an agent branch - failing closed. The command was NOT analyzed and was NOT confirmed to be a force-add. Retry the call or inspect the guard.'
110
+ : 'agent/worktree-agent branches must not run git add -f or git add --force. Respect the SDK skipped_gitignored/skipped_commit_docs_false contract and leave gitignored files untracked.';
111
+ const output = {
112
+ decision: 'block',
113
+ code: 'WORKTREE_AGENT_FORCE_ADD_FORBIDDEN',
114
+ origin: failClosed ? 'fail-closed' : 'force-add-detected',
115
+ reason,
116
+ };
117
+ process.stdout.write(JSON.stringify(output));
118
+ // Kimi CLI's exit-2 protocol feeds stderr back to the model (#2304)
119
+ process.stderr.write(output.reason);
120
+ }
121
+
122
+ // #3504 fail-closed context re-derivation. Runs INSIDE the outer catch, after
123
+ // an internal error, and answers exactly one question: does the blocking
124
+ // context of the force-add guard hold for this payload? Each stage is guarded —
125
+ // a re-derivation that itself throws must degrade to "cannot establish" (false),
126
+ // never take down the catch. Deliberately does NOT re-detect the force-add: the
127
+ // error may live in the detector itself, and on an agent branch with the guard
128
+ // enabled, a Bash call under an internal error is conservative-correct to block.
129
+ // Anything it cannot establish (unparseable payload, non-Bash tool, guard
130
+ // disabled, branch not determinably agent-*) fails open, preserving the
131
+ // advisory legs' fail-open posture.
132
+ function failClosedBlockContext(rawInput) {
133
+ let data;
134
+ try {
135
+ data = normalizeKimiPayload(JSON.parse(rawInput));
136
+ } catch {
137
+ return false;
138
+ }
139
+ if (data === null || typeof data !== 'object' || data.tool_name !== 'Bash') return false;
140
+ const cwd = payloadCwd(data);
141
+ let enabled;
142
+ try {
143
+ enabled = workflowGuardEnabled(cwd);
144
+ } catch {
145
+ return false;
146
+ }
147
+ if (!enabled) return false;
148
+ let branch;
149
+ try {
150
+ branch = currentBranch(cwd);
151
+ } catch {
152
+ return false;
153
+ }
154
+ return isAgentBranch(branch);
155
+ }
156
+
70
157
  function workflowGuardEnabled(cwd) {
71
158
  const configPath = path.join(cwd, '.planning', 'config.json');
72
159
  if (!fs.existsSync(configPath)) return false;
@@ -180,6 +267,15 @@ process.stdin.on('end', () => {
180
267
  clearTimeout(stdinTimeout);
181
268
  try {
182
269
  const data = normalizeKimiPayload(JSON.parse(input));
270
+ // #3504 test-only fault seam: throws right after parse so the fail-closed
271
+ // posture of the outer catch is exercisable — no JSON-expressible input
272
+ // throws in this handler today (#2547/#2595 hardened every read). Gated on
273
+ // GSD_TEST_MODE as well so a leaked GSD_TEST_WORKFLOW_GUARD_FAULT in a real
274
+ // shell cannot wedge a production session. The fault's failure direction
275
+ // is CLOSED: the catch below may block, never bypass a block.
276
+ if (process.env.GSD_TEST_MODE === '1' && process.env.GSD_TEST_WORKFLOW_GUARD_FAULT === '1') {
277
+ throw new Error('GSD_TEST_WORKFLOW_GUARD_FAULT: injected fault');
278
+ }
183
279
  const toolName = data.tool_name;
184
280
  const cwd = data.cwd || process.cwd();
185
281
  const isWorkflowGuardEnabled = workflowGuardEnabled(cwd);
@@ -192,14 +288,7 @@ process.stdin.on('end', () => {
192
288
  for (const gitCwd of forceGitAddCwds(command, cwd)) {
193
289
  const branch = currentBranch(gitCwd);
194
290
  if (/^(worktree-)?agent-/.test(branch)) {
195
- const output = {
196
- decision: 'block',
197
- code: 'WORKTREE_AGENT_FORCE_ADD_FORBIDDEN',
198
- reason: 'agent/worktree-agent branches must not run git add -f or git add --force. Respect the SDK skipped_gitignored/skipped_commit_docs_false contract and leave gitignored files untracked.',
199
- };
200
- process.stdout.write(JSON.stringify(output));
201
- // Kimi CLI's exit-2 protocol feeds stderr back to the model (#2304)
202
- process.stderr.write(output.reason);
291
+ emitForceAddBlock();
203
292
  process.exit(2);
204
293
  }
205
294
  }
@@ -264,8 +353,17 @@ process.stdin.on('end', () => {
264
353
  };
265
354
 
266
355
  process.stdout.write(JSON.stringify(output));
267
- } catch (e) {
268
- // Silent fail — never block tool execution
356
+ } catch {
357
+ // #3504: split posture on internal error. The ONE hard block in this hook
358
+ // (force-add on agent branches) fails CLOSED — if the blocking context can
359
+ // be re-derived from the payload (Bash tool + guard enabled + determinably
360
+ // an agent branch), exit 2 rather than silently allowing. Everything else
361
+ // keeps the historical fail-open posture: a broken advisory guard must
362
+ // never wedge the session's tool calls.
363
+ if (failClosedBlockContext(input)) {
364
+ emitForceAddBlock('fail-closed');
365
+ process.exit(2);
366
+ }
269
367
  process.exit(0);
270
368
  }
271
369
  });
@@ -19,9 +19,19 @@
19
19
  * hook's own __dirname:
20
20
  *
21
21
  * const { isGitSubcommand } = require(path.join(__dirname, 'lib', 'git-cmd.js'));
22
+ *
23
+ * `tokenize()` delegates to the shared `src/token-scanner.cts` seam (ADR-3212
24
+ * §4, epic #3212 Phase 3, #3414) — the built `gsd-core/bin/lib/token-scanner.cjs`
25
+ * artifact, not a sibling hooks/-tree file, because hook scripts are staged as
26
+ * standalone files at install time and a sibling require is a staging
27
+ * dependency that can fail silently (see gsd-workflow-guard.js's own
28
+ * KIMI_TOOL_NAMES comment for the precedent this follows). Re-exported here
29
+ * unchanged — every existing caller's behavior is identical (parity-asserted
30
+ * in tests/token-scanner.test.cjs row 5).
22
31
  */
23
32
 
24
33
  const path = require('path');
34
+ const { tokenizeShellLike } = require(path.join(__dirname, '..', '..', 'gsd-core', 'bin', 'lib', 'token-scanner.cjs'));
25
35
 
26
36
  /**
27
37
  * Git global options that take a following argument.
@@ -29,6 +39,7 @@ const path = require('path');
29
39
  */
30
40
  const ARGUMENT_TAKING_FLAGS = new Set([
31
41
  '-C', // working directory
42
+ '-c', // config override (separate-arg form: `git -c k=v …`; #3504)
32
43
  '--git-dir', // path to git repository
33
44
  '--work-tree', // path to working tree
34
45
  '--namespace', // git namespace
@@ -57,94 +68,116 @@ const BOOLEAN_FLAGS = new Set([
57
68
  * Handles single-quoted strings, double-quoted strings, and unquoted tokens.
58
69
  * Does NOT perform variable expansion or brace expansion.
59
70
  *
71
+ * Delegates to the shared `src/token-scanner.cts` seam — see the module
72
+ * header comment for why the built artifact, not a sibling require, is used.
73
+ *
60
74
  * @param {string} cmd
61
75
  * @returns {string[]}
62
76
  */
63
77
  function tokenize(cmd) {
64
- const tokens = [];
65
- let i = 0;
66
- const len = cmd.length;
67
-
68
- while (i < len) {
69
- // Skip whitespace
70
- while (i < len && /\s/.test(cmd[i])) i++;
71
- if (i >= len) break;
72
-
73
- let token = '';
74
- while (i < len && !/\s/.test(cmd[i])) {
75
- if (cmd[i] === "'") {
76
- // Single-quoted string: take everything until closing '
77
- i++;
78
- while (i < len && cmd[i] !== "'") token += cmd[i++];
79
- if (i < len) i++; // consume closing '
80
- } else if (cmd[i] === '"') {
81
- // Double-quoted string: take everything until closing " (no escape handling)
82
- i++;
83
- while (i < len && cmd[i] !== '"') token += cmd[i++];
84
- if (i < len) i++; // consume closing "
85
- } else {
86
- token += cmd[i++];
87
- }
88
- }
89
- if (token) tokens.push(token);
90
- }
91
-
92
- return tokens;
78
+ return tokenizeShellLike(cmd);
93
79
  }
94
80
 
95
81
  /**
96
- * Return true if `cmd` invokes the git subcommand `sub`.
82
+ * Walk past leading env-prefix assignments and global git options, same as
83
+ * `isGitSubcommand`'s phases 1-3. Returns the index of the subcommand token,
84
+ * or -1 if the command does not resolve to a git invocation at all.
97
85
  *
98
- * @param {string} cmd - Full shell command string (may include env vars, full paths)
99
- * @param {string} sub - Subcommand to test for, e.g. 'commit'
100
- * @returns {boolean}
86
+ * @param {string[]} tokens
87
+ * @returns {number}
101
88
  */
102
- function isGitSubcommand(cmd, sub) {
103
- if (!cmd || !sub) return false;
104
-
105
- const tokens = tokenize(cmd);
89
+ function skipToSubcommand(tokens) {
106
90
  let i = 0;
107
-
108
- // Phase 1: skip leading VAR=VALUE environment assignments
109
91
  while (i < tokens.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[i])) {
110
92
  i++;
111
93
  }
112
-
113
- // Phase 2: the next token must be the git executable
114
- if (i >= tokens.length) return false;
94
+ if (i >= tokens.length) return -1;
115
95
  const gitToken = tokens[i++];
116
- if (path.basename(gitToken) !== 'git') return false;
96
+ if (path.basename(gitToken) !== 'git') return -1;
117
97
 
118
- // Phase 3: consume git global options
119
98
  while (i < tokens.length) {
120
99
  const t = tokens[i];
121
-
122
- // --flag=value form for argument-taking flags
123
100
  const eqIdx = t.indexOf('=');
124
101
  const flagName = eqIdx !== -1 ? t.slice(0, eqIdx) : t;
125
102
  if (ARGUMENT_TAKING_FLAGS.has(flagName)) {
126
- if (eqIdx !== -1) {
127
- // consumed as one token: --git-dir=.git
128
- i++;
129
- } else {
130
- // consumed as two tokens: -C /path
131
- i += 2;
132
- }
103
+ i += eqIdx !== -1 ? 1 : 2;
104
+ continue;
105
+ }
106
+ // #3504: glued `-ckey=value` form — git accepts the config override with
107
+ // its argument attached (`git -cfoo.bar=1 …`). The eq-slice above yields
108
+ // flagName `-cfoo`, which no set contains, so without this arm the walk
109
+ // stops and the whole invocation is misclassified as not-git.
110
+ if (/^-c\S*=/.test(t)) {
111
+ i++;
133
112
  continue;
134
113
  }
135
-
136
114
  if (BOOLEAN_FLAGS.has(t)) {
137
115
  i++;
138
116
  continue;
139
117
  }
140
-
141
- // Not a global option — this is the subcommand
142
118
  break;
143
119
  }
120
+ return i;
121
+ }
122
+
123
+ /**
124
+ * Extract the branch-name argument from a git command line that creates or
125
+ * references one — `git checkout -b <name>` or `git branch <name>`. Returns
126
+ * null for any other command, including plain `git checkout <ref>` (switches
127
+ * branches, does not create one) and commands where a checkout/branch-shaped
128
+ * substring appears only inside a quoted argument (e.g. a commit message).
129
+ *
130
+ * New capability (ADR-3212 §4, epic #3212 Phase 3, #3414) exercising the
131
+ * shared scanner on the domain the ADR names ("a branch name... [is] not
132
+ * regular") — not a migration of existing duplicated logic; no prior
133
+ * implementation of this existed in the repo (design doc §1.2).
134
+ *
135
+ * @param {string} cmd
136
+ * @returns {string | null}
137
+ */
138
+ function extractBranchArgument(cmd) {
139
+ if (!cmd) return null;
140
+ const tokens = tokenizeShellLike(cmd);
141
+ const subIdx = skipToSubcommand(tokens);
142
+ if (subIdx === -1 || subIdx >= tokens.length) return null;
143
+ const sub = tokens[subIdx];
144
+
145
+ if (sub === 'checkout') {
146
+ for (let j = subIdx + 1; j < tokens.length; j++) {
147
+ if (tokens[j] === '-b' && j + 1 < tokens.length) return tokens[j + 1];
148
+ }
149
+ return null;
150
+ }
151
+
152
+ if (sub === 'branch') {
153
+ for (let j = subIdx + 1; j < tokens.length; j++) {
154
+ if (!tokens[j].startsWith('-')) return tokens[j];
155
+ }
156
+ return null;
157
+ }
158
+
159
+ return null;
160
+ }
161
+
162
+ /**
163
+ * Return true if `cmd` invokes the git subcommand `sub`.
164
+ *
165
+ * @param {string} cmd - Full shell command string (may include env vars, full paths)
166
+ * @param {string} sub - Subcommand to test for, e.g. 'commit'
167
+ * @returns {boolean}
168
+ */
169
+ function isGitSubcommand(cmd, sub) {
170
+ if (!cmd || !sub) return false;
171
+
172
+ // Phases 1-3 (env-prefix skip, git-executable check, global-option consume)
173
+ // extracted verbatim into skipToSubcommand — byte-identical logic, shared
174
+ // with extractBranchArgument rather than a second copy (#3212 Phase 3).
175
+ const tokens = tokenizeShellLike(cmd);
176
+ const subIdx = skipToSubcommand(tokens);
144
177
 
145
178
  // Phase 4: check the subcommand
146
- if (i >= tokens.length) return false;
147
- return tokens[i] === sub;
179
+ if (subIdx === -1 || subIdx >= tokens.length) return false;
180
+ return tokens[subIdx] === sub;
148
181
  }
149
182
 
150
- module.exports = { isGitSubcommand, tokenize };
183
+ module.exports = { isGitSubcommand, tokenize, extractBranchArgument, skipToSubcommand };
@@ -0,0 +1,45 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * injection-patterns.js — the shared prompt-injection pattern list (#3504, epic #1900).
5
+ *
6
+ * Single source of truth for the standard injection signatures used by BOTH
7
+ * gsd-prompt-guard.js (PreToolUse scan of writes into .planning/) and
8
+ * gsd-read-injection-scanner.js (PostToolUse scan of Read/WebFetch/WebSearch
9
+ * content). Previously each hook carried a byte-identical copy ("inlined for
10
+ * hook independence") that could silently drift — a pattern tightened in one
11
+ * would stop protecting the other surface.
12
+ *
13
+ * Why a shared lib require is safe here (the old inlining rationale, retired):
14
+ * the installer stages hooks/lib/ from the GSD_HOOK_LIB_FILES allowlist for the
15
+ * shared-bundle surfaces (Claude-family settings.json runtimes and Kimi), the
16
+ * Cursor stager auto-discovers require('./lib/...') in staged scripts and fails
17
+ * the install loudly when a helper is missing (#2587), and the plugin path
18
+ * ships this directory wholesale.
19
+ *
20
+ * Deliberately NOT unified with src/security.cts's scanForInjection set: that
21
+ * set runs inside the compiled lib tree; hooks must stay loadable standalone
22
+ * without it. The two lists are different surfaces by design, not drift.
23
+ *
24
+ * Keep this file free of literal 'gsd:' text — the stager rewrites that marker
25
+ * in staged hook content.
26
+ */
27
+
28
+ const INJECTION_PATTERNS = Object.freeze([
29
+ /ignore\s+(all\s+)?previous\s+instructions/i,
30
+ /ignore\s+(all\s+)?above\s+instructions/i,
31
+ /disregard\s+(all\s+)?previous/i,
32
+ /forget\s+(all\s+)?(your\s+)?instructions/i,
33
+ /override\s+(system|previous)\s+(prompt|instructions)/i,
34
+ /you\s+are\s+now\s+(?:a|an|the)\s+/i,
35
+ /act\s+as\s+(?:a|an|the)\s+(?!plan|phase|wave)/i,
36
+ /pretend\s+(?:you(?:'re| are)\s+|to\s+be\s+)/i,
37
+ /from\s+now\s+on,?\s+you\s+(?:are|will|should|must)/i,
38
+ /(?:print|output|reveal|show|display|repeat)\s+(?:your\s+)?(?:system\s+)?(?:prompt|instructions)/i,
39
+ /<\/?(?:system|assistant|human)>/i,
40
+ /\[SYSTEM\]/i,
41
+ /\[INST\]/i,
42
+ /<<\s*SYS\s*>>/i,
43
+ ]);
44
+
45
+ module.exports = { INJECTION_PATTERNS };
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+ // hooks/lib/isolation-deny-reason.js — shared, frozen reason-code enum for
3
+ // the #3045 dispatch-isolation guards' block/deny decisions
4
+ // (hooks/gsd-agent-isolation-guard.js, hooks/gsd-cursor-subagent-start.js).
5
+ //
6
+ // CONTRIBUTING.md ("Prohibited: Raw Text Matching on Test Outputs") bans
7
+ // asserting on a hook's free-form, human-readable reason/user_message prose
8
+ // — that text is for the operator/model reading the denial and may change
9
+ // wording without notice. Every block/deny decision therefore ALSO carries
10
+ // one of these STABLE codes (surfaced on the hook's stdout JSON as
11
+ // `reason_code`), so tests assert `out.reason_code === REASON_CODE.X`
12
+ // instead of regexing the message (mirrors the REASON enum convention in
13
+ // gsd-core/bin/verify-reapply-patches.cjs).
14
+ //
15
+ // Adding a new code requires updating this enum AND any test that locks the
16
+ // documented set.
17
+ const REASON_CODE = Object.freeze({
18
+ // The compiled runtime library (gsd-core/bin/lib/*.cjs) is missing and
19
+ // could not be self-built (ensure-runtime-build.cjs's RuntimeBuildError).
20
+ RUNTIME_BUILD_FAILED: 'runtime_build_failed',
21
+ // The project's dispatch-isolation configuration ('.planning/config.json')
22
+ // could not be read or resolved for a reason OTHER than a runtime-build
23
+ // failure (unreadable/malformed config, unexpected resolver error).
24
+ CONFIG_UNREADABLE: 'config_unreadable',
25
+ // Isolation resolves to "harness-worktree" but the Agent()/Task() dispatch
26
+ // is missing the harness's isolation flag/kwarg.
27
+ HARNESS_FLAG_MISSING: 'harness_flag_missing',
28
+ // Isolation resolves to "harness-worktree" but the dispatch payload carries
29
+ // no usable subagent_type, so the guard cannot confirm it is a GSD executor.
30
+ NO_SUBAGENT_TYPE: 'no_subagent_type',
31
+ // Isolation resolves to "harness-worktree" but whether the workspace root
32
+ // is an isolated worktree could not be determined (e.g. git unresponsive).
33
+ CANNOT_DETERMINE_ISOLATION: 'cannot_determine_isolation',
34
+ // Isolation resolves to "harness-worktree" and the workspace root is
35
+ // confirmed NOT an isolated worktree.
36
+ NOT_ISOLATED_WORKTREE: 'not_isolated_worktree',
37
+ });
38
+
39
+ module.exports = { REASON_CODE };
@@ -130,6 +130,15 @@ function resolveSentinelRoot(cwd) {
130
130
  if (fs.existsSync(path.join(cwd, '.planning'))) {
131
131
  return cwd;
132
132
  }
133
+ // #3582: worktree-safety.cjs / project-root.cjs are tsc build artifacts
134
+ // (ADR-457), gitignored and absent on a raw plugin-marketplace / git-clone
135
+ // install that never ran `npm run build:lib`. Self-heal before either
136
+ // require below; a RuntimeBuildError (or any other failure) falls through
137
+ // to the existing catch's degrade-to-raw-`cwd` — unchanged behavior, just
138
+ // now attempted-healed-first rather than silently degrading on the first
139
+ // cold-tree encounter.
140
+ const { ensureRuntimeBuild } = require('../../gsd-core/bin/ensure-runtime-build.cjs');
141
+ ensureRuntimeBuild();
133
142
  const { resolveWorktreeRoot } = require('../../gsd-core/bin/lib/worktree-safety.cjs');
134
143
  const { root } = resolveWorktreeRoot(cwd);
135
144
  const { findProjectRoot } = require('../../gsd-core/bin/lib/project-root.cjs');