@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
@@ -43,8 +43,9 @@
43
43
  // authoritative — `none`/`orchestrator-worktree` ALLOW immediately
44
44
  // (sequential/orchestrator-managed dispatch is legitimate, not a bug); an
45
45
  // absent/stale sentinel falls back to a conservative registry+config check
46
- // (GSD_RUNTIME env > .planning/config.json `runtime` — no confident signal
47
- // degrades to inert rather than guessing 'claude', see resolveRegistryIsolation)
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)
48
49
  // gated additionally by `workflow.use_worktrees` — read directly, in-process,
49
50
  // no subprocess spawn.
50
51
  //
@@ -63,6 +64,15 @@ const fs = require('fs');
63
64
  const path = require('path');
64
65
  const os = require('os');
65
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');
66
76
 
67
77
  // No other executor-shaped subagent_type exists in agents/ today
68
78
  // (verified: only agents/gsd-executor.md). A Set, not a bare string compare,
@@ -85,6 +95,42 @@ function parseHarnessFlag(flag) {
85
95
  return { param: m[1], value: m[2] };
86
96
  }
87
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
+
88
134
  /**
89
135
  * Resolve this project's declared `runtime` identity WITHOUT defaulting to
90
136
  * 'claude' when no explicit signal exists (#3045 MAJOR 2).
@@ -100,9 +146,10 @@ function parseHarnessFlag(flag) {
100
146
  *
101
147
  * Returns `{ runtimeId, confident }`. `confident` is true only when an
102
148
  * explicit signal exists (GSD_RUNTIME env override, a `runtime` key literally
103
- * present in config.json, or a `runtime` persisted to `~/.gsd/defaults.json`
104
- * by the installer — see below); false means "cannot determine" and callers
105
- * must NOT silently substitute 'claude' — see resolveRegistryIsolation.
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.
106
153
  *
107
154
  * #3045 BLOCKER 2 fix: precedence is GSD_RUNTIME env > config.json `runtime`
108
155
  * key > `~/.gsd/defaults.json` `runtime`. The first two are unchanged; the
@@ -117,6 +164,17 @@ function parseHarnessFlag(flag) {
117
164
  * (`nativeModelAliases` short-circuits it) and therefore correctly still rely
118
165
  * on config.json/env. Reading the installer's own persisted signal makes
119
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.
120
178
  */
121
179
  function resolveRuntimeIdentity(cwd, configPath, resolveRuntimeNameFromCandidates) {
122
180
  const envRuntime = resolveRuntimeNameFromCandidates(process.env.GSD_RUNTIME);
@@ -132,6 +190,13 @@ function resolveRuntimeIdentity(cwd, configPath, resolveRuntimeNameFromCandidate
132
190
  if (configRuntime) return { runtimeId: configRuntime, confident: true };
133
191
  }
134
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
+
135
200
  // #3045 BLOCKER 2: fall back to the installer-persisted default. Read
136
201
  // defensively — an absent/corrupt/non-object defaults.json is "no signal",
137
202
  // never a resolution failure (this function only ever throws for the
@@ -183,6 +248,15 @@ function resolveHarnessFlag(runtimeId, runtimes) {
183
248
  * run, e.g. a manual Agent() call before any sentinel has been written).
184
249
  */
185
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();
186
260
  const { resolveRuntimeNameFromCandidates } = require('../gsd-core/bin/lib/runtime-name-policy.cjs');
187
261
  const { runtimes } = require('../gsd-core/bin/lib/capability-registry.cjs');
188
262
 
@@ -360,13 +434,27 @@ function evaluateDispatch(data, { clock = Date } = {}) {
360
434
  if (!state.gsdProject) return { action: 'allow' };
361
435
 
362
436
  if (state.error) {
363
- const reason =
364
- `Agent isolation guard: could not read or resolve this project's dispatch-isolation ` +
365
- `configuration ('.planning/config.json' under '${cwd}'). Refusing to dispatch ` +
366
- `subagent_type="${subagentType}" without being able to verify whether isolation is ` +
367
- `required — a guard that cannot verify must not answer "safe" (#3050). Retry once the ` +
368
- `project configuration is readable.`;
369
- return { action: 'block', reason };
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 };
370
458
  }
371
459
 
372
460
  if (state.isolation !== 'harness-worktree') return { action: 'allow' };
@@ -382,7 +470,7 @@ function evaluateDispatch(data, { clock = Date } = {}) {
382
470
  `${parsed.param}="${parsed.value}". Add ${parsed.param}="${parsed.value}" to the Agent() ` +
383
471
  `call so the executor runs in an isolated worktree instead of the primary checkout ` +
384
472
  `(gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md).`;
385
- return { action: 'block', reason };
473
+ return { action: 'block', reason, reasonCode: REASON_CODE.HARNESS_FLAG_MISSING };
386
474
  }
387
475
 
388
476
  /* istanbul ignore next -- stdin adapter, exercised via spawnSync in tests */
@@ -397,7 +485,7 @@ function main() {
397
485
  const data = JSON.parse(input);
398
486
  const decision = evaluateDispatch(data);
399
487
  if (decision.action === 'block') {
400
- const out = { decision: 'block', reason: decision.reason };
488
+ const out = { decision: 'block', reason: decision.reason, reason_code: decision.reasonCode };
401
489
  process.stdout.write(JSON.stringify(out));
402
490
  // Kimi feeds stderr (not stdout) back to the model on exit 2.
403
491
  process.stderr.write(decision.reason);
@@ -425,4 +513,5 @@ module.exports = {
425
513
  resolveHarnessFlag,
426
514
  resolveRegistryIsolation,
427
515
  parseHarnessFlag,
516
+ _setInstallRuntimeMarkerForTests,
428
517
  };
@@ -11,18 +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) never yielded a name in
19
- // the installed tree — at the time it resolved to the synthetic
20
- // {"type":"commonjs"} marker GSD wrote at the config root, which has no `.name`,
21
- // so the background check never reported updates. Since #2544 GSD writes no
22
- // marker there at all, so that require would now fail to resolve outright.
23
- // Either way the name must come from the baked seam, never a walk-up.
24
- const { checkLatestVersion } = require('../gsd-core/bin/check-latest-version.cjs');
25
- 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
+ }
26
69
  // Authoritative list of managed hooks — shared with tests to retire source-grep
27
70
  // assertions (pending-migration-to-typed-ir [#455]).
28
71
  // NOTE: managed-hooks-registry.cjs must be in HOOKS_TO_COPY (scripts/build-hooks.js)
@@ -63,7 +106,7 @@ try {
63
106
  // (e.g., gsd-intel-*.js) must be ignored to avoid permanent stale warnings (#1750)
64
107
  // MANAGED_HOOKS is imported from ./managed-hooks-registry.cjs above.
65
108
 
66
- let staleHooks = [];
109
+ const staleHooks = [];
67
110
  if (configDir) {
68
111
  // #3023: the bundle's directory name is runtime-descriptor-driven (pi stages
69
112
  // it as `gsd-hooks/`), so deriving it as `<configDir>/hooks` silently scanned
@@ -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([\\/]|$)/;
@@ -62,6 +62,15 @@ const os = require('os');
62
62
  // writeCursorHooksJson so the require always resolves post-install.
63
63
  const { resolveStatePath } = require('./lib/cursor-workspace.js');
64
64
  const { readSentinel, VALID_ISOLATION, extractDispatchIdentifiers, sentinelAppliesToDispatch } = require('./lib/isolation-sentinel.js');
65
+ const { REASON_CODE } = require('./lib/isolation-deny-reason.js');
66
+ // #3582: gsd-core/bin/lib/*.cjs (runtime-homes.cjs, worktree-safety.cjs,
67
+ // runtime-name-policy.cjs, capability-registry.cjs — required below, inside
68
+ // resolveIsolationEvidence and resolveFallbackIsolation) are tsc build
69
+ // artifacts (ADR-457), gitignored and absent on a raw plugin-marketplace /
70
+ // git-clone install that never ran `npm run build:lib`. Self-heal once, in
71
+ // evaluateRootIsolation, before any of those four requires run — see the
72
+ // call site below. This module itself depends on nothing under ./lib.
73
+ const { ensureRuntimeBuild, RuntimeBuildError } = require('../gsd-core/bin/ensure-runtime-build.cjs');
65
74
 
66
75
  const MSG_PRESENT =
67
76
  'GSD: Subagent session started — review .planning/STATE.md for the current phase and any blockers before acting.';
@@ -333,6 +342,37 @@ function resolveIsolationDecision(data, { clock = Date, realpath = fs.realpathSy
333
342
  return { action: 'allow' };
334
343
  }
335
344
 
345
+ // ─── #3566: per-install runtime marker ────────────────────────────────────────
346
+ // Same contract as hooks/gsd-agent-isolation-guard.js's readInstallRuntimeMarker
347
+ // (mirroring src/model-resolver.cts #2297): bin/install.js writes
348
+ // `<install>/gsd-core/.gsd-runtime` beside VERSION for every runtime install;
349
+ // this hook ships at `<install>/hooks/`, so the marker is the `gsd-core` sibling
350
+ // of this file's own directory — the same sibling-layout assumption the
351
+ // require('../gsd-core/bin/lib/…') calls below already make. Epic #3473 B3 owns
352
+ // consolidating every marker reader into one shared seam.
353
+ let _installMarkerCache; // undefined = unread; null = known absent; string = value
354
+
355
+ function readInstallRuntimeMarker() {
356
+ if (_installMarkerCache !== undefined) return _installMarkerCache;
357
+ try {
358
+ const markerPath = path.join(__dirname, '..', 'gsd-core', '.gsd-runtime');
359
+ const raw = fs.readFileSync(markerPath, 'utf-8').trim();
360
+ _installMarkerCache = raw || null;
361
+ } catch {
362
+ // No marker: dev/source tree, or an install predating #2297 — "no signal
363
+ // from this rung", never a resolution failure.
364
+ _installMarkerCache = null;
365
+ }
366
+ return _installMarkerCache;
367
+ }
368
+
369
+ // Test seam — same contract as model-resolver.cts's #2297 seam; the dev/source
370
+ // tree has no marker file, so spawned-hook tests (fresh process, no marker)
371
+ // are unaffected.
372
+ function _setInstallRuntimeMarkerForTests(value) {
373
+ _installMarkerCache = value;
374
+ }
375
+
336
376
  /**
337
377
  * Conservative fallback resolution used when the #3045 sentinel is absent or
338
378
  * stale for `root`: re-derive isolation from the registry CAPABILITY, gated
@@ -355,7 +395,8 @@ function resolveIsolationDecision(data, { clock = Date, realpath = fs.realpathSy
355
395
  * otherwise legitimate dispatches, unlike `hooks/gsd-agent-isolation-guard.js`,
356
396
  * which degrades an undeterminable runtime to inert (#3045 MAJOR 2). Aligned
357
397
  * here: an explicit signal is now required — `GSD_RUNTIME` > config.json
358
- * `runtime` key > `~/.gsd/defaults.json` `runtime` (mirrors the Claude hook's
398
+ * `runtime` key > the per-install `.gsd-runtime` marker (#3566) >
399
+ * `~/.gsd/defaults.json` `runtime` (mirrors the Claude hook's
359
400
  * `resolveRuntimeIdentity`; `bin/install.js`'s `writeNonClaudeDefaults`
360
401
  * persists the installed runtime there for every non-Claude install,
361
402
  * including Cursor, so a REAL Cursor+GSD install still resolves confidently
@@ -372,6 +413,13 @@ function resolveFallbackIsolation(root, configPath) {
372
413
  if (!runtimeId && parsedConfig && typeof parsedConfig === 'object' && 'runtime' in parsedConfig) {
373
414
  runtimeId = resolveRuntimeNameFromCandidates(parsedConfig.runtime) || null;
374
415
  }
416
+ if (!runtimeId) {
417
+ // #3566: the per-install marker, above the host-wide defaults — same fix as
418
+ // hooks/gsd-agent-isolation-guard.js's resolveRuntimeIdentity. defaults.json
419
+ // is host-wide and names whichever runtime installed LAST (#2840's poison);
420
+ // the marker describes THIS install (written for every runtime since #2297).
421
+ runtimeId = resolveRuntimeNameFromCandidates(readInstallRuntimeMarker()) || null;
422
+ }
375
423
  if (!runtimeId) {
376
424
  try {
377
425
  const defaultsPath = path.join(os.homedir(), '.gsd', 'defaults.json');
@@ -419,6 +467,28 @@ function evaluateRootIsolation(root, subagentType, { clock = Date, dispatchIds =
419
467
  }
420
468
  if (!isGsdProject) return { action: 'allow' };
421
469
 
470
+ // #3582: self-heal the compiled runtime library BEFORE any of its four
471
+ // downstream requires (resolveFallbackIsolation's two, resolveIsolationEvidence's
472
+ // two — reached only below this point). Checked separately from the
473
+ // sentinel/fallback try block below so a build failure surfaces its own
474
+ // actionable RuntimeBuildError message rather than being folded into the
475
+ // generic "could not read or resolve ... configuration" deny reason (the
476
+ // #3050 misreport this issue exists to fix). Still fails closed either way.
477
+ try {
478
+ ensureRuntimeBuild();
479
+ } catch (err) {
480
+ return {
481
+ action: 'deny',
482
+ reason:
483
+ `GSD subagent isolation guard: cannot resolve this project's dispatch-isolation ` +
484
+ `configuration because the GSD runtime library failed to self-build. ` +
485
+ `${err instanceof RuntimeBuildError ? err.message : String(err && err.message || err)} ` +
486
+ `Refusing to allow this subagent to spawn until the runtime library is built — a guard ` +
487
+ `that cannot verify must not answer "safe" (#3050).`,
488
+ reasonCode: REASON_CODE.RUNTIME_BUILD_FAILED,
489
+ };
490
+ }
491
+
422
492
  let declaredIsolation;
423
493
  try {
424
494
  // #3045 BLOCKER fix: a fresh sentinel is authoritative for THIS
@@ -439,6 +509,7 @@ function evaluateRootIsolation(root, subagentType, { clock = Date, dispatchIds =
439
509
  `Refusing to allow this subagent to spawn without being able to verify whether ` +
440
510
  `isolation is required — a guard that cannot verify must not answer "safe" (#3050). ` +
441
511
  `Retry once the project configuration is readable.`,
512
+ reasonCode: REASON_CODE.CONFIG_UNREADABLE,
442
513
  };
443
514
  }
444
515
 
@@ -455,6 +526,7 @@ function evaluateRootIsolation(root, subagentType, { clock = Date, dispatchIds =
455
526
  `"harness-worktree", but the subagentStart payload for this dispatch carries no usable ` +
456
527
  `subagent_type. Refusing to allow it to spawn without being able to confirm whether it ` +
457
528
  `is a GSD executor — a guard that cannot verify must not answer "safe" (#3050).`,
529
+ reasonCode: REASON_CODE.NO_SUBAGENT_TYPE,
458
530
  };
459
531
  }
460
532
 
@@ -471,6 +543,7 @@ function evaluateRootIsolation(root, subagentType, { clock = Date, dispatchIds =
471
543
  `could not be determined (git did not respond). Refusing to allow subagent_type=` +
472
544
  `"${subagentType}" to spawn without being able to verify isolation — a guard that ` +
473
545
  `cannot verify must not answer "safe" (#3050). Retry once git is responsive.`,
546
+ reasonCode: REASON_CODE.CANNOT_DETERMINE_ISOLATION,
474
547
  };
475
548
  }
476
549
 
@@ -483,6 +556,7 @@ function evaluateRootIsolation(root, subagentType, { clock = Date, dispatchIds =
483
556
  `directly, with no consent and no warning. Start an isolated session first (the ` +
484
557
  `"--worktree" CLI flag or the "/worktree" chat command; Cursor manages these worktrees ` +
485
558
  `under "~/.cursor/worktrees/") and retry.`,
559
+ reasonCode: REASON_CODE.NOT_ISOLATED_WORKTREE,
486
560
  };
487
561
  }
488
562
 
@@ -532,7 +606,7 @@ function main() {
532
606
  decision = { action: 'allow' };
533
607
  }
534
608
  if (decision.action === 'deny') {
535
- const out = { permission: 'deny', user_message: decision.reason };
609
+ const out = { permission: 'deny', user_message: decision.reason, reason_code: decision.reasonCode };
536
610
  if (additionalContext !== null) out.additional_context = additionalContext;
537
611
  process.stdout.write(JSON.stringify(out));
538
612
  return;
@@ -557,4 +631,5 @@ module.exports = {
557
631
  resolveFallbackIsolation,
558
632
  resolveIsolationEvidence,
559
633
  getWorkspaceRoots,
634
+ _setInstallRuntimeMarkerForTests,
560
635
  };
@@ -20,13 +20,14 @@
20
20
 
21
21
  'use strict';
22
22
 
23
- let raw = '';
24
23
  const stdinTimeout = setTimeout(() => {
25
24
  process.exit(0);
26
25
  }, 10000);
27
26
 
28
27
  process.stdin.setEncoding('utf8');
29
- process.stdin.on('data', (chunk) => { raw += chunk; });
28
+ // Drain stdin (puts the stream into flowing mode so 'end' fires); the
29
+ // payload itself is unused — this hook responds unconditionally.
30
+ process.stdin.on('data', () => {});
30
31
  process.stdin.on('end', () => {
31
32
  clearTimeout(stdinTimeout);
32
33
  try {
@@ -11,26 +11,14 @@
11
11
  // The goal is to surface suspicious content so the orchestrator can inspect it,
12
12
  // not to create false-positive deadlocks.
13
13
 
14
- const fs = require('fs');
15
14
  const path = require('path');
16
15
 
17
- // Prompt injection patterns (subset of security.cjs patterns, inlined for hook independence)
18
- const INJECTION_PATTERNS = [
19
- /ignore\s+(all\s+)?previous\s+instructions/i,
20
- /ignore\s+(all\s+)?above\s+instructions/i,
21
- /disregard\s+(all\s+)?previous/i,
22
- /forget\s+(all\s+)?(your\s+)?instructions/i,
23
- /override\s+(system|previous)\s+(prompt|instructions)/i,
24
- /you\s+are\s+now\s+(?:a|an|the)\s+/i,
25
- /act\s+as\s+(?:a|an|the)\s+(?!plan|phase|wave)/i,
26
- /pretend\s+(?:you(?:'re| are)\s+|to\s+be\s+)/i,
27
- /from\s+now\s+on,?\s+you\s+(?:are|will|should|must)/i,
28
- /(?:print|output|reveal|show|display|repeat)\s+(?:your\s+)?(?:system\s+)?(?:prompt|instructions)/i,
29
- /<\/?(?:system|assistant|human)>/i,
30
- /\[SYSTEM\]/i,
31
- /\[INST\]/i,
32
- /<<\s*SYS\s*>>/i,
33
- ];
16
+ // Prompt injection patterns — shared with gsd-read-injection-scanner.js via
17
+ // hooks/lib/injection-patterns.js so the two surfaces cannot drift (#3504).
18
+ // Deliberately a subset of security.cjs's set: hooks stay loadable without the
19
+ // compiled lib tree. Staging of the lib helper is allowlisted in
20
+ // GSD_HOOK_LIB_FILES (bin/install.js).
21
+ const { INJECTION_PATTERNS } = require('./lib/injection-patterns.js');
34
22
 
35
23
  // #2304: Kimi's native hook bus delivers Kimi's tool vocabulary in the payload
36
24
  // (Write → WriteFile, Edit/MultiEdit → StrReplaceFile) while the [[hooks]]
@@ -153,8 +141,21 @@ process.stdin.on('end', () => {
153
141
  process.exit(0);
154
142
  }
155
143
 
156
- // Get the content being written
157
- const content = data.tool_input?.content || data.tool_input?.new_string || '';
144
+ // Get the content being written. #3504 (isolated review finding 3): the
145
+ // bare `||` chain handed a NON-STRING truthy `content` straight to
146
+ // pattern.test(), where ToString can throw ("Cannot convert object to a
147
+ // primitive value" for `{"toString": null}`) into the outer catch — the
148
+ // exact crash-to-allow class #2547/#2595 hardened inside
149
+ // normalizeKimiPayload, unreached on this read. Guarded selection: take
150
+ // the first field that is a string, or String-coerces without throwing,
151
+ // so a poisoned `content` no longer shadows a real `new_string`.
152
+ let content = '';
153
+ for (const candidate of [data.tool_input?.content, data.tool_input?.new_string]) {
154
+ if (typeof candidate === 'string' && candidate) { content = candidate; break; }
155
+ if (candidate && typeof candidate !== 'string') {
156
+ try { const s = String(candidate); if (s) { content = s; break; } } catch { /* keep looking */ }
157
+ }
158
+ }
158
159
  if (!content) {
159
160
  process.exit(0);
160
161
  }