@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
@@ -22,6 +22,15 @@ const WORKSTREAM_SESSION_ENV_KEYS = [
22
22
  'GSD_SESSION_KEY',
23
23
  'CODEX_THREAD_ID',
24
24
  'CLAUDE_SESSION_ID',
25
+ // #3557 — Claude Code (≥ 2.1.132) exports its session id to Bash-tool
26
+ // subprocesses as CLAUDE_CODE_SESSION_ID. Without it the probe returned
27
+ // null on Claude Code, so every concurrent session in a working tree
28
+ // shared the single .planning/active-workstream pointer and cross-
29
+ // workstream STATE.md writes landed silently in the wrong file. Inserted
30
+ // beside the other runtime keys without reordering any existing entry;
31
+ // ahead of CLAUDE_CODE_SSE_PORT so the canonical id wins when both are
32
+ // present (runtime identity outranks terminal identity in this list).
33
+ 'CLAUDE_CODE_SESSION_ID',
25
34
  'CLAUDE_CODE_SSE_PORT',
26
35
  'OPENCODE_SESSION_ID',
27
36
  'GEMINI_SESSION_ID',
@@ -183,21 +192,134 @@ function pickActiveWorkstreamAdapter(cwd, opts = {}) {
183
192
  }
184
193
  return createSharedPointerAdapter(cwd);
185
194
  }
186
- function getActiveWorkstream(cwd, opts = {}) {
187
- const adapter = pickActiveWorkstreamAdapter(cwd, opts);
188
- if (!adapter)
189
- return null;
190
- const name = adapter.read();
191
- if (!name || !validateWorkstreamName(name)) {
192
- adapter.clear();
193
- return null;
195
+ /**
196
+ * Read-resolution chain for getActiveWorkstream/peekActiveWorkstream (#3579).
197
+ *
198
+ * pickActiveWorkstreamAdapter (above) picks exactly one adapter and remains
199
+ * the seam for WRITE paths (set/clear), where "which pointer do I mutate" has
200
+ * only one right answer: the session pointer when a session key exists,
201
+ * otherwise the shared marker. Reads are different — a session that has
202
+ * never called `workstream use` has no opinion of its own, so it should
203
+ * inherit the repo-wide `.planning/active-workstream` marker rather than
204
+ * resolve to nothing. This returns an ORDERED chain: [owned, ...fallbacks].
205
+ * `chain[0]` ("owned") is exactly what pickActiveWorkstreamAdapter would have
206
+ * returned — resolveFromChain() self-heals only chain[0], never a fallback,
207
+ * so one session's read can never delete another scope's marker. Fallbacks
208
+ * are consulted ONLY when chain[0].read() comes back absent/empty; a session
209
+ * with its own (even stale/invalid) pointer never falls through — that is
210
+ * the isolation guarantee and it must not be weakened by inheritance.
211
+ */
212
+ function pickActiveWorkstreamAdapterChain(cwd, opts = {}) {
213
+ if (opts.activeWorkstreamAdapter) {
214
+ return [opts.activeWorkstreamAdapter];
194
215
  }
195
- const wsDir = node_path_1.default.join(planningRoot(cwd), 'workstreams', name);
196
- if (!node_fs_1.default.existsSync(wsDir)) {
197
- adapter.clear();
216
+ // #3579 item 3: when a caller supplies `opts.activeWorkstreamAdapters` at
217
+ // all, honor ONLY what it provides. The prior `|| createXPointerAdapter(...)`
218
+ // fallback synthesized a REAL filesystem adapter for whichever half a test
219
+ // double omitted — so a test injecting only `{ session }` silently touched
220
+ // the real shared marker file, and one injecting only `{ shared }` silently
221
+ // touched the real session-scoped tmp file. A missing half now gets a
222
+ // no-op in-memory adapter (always reads null) instead — this preserves the
223
+ // chain[0]-is-owned / rest-are-fallback shape resolveFromChain relies on
224
+ // without ever reaching disk. A caller that wants a real adapter for one
225
+ // half can still construct and pass it explicitly.
226
+ const injected = opts.activeWorkstreamAdapters;
227
+ const sessionKey = getWorkstreamSessionKey();
228
+ if (!sessionKey) {
229
+ const shared = injected
230
+ ? (injected.shared ?? createMemoryPointerAdapter(null))
231
+ : createSharedPointerAdapter(cwd);
232
+ return [shared];
233
+ }
234
+ const session = injected
235
+ ? (injected.session ?? createMemoryPointerAdapter(null))
236
+ : createSessionScopedPointerAdapter(cwd, sessionKey);
237
+ const shared = injected
238
+ ? (injected.shared ?? createMemoryPointerAdapter(null))
239
+ : createSharedPointerAdapter(cwd);
240
+ return session ? [session, shared] : [shared];
241
+ }
242
+ /**
243
+ * Shared "does this stored name resolve" predicate — format-valid AND its
244
+ * workstream directory exists. Factored out so resolveFromChain's owned/
245
+ * fallback arms (and diagnoseUnresolvedActiveWorkstream, #3579 item 1) share
246
+ * one definition of "resolvable" instead of re-deriving the same two checks.
247
+ */
248
+ function resolvesToExistingWorkstream(cwd, name) {
249
+ if (!name || !validateWorkstreamName(name))
250
+ return false;
251
+ return node_fs_1.default.existsSync(node_path_1.default.join(planningRoot(cwd), 'workstreams', name));
252
+ }
253
+ /**
254
+ * Resolves a stored workstream name by walking an adapter chain.
255
+ *
256
+ * chain[0] is "owned" by this resolution: an absent/empty read falls through
257
+ * to the next adapter, but a present-and-bad read (invalid name, or a name
258
+ * whose workstream dir no longer exists) is resolved right there — self-
259
+ * healed via adapter.clear() when `selfHeal` is true, and never consulted
260
+ * further. Anything after chain[0] is a read-only fallback (the inherited
261
+ * marker): a bad value there resolves to null WITHOUT ever calling clear(),
262
+ * so a pointer-less session's read can never delete the shared marker that
263
+ * other sessions/scopes still depend on.
264
+ */
265
+ function resolveFromChain(cwd, chain, selfHeal) {
266
+ if (chain.length === 0)
198
267
  return null;
268
+ const [owned, ...fallbacks] = chain;
269
+ const ownedName = owned.read();
270
+ if (ownedName) {
271
+ if (!resolvesToExistingWorkstream(cwd, ownedName)) {
272
+ if (selfHeal)
273
+ owned.clear();
274
+ return null;
275
+ }
276
+ return ownedName;
277
+ }
278
+ for (const adapter of fallbacks) {
279
+ const name = adapter.read();
280
+ if (resolvesToExistingWorkstream(cwd, name))
281
+ return name;
282
+ }
283
+ return null;
284
+ }
285
+ /**
286
+ * Diagnostic sibling of resolveFromChain (#3579 item 1). getActiveWorkstream/
287
+ * peekActiveWorkstream collapse EVERY unresolvable case to `null`, which is
288
+ * exactly right for routing — but a fail-safe guard reporting "no active
289
+ * workstream is set" to an operator needs to distinguish two very different
290
+ * situations that both produce that same `null`:
291
+ *
292
+ * (a) no marker/pointer exists anywhere in the chain at all, vs.
293
+ * (b) a marker/pointer EXISTS (names a value) but that value didn't
294
+ * resolve — either the name fails validateWorkstreamName, or it's a
295
+ * well-formed name whose `workstreams/<name>` directory is missing.
296
+ *
297
+ * Walks the same chain resolveFromChain uses and, for the first adapter that
298
+ * held a non-empty raw value, reports why it didn't resolve. Read-only: never
299
+ * calls adapter.clear() (mirrors peekActiveWorkstream, not getActiveWorkstream
300
+ * — a diagnostic read must not have side effects). Reuses
301
+ * resolvesToExistingWorkstream so this can never disagree with the actual
302
+ * resolution predicate above.
303
+ */
304
+ function diagnoseUnresolvedActiveWorkstream(cwd, opts = {}) {
305
+ const chain = pickActiveWorkstreamAdapterChain(cwd, opts);
306
+ for (const adapter of chain) {
307
+ const raw = adapter.read();
308
+ if (!raw)
309
+ continue;
310
+ if (resolvesToExistingWorkstream(cwd, raw))
311
+ continue;
312
+ return {
313
+ present: true,
314
+ value: raw,
315
+ reason: validateWorkstreamName(raw) ? 'missing_workstream_dir' : 'invalid_name',
316
+ };
199
317
  }
200
- return name;
318
+ return { present: false, value: null, reason: null };
319
+ }
320
+ function getActiveWorkstream(cwd, opts = {}) {
321
+ const chain = pickActiveWorkstreamAdapterChain(cwd, opts);
322
+ return resolveFromChain(cwd, chain, true);
201
323
  }
202
324
  /**
203
325
  * Read-only sibling of getActiveWorkstream (#2850): identical resolution —
@@ -212,16 +334,8 @@ function getActiveWorkstream(cwd, opts = {}) {
212
334
  * pointer file is left exactly as it was for whatever created it to fix.
213
335
  */
214
336
  function peekActiveWorkstream(cwd, opts = {}) {
215
- const adapter = pickActiveWorkstreamAdapter(cwd, opts);
216
- if (!adapter)
217
- return null;
218
- const name = adapter.read();
219
- if (!name || !validateWorkstreamName(name))
220
- return null;
221
- const wsDir = node_path_1.default.join(planningRoot(cwd), 'workstreams', name);
222
- if (!node_fs_1.default.existsSync(wsDir))
223
- return null;
224
- return name;
337
+ const chain = pickActiveWorkstreamAdapterChain(cwd, opts);
338
+ return resolveFromChain(cwd, chain, false);
225
339
  }
226
340
  function setActiveWorkstream(cwd, name, opts = {}) {
227
341
  const adapter = pickActiveWorkstreamAdapter(cwd, opts);
@@ -311,8 +425,10 @@ module.exports = {
311
425
  createSessionScopedPointerAdapter,
312
426
  createMemoryPointerAdapter,
313
427
  pickActiveWorkstreamAdapter,
428
+ pickActiveWorkstreamAdapterChain,
314
429
  getActiveWorkstream,
315
430
  peekActiveWorkstream,
431
+ diagnoseUnresolvedActiveWorkstream,
316
432
  setActiveWorkstream,
317
433
  clearActiveWorkstream,
318
434
  parseCliWorkstream,
@@ -19,14 +19,52 @@ const modelProfiles = require("./model-profiles.cjs");
19
19
  const { MODEL_PROFILES } = modelProfiles;
20
20
  const runtime_homes_cjs_1 = require("./runtime-homes.cjs");
21
21
  const runtime_name_policy_cjs_1 = require("./runtime-name-policy.cjs");
22
+ // #3242 — model-catalog is a genuine leaf (only node:path + its own JSON), which is
23
+ // exactly why Phase 1 (#3241) moved isAnthropicFlavoredModel there: this module can
24
+ // consume it without dragging model-resolver's config-loader chain into a
25
+ // pure read/verify surface.
26
+ const model_catalog_cjs_1 = require("./model-catalog.cjs");
27
+ // #3243 — the Codex `.toml` block-range/BOM/scan primitives moved into the typed
28
+ // IR module (Phase 3), which this reader now imports rather than defining
29
+ // locally. Behavior is unchanged: scanTomlLines/stripBOM here are the exact
30
+ // same lenient functions that used to live in this file — see
31
+ // codex-agent-toml.cts's module header for the reader/writer reconciliation.
32
+ const codex_agent_toml_cjs_1 = require("./codex-agent-toml.cjs");
33
+ /**
34
+ * Frozen reason enum for {@link checkCodexModelPosture}. Per CONTRIBUTING's
35
+ * typed-IR rule ("Error / status / reason → a frozen enum"): callers and tests
36
+ * assert on these wire values, never on prose. Adding a member is a deliberate
37
+ * three-way coordinated change — enum, emitting site, and the enum-lock test.
38
+ */
39
+ const POSTURE_REASON = Object.freeze({
40
+ ANTHROPIC_FLAVORED_MODEL: 'anthropic_flavored_model',
41
+ ORPHANED_REASONING_EFFORT: 'orphaned_reasoning_effort',
42
+ UNREADABLE: 'unreadable',
43
+ NOT_CODEX: 'not_codex',
44
+ AGENTS_DIR_MISSING: 'agents_dir_missing',
45
+ });
46
+ // Matches the value-truncation convention in bin/install.js's
47
+ // _warnCodexModelOverrideDropped: values over 64 chars are capped so an
48
+ // oversized or secret-shaped config value can never reach a report in full.
49
+ function truncatePostureValue(value) {
50
+ return value.length > 64 ? `${value.slice(0, 64)}…` : value;
51
+ }
22
52
  /**
23
53
  * Resolve the agents directory for the given runtime.
24
54
  *
25
55
  * Priority:
26
56
  * 1. GSD_AGENTS_DIR env var (explicit override, any runtime)
27
- * 2. For claude runtime: __dirname-relative path (agents/ sibling of gsd-core/)
28
- * This is correct for both repo runs and real installs (the runtime config dir's
29
- * agents/ folder) because gsd-tools.cjs lives inside gsd-core/bin/ in both cases.
57
+ * 2. For claude runtime: __dirname-relative path (agents/ sibling of
58
+ * gsd-core/) — correct for repo runs and runtime-config-dir installs,
59
+ * where the sibling agents/ IS the user's agents dir — UNLESS that path
60
+ * carries an exact node_modules segment. gsd-tools.cjs lives inside
61
+ * gsd-core/bin/ in every install shape, but on an npm-global install
62
+ * gsd-core/ sits inside the package (not the runtime config dir) and the
63
+ * package ships its own agents/, so the install-relative path resolves
64
+ * to the bundled copy and the check validates the package against
65
+ * itself — agents_installed can never be false. In that case resolve
66
+ * getGlobalConfigDir('claude')/agents (honours CLAUDE_CONFIG_DIR) like
67
+ * every other runtime (#3203).
30
68
  * 3. For non-claude runtimes with a manifest-backed project-local install:
31
69
  * <projectRoot>/<localConfigDir>/agents (or <projectRoot>/agents when
32
70
  * the runtime's local install targets the project root). Requiring the
@@ -43,7 +81,16 @@ function getAgentsDir(runtime, projectRoot) {
43
81
  }
44
82
  const resolved = runtime ?? (process.env['GSD_RUNTIME'] || 'claude');
45
83
  if (resolved === 'claude') {
46
- return node_path_1.default.join(__dirname, '..', '..', '..', 'agents');
84
+ const installRelative = node_path_1.default.join(__dirname, '..', '..', '..', 'agents');
85
+ // #3203: a lexical guard, not an install-shape test. It targets the
86
+ // layouts where the sibling agents/ is the package's own bundled copy and
87
+ // the check would otherwise validate the package against itself; a path
88
+ // merely carrying a directory of that name resolves the same way, and a
89
+ // non-empty GSD_AGENTS_DIR overrides both.
90
+ if (installRelative.split(node_path_1.default.sep).includes('node_modules')) {
91
+ return node_path_1.default.join((0, runtime_homes_cjs_1.getGlobalConfigDir)('claude'), 'agents');
92
+ }
93
+ return installRelative;
47
94
  }
48
95
  if (projectRoot) {
49
96
  // eslint-disable-next-line @typescript-eslint/no-require-imports
@@ -94,18 +141,7 @@ function checkAgentsInstalled(runtime, projectRoot) {
94
141
  };
95
142
  }
96
143
  for (const agent of expectedAgents) {
97
- const agentFile = node_path_1.default.join(agentsDir, `${agent}.md`);
98
- const agentFileCopilot = node_path_1.default.join(agentsDir, `${agent}.agent.md`);
99
- const agentFileCodex = node_path_1.default.join(agentsDir, `${agent}.toml`);
100
- const agentFileKimiYaml = node_path_1.default.join(agentsDir, 'subagents', `${agent}.yaml`);
101
- const agentFileKimiPrompt = node_path_1.default.join(agentsDir, 'subagents', `${agent}.md`);
102
- const kimiAgentInstalled = resolvedRuntime === 'kimi' &&
103
- node_fs_1.default.existsSync(agentFileKimiYaml) &&
104
- node_fs_1.default.existsSync(agentFileKimiPrompt);
105
- if (node_fs_1.default.existsSync(agentFile) ||
106
- node_fs_1.default.existsSync(agentFileCopilot) ||
107
- node_fs_1.default.existsSync(agentFileCodex) ||
108
- kimiAgentInstalled) {
144
+ if (agentFileExists(agentsDir, agent, resolvedRuntime)) {
109
145
  installed.push(agent);
110
146
  }
111
147
  else {
@@ -119,22 +155,13 @@ function checkAgentsInstalled(runtime, projectRoot) {
119
155
  // when the plain presence check above passed (e.g. .md present, .toml absent).
120
156
  // If no manifest is found the check is a no-op (graceful for claude/bundled).
121
157
  const incomplete = [];
122
- const manifestPath = node_path_1.default.join(node_path_1.default.dirname(agentsDir), 'gsd-file-manifest.json');
123
- let manifestFiles = {};
124
- try {
125
- const raw = node_fs_1.default.readFileSync(manifestPath, 'utf8');
126
- const parsed = JSON.parse(raw);
127
- if (parsed !== null &&
128
- typeof parsed === 'object' &&
129
- 'files' in parsed &&
130
- typeof parsed['files'] === 'object' &&
131
- parsed['files'] !== null) {
132
- manifestFiles = parsed['files'];
133
- }
134
- }
135
- catch {
136
- // No manifest or unreadable — completeness check is skipped
137
- }
158
+ // #2872: the manifest read is the Installer Migration Module's, not a
159
+ // fourth private copy of it. Lazily required — matching this file's own
160
+ // capability-registry idiom — so a pure read/verify surface on the
161
+ // init/verify hot path takes no new static dependency.
162
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
163
+ const { readInstallManifest } = require('./installer-migrations.cjs');
164
+ const manifestFiles = readInstallManifest(node_path_1.default.dirname(agentsDir)).files;
138
165
  if (Object.keys(manifestFiles).length > 0) {
139
166
  for (const agent of expectedAgents) {
140
167
  // Find all manifest keys that belong to this agent:
@@ -169,7 +196,178 @@ function checkAgentsInstalled(runtime, projectRoot) {
169
196
  agent_runtime: resolvedRuntime,
170
197
  };
171
198
  }
199
+ /**
200
+ * Validate Codex `.toml` agent files for Anthropic-flavored `model` pins and
201
+ * orphaned `model_reasoning_effort` values (ADR-2313 D6, #3242).
202
+ *
203
+ * A new sibling export to {@link checkAgentsInstalled}, deliberately — that
204
+ * function carries 33 upstream dependents and cyclomatic complexity 25, so this
205
+ * posture check gets zero new branches there (see 40-design.md "Rejected" #1).
206
+ * Presence is `checkAgentsInstalled`'s job; this function's job starts only once
207
+ * the runtime is confirmed `codex` and only inspects posture, never presence.
208
+ *
209
+ * Read-only: detects, never repairs (repair is Phase 3, #3243).
210
+ *
211
+ * @param runtime - the active runtime name; defaults to GSD_RUNTIME env, then 'claude'
212
+ * @param projectRoot - canonical project root for local-install discovery
213
+ */
214
+ function checkCodexModelPosture(runtime, projectRoot) {
215
+ // Short-circuit BEFORE any filesystem access: a non-codex runtime must never
216
+ // have its agents directory resolved or a stray .toml inspected, however
217
+ // violating that file's contents would be if it were ever read (#3242 row 25).
218
+ const resolvedRuntime = runtime ?? (process.env['GSD_RUNTIME'] || 'claude');
219
+ if (resolvedRuntime !== 'codex') {
220
+ return {
221
+ ok: true,
222
+ violations: [],
223
+ checked: [],
224
+ agents_dir: '',
225
+ agent_runtime: resolvedRuntime,
226
+ reason: POSTURE_REASON.NOT_CODEX,
227
+ };
228
+ }
229
+ const agentsDir = getAgentsDir(resolvedRuntime, projectRoot);
230
+ if (!node_fs_1.default.existsSync(agentsDir)) {
231
+ // Presence is checkAgentsInstalled's job — an absent agents dir here is a
232
+ // distinct, non-violating outcome, not a failure of this check.
233
+ return {
234
+ ok: true,
235
+ violations: [],
236
+ checked: [],
237
+ agents_dir: agentsDir,
238
+ agent_runtime: resolvedRuntime,
239
+ reason: POSTURE_REASON.AGENTS_DIR_MISSING,
240
+ };
241
+ }
242
+ // Skip symlinks — matches cmdEffortSync's existing idiom in commands.cts
243
+ // ("Skip symlinks — only write regular files..."). Here the risk is reading
244
+ // (not writing) through a symlink: readFileSync follows symlinks, so an
245
+ // agents-dir symlink pointing at an arbitrary file would let that target's
246
+ // contents be echoed into this function's `value` field. A symlinked agent
247
+ // file is a structural install choice (checkAgentsInstalled's territory),
248
+ // not a model-content posture defect, so it is silently excluded from
249
+ // `checked` rather than reported as a distinct violation — same shape as
250
+ // cmdEffortSync, which silently drops symlinks from its file list rather
251
+ // than inventing a new skip/violation reason.
252
+ const tomlFiles = node_fs_1.default
253
+ .readdirSync(agentsDir)
254
+ .filter((entry) => {
255
+ if (!entry.endsWith('.toml'))
256
+ return false;
257
+ try {
258
+ return node_fs_1.default.lstatSync(node_path_1.default.join(agentsDir, entry)).isFile();
259
+ }
260
+ catch {
261
+ return false;
262
+ }
263
+ })
264
+ .sort();
265
+ const checked = [];
266
+ const violations = [];
267
+ for (const entry of tomlFiles) {
268
+ const agentName = entry.slice(0, -'.toml'.length);
269
+ const filePath = node_path_1.default.join(agentsDir, entry);
270
+ checked.push(agentName);
271
+ let raw;
272
+ try {
273
+ raw = node_fs_1.default.readFileSync(filePath, 'utf8');
274
+ }
275
+ catch {
276
+ // Never throws, never silently skips — an unreadable file is reported and
277
+ // the loop continues checking the rest (40-design.md "Rejected" #5).
278
+ violations.push({ agent: agentName, file: filePath, reason: POSTURE_REASON.UNREADABLE });
279
+ continue;
280
+ }
281
+ const { model, hasReasoningEffort } = (0, codex_agent_toml_cjs_1.scanTomlLines)((0, codex_agent_toml_cjs_1.stripBOM)(raw));
282
+ if (model !== null && (0, model_catalog_cjs_1.isAnthropicFlavoredModel)(model)) {
283
+ violations.push({
284
+ agent: agentName,
285
+ file: filePath,
286
+ reason: POSTURE_REASON.ANTHROPIC_FLAVORED_MODEL,
287
+ value: truncatePostureValue(model),
288
+ });
289
+ }
290
+ else if (model === null && hasReasoningEffort) {
291
+ // #838 coupling: a reasoning-effort pin with no model pin means Codex
292
+ // inherits the session model while the effort pin silently disagrees.
293
+ violations.push({
294
+ agent: agentName,
295
+ file: filePath,
296
+ reason: POSTURE_REASON.ORPHANED_REASONING_EFFORT,
297
+ });
298
+ }
299
+ }
300
+ return {
301
+ ok: violations.length === 0,
302
+ violations,
303
+ checked,
304
+ agents_dir: agentsDir,
305
+ agent_runtime: resolvedRuntime,
306
+ };
307
+ }
308
+ /**
309
+ * Probe a single agents dir for `<name>` across runtime filename variants.
310
+ * Mirrors {@link checkAgentsInstalled}'s probe (`.md`, `.agent.md`, `.toml`,
311
+ * and the kimi `subagents/<name>.{yaml,md}` pair) so the two can never disagree
312
+ * about which on-disk shapes count as "installed". Not exported — internal to
313
+ * {@link resolveAgentHint}.
314
+ */
315
+ function agentFileExists(agentsDir, name, runtime) {
316
+ const base = node_path_1.default.join(agentsDir, `${name}.md`);
317
+ const copilot = node_path_1.default.join(agentsDir, `${name}.agent.md`);
318
+ const codex = node_path_1.default.join(agentsDir, `${name}.toml`);
319
+ if (node_fs_1.default.existsSync(base) || node_fs_1.default.existsSync(copilot) || node_fs_1.default.existsSync(codex)) {
320
+ return true;
321
+ }
322
+ // kimi requires BOTH the persona yaml and the prompt md (same as checkAgentsInstalled).
323
+ const kimiYaml = node_path_1.default.join(agentsDir, 'subagents', `${name}.yaml`);
324
+ const kimiPrompt = node_path_1.default.join(agentsDir, 'subagents', `${name}.md`);
325
+ return runtime === 'kimi' && node_fs_1.default.existsSync(kimiYaml) && node_fs_1.default.existsSync(kimiPrompt);
326
+ }
327
+ /**
328
+ * Resolve a per-plan `agent_hint` specialist name to a dispatchable subagent
329
+ * type on the active runtime (#1689). Unlike {@link checkAgentsInstalled},
330
+ * which validates the fixed GSD roster, this answers "does an agent file for
331
+ * this ARBITRARY name exist in the active runtime's agent dir(s)?" — so a plan
332
+ * can opt into a domain specialist (e.g. a Flutter engineer) that shares the
333
+ * gsd-executor contract without being part of the built-in roster.
334
+ *
335
+ * Probes BOTH the runtime-canonical agents dir ({@link getAgentsDir}, which
336
+ * honors `GSD_AGENTS_DIR`, project-local manifest-backed installs, and the
337
+ * claude install-relative path) AND the runtime's global config agents dir, so
338
+ * a specialist installed at either level is recognized. The decision in #1689
339
+ * explicitly requires consulting the active runtime's agent dir rather than
340
+ * only the Claude runtime's user-global and project-local agent dirs.
341
+ *
342
+ * @returns the name when a matching agent file exists; `null` when it does not
343
+ * (the caller falls back to `gsd-executor`). An empty/whitespace name always
344
+ * returns `null`.
345
+ */
346
+ function resolveAgentHint(name, runtime, projectRoot) {
347
+ const trimmed = String(name ?? '').trim();
348
+ if (trimmed === '')
349
+ return null;
350
+ // A hint is a bare agent name. Reject path separators and `..` so a value
351
+ // like `../../README` cannot path-traverse out of the agents dir via
352
+ // path.join and match an unrelated file — that would echo an invalid
353
+ // subagent_type and block the wave, defeating fail-closed resolution.
354
+ if (trimmed.includes('/') || trimmed.includes('\\') || trimmed.includes('..'))
355
+ return null;
356
+ const resolvedRuntime = runtime ?? (process.env['GSD_RUNTIME'] || 'claude');
357
+ const candidateDirs = new Set();
358
+ candidateDirs.add(getAgentsDir(resolvedRuntime, projectRoot));
359
+ candidateDirs.add(node_path_1.default.join((0, runtime_homes_cjs_1.getGlobalConfigDir)(resolvedRuntime), 'agents'));
360
+ for (const dir of candidateDirs) {
361
+ if (agentFileExists(dir, trimmed, resolvedRuntime)) {
362
+ return trimmed;
363
+ }
364
+ }
365
+ return null;
366
+ }
172
367
  module.exports = {
173
368
  getAgentsDir,
174
369
  checkAgentsInstalled,
370
+ checkCodexModelPosture,
371
+ POSTURE_REASON,
372
+ resolveAgentHint,
175
373
  };
@@ -63,6 +63,7 @@ exports.parseCoverageMatrix = parseCoverageMatrix;
63
63
  exports.validateCoverageMatrix = validateCoverageMatrix;
64
64
  exports.renderCoverageMatrix = renderCoverageMatrix;
65
65
  const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
66
+ const pattern_cjs_1 = require("./pattern.cjs");
66
67
  /**
67
68
  * Curated default trigger vocabulary. ADDITIVE-ONLY (Hyrum's Law). Tunable via
68
69
  * the `terms` parameter.
@@ -156,9 +157,6 @@ function resolveTerms(terms) {
156
157
  };
157
158
  return { verbs: merge('verbs'), nouns: merge('nouns') };
158
159
  }
159
- function escapeRegex(s) {
160
- return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
161
- }
162
160
  function makeSnippet(line, anchor) {
163
161
  const cleaned = line.replace(/\s+/g, ' ').trim();
164
162
  if (cleaned.length <= 120)
@@ -402,10 +400,10 @@ function detectApiIntegration(text, terms) {
402
400
  // Trailing boundary is a LOOKAHEAD (not consumed) so back-to-back terms
403
401
  // separated by one boundary char are both found.
404
402
  const verbRe = hasCompoundTerms
405
- ? new RegExp('(^|[^a-zA-Z0-9])(' + effective.verbs.map(escapeRegex).join('|') + ')(?=[^a-zA-Z0-9]|$)', 'gi')
403
+ ? new RegExp('(^|[^a-zA-Z0-9])(' + effective.verbs.map(pattern_cjs_1.escapeRegex).join('|') + ')(?=[^a-zA-Z0-9]|$)', 'gi')
406
404
  : null;
407
405
  const nounRe = hasCompoundTerms
408
- ? new RegExp('(^|[^a-zA-Z0-9])(' + effective.nouns.map(escapeRegex).join('|') + ')(?=[^a-zA-Z0-9]|$)', 'gi')
406
+ ? new RegExp('(^|[^a-zA-Z0-9])(' + effective.nouns.map(pattern_cjs_1.escapeRegex).join('|') + ')(?=[^a-zA-Z0-9]|$)', 'gi')
409
407
  : null;
410
408
  const surfaceRe = new RegExp(SERVICE_SURFACE_API_RE.source, 'g');
411
409
  const nounSet = new Set(effective.nouns);
@@ -27,6 +27,9 @@ exports.CANONICAL_EXACT = new Set([
27
27
  'config.json',
28
28
  'CLAUDE.md',
29
29
  'RETROSPECTIVE.md',
30
+ 'WINDOWS.md', // #3224: broken-windows ledger (src/broken-windows.cts, LEDGER_FILE_NAME)
31
+ 'STATE-ARCHIVE.md', // state.cts's cmdStatePrune writes this at the .planning/ root
32
+ 'milestone.lock', // #3311: milestone (phase + session) claim (src/milestone-lock.cts); persistent, unlike the transient STATE.md.lock/WAITING.json
30
33
  ]);
31
34
  // Pattern-match canonical file names (regex tests on the basename)
32
35
  // Each pattern includes the name of the workflow that produces it as a comment.
@@ -37,6 +37,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
37
37
  exports.DEFAULT_ASSUMPTION_DELTA_TERMS = void 0;
38
38
  exports.detectAssumptionDelta = detectAssumptionDelta;
39
39
  const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
40
+ const pattern_cjs_1 = require("./pattern.cjs");
40
41
  /**
41
42
  * Curated default trigger vocabulary. Each kind lists cue terms that signal a
42
43
  * core-assumption monopoly has been lost. ADDITIVE-ONLY (Hyrum's Law: once
@@ -168,7 +169,7 @@ function detectAssumptionDelta(text, terms) {
168
169
  continue;
169
170
  // Word-boundary anchored, case-insensitive — same shape as ui-safety-gate.
170
171
  // (^|[^a-zA-Z0-9])(TERM)([^a-zA-Z0-9]|$) prevents interior-substring matches.
171
- const escaped = cueTerms.map(escapeRegex).join('|');
172
+ const escaped = cueTerms.map(pattern_cjs_1.escapeRegex).join('|');
172
173
  const pattern = new RegExp('(^|[^a-zA-Z0-9])(' + escaped + ')([^a-zA-Z0-9]|$)', 'gi');
173
174
  const seen = new Set();
174
175
  for (const line of stripped.split('\n')) {
@@ -188,9 +189,6 @@ function detectAssumptionDelta(text, terms) {
188
189
  }
189
190
  return { detected: signals.length > 0, signals, terms: effective };
190
191
  }
191
- function escapeRegex(s) {
192
- return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
193
- }
194
192
  // ── CLI entry point ──────────────────────────────────────────────────────────
195
193
  // Reads phase-section text from STDIN (not argv) to avoid OS ARG_MAX limits.
196
194
  // Invoked by workflow bash as: echo "$PHASE_SECTION" | node .../assumption-delta.cjs [--json]
@@ -71,7 +71,7 @@ function routeAuditOpen({ args, cwd, raw, error, _audit, _core }) {
71
71
  routeHubCommandFamily({
72
72
  family: 'audit-open',
73
73
  args: hubArgs,
74
- subcommands: ['run'],
74
+ subcommands: ['run', 'acknowledge'],
75
75
  defaultSubcommand: 'run',
76
76
  handlers: {
77
77
  run: () => {
@@ -86,8 +86,15 @@ function routeAuditOpen({ args, cwd, raw, error, _audit, _core }) {
86
86
  c.output(null, true, a.formatAuditReport(result));
87
87
  }
88
88
  },
89
+ // #3458 follow-up (design point A4): `audit-open acknowledge --category
90
+ // ... --milestone ...` writes/refreshes an `audit_acknowledged`
91
+ // suppression marker. `hubArgs.slice(2)` drops the family token and the
92
+ // `acknowledge` subcommand token itself, leaving just this
93
+ // subcommand's OWN `--flag value` pairs — the same slice
94
+ // `routeHubCommandFamily` itself passes to `hub.dispatch`'s `args`.
95
+ acknowledge: () => a.cmdAuditAcknowledge(cwd, hubArgs.slice(2), raw),
89
96
  },
90
- unknownMessage: (subcommand) => `Unknown audit-open subcommand: "${subcommand}". audit-open takes no subcommands (use --json for JSON output).`,
97
+ unknownMessage: (subcommand) => `Unknown audit-open subcommand: "${subcommand}". Available: run (default, use --json for JSON output), acknowledge.`,
91
98
  error,
92
99
  cwd,
93
100
  raw,