@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
@@ -23,7 +23,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
23
23
  const node_path_1 = __importDefault(require("node:path"));
24
24
  // eslint-disable-next-line @typescript-eslint/no-require-imports
25
25
  const phaseIdModule = require("./phase-id.cjs");
26
- const { comparePhaseNum } = phaseIdModule;
26
+ const { comparePhaseNum, scopeToPhase } = phaseIdModule;
27
27
  // eslint-disable-next-line @typescript-eslint/no-require-imports
28
28
  const planningWorkspace = require("./planning-workspace.cjs");
29
29
  const { findContextMdIn } = planningWorkspace;
@@ -72,17 +72,30 @@ function extractOneLinerFromBody(content) {
72
72
  if (!content)
73
73
  return null;
74
74
  const normalized = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
75
- const body = normalized.replace(/^---\n[\s\S]*?\n---\n*/, '');
76
- const match = body.match(/^#[^\n]*\n+\*\*([^*\n]+)\*\*([^\n]*)/m);
77
- if (!match)
78
- return null;
79
- const boldInner = match[1].trim();
80
- const afterBold = match[2];
81
- if (/:\s*$/.test(boldInner)) {
82
- const prose = afterBold.trim();
83
- return prose.length > 0 ? prose : null;
75
+ const body = normalized.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n*/, '');
76
+ // #3170: anchor to a summary-shaped heading (Summary / Overview /
77
+ // Accomplishments) so an incidental first heading (a rule list, task
78
+ // breakdown, deviation note) does not contribute its first bold run as the
79
+ // deliverable one-liner. Iterate headings in document order and extract from
80
+ // the first summary-shaped one that has a bold run; fall back to null (not the
81
+ // wrong text) when no such heading exists.
82
+ const headingRe = /^#+\s*([^\n]*)\n+\*\*([^*\n]+)\*\*([^\n]*)/gm;
83
+ let match;
84
+ while ((match = headingRe.exec(body)) !== null) {
85
+ if (!/summary|overview|accomplish/i.test(match[1]))
86
+ continue;
87
+ const boldInner = match[2].trim();
88
+ const afterBold = match[3];
89
+ if (/:\s*$/.test(boldInner)) {
90
+ const prose = afterBold.trim();
91
+ if (prose.length > 0)
92
+ return prose;
93
+ }
94
+ else if (boldInner.length > 0) {
95
+ return boldInner;
96
+ }
84
97
  }
85
- return boldInner.length > 0 ? boldInner : null;
98
+ return null;
86
99
  }
87
100
  // ─── Misc utilities ───────────────────────────────────────────────────────────
88
101
  function pathExistsInternal(cwd, targetPath) {
@@ -98,29 +111,119 @@ function pathExistsInternal(cwd, targetPath) {
98
111
  function generateSlugInternal(text) {
99
112
  if (!text)
100
113
  return null;
101
- return text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').substring(0, 60);
102
- }
103
- // ─── Phase file helpers ──────────────────────────────────────────────────────
104
- /** Filter a file list to just PLAN.md / *-PLAN.md entries. */
105
- function filterPlanFiles(files) {
106
- return files.filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md');
114
+ // #2849: strip leading/trailing hyphens AFTER truncation, not only before.
115
+ // .substring(0, 60) can land on a separator, re-introducing a trailing hyphen
116
+ // the strip step exists to prevent. Truncation cannot add a leading hyphen, so
117
+ // running the full ^-+|-+$ pass last is equivalent for leading hyphens and
118
+ // fixes the trailing-hyphen-after-truncation case.
119
+ return transliterateForSlug(text).replace(/[^a-z0-9]+/g, '-').substring(0, 60).replace(/^-+|-+$/g, '');
107
120
  }
108
- /** Filter a file list to just SUMMARY.md / *-SUMMARY.md entries. */
109
- function filterSummaryFiles(files) {
110
- return files.filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
121
+ // ─── Transliteration (#2848) ─────────────────────────────────────────────────
122
+ //
123
+ // Non-Latin titles used to reduce to an empty slug: the `[^a-z0-9]+` strip
124
+ // removed every character of an all-Cyrillic title and the hyphen cleanup left
125
+ // "". Callers then created unnamed phase directories (`01-`) and empty
126
+ // `milestone_slug` init JSON. The fix transliterates Cyrillic to ASCII BEFORE
127
+ // the existing ASCII filter, so a non-Latin title yields a usable ASCII slug
128
+ // while Latin-script text (which hits zero map entries) is byte-for-byte
129
+ // unchanged — the negative control is satisfied by construction.
130
+ //
131
+ // Multi-letter mappings (ж→zh, ч→ch, ш→sh, щ→sch, ю→yu, я→ya) are applied as a
132
+ // single pass; soft/hard signs (ъ, ь) drop to nothing rather than a hyphen.
133
+ // Scope is Cyrillic (Russian + the reported Ukrainian/Belarusian extras
134
+ // і ї є ґ ў) per the issue's confirmed-working patch. CJK and other
135
+ // non-transliterated scripts keep the existing strip-to-ASCII behavior.
136
+ const CYRILLIC_TRANSLITERATION = {
137
+ // multi-letter first (longest-match-safe within a single pass via ordered keys)
138
+ а: 'a', б: 'b', в: 'v', г: 'g', д: 'd', е: 'e', ё: 'e', ж: 'zh',
139
+ з: 'z', и: 'i', й: 'y', к: 'k', л: 'l', м: 'm', н: 'n', о: 'o',
140
+ п: 'p', р: 'r', с: 's', т: 't', у: 'u', ф: 'f', х: 'h', ц: 'ts',
141
+ ч: 'ch', ш: 'sh', щ: 'sch', ъ: '', ы: 'y', ь: '', э: 'e', ю: 'yu',
142
+ я: 'ya',
143
+ // Ukrainian / Belarusian extras reported in #2848
144
+ є: 'ye', і: 'i', ї: 'yi', ґ: 'g', ў: 'u',
145
+ };
146
+ const CYRILLIC_TRANSLITERATION_KEYS = Object.keys(CYRILLIC_TRANSLITERATION);
147
+ /**
148
+ * Lowercase + transliterate Cyrillic characters to ASCII. The output still
149
+ * contains non-ASCII for scripts outside the map (CJK, etc.) — the caller's
150
+ * existing `[^a-z0-9]+` filter handles those. Latin-script input is returned
151
+ * lowercased with no other change.
152
+ *
153
+ * Shared by `generateSlugInternal` (core-utils) and `slugify` (gsd2-import) so
154
+ * the transliteration step is not duplicated across the two slug helpers (#2848
155
+ * explicitly requires both be fixed).
156
+ */
157
+ function transliterateForSlug(text) {
158
+ const lowered = text.toLowerCase();
159
+ let out = '';
160
+ for (const ch of lowered) {
161
+ out += CYRILLIC_TRANSLITERATION_KEYS.includes(ch)
162
+ ? CYRILLIC_TRANSLITERATION[ch]
163
+ : ch;
164
+ }
165
+ return out;
111
166
  }
112
167
  /**
113
168
  * Read a phase directory and return counts/flags for common file types.
169
+ *
170
+ * #3183 (ADR-3180 Decision 2): `plans`/`summaries` are derived from the
171
+ * canonical `scanPhasePlans` rather than a local re-derivation, so this
172
+ * primitive can no longer diverge from the single owner of live-plan
173
+ * counting. `scanPhasePlans`
174
+ * lives in plan-scan.cjs, which itself imports `countMatchedSummaries` from
175
+ * THIS module — a top-level import here would be circular, so the require
176
+ * is deferred (lazy, inside the function body) to break the cycle at load
177
+ * time. This mirrors the lazy-require seam already used elsewhere in this
178
+ * repo (see src/audit-command-router.cts) for the same "module A needs
179
+ * module B which needs module A" shape.
180
+ *
181
+ * `hasResearch`/`hasContext`/`hasVerification`/`hasReviews` stay on the raw
182
+ * `readdirSync` listing — they are not plan-scan concerns.
183
+ *
184
+ * #3511 BLOCKER-2: the raw listing is scoped through `scopeToPhase` (keyed on
185
+ * `path.basename(phaseDir)`) before any of the four artifact predicates run,
186
+ * so a stray cross-phase file (e.g. `04-VERIFICATION.md` sitting inside phase
187
+ * 03's directory) cannot flip `hasResearch`/`hasContext`/`hasVerification`/
188
+ * `hasReviews` true for a phase it does not belong to — the same membership
189
+ * rule every other aggregate phase-directory scan (`uat.cts`, `audit.cts`,
190
+ * `phase.cts`, `state.cts`) already routes through. `hasContext` is scoped by
191
+ * passing the already-scoped array into `findContextMdIn` at this call site
192
+ * only — `findContextMdIn` itself is unchanged and its other 4 call sites
193
+ * (roadmap.cts, gap-checker.cts, init.cts) are unaffected.
194
+ *
195
+ * Degrades on an unreadable directory instead of throwing: empty arrays,
196
+ * every flag false, scope UNREADABLE (mirroring scanPhasePlans's own
197
+ * degrade path).
114
198
  */
115
199
  function getPhaseFileStats(phaseDir) {
116
- const files = node_fs_1.default.readdirSync(phaseDir);
200
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
201
+ const scanPhasePlans = require('./plan-scan.cjs');
202
+ const scan = scanPhasePlans(phaseDir);
203
+ let files;
204
+ try {
205
+ files = node_fs_1.default.readdirSync(phaseDir);
206
+ }
207
+ catch {
208
+ return {
209
+ plans: scan.planFiles,
210
+ summaries: scan.summaryFiles,
211
+ hasResearch: false,
212
+ hasContext: false,
213
+ hasVerification: false,
214
+ hasReviews: false,
215
+ scope: scan.scope,
216
+ };
217
+ }
218
+ const scopedFiles = scopeToPhase(files, node_path_1.default.basename(phaseDir));
117
219
  return {
118
- plans: filterPlanFiles(files),
119
- summaries: filterSummaryFiles(files),
120
- hasResearch: files.some(f => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md'),
121
- hasContext: findContextMdIn(files) !== null,
122
- hasVerification: files.some(f => f.endsWith('-VERIFICATION.md') || f === 'VERIFICATION.md'),
123
- hasReviews: files.some(f => f.endsWith('-REVIEWS.md') || f === 'REVIEWS.md'),
220
+ plans: scan.planFiles,
221
+ summaries: scan.summaryFiles,
222
+ hasResearch: scopedFiles.some(f => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md'),
223
+ hasContext: findContextMdIn(scopedFiles) !== null,
224
+ hasVerification: scopedFiles.some(f => f.endsWith('-VERIFICATION.md') || f === 'VERIFICATION.md'),
225
+ hasReviews: scopedFiles.some(f => f.endsWith('-REVIEWS.md') || f === 'REVIEWS.md'),
226
+ scope: scan.scope,
124
227
  };
125
228
  }
126
229
  /**
@@ -222,32 +325,106 @@ function countMatchedSummaries(planFiles, summaryFiles) {
222
325
  const summarySet = new Set(summaryFiles);
223
326
  let matched = 0;
224
327
  for (const plan of planFiles) {
225
- const slashIdx = plan.lastIndexOf('/');
226
- const dir = slashIdx >= 0 ? plan.slice(0, slashIdx + 1) : '';
227
- const base = (dir ? plan.slice(dir.length) : plan).replace(/\.md$/i, '');
228
- const candidates = [
229
- dir + base.replace(/PLAN/i, 'SUMMARY') + '.md',
230
- dir + base + '-SUMMARY.md',
231
- ];
232
- const extended = base.match(/^(\d+)-PLAN-(\d+)/i);
233
- if (extended)
234
- candidates.push(dir + extended[1] + '-' + extended[2] + '-SUMMARY.md');
235
- if (candidates.some((c) => summarySet.has(c)))
328
+ if (summaryCandidates(plan).some((c) => summarySet.has(c)))
236
329
  matched++;
237
330
  }
238
331
  return matched;
239
332
  }
333
+ /**
334
+ * The candidate `*-SUMMARY.md` filenames a single plan's completion record
335
+ * could take, per the three naming conventions documented above
336
+ * `countMatchedSummaries`. Extracted so `findUnsummarizedPlans` can reuse the
337
+ * exact same matching rule without duplicating it (a divergence between the
338
+ * count and the list would let a plan be counted as matched while still
339
+ * appearing in the unsummarized set, or vice versa).
340
+ */
341
+ function summaryCandidates(plan) {
342
+ const slashIdx = plan.lastIndexOf('/');
343
+ const dir = slashIdx >= 0 ? plan.slice(0, slashIdx + 1) : '';
344
+ const base = (dir ? plan.slice(dir.length) : plan).replace(/\.md$/i, '');
345
+ const candidates = [
346
+ dir + base.replace(/PLAN/i, 'SUMMARY') + '.md',
347
+ dir + base + '-SUMMARY.md',
348
+ ];
349
+ const extended = base.match(/^(\d+)-PLAN-(\d+)/i);
350
+ if (extended)
351
+ candidates.push(dir + extended[1] + '-' + extended[2] + '-SUMMARY.md');
352
+ // #3183: canonical-id form. Restores the coverage of the pre-migration
353
+ // bespoke I001 rule (verify.cts, pre-#3183, via validate.cjs's now-unused
354
+ // `canonicalPlanStem` — behaviourally identical to `extractCanonicalPlanId`,
355
+ // confirmed empirically), which matched a plan carrying a descriptive slug
356
+ // after its <phase>-<plan> id — e.g. `68-01-scaffolding-PLAN.md` — against
357
+ // a summary named only by the bare id — `68-01-SUMMARY.md`. None of the
358
+ // three candidates above produce that filename.
359
+ //
360
+ // Narrowed to the case `extractCanonicalPlanId` actually extracted an
361
+ // <id>-<id> pair (its result differs from the plan's own PLAN-stripped
362
+ // base). When no pair is found it falls back to returning that same base
363
+ // unchanged, which would otherwise push a redundant candidate identical to
364
+ // the `<stem>-SUMMARY.md` form above (e.g. `setup-PLAN.md` -> canonical
365
+ // 'setup' -> 'setup-SUMMARY.md', already candidate #2) rather than the
366
+ // original rule's actual behavior of matching only real id pairs.
367
+ //
368
+ // Collision, matching the original rule byte-for-behaviour: two plans that
369
+ // share the same <phase>-<plan> id but differ only in their descriptive
370
+ // slug (`68-01-alpha-PLAN.md` + `68-01-beta-PLAN.md`) both generate the
371
+ // SAME candidate `68-01-SUMMARY.md` and therefore BOTH read as summarized
372
+ // off one shared summary file. This is not a new regression: the
373
+ // pre-migration bespoke rule collapsed the same way (it populated one
374
+ // `summaryBases` Set keyed by canonical stem, so any plan whose canonical
375
+ // stem hit the set counted as matched, with no cardinality check against
376
+ // how many plans shared that stem).
377
+ const planStem = base.replace(/-PLAN$/i, '');
378
+ const canonicalId = extractCanonicalPlanId(base + '.md');
379
+ if (canonicalId !== planStem)
380
+ candidates.push(dir + canonicalId + '-SUMMARY.md');
381
+ return candidates;
382
+ }
383
+ /**
384
+ * #2648: the plan files in `planFiles` that have NO matching completion record
385
+ * in `summaryFiles`, using the identical matching rule as `countMatchedSummaries`
386
+ * (so the count and the named list can never disagree). Callers that must NAME
387
+ * the missing plans — e.g. phase.complete's fail-closed coverage gate, which
388
+ * refuses completion when any non-retired plan lacks a SUMMARY — need the list,
389
+ * not just the count. `planFiles` is expected to be already superseded-filtered
390
+ * (the caller passes `scanPhasePlans(...).planFiles`, which drops
391
+ * `status: superseded` plans), so a deliberately-retired plan never appears
392
+ * here and never blocks completion.
393
+ */
394
+ function findUnsummarizedPlans(planFiles, summaryFiles) {
395
+ const summarySet = new Set(summaryFiles);
396
+ return planFiles.filter((plan) => !summaryCandidates(plan).some((c) => summarySet.has(c)));
397
+ }
398
+ /**
399
+ * #3183: the mirror image of `findUnsummarizedPlans` — the summary files in
400
+ * `summaryFiles` that do NOT pair with ANY plan in `planFiles`, using the
401
+ * identical `summaryCandidates` matching rule as `countMatchedSummaries` /
402
+ * `findUnsummarizedPlans`. Callers that must name orphaned summaries (a
403
+ * stray non-plan summary, or a summary whose plan was renamed/removed) need
404
+ * this instead of a bespoke exact-suffix Set-diff, which cannot recognize
405
+ * the nested or extended naming forms `summaryCandidates` already handles —
406
+ * a divergence that produced false "orphan summary" warnings.
407
+ */
408
+ function findOrphanSummaries(planFiles, summaryFiles) {
409
+ const claimed = new Set();
410
+ for (const plan of planFiles) {
411
+ for (const candidate of summaryCandidates(plan))
412
+ claimed.add(candidate);
413
+ }
414
+ return summaryFiles.filter((s) => !claimed.has(s));
415
+ }
240
416
  module.exports = {
241
417
  toPosixPath,
242
418
  detectSubRepos,
243
419
  extractOneLinerFromBody,
244
420
  pathExistsInternal,
245
421
  generateSlugInternal,
246
- filterPlanFiles,
247
- filterSummaryFiles,
422
+ transliterateForSlug,
248
423
  getPhaseFileStats,
249
424
  readSubdirectories,
250
425
  timeAgo,
251
426
  extractCanonicalPlanId,
252
427
  countMatchedSummaries,
428
+ findUnsummarizedPlans,
429
+ findOrphanSummaries,
253
430
  };
@@ -21,6 +21,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
21
21
  exports.extractDecisions = extractDecisions;
22
22
  exports.parseDecisions = parseDecisions;
23
23
  const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
24
+ const token_scanner_cjs_1 = require("./token-scanner.cjs");
24
25
  const DISCRETION_HEADINGS = new Set([
25
26
  "claude's discretion",
26
27
  'claudes discretion',
@@ -89,12 +90,14 @@ function parseDecisionLines(block) {
89
90
  let category = '';
90
91
  let inDiscretion = false;
91
92
  let current = null;
93
+ let openIndent = null;
92
94
  let parseMisses = 0;
93
95
  const flush = () => {
94
96
  if (current) {
95
97
  current.text = current.text.trim();
96
98
  out.push(current);
97
99
  current = null;
100
+ openIndent = null;
98
101
  }
99
102
  };
100
103
  for (const line of lines) {
@@ -115,6 +118,23 @@ function parseDecisionLines(block) {
115
118
  inDiscretion = DISCRETION_HEADINGS.has(normalized);
116
119
  continue;
117
120
  }
121
+ // Nested bullet under an open decision (#3212 Phase 3, #3169): a bullet
122
+ // indented deeper than the currently-open decision's own bullet is that
123
+ // decision's elaboration (e.g. a cross-reference to a sibling decision),
124
+ // not a fresh declaration attempt — fold it into current.text exactly
125
+ // like a continuation line, before it ever reaches the declaration/
126
+ // parse-miss regexes below. A bullet at the SAME or a SHALLOWER indent
127
+ // is unaffected — tested exactly as before this fix. See design doc
128
+ // .gsd/phase/chore-3414-tokenizer-first-seam/40-design.md §1.3 for why
129
+ // nesting depth, not bullet content, is the signal that distinguishes
130
+ // this from a genuinely malformed top-level declaration.
131
+ if (current &&
132
+ openIndent !== null &&
133
+ trimmed.startsWith('-') &&
134
+ (0, token_scanner_cjs_1.indentWidth)(line) > openIndent) {
135
+ current.text += ' ' + trimmed;
136
+ continue;
137
+ }
118
138
  // Colon form: `- **D-NN[ [tags]]:** text`
119
139
  const colonMatch = line.match(bulletColonRe);
120
140
  if (colonMatch) {
@@ -125,6 +145,7 @@ function parseDecisionLines(block) {
125
145
  : [];
126
146
  const trackable = !inDiscretion && !tags.some((t) => NON_TRACKABLE_TAGS.has(t));
127
147
  current = { id, text: colonMatch[3], category, tags, trackable };
148
+ openIndent = (0, token_scanner_cjs_1.indentWidth)(line);
128
149
  continue;
129
150
  }
130
151
  // Em-dash form: `- **D-NN[ [tags]] — title** body`
@@ -140,6 +161,7 @@ function parseDecisionLines(block) {
140
161
  // title itself is embedded in the bold run but we report the body as text
141
162
  // (consistent with how the gate cares only about coverage, not title/body split).
142
163
  current = { id, text: emDashMatch[3] || '', category, tags, trackable };
164
+ openIndent = (0, token_scanner_cjs_1.indentWidth)(line);
143
165
  continue;
144
166
  }
145
167
  // Titled-colon form: `- **D-NN[ [tags]]: Title.** body` (#1639). Checked LAST — it is
@@ -155,6 +177,7 @@ function parseDecisionLines(block) {
155
177
  : [];
156
178
  const trackable = !inDiscretion && !tags.some((t) => NON_TRACKABLE_TAGS.has(t));
157
179
  current = { id, text: titledColonMatch[3] || '', category, tags, trackable };
180
+ openIndent = (0, token_scanner_cjs_1.indentWidth)(line);
158
181
  continue;
159
182
  }
160
183
  // Parse-miss guard (FIX B + #1343): a line that looks like a `D-NN` decision
@@ -30,6 +30,7 @@ const { pathExistsInternal, toPosixPath } = coreUtils;
30
30
  // eslint-disable-next-line @typescript-eslint/no-require-imports
31
31
  const agentInstallCheck = require("./agent-install-check.cjs");
32
32
  const { checkAgentsInstalled } = agentInstallCheck;
33
+ const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
33
34
  const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
34
35
  // ─── Constants ────────────────────────────────────────────────────────────────
35
36
  const GSD_MARKER = '<!-- generated-by: gsd-doc-writer -->';
@@ -256,7 +257,7 @@ function cmdDocsInit(cwd, raw) {
256
257
  };
257
258
  // Inject project_root and agent installation status (mirrors withProjectRoot in init.cjs)
258
259
  result['project_root'] = cwd;
259
- const agentStatus = checkAgentsInstalled();
260
+ const agentStatus = checkAgentsInstalled((0, runtime_slash_cjs_1.resolveRuntime)(cwd), cwd);
260
261
  result['agents_installed'] = agentStatus.agents_installed;
261
262
  result['missing_agents'] = agentStatus.missing_agents;
262
263
  // #2402: withProjectRoot injects response_language when set; cmdDocsInit predates
@@ -267,4 +268,4 @@ function cmdDocsInit(cwd, raw) {
267
268
  }
268
269
  output(result, raw, undefined);
269
270
  }
270
- module.exports = { cmdDocsInit };
271
+ module.exports = { cmdDocsInit, detectMonorepoWorkspaces };
@@ -219,6 +219,11 @@ function _isNonTerminal(status) {
219
219
  * - Same `job_id` for the same `plan_id` -> allowed (status progression).
220
220
  * - A prior job for the same `plan_id` that is already terminal -> allowed
221
221
  * (the duplicate guard only protects against re-dispatching live work).
222
+ * - If a SIBLING manifest (not the target) cannot be read or parsed, the
223
+ * duplicate scan cannot be completed and may be hiding a live duplicate
224
+ * -> refuse (`scan_incomplete`), naming the offending file's path so an
225
+ * operator can quarantine or repair it. Never silently skip a sibling the
226
+ * scan could not inspect.
222
227
  */
223
228
  function writeManifest(manifest, planningDir, opts = {}) {
224
229
  const fs = opts.fs ?? node_fs_1.default;
@@ -240,18 +245,28 @@ function writeManifest(manifest, planningDir, opts = {}) {
240
245
  try {
241
246
  raw = String(fs.readFileSync(p));
242
247
  }
243
- catch {
244
- continue;
248
+ catch (e) {
249
+ return {
250
+ ok: false,
251
+ kind: 'scan_incomplete',
252
+ message: `duplicate scan incomplete: failed to READ sibling manifest ${p} (${e.message}); quarantine or repair this file before retrying`,
253
+ offendingPath: p,
254
+ };
245
255
  }
246
256
  let existing;
247
257
  try {
248
258
  existing = JSON.parse(raw);
249
259
  }
250
- catch {
260
+ catch (e) {
251
261
  if (p === target) {
252
262
  return { ok: false, kind: 'malformed_existing', message: `target manifest ${p} is not valid JSON` };
253
263
  }
254
- continue;
264
+ return {
265
+ ok: false,
266
+ kind: 'scan_incomplete',
267
+ message: `duplicate scan incomplete: failed to PARSE sibling manifest ${p} (${e.message}); quarantine or repair this file before retrying`,
268
+ offendingPath: p,
269
+ };
255
270
  }
256
271
  const samePlan = existing.plan_id === manifest.plan_id;
257
272
  const sameJob = existing.job_id === manifest.job_id;
@@ -8,6 +8,13 @@
8
8
  *
9
9
  * Parses the real fallow `audit --format json` schema (schema_version 3
10
10
  * envelope, nested dead_code/duplication sections). See fallow 2.70.0+.
11
+ *
12
+ * #3411 Phase 2 (#3618): binary resolution no longer lives here — it delegates
13
+ * to the platform seam's `resolveExecutableBinary` (shell-command-projection.cts).
14
+ * This file's prior private resolver deliberately included an extensionless
15
+ * `fallow` as a win32 candidate; the seam does not, and that is a fix, not a
16
+ * regression — an extensionless file sitting beside `fallow.cmd` is npm's POSIX
17
+ * `sh` shim, which `CreateProcess` cannot run (#3275).
11
18
  */
12
19
  var __importDefault = (this && this.__importDefault) || function (mod) {
13
20
  return (mod && mod.__esModule) ? mod : { "default": mod };
@@ -19,51 +26,20 @@ exports.normalizeFallowReport = normalizeFallowReport;
19
26
  exports.normalizeFallowReportFile = normalizeFallowReportFile;
20
27
  const node_fs_1 = __importDefault(require("node:fs"));
21
28
  const node_path_1 = __importDefault(require("node:path"));
22
- function candidateNames() {
23
- return process.platform === 'win32'
24
- ? ['fallow.exe', 'fallow.cmd', 'fallow.bat', 'fallow']
25
- : ['fallow'];
26
- }
27
- function isExecutableFile(filePath) {
28
- try {
29
- const stat = node_fs_1.default.statSync(filePath);
30
- if (!stat.isFile())
31
- return false;
32
- if (process.platform === 'win32')
33
- return true;
34
- node_fs_1.default.accessSync(filePath, node_fs_1.default.constants.X_OK);
35
- return true;
36
- }
37
- catch {
38
- return false;
39
- }
40
- }
41
- function findInPath(envPath) {
42
- if (!envPath)
43
- return null;
44
- const names = candidateNames();
45
- const segments = envPath.split(node_path_1.default.delimiter).filter(Boolean);
46
- for (const segment of segments) {
47
- for (const name of names) {
48
- const candidate = node_path_1.default.join(segment, name);
49
- if (isExecutableFile(candidate))
50
- return candidate;
51
- }
52
- }
53
- return null;
54
- }
55
- function findInNodeModules(cwd) {
56
- const names = candidateNames();
57
- const binDir = node_path_1.default.join(cwd, 'node_modules', '.bin');
58
- for (const name of names) {
59
- const candidate = node_path_1.default.join(binDir, name);
60
- if (isExecutableFile(candidate))
61
- return candidate;
62
- }
63
- return null;
64
- }
29
+ const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
65
30
  function resolveFallowBinary({ cwd, envPath = process.env['PATH'] ?? '' }) {
66
- return findInNodeModules(cwd) || findInPath(envPath) || null;
31
+ return (0, shell_command_projection_cjs_1.resolveExecutableBinary)('fallow', {
32
+ prependPaths: [node_path_1.default.join(cwd, 'node_modules', '.bin')],
33
+ // #3619 (epic #3411 Phase 3): pathOverride carries envPath as the search
34
+ // path while leaving `env` unset, so the seam falls back to its default
35
+ // `env` (process.env) for everything else — PATHEXT included. Ambient
36
+ // PATHEXT therefore still governs win32 resolution exactly as before, but
37
+ // this file never reads it itself: that's what keeps this module clean
38
+ // under local/no-private-binary-resolution, which forbids a PATHEXT read
39
+ // outside the seam.
40
+ pathOverride: envPath,
41
+ requireExecutable: true,
42
+ });
67
43
  }
68
44
  function requireFallowBinary({ cwd, envPath = process.env['PATH'] ?? '' }) {
69
45
  const binary = resolveFallowBinary({ cwd, envPath });