@opengsd/gsd-core 1.9.1 → 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 (426) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -3
  3. package/.opencode/plugins/gsd-core.js +8 -1
  4. package/agents/gsd-code-fixer.md +27 -3
  5. package/agents/gsd-debug-session-manager.md +11 -0
  6. package/agents/gsd-debugger.md +12 -246
  7. package/agents/gsd-doc-synthesizer.md +2 -4
  8. package/agents/gsd-executor.md +12 -10
  9. package/agents/gsd-integration-checker.md +3 -0
  10. package/agents/gsd-mempalace-curator.md +5 -2
  11. package/agents/gsd-phase-researcher.md +20 -1
  12. package/agents/gsd-plan-checker.md +46 -0
  13. package/agents/gsd-planner.md +49 -54
  14. package/agents/gsd-roadmapper.md +21 -3
  15. package/agents/gsd-user-profiler.md +3 -0
  16. package/agents/gsd-verifier.md +26 -73
  17. package/bin/install.js +1272 -1238
  18. package/bin/lib/ui-safety-gate.cjs +2 -0
  19. package/commands/gsd/code-review.md +1 -1
  20. package/commands/gsd/execute-phase.md +1 -1
  21. package/commands/gsd/map-codebase.md +1 -1
  22. package/commands/gsd/mempalace-capture.md +2 -2
  23. package/commands/gsd/mempalace-recall.md +1 -1
  24. package/commands/gsd/new-milestone.md +2 -2
  25. package/commands/gsd/plan-phase.md +1 -1
  26. package/commands/gsd/quick.md +1 -1
  27. package/commands/gsd/review-backlog.md +2 -1
  28. package/commands/gsd/verify-work.md +1 -1
  29. package/gsd-core/bin/gsd-tools.cjs +1009 -115
  30. package/gsd-core/bin/lib/active-workstream-store.cjs +153 -12
  31. package/gsd-core/bin/lib/agent-install-check.cjs +268 -38
  32. package/gsd-core/bin/lib/api-coverage.cjs +123 -5
  33. package/gsd-core/bin/lib/artifacts.cjs +3 -0
  34. package/gsd-core/bin/lib/assumption-delta.cjs +2 -4
  35. package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
  36. package/gsd-core/bin/lib/audit.cjs +926 -202
  37. package/gsd-core/bin/lib/broken-windows.cjs +36 -6
  38. package/gsd-core/bin/lib/capability-consent.cjs +149 -15
  39. package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
  40. package/gsd-core/bin/lib/capability-registry.cjs +608 -148
  41. package/gsd-core/bin/lib/capability-source.cjs +92 -0
  42. package/gsd-core/bin/lib/capability-trust.cjs +444 -25
  43. package/gsd-core/bin/lib/capability-validator.cjs +507 -24
  44. package/gsd-core/bin/lib/capability-writer.cjs +3 -2
  45. package/gsd-core/bin/lib/check-command-router.cjs +114 -38
  46. package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
  47. package/gsd-core/bin/lib/codex-agent-toml.cjs +329 -0
  48. package/gsd-core/bin/lib/command-aliases.cjs +94 -0
  49. package/gsd-core/bin/lib/command-roster.cjs +44 -1
  50. package/gsd-core/bin/lib/commands.cjs +665 -99
  51. package/gsd-core/bin/lib/commonjs-marker.cjs +142 -0
  52. package/gsd-core/bin/lib/complexity-trigger.cjs +1172 -0
  53. package/gsd-core/bin/lib/config-loader.cjs +76 -0
  54. package/gsd-core/bin/lib/config.cjs +22 -2
  55. package/gsd-core/bin/lib/context-composer.cjs +278 -0
  56. package/gsd-core/bin/lib/context-predicates.cjs +506 -0
  57. package/gsd-core/bin/lib/core-utils.cjs +217 -40
  58. package/gsd-core/bin/lib/decisions.cjs +23 -0
  59. package/gsd-core/bin/lib/docs.cjs +3 -2
  60. package/gsd-core/bin/lib/external-job.cjs +19 -4
  61. package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
  62. package/gsd-core/bin/lib/frontmatter.cjs +239 -32
  63. package/gsd-core/bin/lib/gap-checker.cjs +68 -7
  64. package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +57 -6
  65. package/gsd-core/bin/lib/git-base-branch.cjs +160 -15
  66. package/gsd-core/bin/lib/graphify.cjs +142 -27
  67. package/gsd-core/bin/lib/gsd2-import.cjs +37 -5
  68. package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
  69. package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
  70. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +145 -0
  71. package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
  72. package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
  73. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
  74. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +265 -0
  75. package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
  76. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
  77. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +173 -0
  78. package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
  79. package/gsd-core/bin/lib/health-diagnostic.cjs +431 -0
  80. package/gsd-core/bin/lib/host-integration.cjs +13 -1
  81. package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
  82. package/gsd-core/bin/lib/init-command-router.cjs +83 -8
  83. package/gsd-core/bin/lib/init.cjs +1325 -169
  84. package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
  85. package/gsd-core/bin/lib/install-engine.cjs +805 -264
  86. package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
  87. package/gsd-core/bin/lib/install-model-override-resolver.cjs +203 -0
  88. package/gsd-core/bin/lib/install-profiles.cjs +160 -57
  89. package/gsd-core/bin/lib/install-scope.cjs +270 -0
  90. package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
  91. package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
  92. package/gsd-core/bin/lib/installer-migration-authoring.cjs +3 -1
  93. package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
  94. package/gsd-core/bin/lib/installer-migrations/007-retire-config-root-commonjs-marker.cjs +149 -0
  95. package/gsd-core/bin/lib/installer-migrations/008-cursor-retire-commands-surface.cjs +55 -0
  96. package/gsd-core/bin/lib/installer-migrations/009-pi-retire-reserved-hooks-dir.cjs +199 -0
  97. package/gsd-core/bin/lib/installer-migrations.cjs +206 -13
  98. package/gsd-core/bin/lib/io.cjs +38 -3
  99. package/gsd-core/bin/lib/markdown-sectionizer.cjs +8 -1
  100. package/gsd-core/bin/lib/markdown-table.cjs +133 -20
  101. package/gsd-core/bin/lib/mcp-catalog.cjs +518 -0
  102. package/gsd-core/bin/lib/mcp-server.cjs +135 -3
  103. package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
  104. package/gsd-core/bin/lib/milestone.cjs +821 -109
  105. package/gsd-core/bin/lib/model-catalog.cjs +59 -1
  106. package/gsd-core/bin/lib/model-resolver.cjs +183 -40
  107. package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
  108. package/gsd-core/bin/lib/pattern.cjs +122 -0
  109. package/gsd-core/bin/lib/phase-estimation.cjs +1 -1
  110. package/gsd-core/bin/lib/phase-id.cjs +507 -36
  111. package/gsd-core/bin/lib/phase-lifecycle.cjs +28 -3
  112. package/gsd-core/bin/lib/phase-locator.cjs +258 -58
  113. package/gsd-core/bin/lib/phase.cjs +891 -156
  114. package/gsd-core/bin/lib/plan-dependency-graph.cjs +303 -0
  115. package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
  116. package/gsd-core/bin/lib/plan-scan.cjs +86 -2
  117. package/gsd-core/bin/lib/planning-scope.cjs +31 -0
  118. package/gsd-core/bin/lib/planning-snapshot.cjs +890 -0
  119. package/gsd-core/bin/lib/planning-workspace.cjs +60 -6
  120. package/gsd-core/bin/lib/probe-core.cjs +1 -1
  121. package/gsd-core/bin/lib/profile-output.cjs +1 -1
  122. package/gsd-core/bin/lib/prompt-budget.cjs +128 -165
  123. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +740 -0
  124. package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +85 -0
  125. package/gsd-core/bin/lib/review-lane-descriptor.cjs +108 -0
  126. package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
  127. package/gsd-core/bin/lib/review-lane-runner.cjs +447 -68
  128. package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
  129. package/gsd-core/bin/lib/roadmap-command-router.cjs +76 -9
  130. package/gsd-core/bin/lib/roadmap-parser.cjs +1035 -194
  131. package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
  132. package/gsd-core/bin/lib/roadmap.cjs +405 -84
  133. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +795 -100
  134. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
  135. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +440 -57
  136. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
  137. package/gsd-core/bin/lib/runtime-homes.cjs +220 -41
  138. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +220 -44
  139. package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
  140. package/gsd-core/bin/lib/runtime-slash.cjs +27 -9
  141. package/gsd-core/bin/lib/section-manifest.cjs +209 -0
  142. package/gsd-core/bin/lib/security.cjs +104 -5
  143. package/gsd-core/bin/lib/shell-command-projection.cjs +388 -30
  144. package/gsd-core/bin/lib/smart-entry.cjs +154 -22
  145. package/gsd-core/bin/lib/state-command-router.cjs +5 -1
  146. package/gsd-core/bin/lib/state-document.cjs +152 -8
  147. package/gsd-core/bin/lib/state-transition.cjs +424 -105
  148. package/gsd-core/bin/lib/state.cjs +1927 -401
  149. package/gsd-core/bin/lib/surface.cjs +35 -10
  150. package/gsd-core/bin/lib/text-lines.cjs +80 -0
  151. package/gsd-core/bin/lib/token-scanner.cjs +76 -0
  152. package/gsd-core/bin/lib/uat-predicate.cjs +20 -4
  153. package/gsd-core/bin/lib/uat.cjs +706 -64
  154. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
  155. package/gsd-core/bin/lib/ui-safety-gate.cjs +14 -5
  156. package/gsd-core/bin/lib/unusable-input.cjs +33 -0
  157. package/gsd-core/bin/lib/update-context.cjs +8 -2
  158. package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
  159. package/gsd-core/bin/lib/validate.cjs +20 -6
  160. package/gsd-core/bin/lib/vendor/README.md +37 -0
  161. package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
  162. package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
  163. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  164. package/gsd-core/bin/lib/verification.cjs +287 -20
  165. package/gsd-core/bin/lib/verify.cjs +368 -880
  166. package/gsd-core/bin/lib/workflow-fragments.cjs +557 -0
  167. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +203 -19
  168. package/gsd-core/bin/lib/workstream-inventory.cjs +576 -31
  169. package/gsd-core/bin/lib/workstream.cjs +8 -2
  170. package/gsd-core/bin/lib/worktree-base-ref.cjs +50 -6
  171. package/gsd-core/bin/lib/worktree-safety.cjs +450 -125
  172. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
  173. package/gsd-core/bin/shared/config-schema.manifest.json +9 -1
  174. package/gsd-core/references/agent-contracts.md +43 -26
  175. package/gsd-core/references/artifact-types.md +10 -3
  176. package/gsd-core/references/autonomous-ui-design-contract.md +42 -0
  177. package/gsd-core/references/checkpoints.md +2 -2
  178. package/gsd-core/references/context-budget.md +1 -1
  179. package/gsd-core/references/debugger-techniques.md +255 -0
  180. package/gsd-core/references/dispatch-isolation-gate.md +138 -0
  181. package/gsd-core/references/doc-conflict-engine.md +1 -1
  182. package/gsd-core/references/execute-mvp-tdd.md +3 -3
  183. package/gsd-core/references/execute-phase-between-wave-reset.md +6 -2
  184. package/gsd-core/references/execute-phase-context-guard.md +1 -1
  185. package/gsd-core/references/execute-phase-response-language.md +1 -1
  186. package/gsd-core/references/execute-phase-wave-guard.md +6 -2
  187. package/gsd-core/references/gate-prompts.md +1 -1
  188. package/gsd-core/references/git-planning-commit.md +2 -1
  189. package/gsd-core/references/loop-hook-dispatch.md +39 -2
  190. package/gsd-core/references/model-profiles.md +12 -4
  191. package/gsd-core/references/mvp-concepts.md +9 -9
  192. package/gsd-core/references/planner-guidance.md +3 -9
  193. package/gsd-core/references/planner-preconditions.md +1 -1
  194. package/gsd-core/references/planner-reviews.md +1 -1
  195. package/gsd-core/references/planning-config.md +8 -6
  196. package/gsd-core/references/research-documentation-lookup.md +5 -3
  197. package/gsd-core/references/revision-loop.md +1 -1
  198. package/gsd-core/references/specless-probe-fallback.md +8 -7
  199. package/gsd-core/references/universal-anti-patterns.md +3 -3
  200. package/gsd-core/references/verifier-phase-gates.md +192 -0
  201. package/gsd-core/references/verifier-wiring-patterns.md +100 -0
  202. package/gsd-core/references/verify-mvp-mode.md +1 -1
  203. package/gsd-core/references/workstream-flag.md +22 -6
  204. package/gsd-core/references/worktree-branch-check.md +2 -2
  205. package/gsd-core/templates/discussion-log.md +1 -1
  206. package/gsd-core/templates/phase-prompt.md +2 -4
  207. package/gsd-core/templates/state.md +4 -4
  208. package/gsd-core/templates/summary-complex.md +2 -0
  209. package/gsd-core/templates/summary-minimal.md +2 -0
  210. package/gsd-core/templates/summary-standard.md +2 -0
  211. package/gsd-core/templates/summary.md +2 -0
  212. package/gsd-core/templates/verification-report.md +9 -1
  213. package/gsd-core/workflows/ai-integration-phase.md +9 -11
  214. package/gsd-core/workflows/audit-milestone.md +3 -0
  215. package/gsd-core/workflows/autonomous/steps/converge-banner.md +1 -0
  216. package/gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md +11 -0
  217. package/gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md +7 -0
  218. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +21 -0
  219. package/gsd-core/workflows/autonomous/steps/converge-loop.md +7 -0
  220. package/gsd-core/workflows/autonomous.md +33 -70
  221. package/gsd-core/workflows/cleanup.md +62 -3
  222. package/gsd-core/workflows/code-review/steps/dispatch-fix.md +39 -0
  223. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +93 -0
  224. package/gsd-core/workflows/code-review-fix.md +37 -10
  225. package/gsd-core/workflows/code-review.md +74 -166
  226. package/gsd-core/workflows/complete-milestone/steps/git-tag.md +29 -0
  227. package/gsd-core/workflows/complete-milestone.md +160 -95
  228. package/gsd-core/workflows/debug.md +16 -17
  229. package/gsd-core/workflows/diagnose-issues.md +56 -8
  230. package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -1
  231. package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
  232. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +15 -0
  233. package/gsd-core/workflows/discuss-phase-assumptions.md +7 -17
  234. package/gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md +51 -0
  235. package/gsd-core/workflows/docs-update.md +8 -51
  236. package/gsd-core/workflows/edit-phase.md +26 -1
  237. package/gsd-core/workflows/eval-review.md +3 -5
  238. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +64 -7
  239. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +50 -0
  240. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +31 -0
  241. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
  242. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +21 -0
  243. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +42 -0
  244. package/gsd-core/workflows/execute-phase/steps/regression-gate.md +43 -37
  245. package/gsd-core/workflows/execute-phase.md +103 -187
  246. package/gsd-core/workflows/execute-plan.md +36 -4
  247. package/gsd-core/workflows/explore.md +131 -4
  248. package/gsd-core/workflows/fast.md +10 -2
  249. package/gsd-core/workflows/health.md +73 -4
  250. package/gsd-core/workflows/help/modes/full.md +6 -1
  251. package/gsd-core/workflows/import.md +4 -4
  252. package/gsd-core/workflows/ingest-docs.md +7 -6
  253. package/gsd-core/workflows/mvp-phase.md +6 -3
  254. package/gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md +16 -0
  255. package/gsd-core/workflows/new-milestone/steps/reset-phase-safety.md +19 -0
  256. package/gsd-core/workflows/new-milestone.md +35 -47
  257. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +176 -0
  258. package/gsd-core/workflows/new-project/steps/auto-mode-detection.md +32 -0
  259. package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +18 -0
  260. package/gsd-core/workflows/new-project.md +27 -240
  261. package/gsd-core/workflows/next.md +12 -0
  262. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +15 -0
  263. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +110 -0
  264. package/gsd-core/workflows/plan-phase/steps/prd-express-gate.md +8 -0
  265. package/gsd-core/workflows/plan-phase/steps/research-only-early-exit.md +17 -0
  266. package/gsd-core/workflows/plan-phase/steps/research-only-modifiers.md +16 -0
  267. package/gsd-core/workflows/plan-phase/steps/reviews-prerequisite.md +17 -0
  268. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +149 -0
  269. package/gsd-core/workflows/plan-phase.md +89 -209
  270. package/gsd-core/workflows/plan-review-convergence.md +50 -2
  271. package/gsd-core/workflows/progress/steps/forensic-audit.md +125 -0
  272. package/gsd-core/workflows/progress/steps/mvp-display.md +18 -0
  273. package/gsd-core/workflows/progress.md +45 -159
  274. package/gsd-core/workflows/quick/steps/discussion-phase.md +124 -0
  275. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +111 -0
  276. package/gsd-core/workflows/quick/steps/quick-verification.md +67 -0
  277. package/gsd-core/workflows/quick/steps/research-phase.md +72 -0
  278. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +37 -0
  279. package/gsd-core/workflows/quick.md +55 -405
  280. package/gsd-core/workflows/resume-project.md +3 -0
  281. package/gsd-core/workflows/review/steps/reviewer-instances-note-1.md +4 -0
  282. package/gsd-core/workflows/review/steps/reviewer-instances-note-2.md +3 -0
  283. package/gsd-core/workflows/review.md +41 -13
  284. package/gsd-core/workflows/section-manifest.json +219 -0
  285. package/gsd-core/workflows/secure-phase.md +1 -1
  286. package/gsd-core/workflows/session-report.md +2 -1
  287. package/gsd-core/workflows/settings.md +66 -2
  288. package/gsd-core/workflows/ship.md +104 -44
  289. package/gsd-core/workflows/sketch.md +1 -1
  290. package/gsd-core/workflows/spec-phase.md +41 -20
  291. package/gsd-core/workflows/spike-wrap-up.md +20 -5
  292. package/gsd-core/workflows/spike.md +50 -16
  293. package/gsd-core/workflows/sync-skills.md +106 -13
  294. package/gsd-core/workflows/transition/steps/workstream-collision-check.md +17 -0
  295. package/gsd-core/workflows/transition.md +53 -31
  296. package/gsd-core/workflows/ui-phase.md +13 -12
  297. package/gsd-core/workflows/ui-review.md +2 -2
  298. package/gsd-core/workflows/update/steps/channel-banner.md +7 -0
  299. package/gsd-core/workflows/update.md +19 -8
  300. package/gsd-core/workflows/validate-phase.md +1 -1
  301. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +36 -0
  302. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +21 -0
  303. package/gsd-core/workflows/verify-work.md +17 -65
  304. package/hooks/dist/gsd-agent-isolation-guard.js +517 -0
  305. package/hooks/dist/gsd-check-update-worker.js +64 -12
  306. package/hooks/dist/gsd-check-update.js +19 -1
  307. package/hooks/dist/gsd-cursor-pre-tool.js +0 -3
  308. package/hooks/dist/gsd-cursor-subagent-start.js +607 -26
  309. package/hooks/dist/gsd-cursor-subagent-stop.js +3 -2
  310. package/hooks/dist/gsd-prompt-guard.js +21 -20
  311. package/hooks/dist/gsd-read-injection-scanner.js +45 -24
  312. package/hooks/dist/gsd-statusline.js +90 -6
  313. package/hooks/dist/gsd-update-banner.js +22 -1
  314. package/hooks/dist/gsd-workflow-guard.js +134 -36
  315. package/hooks/dist/gsd-worktree-path-guard.js +2 -1
  316. package/hooks/dist/gsd-write-guard.js +359 -0
  317. package/hooks/dist/lib/git-cmd.js +92 -59
  318. package/hooks/dist/lib/injection-patterns.js +45 -0
  319. package/hooks/dist/lib/isolation-deny-reason.js +39 -0
  320. package/hooks/dist/lib/isolation-sentinel.js +277 -0
  321. package/hooks/dist/managed-hooks-registry.cjs +2 -0
  322. package/hooks/gsd-agent-isolation-guard.js +517 -0
  323. package/hooks/gsd-check-update-worker.js +64 -12
  324. package/hooks/gsd-check-update.js +19 -1
  325. package/hooks/gsd-cursor-pre-tool.js +0 -3
  326. package/hooks/gsd-cursor-subagent-start.js +607 -26
  327. package/hooks/gsd-cursor-subagent-stop.js +3 -2
  328. package/hooks/gsd-prompt-guard.js +21 -20
  329. package/hooks/gsd-read-injection-scanner.js +45 -24
  330. package/hooks/gsd-statusline.js +90 -6
  331. package/hooks/gsd-update-banner.js +22 -1
  332. package/hooks/gsd-workflow-guard.js +134 -36
  333. package/hooks/gsd-worktree-path-guard.js +2 -1
  334. package/hooks/gsd-write-guard.js +359 -0
  335. package/hooks/hooks.json +12 -0
  336. package/hooks/lib/git-cmd.js +92 -59
  337. package/hooks/lib/injection-patterns.js +45 -0
  338. package/hooks/lib/isolation-deny-reason.js +39 -0
  339. package/hooks/lib/isolation-sentinel.js +277 -0
  340. package/hooks/managed-hooks-registry.cjs +2 -0
  341. package/package.json +31 -10
  342. package/pi/gsd.cjs +71 -12
  343. package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
  344. package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
  345. package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
  346. package/scripts/build-hooks.js +9 -0
  347. package/scripts/changeset/lint.cjs +68 -6
  348. package/scripts/changeset/serialize.cjs +5 -1
  349. package/scripts/check-alias-drift.cjs +7 -43
  350. package/scripts/check-contract-drift.cjs +297 -0
  351. package/scripts/ci-test-scope.cjs +19 -2
  352. package/scripts/command-contract-helpers.cjs +903 -1
  353. package/scripts/gen-adr-index.cjs +728 -38
  354. package/scripts/gen-capability-matrix.cjs +1 -1
  355. package/scripts/gen-capability-registry.cjs +3 -15
  356. package/scripts/gen-context-index.cjs +439 -0
  357. package/scripts/gen-health-docs.cjs +390 -0
  358. package/scripts/gen-inventory-manifest.cjs +150 -4
  359. package/scripts/gen-loop-host-contract.cjs +4 -24
  360. package/scripts/gen-prompt-budget-parity-corpus.cjs +645 -0
  361. package/scripts/gen-registry.cjs +3 -14
  362. package/scripts/gen-section-manifest.cjs +638 -0
  363. package/scripts/generate-package-identity.cjs +4 -2
  364. package/scripts/lib/alias-drift-families.cjs +46 -0
  365. package/scripts/lib/drift-scan.cjs +278 -0
  366. package/scripts/lint-allow-test-rule-refs.allowlist.json +15 -54
  367. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
  368. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
  369. package/scripts/lint-canary-version-leak.cjs +73 -0
  370. package/scripts/lint-command-contract.cjs +96 -13
  371. package/scripts/lint-compiled-artifact-sync.cjs +6 -1
  372. package/scripts/lint-completion-predicate-drift.cjs +933 -0
  373. package/scripts/lint-completion-ratio-drift.cjs +214 -0
  374. package/scripts/lint-default-flip-documentation.cjs +193 -0
  375. package/scripts/lint-docs-command-form.cjs +195 -0
  376. package/scripts/lint-docs-required.cjs +9 -1
  377. package/scripts/lint-emitted-drift-ack.cjs +215 -20
  378. package/scripts/lint-eslint-glob-coverage.allowlist.json +34 -0
  379. package/scripts/lint-eslint-glob-coverage.cjs +340 -0
  380. package/scripts/lint-example-parser-parity.cjs +395 -0
  381. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
  382. package/scripts/lint-health-diagnostic-rule-table.cjs +404 -0
  383. package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
  384. package/scripts/lint-milestone-window-drift.cjs +468 -0
  385. package/scripts/lint-phase-enumeration-drift.cjs +479 -0
  386. package/scripts/lint-plan-count-drift.cjs +318 -0
  387. package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
  388. package/scripts/lint-planning-prompt-drift.cjs +434 -0
  389. package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
  390. package/scripts/lint-regression-test-names.cjs +15 -13
  391. package/scripts/lint-removed-but-needed.cjs +320 -0
  392. package/scripts/lint-state-field-drift.cjs +805 -0
  393. package/scripts/lint-state-write-path-drift.cjs +1045 -0
  394. package/scripts/lint-test-file-count.allowlist.json +40 -3
  395. package/scripts/lint-unreachable-guard-drift.cjs +843 -0
  396. package/scripts/lint-vendored-deps.cjs +124 -0
  397. package/scripts/mutation-matrix.cjs +13 -0
  398. package/scripts/pr-changed-files.cjs +63 -0
  399. package/scripts/pr-template-policy.cjs +14 -4
  400. package/scripts/prompt-injection-scan.sh +52 -6
  401. package/scripts/require-issue-link-policy.cjs +192 -0
  402. package/scripts/state-write-path-drift-baseline.json +19 -0
  403. package/scripts/sync-runtime-launcher.cjs +2 -4
  404. package/skills/gsd-autonomous/SKILL.md +0 -1
  405. package/skills/gsd-code-review/SKILL.md +1 -1
  406. package/skills/gsd-execute-phase/SKILL.md +1 -2
  407. package/skills/gsd-map-codebase/SKILL.md +1 -1
  408. package/skills/gsd-mempalace-capture/SKILL.md +2 -2
  409. package/skills/gsd-mempalace-recall/SKILL.md +1 -1
  410. package/skills/gsd-new-milestone/SKILL.md +2 -2
  411. package/skills/gsd-next/SKILL.md +0 -1
  412. package/skills/gsd-plan-phase/SKILL.md +1 -2
  413. package/skills/gsd-progress/SKILL.md +0 -1
  414. package/skills/gsd-quick/SKILL.md +1 -1
  415. package/skills/gsd-review-backlog/SKILL.md +2 -1
  416. package/skills/gsd-stats/SKILL.md +0 -1
  417. package/skills/gsd-verify-work/SKILL.md +1 -1
  418. package/vscode/package.json +1 -1
  419. package/gsd-core/workflows/discovery-phase.md +0 -298
  420. package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
  421. package/gsd-core/workflows/verify-phase.md +0 -577
  422. package/scripts/affected-tests-lib.cjs +0 -554
  423. package/scripts/gen-emitted-baseline.cjs +0 -145
  424. package/scripts/lint-allow-test-rule-refs.cjs +0 -162
  425. package/scripts/run-affected-tests.cjs +0 -7
  426. package/scripts/run-tests.cjs +0 -1050
@@ -0,0 +1,517 @@
1
+ #!/usr/bin/env node
2
+ // gsd-hook-version: {{GSD_VERSION}}
3
+ // GSD Agent Isolation Dispatch Guard — PreToolUse hook (#3045)
4
+ //
5
+ // Problem: `gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md`
6
+ // resolves the project's dispatch isolation correctly
7
+ // (`gsd_run query dispatch-isolation --raw`), but DELIVERY of that value into
8
+ // the model-authored `Agent(subagent_type="gsd-executor", ...)` call is a
9
+ // prose instruction ("substitute $HARNESS_FLAG's value ... on Claude Code it
10
+ // is literally isolation=\"worktree\""). Nothing verifies the model actually
11
+ // copied it. When it is omitted, the executor runs and commits directly in
12
+ // the user's PRIMARY checkout instead of an isolated worktree, with no
13
+ // consent and no warning.
14
+ //
15
+ // A prose backstop cannot fix a prose defect — it is the same class of
16
+ // artifact the model may equally skip. This hook enforces the invariant at
17
+ // the tooling layer instead: HARD-BLOCKING.
18
+ //
19
+ // Applicability (must positively determine all three to act — otherwise
20
+ // inert):
21
+ // 1. this is a GSD project (`.planning/config.json` exists under cwd),
22
+ // 2. the project's resolved dispatch isolation is `harness-worktree`,
23
+ // 3. the dispatch target is an executor (`subagent_type === "gsd-executor"`;
24
+ // no other executor-shaped subagent type exists in agents/ today).
25
+ //
26
+ // Fail-closed exception (#3050 lesson: a guard that cannot verify must not
27
+ // answer "safe"): if the project IS a GSD project but the hook cannot read
28
+ // or resolve its dispatch-isolation configuration, it DENIES rather than
29
+ // defaulting to the "safe-looking" none/allow value that
30
+ // `gsd-core/bin/gsd-tools.cjs`'s own `routeDispatchIsolation` degrades to on
31
+ // error. That existing query is fail-OPEN by design (sequential execution
32
+ // is always safe for the SCHEDULER); this guard's job is the opposite
33
+ // invariant (never dispatch unisolated when isolation was promised), so it
34
+ // cannot reuse that fail-open default and instead resolves isolation
35
+ // itself, distinguishing "resolved cleanly" from "could not resolve".
36
+ //
37
+ // Isolation resolution (#3045 BLOCKER fix, see hooks/lib/isolation-sentinel.js):
38
+ // prefers the workflow's own PERSISTED per-dispatch decision (a sentinel
39
+ // `record-dispatch-isolation` writes after `executor-isolation-dispatch.md`
40
+ // resolves ISOLATION in shell, applying workflow.use_worktrees, the #2474
41
+ // per-plan submodule degrade, and the #683/#3060 base-check auto-degrade)
42
+ // over re-deriving a host CAPABILITY from the registry. A fresh sentinel is
43
+ // authoritative — `none`/`orchestrator-worktree` ALLOW immediately
44
+ // (sequential/orchestrator-managed dispatch is legitimate, not a bug); an
45
+ // absent/stale sentinel falls back to a conservative registry+config check
46
+ // (GSD_RUNTIME env > .planning/config.json `runtime` > the per-install
47
+ // `.gsd-runtime` marker, #3566 — no confident signal degrades to inert rather
48
+ // than guessing 'claude', see resolveRegistryIsolation)
49
+ // gated additionally by `workflow.use_worktrees` — read directly, in-process,
50
+ // no subprocess spawn.
51
+ //
52
+ // Triggers on: Agent/Task tool calls with subagent_type === "gsd-executor"
53
+ // (both names accepted — #3045 MAJOR 1: only Agent was previously matched,
54
+ // silently inert on any host/version whose subagent tool is named Task).
55
+ // Action: BLOCK (exit 2) when isolation should be enforced and is not
56
+ // No-op: any tool other than Agent/Task, non-executor targets, GSD projects
57
+ // whose resolved isolation is not harness-worktree, non-GSD projects,
58
+ // malformed payloads, or a dispatch that already carries the correct
59
+ // isolation parameter.
60
+
61
+ 'use strict';
62
+
63
+ const fs = require('fs');
64
+ const path = require('path');
65
+ const os = require('os');
66
+ const { readSentinel, VALID_ISOLATION, extractDispatchIdentifiers, sentinelAppliesToDispatch } = require('./lib/isolation-sentinel.js');
67
+ const { REASON_CODE } = require('./lib/isolation-deny-reason.js');
68
+ // #3582: gsd-core/bin/lib/*.cjs (runtime-name-policy.cjs, capability-registry.cjs
69
+ // below) are tsc build artifacts (ADR-457), gitignored and absent on a raw
70
+ // plugin-marketplace / git-clone install that never ran `npm run build:lib`.
71
+ // Self-heal before the first such require (resolveRegistryIsolation, below) —
72
+ // see ensureRuntimeBuild's own header for the full rationale. This module
73
+ // itself (gsd-core/bin/ensure-runtime-build.cjs) depends on nothing under
74
+ // ./lib, so requiring it here is always safe.
75
+ const { ensureRuntimeBuild, RuntimeBuildError } = require('../gsd-core/bin/ensure-runtime-build.cjs');
76
+
77
+ // No other executor-shaped subagent_type exists in agents/ today
78
+ // (verified: only agents/gsd-executor.md). A Set, not a bare string compare,
79
+ // so a future sibling executor role can be added here without touching the
80
+ // matching logic below.
81
+ const EXECUTOR_SUBAGENT_TYPES = new Set(['gsd-executor']);
82
+
83
+ /**
84
+ * Parse a registry `harnessIsolationFlag` of the shape `key="value"` (the
85
+ * only shape an `Agent()` tool_input kwarg can express) into its parameter
86
+ * name and expected value. Bare CLI-flag shapes (e.g. a hypothetical
87
+ * `--worktree`) have no tool_input kwarg equivalent and are not checkable
88
+ * here — this hook is scoped to the Claude Code `Agent` tool's keyword-arg
89
+ * dispatch surface.
90
+ */
91
+ function parseHarnessFlag(flag) {
92
+ if (typeof flag !== 'string') return null;
93
+ const m = /^([A-Za-z_][\w-]*)="([^"]*)"$/.exec(flag);
94
+ if (!m) return null;
95
+ return { param: m[1], value: m[2] };
96
+ }
97
+
98
+ // ─── #3566: per-install runtime marker ────────────────────────────────────────
99
+ // bin/install.js writes `<install>/gsd-core/.gsd-runtime` for EVERY runtime
100
+ // install (#2297), co-located with VERSION. Unlike `~/.gsd/defaults.json` —
101
+ // which is host-wide and names whichever runtime's install ran LAST, the exact
102
+ // leakage #2840's config.cjs change exists to prevent — the marker describes
103
+ // THIS install, which is the property runtime identity needs on a machine
104
+ // with 2+ runtimes. Mirrors readInstallRuntimeMarker in src/model-resolver.cts
105
+ // (same cache + test-seam shape); this hook cannot import that module without
106
+ // dragging the whole model-resolution stack into a PreToolUse hot path, so the
107
+ // 5-line read lives here against the same sibling-layout assumption the hook's
108
+ // own require('../gsd-core/bin/lib/…') already makes. Epic #3473 B3 owns
109
+ // consolidating every marker reader into one shared seam.
110
+ let _installMarkerCache; // undefined = unread; null = known absent; string = value
111
+
112
+ function readInstallRuntimeMarker() {
113
+ if (_installMarkerCache !== undefined) return _installMarkerCache;
114
+ try {
115
+ const markerPath = path.join(__dirname, '..', 'gsd-core', '.gsd-runtime');
116
+ const raw = fs.readFileSync(markerPath, 'utf-8').trim();
117
+ _installMarkerCache = raw || null;
118
+ } catch {
119
+ // No marker: dev/source tree, or an install predating #2297 — "no signal
120
+ // from this rung", never a resolution failure. Falls through to the
121
+ // defaults rung below.
122
+ _installMarkerCache = null;
123
+ }
124
+ return _installMarkerCache;
125
+ }
126
+
127
+ // Test seam for the marker rung (the dev/source tree has no marker file, so
128
+ // the read always bottoms out at null there — same seam contract as
129
+ // model-resolver.cts's _setInstallRuntimeMarkerForTests, #2297).
130
+ function _setInstallRuntimeMarkerForTests(value) {
131
+ _installMarkerCache = value;
132
+ }
133
+
134
+ /**
135
+ * Resolve this project's declared `runtime` identity WITHOUT defaulting to
136
+ * 'claude' when no explicit signal exists (#3045 MAJOR 2).
137
+ *
138
+ * `gsd-core/templates/config.json` — the actual scaffold used to write every
139
+ * new project's config.json — ships with NO `runtime` key, so "no signal"
140
+ * is the COMMON case, not a corner case. Previously this resolution silently
141
+ * defaulted to 'claude' in that case, which meant every non-Claude runtime
142
+ * that also installs this hook (any `hostIntegration.hooksSurface ===
143
+ * 'settings-json'` runtime, not only Claude) had Claude's
144
+ * `harnessIsolationFlag` ("isolation=\"worktree\"") demanded on its Agent()
145
+ * -equivalent dispatch — a kwarg that runtime's own tool never accepts.
146
+ *
147
+ * Returns `{ runtimeId, confident }`. `confident` is true only when an
148
+ * explicit signal exists (GSD_RUNTIME env override, a `runtime` key literally
149
+ * present in config.json, the per-install `.gsd-runtime` marker, or a
150
+ * `runtime` persisted to `~/.gsd/defaults.json` by the installer — see
151
+ * below); false means "cannot determine" and callers must NOT silently
152
+ * substitute 'claude' — see resolveRegistryIsolation.
153
+ *
154
+ * #3045 BLOCKER 2 fix: precedence is GSD_RUNTIME env > config.json `runtime`
155
+ * key > `~/.gsd/defaults.json` `runtime`. The first two are unchanged; the
156
+ * third is NEW — `bin/install.js`'s `writeNonClaudeDefaults` already persists
157
+ * the installed runtime to `~/.gsd/defaults.json` for every non-Claude
158
+ * runtime install (`defaults.runtime = runtime`, #2395), so this is real,
159
+ * already-shipping data, not a new write. Before this fix, "no signal" was
160
+ * the COMMON case for any project whose config.json was scaffolded from
161
+ * `gsd-core/templates/config.json` (which ships with NO `runtime` key) and
162
+ * whose session had no `GSD_RUNTIME` override — i.e. nearly every non-Claude
163
+ * install, since Claude installs never reach `writeNonClaudeDefaults` at all
164
+ * (`nativeModelAliases` short-circuits it) and therefore correctly still rely
165
+ * on config.json/env. Reading the installer's own persisted signal makes
166
+ * "confident" the common case instead.
167
+ *
168
+ * #3566: the per-install `.gsd-runtime` marker now sits BETWEEN config.json
169
+ * and defaults.json. defaults.json is host-wide and names whichever runtime
170
+ * installed LAST — on a 2-runtime machine that confidently resolves the WRONG
171
+ * runtime (a Codex install's `runtime:"codex"` leaking into Claude projects),
172
+ * and when the wrong runtime declares no harnessIsolationFlag the guard goes
173
+ * silently inert. The marker describes THIS install (written for every
174
+ * runtime since #2297), which is the source #2840's config.cjs change names
175
+ * as correct. defaults.json stays as the final rung so single-runtime default
176
+ * installs and pre-#2297 installs (no marker on disk) keep the #3045
177
+ * BLOCKER 2 behavior.
178
+ */
179
+ function resolveRuntimeIdentity(cwd, configPath, resolveRuntimeNameFromCandidates) {
180
+ const envRuntime = resolveRuntimeNameFromCandidates(process.env.GSD_RUNTIME);
181
+ if (envRuntime) return { runtimeId: envRuntime, confident: true };
182
+
183
+ // A throw here (corrupt JSON, EISDIR, permission error) propagates to the
184
+ // caller's catch as a resolution failure — this function only decides
185
+ // "confident vs not", never resolution failure.
186
+ const raw = fs.readFileSync(configPath, 'utf-8');
187
+ const parsed = JSON.parse(raw);
188
+ if (parsed && typeof parsed === 'object' && 'runtime' in parsed) {
189
+ const configRuntime = resolveRuntimeNameFromCandidates(parsed.runtime);
190
+ if (configRuntime) return { runtimeId: configRuntime, confident: true };
191
+ }
192
+
193
+ // #3566: the per-install marker, above the host-wide defaults — see the
194
+ // block comment on readInstallRuntimeMarker. An empty/whitespace-only file
195
+ // or an unknown value degrades exactly like the other rungs (no signal /
196
+ // future-runtime tolerance via resolveRuntimeNameFromCandidates).
197
+ const markerRuntime = resolveRuntimeNameFromCandidates(readInstallRuntimeMarker());
198
+ if (markerRuntime) return { runtimeId: markerRuntime, confident: true };
199
+
200
+ // #3045 BLOCKER 2: fall back to the installer-persisted default. Read
201
+ // defensively — an absent/corrupt/non-object defaults.json is "no signal",
202
+ // never a resolution failure (this function only ever throws for the
203
+ // config.json read above, which the caller's catch already handles).
204
+ try {
205
+ const defaultsPath = path.join(os.homedir(), '.gsd', 'defaults.json');
206
+ const defaultsRaw = fs.readFileSync(defaultsPath, 'utf-8');
207
+ const defaultsParsed = JSON.parse(defaultsRaw);
208
+ if (defaultsParsed && typeof defaultsParsed === 'object' && 'runtime' in defaultsParsed) {
209
+ const defaultsRuntime = resolveRuntimeNameFromCandidates(defaultsParsed.runtime);
210
+ if (defaultsRuntime) return { runtimeId: defaultsRuntime, confident: true };
211
+ }
212
+ } catch {
213
+ // Absent or unreadable ~/.gsd/defaults.json — no signal, fall through.
214
+ }
215
+
216
+ return { runtimeId: null, confident: false };
217
+ }
218
+
219
+ /**
220
+ * Resolve the registry-declared `harnessIsolationFlag` descriptor for
221
+ * `runtimeId` — pure host-CAPABILITY lookup, used both when the sentinel
222
+ * confirms harness-worktree but omitted the flag, and by the conservative
223
+ * fallback path. Returns `null` when the host declares no usable flag.
224
+ */
225
+ function resolveHarnessFlag(runtimeId, runtimes) {
226
+ const runtimeEntry = runtimes != null ? runtimes[runtimeId] : null;
227
+ const declaredFlag = runtimeEntry?.runtime?.harnessIsolationFlag ?? null;
228
+ return (typeof declaredFlag === 'string' && declaredFlag.length > 0) ? declaredFlag : null;
229
+ }
230
+
231
+ /**
232
+ * Conservative fallback resolution used when the #3045 sentinel is absent or
233
+ * stale: re-derive isolation from the registry CAPABILITY, gated by
234
+ * `workflow.use_worktrees` (config-schema key confirmed present in
235
+ * gsd-core/bin/shared/config-schema.manifest.json's validKeys, so it survives
236
+ * loadConfig's whitelist; read directly from the raw config.json here — same
237
+ * side-effect-free approach cmdConfigGet itself uses, not through loadConfig).
238
+ *
239
+ * MAJOR 2: when the runtime cannot be confidently determined (no GSD_RUNTIME
240
+ * override, no `runtime` key in config.json — the common case, since the
241
+ * project scaffold ships without one), this resolves to 'none' (inert)
242
+ * rather than guessing 'claude' and demanding Claude's flag on a host that
243
+ * may not even be Claude. Denying every dispatch on an undeterminable
244
+ * runtime would repeat the exact false-positive class the #3045 BLOCKER
245
+ * itself was — this is the fallback path only (the sentinel-fresh path above
246
+ * already carries the workflow's own confirmed decision + flag, so this
247
+ * degrades coverage only for dispatches that happen outside a GSD workflow
248
+ * run, e.g. a manual Agent() call before any sentinel has been written).
249
+ */
250
+ function resolveRegistryIsolation(cwd, configPath) {
251
+ // #3582: self-heal the compiled runtime library BEFORE either require
252
+ // below — this is the only reaching path to both (resolveRegistryIsolation
253
+ // is the sole caller of each), so one call here covers both. Throws
254
+ // RuntimeBuildError on an unbuildable tree; the caller (resolveIsolationState)
255
+ // already wraps this whole function in try/catch and folds any error into
256
+ // its fail-closed `error` result — evaluateDispatch below distinguishes a
257
+ // RuntimeBuildError there so it surfaces this seam's actionable message
258
+ // instead of being misreported as an unreadable config.json (#3050 lesson).
259
+ ensureRuntimeBuild();
260
+ const { resolveRuntimeNameFromCandidates } = require('../gsd-core/bin/lib/runtime-name-policy.cjs');
261
+ const { runtimes } = require('../gsd-core/bin/lib/capability-registry.cjs');
262
+
263
+ const { runtimeId, confident } = resolveRuntimeIdentity(cwd, configPath, resolveRuntimeNameFromCandidates);
264
+ if (!confident) {
265
+ return { isolation: 'none', harnessFlag: null };
266
+ }
267
+
268
+ const runtimeEntry = runtimes != null ? runtimes[runtimeId] : null;
269
+ const declared = runtimeEntry?.runtime?.hostIntegration?.dispatch?.isolation ?? null;
270
+ let isolation = (typeof declared === 'string' && VALID_ISOLATION.has(declared)) ? declared : 'none';
271
+
272
+ let harnessFlag = null;
273
+ if (isolation === 'harness-worktree') {
274
+ harnessFlag = resolveHarnessFlag(runtimeId, runtimes);
275
+ if (harnessFlag === null) {
276
+ // A host claiming harness isolation with no declared flag gives this
277
+ // guard nothing to check for — degrade to 'none' rather than block on
278
+ // an unspecifiable requirement.
279
+ isolation = 'none';
280
+ }
281
+ }
282
+
283
+ if (isolation === 'harness-worktree') {
284
+ let useWorktrees = true;
285
+ try {
286
+ const raw = fs.readFileSync(configPath, 'utf-8');
287
+ const parsedCfg = JSON.parse(raw);
288
+ if (parsedCfg && typeof parsedCfg === 'object' && parsedCfg.workflow &&
289
+ typeof parsedCfg.workflow === 'object' && parsedCfg.workflow.use_worktrees === false) {
290
+ useWorktrees = false;
291
+ }
292
+ } catch {
293
+ // Unreadable config already propagated to the outer caller's catch
294
+ // before this point in practice (resolveRuntimeIdentity reads it
295
+ // first); tolerate defensively and keep the conservative (enforce)
296
+ // default rather than silently disabling the guard.
297
+ }
298
+ if (!useWorktrees) isolation = 'none';
299
+ }
300
+
301
+ return { isolation, harnessFlag };
302
+ }
303
+
304
+ /**
305
+ * Resolve this project's dispatch isolation mode, distinguishing three
306
+ * outcomes:
307
+ * - { gsdProject: false } — not a GSD project, inert
308
+ * - { gsdProject: true, error: <Error> } — cannot verify, DENY
309
+ * - { gsdProject: true, isolation, harnessFlag } — resolved cleanly
310
+ *
311
+ * `.planning/config.json` EXISTING (regardless of whether it can be read) is
312
+ * the GSD-project signal, mirroring gsd-workflow-guard.js /
313
+ * gsd-context-monitor.js. Any failure reading or parsing it, or requiring the
314
+ * sibling registry/policy modules, after that point means "GSD project
315
+ * present, isolation mode unknown" — folded into the DENY path rather than
316
+ * silently defaulting to a mode that happens to look safe.
317
+ *
318
+ * #3045 BLOCKER fix: prefer the workflow's own PERSISTED per-dispatch
319
+ * decision (the sentinel `record-dispatch-isolation` writes) over
320
+ * re-deriving a host CAPABILITY from the registry. The registry's
321
+ * harness-worktree entry means "this host CAN isolate", not "this dispatch
322
+ * SHOULD be isolated" — sequential ISOLATION=none legitimately happens even
323
+ * on a harness-worktree-capable host (project opt-out, per-plan submodule
324
+ * intersection, #683/#3060 base-check auto-degrade). A fresh sentinel is
325
+ * authoritative; an absent/stale one falls back to the conservative
326
+ * registry+config check via resolveRegistryIsolation.
327
+ *
328
+ * `clock` is injectable for the sentinel-staleness check (repo clock-seam
329
+ * convention); defaults to the real `Date`.
330
+ *
331
+ * `dispatchIds` (optional, `{plan, phase}`) is the identifiers extracted from
332
+ * THIS dispatch's own prompt/description text (#3045 SECURITY F2 —
333
+ * `extractDispatchIdentifiers` in `hooks/lib/isolation-sentinel.js`). When
334
+ * supplied and a fresh sentinel disagrees with it on a shared identifier, the
335
+ * sentinel is treated as NOT APPLICABLE to this dispatch (falls through to
336
+ * the conservative registry+config fallback below) rather than trusted —
337
+ * "a mismatch is 'no applicable sentinel', not an allow" (#3045 review).
338
+ */
339
+ function resolveIsolationState(cwd, { clock = Date, dispatchIds = null } = {}) {
340
+ const configPath = path.join(cwd, '.planning', 'config.json');
341
+ let projectExists;
342
+ try {
343
+ fs.accessSync(configPath, fs.constants.F_OK);
344
+ projectExists = true;
345
+ } catch {
346
+ projectExists = false;
347
+ }
348
+ if (!projectExists) {
349
+ return { gsdProject: false, isolation: null, harnessFlag: null, error: null };
350
+ }
351
+
352
+ const sentinel = readSentinel(cwd, { clock });
353
+ if (sentinel.present && !sentinel.stale && sentinelAppliesToDispatch(sentinel, dispatchIds)) {
354
+ if (sentinel.isolation !== 'harness-worktree') {
355
+ return { gsdProject: true, isolation: sentinel.isolation, harnessFlag: null, error: null };
356
+ }
357
+ if (sentinel.harnessFlag) {
358
+ return { gsdProject: true, isolation: 'harness-worktree', harnessFlag: sentinel.harnessFlag, error: null };
359
+ }
360
+ // #3045 BLOCKER 2 fix: the sentinel already PROVED this dispatch requires
361
+ // isolation (it resolved harness-worktree) but carries no usable flag —
362
+ // this must DENY, not degrade to the registry's "not confident -> none"
363
+ // fallback. That degrade exists for the case where NOTHING has resolved
364
+ // isolation yet (the conservative fallback path below); reusing it here
365
+ // was the BLOCKER: a fresh sentinel asserting harness-worktree with no
366
+ // flag fell through to a registry lookup that, on the common "runtime not
367
+ // confidently determinable" case, silently returned isolation:'none' and
368
+ // ALLOWED the dispatch to run unisolated in the primary checkout — the
369
+ // exact failure this guard exists to prevent, and on the default-install
370
+ // path (no `runtime` key in gsd-core/templates/config.json), not a corner
371
+ // case. With the #3045 CORE REDESIGN, `dispatch-isolation` always resolves
372
+ // and records `harnessFlag` together with `isolation` in one atomic write
373
+ // whenever isolation is 'harness-worktree' (routeDispatchIsolation
374
+ // degrades to 'none' itself when no flag is declared) — so a fresh
375
+ // sentinel with isolation:'harness-worktree' and no flag should not occur
376
+ // in practice. This branch is defense-in-depth for a sentinel written by
377
+ // an older gsd-tools.cjs, a hand-crafted/corrupted-in-a-*valid*-way
378
+ // sentinel, or any other path that reaches this state; it MUST deny.
379
+ return {
380
+ gsdProject: true,
381
+ isolation: null,
382
+ harnessFlag: null,
383
+ error: new Error(
384
+ 'dispatch-isolation sentinel resolved "harness-worktree" but recorded no harness_flag — ' +
385
+ 'cannot verify what parameter the dispatch must carry.'
386
+ ),
387
+ };
388
+ }
389
+
390
+ // Sentinel absent, stale, or does not apply to this dispatch (F2 mismatch):
391
+ // conservative fallback (#3045 finding — must still cover fail-closed case
392
+ // (a): a project that opted out of worktrees entirely via
393
+ // workflow.use_worktrees).
394
+ try {
395
+ const { isolation, harnessFlag } = resolveRegistryIsolation(cwd, configPath);
396
+ return { gsdProject: true, isolation, harnessFlag, error: null };
397
+ } catch (err) {
398
+ return { gsdProject: true, isolation: null, harnessFlag: null, error: err };
399
+ }
400
+ }
401
+
402
+ /**
403
+ * Process one PreToolUse payload (already JSON-parsed) and return the
404
+ * decision without touching stdin/stdout/process.exit — the exported,
405
+ * directly-testable core of this hook's logic (#3045 MAJOR: "the clock seam
406
+ * is dead code" fix). The stdin-driven script below is now a thin adapter
407
+ * over this function so tests can `require()` it and inject a `clock`
408
+ * (`{now(): number}`) directly per the repo's clock-seam convention, instead
409
+ * of racing real `Date.now()` across a spawned subprocess boundary.
410
+ *
411
+ * Returns `{ action: 'allow' } | { action: 'block', reason: string }`.
412
+ */
413
+ function evaluateDispatch(data, { clock = Date } = {}) {
414
+ if (!data || typeof data !== 'object') return { action: 'allow' };
415
+ // #3045 MAJOR 1: accept both subagent-dispatch tool names. hooks.json's
416
+ // matcher is "Agent|Task" (mirroring the repo's own PostToolUse
417
+ // precedent) so this must match both, or it is silently inert on any
418
+ // host/version whose subagent tool is named "Task".
419
+ if (data.tool_name !== 'Agent' && data.tool_name !== 'Task') return { action: 'allow' };
420
+
421
+ const toolInput = (data.tool_input && typeof data.tool_input === 'object') ? data.tool_input : {};
422
+ const subagentType = toolInput.subagent_type;
423
+ if (typeof subagentType !== 'string' || !EXECUTOR_SUBAGENT_TYPES.has(subagentType)) {
424
+ return { action: 'allow' };
425
+ }
426
+
427
+ const cwd = data.cwd || process.cwd();
428
+ // #3045 SECURITY F2: best-effort plan/phase extraction from this
429
+ // dispatch's own description text, so a fresh sentinel that disagrees
430
+ // with THIS dispatch is treated as inapplicable rather than trusted.
431
+ const dispatchIds = extractDispatchIdentifiers(toolInput.description);
432
+ const state = resolveIsolationState(cwd, { clock, dispatchIds });
433
+
434
+ if (!state.gsdProject) return { action: 'allow' };
435
+
436
+ if (state.error) {
437
+ // #3582: a missing/unbuildable compiled runtime library (RuntimeBuildError,
438
+ // thrown by ensureRuntimeBuild in resolveRegistryIsolation) is a DIFFERENT,
439
+ // actionable failure from an unreadable/unparsable config.json — surface
440
+ // its own message instead of misreporting it as the generic
441
+ // "could not read or resolve ... configuration" text (the exact #3050
442
+ // misreport this issue exists to fix). Both cases still fail closed
443
+ // (block); only the message differs.
444
+ const isBuildFailure = state.error instanceof RuntimeBuildError;
445
+ const reason = isBuildFailure
446
+ ? `Agent isolation guard: cannot resolve this project's dispatch-isolation ` +
447
+ `configuration because the GSD runtime library failed to self-build. ` +
448
+ `${state.error.message} Refusing to dispatch subagent_type="${subagentType}" until ` +
449
+ `the runtime library is built — a guard that cannot verify must not answer "safe" ` +
450
+ `(#3050).`
451
+ : `Agent isolation guard: could not read or resolve this project's dispatch-isolation ` +
452
+ `configuration ('.planning/config.json' under '${cwd}'). Refusing to dispatch ` +
453
+ `subagent_type="${subagentType}" without being able to verify whether isolation is ` +
454
+ `required — a guard that cannot verify must not answer "safe" (#3050). Retry once the ` +
455
+ `project configuration is readable.`;
456
+ const reasonCode = isBuildFailure ? REASON_CODE.RUNTIME_BUILD_FAILED : REASON_CODE.CONFIG_UNREADABLE;
457
+ return { action: 'block', reason, reasonCode };
458
+ }
459
+
460
+ if (state.isolation !== 'harness-worktree') return { action: 'allow' };
461
+
462
+ const parsed = parseHarnessFlag(state.harnessFlag);
463
+ if (!parsed) return { action: 'allow' };
464
+
465
+ if (toolInput[parsed.param] === parsed.value) return { action: 'allow' };
466
+
467
+ const reason =
468
+ `Agent isolation guard: this project's dispatch isolation resolves to "harness-worktree", ` +
469
+ `but the Agent() dispatch for subagent_type="${subagentType}" is missing ` +
470
+ `${parsed.param}="${parsed.value}". Add ${parsed.param}="${parsed.value}" to the Agent() ` +
471
+ `call so the executor runs in an isolated worktree instead of the primary checkout ` +
472
+ `(gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md).`;
473
+ return { action: 'block', reason, reasonCode: REASON_CODE.HARNESS_FLAG_MISSING };
474
+ }
475
+
476
+ /* istanbul ignore next -- stdin adapter, exercised via spawnSync in tests */
477
+ function main() {
478
+ let input = '';
479
+ const stdinTimeout = setTimeout(() => process.exit(0), 3000);
480
+ process.stdin.setEncoding('utf8');
481
+ process.stdin.on('data', (chunk) => { input += chunk; });
482
+ process.stdin.on('end', () => {
483
+ clearTimeout(stdinTimeout);
484
+ try {
485
+ const data = JSON.parse(input);
486
+ const decision = evaluateDispatch(data);
487
+ if (decision.action === 'block') {
488
+ const out = { decision: 'block', reason: decision.reason, reason_code: decision.reasonCode };
489
+ process.stdout.write(JSON.stringify(out));
490
+ // Kimi feeds stderr (not stdout) back to the model on exit 2.
491
+ process.stderr.write(decision.reason);
492
+ process.exit(2);
493
+ }
494
+ process.exit(0);
495
+ } catch {
496
+ // Silent fail — never block valid tool calls due to hook errors
497
+ // (malformed payload, etc.). This is distinct from resolveIsolationState's
498
+ // internal error handling, which DOES deny — this outer catch only
499
+ // covers payload parsing before applicability could even be determined.
500
+ process.exit(0);
501
+ }
502
+ });
503
+ }
504
+
505
+ if (require.main === module) {
506
+ main();
507
+ }
508
+
509
+ module.exports = {
510
+ evaluateDispatch,
511
+ resolveIsolationState,
512
+ resolveRuntimeIdentity,
513
+ resolveHarnessFlag,
514
+ resolveRegistryIsolation,
515
+ parseHarnessFlag,
516
+ _setInstallRuntimeMarkerForTests,
517
+ };
@@ -11,15 +11,61 @@
11
11
 
12
12
  const fs = require('fs');
13
13
  const path = require('path');
14
- const { isSemverNewer } = require('../gsd-core/bin/lib/semver-compare.cjs');
15
- // Latest-version lookup is delegated to the single deterministic adapter
16
- // (#498). checkLatestVersion() owns the npm-view call, the timeout/semver
17
- // policy, and the package name — sourced from the baked Package Identity seam.
18
- // The previous `require('../package.json').name` (#378) resolved to undefined
19
- // in the installed tree (only a {"type":"commonjs"} marker ships), so the
20
- // background check never reported updates.
21
- const { checkLatestVersion } = require('../gsd-core/bin/check-latest-version.cjs');
22
- const { PACKAGE_NAME } = require('../gsd-core/bin/lib/package-identity.cjs');
14
+
15
+ // #3582: gsd-core/bin/lib/semver-compare.cjs and package-identity.cjs (and,
16
+ // transitively, check-latest-version.cjs's own gsd-core/bin/lib/cli-exit.cjs
17
+ // + shell-command-projection.cjs) are tsc build artifacts (ADR-457),
18
+ // gitignored and absent on a raw plugin-marketplace / git-clone install that
19
+ // never ran `npm run build:lib`. This worker is a DETACHED SessionStart
20
+ // background process (spawned with stdio: 'ignore') — a build failure here
21
+ // must DEGRADE to the no-signal fallbacks below (mirroring the
22
+ // managed-hooks-registry.cjs degrade just below) so the worker still runs to
23
+ // completion and writes a result cache record, rather than dying silently
24
+ // with no visible signal and no cache-file write at all.
25
+ //
26
+ // This try/require/ensureRuntimeBuild/require/catch shape repeats (with
27
+ // different destructured names) in hooks/gsd-check-update.js and
28
+ // hooks/gsd-update-banner.js. It is deliberately NOT extracted into a shared
29
+ // hooks/lib/ helper: scripts/lint-hooks-runtime-build-seam.cjs enforces this
30
+ // exact seam textually, PER FILE — it greps each hooks/ file for its OWN
31
+ // literal `require('.../ensure-runtime-build.cjs')` + `ensureRuntimeBuild(`
32
+ // call co-occurring with its OWN literal `require('.../gsd-core/bin/lib/*.cjs')`.
33
+ // A generic helper taking the compiled module's path as a variable would move
34
+ // the literal compiled-lib require OUT of this file and into the helper,
35
+ // called with a non-literal argument — the scan's regex (see that script's
36
+ // "Known limitations") cannot see a require() called with a variable, so this
37
+ // file would then read as "requires nothing" and the lint would stop
38
+ // protecting it. A ceremony-only helper (just the ensureRuntimeBuild call,
39
+ // each caller keeping its own literal compiled-lib require) fails the SAME
40
+ // way from the other side: it would remove this file's own literal
41
+ // `require('.../ensure-runtime-build.cjs')` + `ensureRuntimeBuild(` call,
42
+ // which the lint also requires to be textually present in THIS file. Either
43
+ // shape needs the lint script itself widened to special-case the helper,
44
+ // which is a bigger, riskier change than the ~6 duplicated lines it would
45
+ // save; kept inline instead.
46
+ let isSemverNewer = () => false;
47
+ let checkLatestVersion = () => ({ ok: false });
48
+ let PACKAGE_NAME = null;
49
+ try {
50
+ const { ensureRuntimeBuild } = require('../gsd-core/bin/ensure-runtime-build.cjs');
51
+ ensureRuntimeBuild();
52
+ ({ isSemverNewer } = require('../gsd-core/bin/lib/semver-compare.cjs'));
53
+ // Latest-version lookup is delegated to the single deterministic adapter
54
+ // (#498). checkLatestVersion() owns the npm-view call, the timeout/semver
55
+ // policy, and the package name — sourced from the baked Package Identity seam.
56
+ // The previous `require('../package.json').name` (#378) never yielded a name in
57
+ // the installed tree — at the time it resolved to the synthetic
58
+ // {"type":"commonjs"} marker GSD wrote at the config root, which has no `.name`,
59
+ // so the background check never reported updates. Since #2544 GSD writes no
60
+ // marker there at all, so that require would now fail to resolve outright.
61
+ // Either way the name must come from the baked seam, never a walk-up.
62
+ ({ checkLatestVersion } = require('../gsd-core/bin/check-latest-version.cjs'));
63
+ ({ PACKAGE_NAME } = require('../gsd-core/bin/lib/package-identity.cjs'));
64
+ } catch (e) {
65
+ // Runtime library missing/broken and could not self-build — degrade to the
66
+ // no-signal fallbacks declared above; the worker still writes a result
67
+ // cache record (package_name: null, update_available: false).
68
+ }
23
69
  // Authoritative list of managed hooks — shared with tests to retire source-grep
24
70
  // assertions (pending-migration-to-typed-ir [#455]).
25
71
  // NOTE: managed-hooks-registry.cjs must be in HOOKS_TO_COPY (scripts/build-hooks.js)
@@ -53,14 +99,20 @@ try {
53
99
  } catch (e) {}
54
100
 
55
101
  // Check for stale hooks — compare hook version headers against installed VERSION
56
- // Hooks are installed at configDir/hooks/ (e.g. ~/.claude/hooks/) (#1421)
102
+ // Since #3023 the bundle directory name is resolved from __dirname (this
103
+ // worker is staged INSIDE the bundle), not assumed to be configDir/hooks —
104
+ // the directory name is runtime-descriptor-driven (e.g. `gsd-hooks/` for pi).
57
105
  // Only check hooks that GSD currently ships — orphaned files from removed features
58
106
  // (e.g., gsd-intel-*.js) must be ignored to avoid permanent stale warnings (#1750)
59
107
  // MANAGED_HOOKS is imported from ./managed-hooks-registry.cjs above.
60
108
 
61
- let staleHooks = [];
109
+ const staleHooks = [];
62
110
  if (configDir) {
63
- const hooksDir = path.join(configDir, 'hooks');
111
+ // #3023: the bundle's directory name is runtime-descriptor-driven (pi stages
112
+ // it as `gsd-hooks/`), so deriving it as `<configDir>/hooks` silently scanned
113
+ // nothing there. This worker is staged INSIDE the bundle, so __dirname is the
114
+ // bundle directory by construction — name-agnostic and one fewer assumption.
115
+ const hooksDir = __dirname;
64
116
  try {
65
117
  if (fs.existsSync(hooksDir)) {
66
118
  const hookFiles = fs.readdirSync(hooksDir).filter(f => MANAGED_HOOKS.includes(f));
@@ -8,7 +8,25 @@ const path = require('path');
8
8
  const os = require('os');
9
9
  const { spawn } = require('child_process');
10
10
 
11
- const { updateCacheFileName } = require('../gsd-core/bin/lib/package-identity.cjs');
11
+ // #3582: gsd-core/bin/lib/package-identity.cjs is a tsc build artifact
12
+ // (ADR-457), gitignored and absent on a raw plugin-marketplace / git-clone
13
+ // install that never ran `npm run build:lib`. This SessionStart hook must
14
+ // DEGRADE (fall back to a generic cache filename) rather than crash session
15
+ // start. gsd-check-update-worker.js — the process this hook spawns — degrades
16
+ // identically and independently, so the shared fallback literal keeps the
17
+ // cache path consistent between writer and reader even in the (rare)
18
+ // doubly-degraded case. This try/require/ensureRuntimeBuild/require/catch
19
+ // shape is deliberately duplicated (not extracted to hooks/lib/) — see
20
+ // gsd-check-update-worker.js's identical #3582 comment for why.
21
+ let updateCacheFileName = 'gsd-update-check.json';
22
+ try {
23
+ const { ensureRuntimeBuild } = require('../gsd-core/bin/ensure-runtime-build.cjs');
24
+ ensureRuntimeBuild();
25
+ ({ updateCacheFileName } = require('../gsd-core/bin/lib/package-identity.cjs'));
26
+ } catch (e) {
27
+ // Runtime library missing/broken and could not self-build — degrade to the
28
+ // fallback filename above rather than crash the SessionStart hook.
29
+ }
12
30
 
13
31
  const homeDir = os.homedir();
14
32
  const cwd = process.cwd();
@@ -22,9 +22,6 @@
22
22
 
23
23
  'use strict';
24
24
 
25
- const fs = require('fs');
26
- const path = require('path');
27
-
28
25
  const WRITE_TOOL_RE = /write|edit|replace|create|delete|remove|append|apply|patch|insert|mkdir/i;
29
26
  const PATH_KEY_RE = /^(path|file|file_?path|filepath|target_?path|target|dir|directory|uri|filename)$/i;
30
27
  const PLANNING_PATH_RE = /(^|[\\/])\.planning([\\/]|$)/;