@opengsd/gsd-core 1.9.1 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (426) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -3
  3. package/.opencode/plugins/gsd-core.js +8 -1
  4. package/agents/gsd-code-fixer.md +27 -3
  5. package/agents/gsd-debug-session-manager.md +11 -0
  6. package/agents/gsd-debugger.md +12 -246
  7. package/agents/gsd-doc-synthesizer.md +2 -4
  8. package/agents/gsd-executor.md +12 -10
  9. package/agents/gsd-integration-checker.md +3 -0
  10. package/agents/gsd-mempalace-curator.md +5 -2
  11. package/agents/gsd-phase-researcher.md +20 -1
  12. package/agents/gsd-plan-checker.md +46 -0
  13. package/agents/gsd-planner.md +49 -54
  14. package/agents/gsd-roadmapper.md +21 -3
  15. package/agents/gsd-user-profiler.md +3 -0
  16. package/agents/gsd-verifier.md +26 -73
  17. package/bin/install.js +1272 -1238
  18. package/bin/lib/ui-safety-gate.cjs +2 -0
  19. package/commands/gsd/code-review.md +1 -1
  20. package/commands/gsd/execute-phase.md +1 -1
  21. package/commands/gsd/map-codebase.md +1 -1
  22. package/commands/gsd/mempalace-capture.md +2 -2
  23. package/commands/gsd/mempalace-recall.md +1 -1
  24. package/commands/gsd/new-milestone.md +2 -2
  25. package/commands/gsd/plan-phase.md +1 -1
  26. package/commands/gsd/quick.md +1 -1
  27. package/commands/gsd/review-backlog.md +2 -1
  28. package/commands/gsd/verify-work.md +1 -1
  29. package/gsd-core/bin/gsd-tools.cjs +1009 -115
  30. package/gsd-core/bin/lib/active-workstream-store.cjs +153 -12
  31. package/gsd-core/bin/lib/agent-install-check.cjs +268 -38
  32. package/gsd-core/bin/lib/api-coverage.cjs +123 -5
  33. package/gsd-core/bin/lib/artifacts.cjs +3 -0
  34. package/gsd-core/bin/lib/assumption-delta.cjs +2 -4
  35. package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
  36. package/gsd-core/bin/lib/audit.cjs +926 -202
  37. package/gsd-core/bin/lib/broken-windows.cjs +36 -6
  38. package/gsd-core/bin/lib/capability-consent.cjs +149 -15
  39. package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
  40. package/gsd-core/bin/lib/capability-registry.cjs +608 -148
  41. package/gsd-core/bin/lib/capability-source.cjs +92 -0
  42. package/gsd-core/bin/lib/capability-trust.cjs +444 -25
  43. package/gsd-core/bin/lib/capability-validator.cjs +507 -24
  44. package/gsd-core/bin/lib/capability-writer.cjs +3 -2
  45. package/gsd-core/bin/lib/check-command-router.cjs +114 -38
  46. package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
  47. package/gsd-core/bin/lib/codex-agent-toml.cjs +329 -0
  48. package/gsd-core/bin/lib/command-aliases.cjs +94 -0
  49. package/gsd-core/bin/lib/command-roster.cjs +44 -1
  50. package/gsd-core/bin/lib/commands.cjs +665 -99
  51. package/gsd-core/bin/lib/commonjs-marker.cjs +142 -0
  52. package/gsd-core/bin/lib/complexity-trigger.cjs +1172 -0
  53. package/gsd-core/bin/lib/config-loader.cjs +76 -0
  54. package/gsd-core/bin/lib/config.cjs +22 -2
  55. package/gsd-core/bin/lib/context-composer.cjs +278 -0
  56. package/gsd-core/bin/lib/context-predicates.cjs +506 -0
  57. package/gsd-core/bin/lib/core-utils.cjs +217 -40
  58. package/gsd-core/bin/lib/decisions.cjs +23 -0
  59. package/gsd-core/bin/lib/docs.cjs +3 -2
  60. package/gsd-core/bin/lib/external-job.cjs +19 -4
  61. package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
  62. package/gsd-core/bin/lib/frontmatter.cjs +239 -32
  63. package/gsd-core/bin/lib/gap-checker.cjs +68 -7
  64. package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +57 -6
  65. package/gsd-core/bin/lib/git-base-branch.cjs +160 -15
  66. package/gsd-core/bin/lib/graphify.cjs +142 -27
  67. package/gsd-core/bin/lib/gsd2-import.cjs +37 -5
  68. package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
  69. package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
  70. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +145 -0
  71. package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
  72. package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
  73. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
  74. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +265 -0
  75. package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
  76. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
  77. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +173 -0
  78. package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
  79. package/gsd-core/bin/lib/health-diagnostic.cjs +431 -0
  80. package/gsd-core/bin/lib/host-integration.cjs +13 -1
  81. package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
  82. package/gsd-core/bin/lib/init-command-router.cjs +83 -8
  83. package/gsd-core/bin/lib/init.cjs +1325 -169
  84. package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
  85. package/gsd-core/bin/lib/install-engine.cjs +805 -264
  86. package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
  87. package/gsd-core/bin/lib/install-model-override-resolver.cjs +203 -0
  88. package/gsd-core/bin/lib/install-profiles.cjs +160 -57
  89. package/gsd-core/bin/lib/install-scope.cjs +270 -0
  90. package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
  91. package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
  92. package/gsd-core/bin/lib/installer-migration-authoring.cjs +3 -1
  93. package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
  94. package/gsd-core/bin/lib/installer-migrations/007-retire-config-root-commonjs-marker.cjs +149 -0
  95. package/gsd-core/bin/lib/installer-migrations/008-cursor-retire-commands-surface.cjs +55 -0
  96. package/gsd-core/bin/lib/installer-migrations/009-pi-retire-reserved-hooks-dir.cjs +199 -0
  97. package/gsd-core/bin/lib/installer-migrations.cjs +206 -13
  98. package/gsd-core/bin/lib/io.cjs +38 -3
  99. package/gsd-core/bin/lib/markdown-sectionizer.cjs +8 -1
  100. package/gsd-core/bin/lib/markdown-table.cjs +133 -20
  101. package/gsd-core/bin/lib/mcp-catalog.cjs +518 -0
  102. package/gsd-core/bin/lib/mcp-server.cjs +135 -3
  103. package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
  104. package/gsd-core/bin/lib/milestone.cjs +821 -109
  105. package/gsd-core/bin/lib/model-catalog.cjs +59 -1
  106. package/gsd-core/bin/lib/model-resolver.cjs +183 -40
  107. package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
  108. package/gsd-core/bin/lib/pattern.cjs +122 -0
  109. package/gsd-core/bin/lib/phase-estimation.cjs +1 -1
  110. package/gsd-core/bin/lib/phase-id.cjs +507 -36
  111. package/gsd-core/bin/lib/phase-lifecycle.cjs +28 -3
  112. package/gsd-core/bin/lib/phase-locator.cjs +258 -58
  113. package/gsd-core/bin/lib/phase.cjs +891 -156
  114. package/gsd-core/bin/lib/plan-dependency-graph.cjs +303 -0
  115. package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
  116. package/gsd-core/bin/lib/plan-scan.cjs +86 -2
  117. package/gsd-core/bin/lib/planning-scope.cjs +31 -0
  118. package/gsd-core/bin/lib/planning-snapshot.cjs +890 -0
  119. package/gsd-core/bin/lib/planning-workspace.cjs +60 -6
  120. package/gsd-core/bin/lib/probe-core.cjs +1 -1
  121. package/gsd-core/bin/lib/profile-output.cjs +1 -1
  122. package/gsd-core/bin/lib/prompt-budget.cjs +128 -165
  123. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +740 -0
  124. package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +85 -0
  125. package/gsd-core/bin/lib/review-lane-descriptor.cjs +108 -0
  126. package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
  127. package/gsd-core/bin/lib/review-lane-runner.cjs +447 -68
  128. package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
  129. package/gsd-core/bin/lib/roadmap-command-router.cjs +76 -9
  130. package/gsd-core/bin/lib/roadmap-parser.cjs +1035 -194
  131. package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
  132. package/gsd-core/bin/lib/roadmap.cjs +405 -84
  133. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +795 -100
  134. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
  135. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +440 -57
  136. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
  137. package/gsd-core/bin/lib/runtime-homes.cjs +220 -41
  138. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +220 -44
  139. package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
  140. package/gsd-core/bin/lib/runtime-slash.cjs +27 -9
  141. package/gsd-core/bin/lib/section-manifest.cjs +209 -0
  142. package/gsd-core/bin/lib/security.cjs +104 -5
  143. package/gsd-core/bin/lib/shell-command-projection.cjs +388 -30
  144. package/gsd-core/bin/lib/smart-entry.cjs +154 -22
  145. package/gsd-core/bin/lib/state-command-router.cjs +5 -1
  146. package/gsd-core/bin/lib/state-document.cjs +152 -8
  147. package/gsd-core/bin/lib/state-transition.cjs +424 -105
  148. package/gsd-core/bin/lib/state.cjs +1927 -401
  149. package/gsd-core/bin/lib/surface.cjs +35 -10
  150. package/gsd-core/bin/lib/text-lines.cjs +80 -0
  151. package/gsd-core/bin/lib/token-scanner.cjs +76 -0
  152. package/gsd-core/bin/lib/uat-predicate.cjs +20 -4
  153. package/gsd-core/bin/lib/uat.cjs +706 -64
  154. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
  155. package/gsd-core/bin/lib/ui-safety-gate.cjs +14 -5
  156. package/gsd-core/bin/lib/unusable-input.cjs +33 -0
  157. package/gsd-core/bin/lib/update-context.cjs +8 -2
  158. package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
  159. package/gsd-core/bin/lib/validate.cjs +20 -6
  160. package/gsd-core/bin/lib/vendor/README.md +37 -0
  161. package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
  162. package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
  163. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  164. package/gsd-core/bin/lib/verification.cjs +287 -20
  165. package/gsd-core/bin/lib/verify.cjs +368 -880
  166. package/gsd-core/bin/lib/workflow-fragments.cjs +557 -0
  167. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +203 -19
  168. package/gsd-core/bin/lib/workstream-inventory.cjs +576 -31
  169. package/gsd-core/bin/lib/workstream.cjs +8 -2
  170. package/gsd-core/bin/lib/worktree-base-ref.cjs +50 -6
  171. package/gsd-core/bin/lib/worktree-safety.cjs +450 -125
  172. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
  173. package/gsd-core/bin/shared/config-schema.manifest.json +9 -1
  174. package/gsd-core/references/agent-contracts.md +43 -26
  175. package/gsd-core/references/artifact-types.md +10 -3
  176. package/gsd-core/references/autonomous-ui-design-contract.md +42 -0
  177. package/gsd-core/references/checkpoints.md +2 -2
  178. package/gsd-core/references/context-budget.md +1 -1
  179. package/gsd-core/references/debugger-techniques.md +255 -0
  180. package/gsd-core/references/dispatch-isolation-gate.md +138 -0
  181. package/gsd-core/references/doc-conflict-engine.md +1 -1
  182. package/gsd-core/references/execute-mvp-tdd.md +3 -3
  183. package/gsd-core/references/execute-phase-between-wave-reset.md +6 -2
  184. package/gsd-core/references/execute-phase-context-guard.md +1 -1
  185. package/gsd-core/references/execute-phase-response-language.md +1 -1
  186. package/gsd-core/references/execute-phase-wave-guard.md +6 -2
  187. package/gsd-core/references/gate-prompts.md +1 -1
  188. package/gsd-core/references/git-planning-commit.md +2 -1
  189. package/gsd-core/references/loop-hook-dispatch.md +39 -2
  190. package/gsd-core/references/model-profiles.md +12 -4
  191. package/gsd-core/references/mvp-concepts.md +9 -9
  192. package/gsd-core/references/planner-guidance.md +3 -9
  193. package/gsd-core/references/planner-preconditions.md +1 -1
  194. package/gsd-core/references/planner-reviews.md +1 -1
  195. package/gsd-core/references/planning-config.md +8 -6
  196. package/gsd-core/references/research-documentation-lookup.md +5 -3
  197. package/gsd-core/references/revision-loop.md +1 -1
  198. package/gsd-core/references/specless-probe-fallback.md +8 -7
  199. package/gsd-core/references/universal-anti-patterns.md +3 -3
  200. package/gsd-core/references/verifier-phase-gates.md +192 -0
  201. package/gsd-core/references/verifier-wiring-patterns.md +100 -0
  202. package/gsd-core/references/verify-mvp-mode.md +1 -1
  203. package/gsd-core/references/workstream-flag.md +22 -6
  204. package/gsd-core/references/worktree-branch-check.md +2 -2
  205. package/gsd-core/templates/discussion-log.md +1 -1
  206. package/gsd-core/templates/phase-prompt.md +2 -4
  207. package/gsd-core/templates/state.md +4 -4
  208. package/gsd-core/templates/summary-complex.md +2 -0
  209. package/gsd-core/templates/summary-minimal.md +2 -0
  210. package/gsd-core/templates/summary-standard.md +2 -0
  211. package/gsd-core/templates/summary.md +2 -0
  212. package/gsd-core/templates/verification-report.md +9 -1
  213. package/gsd-core/workflows/ai-integration-phase.md +9 -11
  214. package/gsd-core/workflows/audit-milestone.md +3 -0
  215. package/gsd-core/workflows/autonomous/steps/converge-banner.md +1 -0
  216. package/gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md +11 -0
  217. package/gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md +7 -0
  218. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +21 -0
  219. package/gsd-core/workflows/autonomous/steps/converge-loop.md +7 -0
  220. package/gsd-core/workflows/autonomous.md +33 -70
  221. package/gsd-core/workflows/cleanup.md +62 -3
  222. package/gsd-core/workflows/code-review/steps/dispatch-fix.md +39 -0
  223. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +93 -0
  224. package/gsd-core/workflows/code-review-fix.md +37 -10
  225. package/gsd-core/workflows/code-review.md +74 -166
  226. package/gsd-core/workflows/complete-milestone/steps/git-tag.md +29 -0
  227. package/gsd-core/workflows/complete-milestone.md +160 -95
  228. package/gsd-core/workflows/debug.md +16 -17
  229. package/gsd-core/workflows/diagnose-issues.md +56 -8
  230. package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -1
  231. package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
  232. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +15 -0
  233. package/gsd-core/workflows/discuss-phase-assumptions.md +7 -17
  234. package/gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md +51 -0
  235. package/gsd-core/workflows/docs-update.md +8 -51
  236. package/gsd-core/workflows/edit-phase.md +26 -1
  237. package/gsd-core/workflows/eval-review.md +3 -5
  238. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +64 -7
  239. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +50 -0
  240. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +31 -0
  241. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
  242. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +21 -0
  243. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +42 -0
  244. package/gsd-core/workflows/execute-phase/steps/regression-gate.md +43 -37
  245. package/gsd-core/workflows/execute-phase.md +103 -187
  246. package/gsd-core/workflows/execute-plan.md +36 -4
  247. package/gsd-core/workflows/explore.md +131 -4
  248. package/gsd-core/workflows/fast.md +10 -2
  249. package/gsd-core/workflows/health.md +73 -4
  250. package/gsd-core/workflows/help/modes/full.md +6 -1
  251. package/gsd-core/workflows/import.md +4 -4
  252. package/gsd-core/workflows/ingest-docs.md +7 -6
  253. package/gsd-core/workflows/mvp-phase.md +6 -3
  254. package/gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md +16 -0
  255. package/gsd-core/workflows/new-milestone/steps/reset-phase-safety.md +19 -0
  256. package/gsd-core/workflows/new-milestone.md +35 -47
  257. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +176 -0
  258. package/gsd-core/workflows/new-project/steps/auto-mode-detection.md +32 -0
  259. package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +18 -0
  260. package/gsd-core/workflows/new-project.md +27 -240
  261. package/gsd-core/workflows/next.md +12 -0
  262. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +15 -0
  263. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +110 -0
  264. package/gsd-core/workflows/plan-phase/steps/prd-express-gate.md +8 -0
  265. package/gsd-core/workflows/plan-phase/steps/research-only-early-exit.md +17 -0
  266. package/gsd-core/workflows/plan-phase/steps/research-only-modifiers.md +16 -0
  267. package/gsd-core/workflows/plan-phase/steps/reviews-prerequisite.md +17 -0
  268. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +149 -0
  269. package/gsd-core/workflows/plan-phase.md +89 -209
  270. package/gsd-core/workflows/plan-review-convergence.md +50 -2
  271. package/gsd-core/workflows/progress/steps/forensic-audit.md +125 -0
  272. package/gsd-core/workflows/progress/steps/mvp-display.md +18 -0
  273. package/gsd-core/workflows/progress.md +45 -159
  274. package/gsd-core/workflows/quick/steps/discussion-phase.md +124 -0
  275. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +111 -0
  276. package/gsd-core/workflows/quick/steps/quick-verification.md +67 -0
  277. package/gsd-core/workflows/quick/steps/research-phase.md +72 -0
  278. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +37 -0
  279. package/gsd-core/workflows/quick.md +55 -405
  280. package/gsd-core/workflows/resume-project.md +3 -0
  281. package/gsd-core/workflows/review/steps/reviewer-instances-note-1.md +4 -0
  282. package/gsd-core/workflows/review/steps/reviewer-instances-note-2.md +3 -0
  283. package/gsd-core/workflows/review.md +41 -13
  284. package/gsd-core/workflows/section-manifest.json +219 -0
  285. package/gsd-core/workflows/secure-phase.md +1 -1
  286. package/gsd-core/workflows/session-report.md +2 -1
  287. package/gsd-core/workflows/settings.md +66 -2
  288. package/gsd-core/workflows/ship.md +104 -44
  289. package/gsd-core/workflows/sketch.md +1 -1
  290. package/gsd-core/workflows/spec-phase.md +41 -20
  291. package/gsd-core/workflows/spike-wrap-up.md +20 -5
  292. package/gsd-core/workflows/spike.md +50 -16
  293. package/gsd-core/workflows/sync-skills.md +106 -13
  294. package/gsd-core/workflows/transition/steps/workstream-collision-check.md +17 -0
  295. package/gsd-core/workflows/transition.md +53 -31
  296. package/gsd-core/workflows/ui-phase.md +13 -12
  297. package/gsd-core/workflows/ui-review.md +2 -2
  298. package/gsd-core/workflows/update/steps/channel-banner.md +7 -0
  299. package/gsd-core/workflows/update.md +19 -8
  300. package/gsd-core/workflows/validate-phase.md +1 -1
  301. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +36 -0
  302. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +21 -0
  303. package/gsd-core/workflows/verify-work.md +17 -65
  304. package/hooks/dist/gsd-agent-isolation-guard.js +517 -0
  305. package/hooks/dist/gsd-check-update-worker.js +64 -12
  306. package/hooks/dist/gsd-check-update.js +19 -1
  307. package/hooks/dist/gsd-cursor-pre-tool.js +0 -3
  308. package/hooks/dist/gsd-cursor-subagent-start.js +607 -26
  309. package/hooks/dist/gsd-cursor-subagent-stop.js +3 -2
  310. package/hooks/dist/gsd-prompt-guard.js +21 -20
  311. package/hooks/dist/gsd-read-injection-scanner.js +45 -24
  312. package/hooks/dist/gsd-statusline.js +90 -6
  313. package/hooks/dist/gsd-update-banner.js +22 -1
  314. package/hooks/dist/gsd-workflow-guard.js +134 -36
  315. package/hooks/dist/gsd-worktree-path-guard.js +2 -1
  316. package/hooks/dist/gsd-write-guard.js +359 -0
  317. package/hooks/dist/lib/git-cmd.js +92 -59
  318. package/hooks/dist/lib/injection-patterns.js +45 -0
  319. package/hooks/dist/lib/isolation-deny-reason.js +39 -0
  320. package/hooks/dist/lib/isolation-sentinel.js +277 -0
  321. package/hooks/dist/managed-hooks-registry.cjs +2 -0
  322. package/hooks/gsd-agent-isolation-guard.js +517 -0
  323. package/hooks/gsd-check-update-worker.js +64 -12
  324. package/hooks/gsd-check-update.js +19 -1
  325. package/hooks/gsd-cursor-pre-tool.js +0 -3
  326. package/hooks/gsd-cursor-subagent-start.js +607 -26
  327. package/hooks/gsd-cursor-subagent-stop.js +3 -2
  328. package/hooks/gsd-prompt-guard.js +21 -20
  329. package/hooks/gsd-read-injection-scanner.js +45 -24
  330. package/hooks/gsd-statusline.js +90 -6
  331. package/hooks/gsd-update-banner.js +22 -1
  332. package/hooks/gsd-workflow-guard.js +134 -36
  333. package/hooks/gsd-worktree-path-guard.js +2 -1
  334. package/hooks/gsd-write-guard.js +359 -0
  335. package/hooks/hooks.json +12 -0
  336. package/hooks/lib/git-cmd.js +92 -59
  337. package/hooks/lib/injection-patterns.js +45 -0
  338. package/hooks/lib/isolation-deny-reason.js +39 -0
  339. package/hooks/lib/isolation-sentinel.js +277 -0
  340. package/hooks/managed-hooks-registry.cjs +2 -0
  341. package/package.json +31 -10
  342. package/pi/gsd.cjs +71 -12
  343. package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
  344. package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
  345. package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
  346. package/scripts/build-hooks.js +9 -0
  347. package/scripts/changeset/lint.cjs +68 -6
  348. package/scripts/changeset/serialize.cjs +5 -1
  349. package/scripts/check-alias-drift.cjs +7 -43
  350. package/scripts/check-contract-drift.cjs +297 -0
  351. package/scripts/ci-test-scope.cjs +19 -2
  352. package/scripts/command-contract-helpers.cjs +903 -1
  353. package/scripts/gen-adr-index.cjs +728 -38
  354. package/scripts/gen-capability-matrix.cjs +1 -1
  355. package/scripts/gen-capability-registry.cjs +3 -15
  356. package/scripts/gen-context-index.cjs +439 -0
  357. package/scripts/gen-health-docs.cjs +390 -0
  358. package/scripts/gen-inventory-manifest.cjs +150 -4
  359. package/scripts/gen-loop-host-contract.cjs +4 -24
  360. package/scripts/gen-prompt-budget-parity-corpus.cjs +645 -0
  361. package/scripts/gen-registry.cjs +3 -14
  362. package/scripts/gen-section-manifest.cjs +638 -0
  363. package/scripts/generate-package-identity.cjs +4 -2
  364. package/scripts/lib/alias-drift-families.cjs +46 -0
  365. package/scripts/lib/drift-scan.cjs +278 -0
  366. package/scripts/lint-allow-test-rule-refs.allowlist.json +15 -54
  367. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
  368. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
  369. package/scripts/lint-canary-version-leak.cjs +73 -0
  370. package/scripts/lint-command-contract.cjs +96 -13
  371. package/scripts/lint-compiled-artifact-sync.cjs +6 -1
  372. package/scripts/lint-completion-predicate-drift.cjs +933 -0
  373. package/scripts/lint-completion-ratio-drift.cjs +214 -0
  374. package/scripts/lint-default-flip-documentation.cjs +193 -0
  375. package/scripts/lint-docs-command-form.cjs +195 -0
  376. package/scripts/lint-docs-required.cjs +9 -1
  377. package/scripts/lint-emitted-drift-ack.cjs +215 -20
  378. package/scripts/lint-eslint-glob-coverage.allowlist.json +34 -0
  379. package/scripts/lint-eslint-glob-coverage.cjs +340 -0
  380. package/scripts/lint-example-parser-parity.cjs +395 -0
  381. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
  382. package/scripts/lint-health-diagnostic-rule-table.cjs +404 -0
  383. package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
  384. package/scripts/lint-milestone-window-drift.cjs +468 -0
  385. package/scripts/lint-phase-enumeration-drift.cjs +479 -0
  386. package/scripts/lint-plan-count-drift.cjs +318 -0
  387. package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
  388. package/scripts/lint-planning-prompt-drift.cjs +434 -0
  389. package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
  390. package/scripts/lint-regression-test-names.cjs +15 -13
  391. package/scripts/lint-removed-but-needed.cjs +320 -0
  392. package/scripts/lint-state-field-drift.cjs +805 -0
  393. package/scripts/lint-state-write-path-drift.cjs +1045 -0
  394. package/scripts/lint-test-file-count.allowlist.json +40 -3
  395. package/scripts/lint-unreachable-guard-drift.cjs +843 -0
  396. package/scripts/lint-vendored-deps.cjs +124 -0
  397. package/scripts/mutation-matrix.cjs +13 -0
  398. package/scripts/pr-changed-files.cjs +63 -0
  399. package/scripts/pr-template-policy.cjs +14 -4
  400. package/scripts/prompt-injection-scan.sh +52 -6
  401. package/scripts/require-issue-link-policy.cjs +192 -0
  402. package/scripts/state-write-path-drift-baseline.json +19 -0
  403. package/scripts/sync-runtime-launcher.cjs +2 -4
  404. package/skills/gsd-autonomous/SKILL.md +0 -1
  405. package/skills/gsd-code-review/SKILL.md +1 -1
  406. package/skills/gsd-execute-phase/SKILL.md +1 -2
  407. package/skills/gsd-map-codebase/SKILL.md +1 -1
  408. package/skills/gsd-mempalace-capture/SKILL.md +2 -2
  409. package/skills/gsd-mempalace-recall/SKILL.md +1 -1
  410. package/skills/gsd-new-milestone/SKILL.md +2 -2
  411. package/skills/gsd-next/SKILL.md +0 -1
  412. package/skills/gsd-plan-phase/SKILL.md +1 -2
  413. package/skills/gsd-progress/SKILL.md +0 -1
  414. package/skills/gsd-quick/SKILL.md +1 -1
  415. package/skills/gsd-review-backlog/SKILL.md +2 -1
  416. package/skills/gsd-stats/SKILL.md +0 -1
  417. package/skills/gsd-verify-work/SKILL.md +1 -1
  418. package/vscode/package.json +1 -1
  419. package/gsd-core/workflows/discovery-phase.md +0 -298
  420. package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
  421. package/gsd-core/workflows/verify-phase.md +0 -577
  422. package/scripts/affected-tests-lib.cjs +0 -554
  423. package/scripts/gen-emitted-baseline.cjs +0 -145
  424. package/scripts/lint-allow-test-rule-refs.cjs +0 -162
  425. package/scripts/run-affected-tests.cjs +0 -7
  426. package/scripts/run-tests.cjs +0 -1050
@@ -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,150 @@ 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);
323
+ }
324
+ /**
325
+ * Read-only sibling of getActiveWorkstream (#2850): identical resolution —
326
+ * adapter -> stored name -> validate format -> workstream dir exists — but
327
+ * NEVER calls adapter.clear(). getActiveWorkstream's self-heal (deleting a
328
+ * stale/invalid pointer) is correct for a command that is actively acting on
329
+ * the active workstream; it is wrong for a read-only consumer invoked on
330
+ * every render (e.g. the statusline hook), which must never mutate
331
+ * persistent, possibly cross-session state as a side effect of drawing a
332
+ * screen. A stale or invalid pointer simply resolves to null here — the
333
+ * caller decides what "unresolvable" means for its own render, and the
334
+ * pointer file is left exactly as it was for whatever created it to fix.
335
+ */
336
+ function peekActiveWorkstream(cwd, opts = {}) {
337
+ const chain = pickActiveWorkstreamAdapterChain(cwd, opts);
338
+ return resolveFromChain(cwd, chain, false);
201
339
  }
202
340
  function setActiveWorkstream(cwd, name, opts = {}) {
203
341
  const adapter = pickActiveWorkstreamAdapter(cwd, opts);
@@ -287,7 +425,10 @@ module.exports = {
287
425
  createSessionScopedPointerAdapter,
288
426
  createMemoryPointerAdapter,
289
427
  pickActiveWorkstreamAdapter,
428
+ pickActiveWorkstreamAdapterChain,
290
429
  getActiveWorkstream,
430
+ peekActiveWorkstream,
431
+ diagnoseUnresolvedActiveWorkstream,
291
432
  setActiveWorkstream,
292
433
  clearActiveWorkstream,
293
434
  parseCliWorkstream,
@@ -3,8 +3,8 @@
3
3
  * Agent Install Check — moved from core.cts (ADR-857 T0 #1268 phase rehome-core-squatters).
4
4
  *
5
5
  * Owns:
6
- * - getAgentsDir(runtime?): string
7
- * - checkAgentsInstalled(runtime?): AgentsInstalledResult
6
+ * - getAgentsDir(runtime?, projectRoot?): string
7
+ * - checkAgentsInstalled(runtime?, projectRoot?): AgentsInstalledResult
8
8
  *
9
9
  * The core.cjs re-export spine was retired in epic #1267; callers import
10
10
  * these symbols from agent-install-check.cjs directly.
@@ -18,25 +18,103 @@ const node_path_1 = __importDefault(require("node:path"));
18
18
  const modelProfiles = require("./model-profiles.cjs");
19
19
  const { MODEL_PROFILES } = modelProfiles;
20
20
  const runtime_homes_cjs_1 = require("./runtime-homes.cjs");
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
+ }
21
52
  /**
22
53
  * Resolve the agents directory for the given runtime.
23
54
  *
24
55
  * Priority:
25
56
  * 1. GSD_AGENTS_DIR env var (explicit override, any runtime)
26
- * 2. For claude runtime: __dirname-relative path (agents/ sibling of gsd-core/)
27
- * This is correct for both repo runs and real installs (the runtime config dir's
28
- * agents/ folder) because gsd-tools.cjs lives inside gsd-core/bin/ in both cases.
29
- * 3. For non-claude runtimes: getGlobalConfigDir(runtime)/agents
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).
68
+ * 3. For non-claude runtimes with a manifest-backed project-local install:
69
+ * <projectRoot>/<localConfigDir>/agents (or <projectRoot>/agents when
70
+ * the runtime's local install targets the project root). Requiring the
71
+ * GSD manifest prevents runtime-native project agents from shadowing a
72
+ * working global GSD install. Symlinked local agent directories are ignored.
73
+ * 4. For non-claude runtimes: getGlobalConfigDir(runtime)/agents
30
74
  *
31
75
  * @param runtime - the active runtime name; defaults to GSD_RUNTIME env, then 'claude'
76
+ * @param projectRoot - canonical project root for local-install discovery
32
77
  */
33
- function getAgentsDir(runtime) {
78
+ function getAgentsDir(runtime, projectRoot) {
34
79
  if (process.env['GSD_AGENTS_DIR']) {
35
80
  return process.env['GSD_AGENTS_DIR'];
36
81
  }
37
82
  const resolved = runtime ?? (process.env['GSD_RUNTIME'] || 'claude');
38
83
  if (resolved === 'claude') {
39
- 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;
94
+ }
95
+ if (projectRoot) {
96
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
97
+ const { runtimes } = require('./capability-registry.cjs');
98
+ const runtimeConfig = runtimes[resolved]?.runtime;
99
+ const localConfigDirName = (0, runtime_name_policy_cjs_1.getDirName)(resolved);
100
+ const localConfigDir = localConfigDirName === runtime_name_policy_cjs_1.NO_LOCAL_CONFIG_DIR_SENTINEL
101
+ ? undefined
102
+ : runtimeConfig?.hostBehaviors?.localTargetIsProjectRoot
103
+ ? projectRoot
104
+ : node_path_1.default.join(projectRoot, localConfigDirName);
105
+ if (!localConfigDir) {
106
+ return node_path_1.default.join((0, runtime_homes_cjs_1.getGlobalConfigDir)(resolved), 'agents');
107
+ }
108
+ const localAgentsDir = node_path_1.default.join(localConfigDir, 'agents');
109
+ const manifestPath = node_path_1.default.join(localConfigDir, 'gsd-file-manifest.json');
110
+ try {
111
+ if (node_fs_1.default.lstatSync(localAgentsDir).isDirectory() && node_fs_1.default.lstatSync(manifestPath).isFile()) {
112
+ return localAgentsDir;
113
+ }
114
+ }
115
+ catch {
116
+ // Local discovery is best-effort; any probe failure preserves global fallback.
117
+ }
40
118
  }
41
119
  return node_path_1.default.join((0, runtime_homes_cjs_1.getGlobalConfigDir)(resolved), 'agents');
42
120
  }
@@ -44,10 +122,11 @@ function getAgentsDir(runtime) {
44
122
  * Check which GSD agents are installed on disk.
45
123
  *
46
124
  * @param runtime - the active runtime name; defaults to GSD_RUNTIME env, then 'claude'
125
+ * @param projectRoot - canonical project root for local-install discovery
47
126
  */
48
- function checkAgentsInstalled(runtime) {
127
+ function checkAgentsInstalled(runtime, projectRoot) {
49
128
  const resolvedRuntime = runtime ?? (process.env['GSD_RUNTIME'] || 'claude');
50
- const agentsDir = getAgentsDir(resolvedRuntime);
129
+ const agentsDir = getAgentsDir(resolvedRuntime, projectRoot);
51
130
  const expectedAgents = Object.keys(MODEL_PROFILES);
52
131
  const installed = [];
53
132
  const missing = [];
@@ -62,18 +141,7 @@ function checkAgentsInstalled(runtime) {
62
141
  };
63
142
  }
64
143
  for (const agent of expectedAgents) {
65
- const agentFile = node_path_1.default.join(agentsDir, `${agent}.md`);
66
- const agentFileCopilot = node_path_1.default.join(agentsDir, `${agent}.agent.md`);
67
- const agentFileCodex = node_path_1.default.join(agentsDir, `${agent}.toml`);
68
- const agentFileKimiYaml = node_path_1.default.join(agentsDir, 'subagents', `${agent}.yaml`);
69
- const agentFileKimiPrompt = node_path_1.default.join(agentsDir, 'subagents', `${agent}.md`);
70
- const kimiAgentInstalled = resolvedRuntime === 'kimi' &&
71
- node_fs_1.default.existsSync(agentFileKimiYaml) &&
72
- node_fs_1.default.existsSync(agentFileKimiPrompt);
73
- if (node_fs_1.default.existsSync(agentFile) ||
74
- node_fs_1.default.existsSync(agentFileCopilot) ||
75
- node_fs_1.default.existsSync(agentFileCodex) ||
76
- kimiAgentInstalled) {
144
+ if (agentFileExists(agentsDir, agent, resolvedRuntime)) {
77
145
  installed.push(agent);
78
146
  }
79
147
  else {
@@ -87,22 +155,13 @@ function checkAgentsInstalled(runtime) {
87
155
  // when the plain presence check above passed (e.g. .md present, .toml absent).
88
156
  // If no manifest is found the check is a no-op (graceful for claude/bundled).
89
157
  const incomplete = [];
90
- const manifestPath = node_path_1.default.join(node_path_1.default.dirname(agentsDir), 'gsd-file-manifest.json');
91
- let manifestFiles = {};
92
- try {
93
- const raw = node_fs_1.default.readFileSync(manifestPath, 'utf8');
94
- const parsed = JSON.parse(raw);
95
- if (parsed !== null &&
96
- typeof parsed === 'object' &&
97
- 'files' in parsed &&
98
- typeof parsed['files'] === 'object' &&
99
- parsed['files'] !== null) {
100
- manifestFiles = parsed['files'];
101
- }
102
- }
103
- catch {
104
- // No manifest or unreadable — completeness check is skipped
105
- }
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;
106
165
  if (Object.keys(manifestFiles).length > 0) {
107
166
  for (const agent of expectedAgents) {
108
167
  // Find all manifest keys that belong to this agent:
@@ -137,7 +196,178 @@ function checkAgentsInstalled(runtime) {
137
196
  agent_runtime: resolvedRuntime,
138
197
  };
139
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
+ }
140
367
  module.exports = {
141
368
  getAgentsDir,
142
369
  checkAgentsInstalled,
370
+ checkCodexModelPosture,
371
+ POSTURE_REASON,
372
+ resolveAgentHint,
143
373
  };