@opengsd/gsd-core 1.9.1 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (426) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -3
  3. package/.opencode/plugins/gsd-core.js +8 -1
  4. package/agents/gsd-code-fixer.md +27 -3
  5. package/agents/gsd-debug-session-manager.md +11 -0
  6. package/agents/gsd-debugger.md +12 -246
  7. package/agents/gsd-doc-synthesizer.md +2 -4
  8. package/agents/gsd-executor.md +12 -10
  9. package/agents/gsd-integration-checker.md +3 -0
  10. package/agents/gsd-mempalace-curator.md +5 -2
  11. package/agents/gsd-phase-researcher.md +20 -1
  12. package/agents/gsd-plan-checker.md +46 -0
  13. package/agents/gsd-planner.md +49 -54
  14. package/agents/gsd-roadmapper.md +21 -3
  15. package/agents/gsd-user-profiler.md +3 -0
  16. package/agents/gsd-verifier.md +26 -73
  17. package/bin/install.js +1272 -1238
  18. package/bin/lib/ui-safety-gate.cjs +2 -0
  19. package/commands/gsd/code-review.md +1 -1
  20. package/commands/gsd/execute-phase.md +1 -1
  21. package/commands/gsd/map-codebase.md +1 -1
  22. package/commands/gsd/mempalace-capture.md +2 -2
  23. package/commands/gsd/mempalace-recall.md +1 -1
  24. package/commands/gsd/new-milestone.md +2 -2
  25. package/commands/gsd/plan-phase.md +1 -1
  26. package/commands/gsd/quick.md +1 -1
  27. package/commands/gsd/review-backlog.md +2 -1
  28. package/commands/gsd/verify-work.md +1 -1
  29. package/gsd-core/bin/gsd-tools.cjs +1009 -115
  30. package/gsd-core/bin/lib/active-workstream-store.cjs +153 -12
  31. package/gsd-core/bin/lib/agent-install-check.cjs +268 -38
  32. package/gsd-core/bin/lib/api-coverage.cjs +123 -5
  33. package/gsd-core/bin/lib/artifacts.cjs +3 -0
  34. package/gsd-core/bin/lib/assumption-delta.cjs +2 -4
  35. package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
  36. package/gsd-core/bin/lib/audit.cjs +926 -202
  37. package/gsd-core/bin/lib/broken-windows.cjs +36 -6
  38. package/gsd-core/bin/lib/capability-consent.cjs +149 -15
  39. package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
  40. package/gsd-core/bin/lib/capability-registry.cjs +608 -148
  41. package/gsd-core/bin/lib/capability-source.cjs +92 -0
  42. package/gsd-core/bin/lib/capability-trust.cjs +444 -25
  43. package/gsd-core/bin/lib/capability-validator.cjs +507 -24
  44. package/gsd-core/bin/lib/capability-writer.cjs +3 -2
  45. package/gsd-core/bin/lib/check-command-router.cjs +114 -38
  46. package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
  47. package/gsd-core/bin/lib/codex-agent-toml.cjs +329 -0
  48. package/gsd-core/bin/lib/command-aliases.cjs +94 -0
  49. package/gsd-core/bin/lib/command-roster.cjs +44 -1
  50. package/gsd-core/bin/lib/commands.cjs +665 -99
  51. package/gsd-core/bin/lib/commonjs-marker.cjs +142 -0
  52. package/gsd-core/bin/lib/complexity-trigger.cjs +1172 -0
  53. package/gsd-core/bin/lib/config-loader.cjs +76 -0
  54. package/gsd-core/bin/lib/config.cjs +22 -2
  55. package/gsd-core/bin/lib/context-composer.cjs +278 -0
  56. package/gsd-core/bin/lib/context-predicates.cjs +506 -0
  57. package/gsd-core/bin/lib/core-utils.cjs +217 -40
  58. package/gsd-core/bin/lib/decisions.cjs +23 -0
  59. package/gsd-core/bin/lib/docs.cjs +3 -2
  60. package/gsd-core/bin/lib/external-job.cjs +19 -4
  61. package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
  62. package/gsd-core/bin/lib/frontmatter.cjs +239 -32
  63. package/gsd-core/bin/lib/gap-checker.cjs +68 -7
  64. package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +57 -6
  65. package/gsd-core/bin/lib/git-base-branch.cjs +160 -15
  66. package/gsd-core/bin/lib/graphify.cjs +142 -27
  67. package/gsd-core/bin/lib/gsd2-import.cjs +37 -5
  68. package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
  69. package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
  70. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +145 -0
  71. package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
  72. package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
  73. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
  74. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +265 -0
  75. package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
  76. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
  77. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +173 -0
  78. package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
  79. package/gsd-core/bin/lib/health-diagnostic.cjs +431 -0
  80. package/gsd-core/bin/lib/host-integration.cjs +13 -1
  81. package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
  82. package/gsd-core/bin/lib/init-command-router.cjs +83 -8
  83. package/gsd-core/bin/lib/init.cjs +1325 -169
  84. package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
  85. package/gsd-core/bin/lib/install-engine.cjs +805 -264
  86. package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
  87. package/gsd-core/bin/lib/install-model-override-resolver.cjs +203 -0
  88. package/gsd-core/bin/lib/install-profiles.cjs +160 -57
  89. package/gsd-core/bin/lib/install-scope.cjs +270 -0
  90. package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
  91. package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
  92. package/gsd-core/bin/lib/installer-migration-authoring.cjs +3 -1
  93. package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
  94. package/gsd-core/bin/lib/installer-migrations/007-retire-config-root-commonjs-marker.cjs +149 -0
  95. package/gsd-core/bin/lib/installer-migrations/008-cursor-retire-commands-surface.cjs +55 -0
  96. package/gsd-core/bin/lib/installer-migrations/009-pi-retire-reserved-hooks-dir.cjs +199 -0
  97. package/gsd-core/bin/lib/installer-migrations.cjs +206 -13
  98. package/gsd-core/bin/lib/io.cjs +38 -3
  99. package/gsd-core/bin/lib/markdown-sectionizer.cjs +8 -1
  100. package/gsd-core/bin/lib/markdown-table.cjs +133 -20
  101. package/gsd-core/bin/lib/mcp-catalog.cjs +518 -0
  102. package/gsd-core/bin/lib/mcp-server.cjs +135 -3
  103. package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
  104. package/gsd-core/bin/lib/milestone.cjs +821 -109
  105. package/gsd-core/bin/lib/model-catalog.cjs +59 -1
  106. package/gsd-core/bin/lib/model-resolver.cjs +183 -40
  107. package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
  108. package/gsd-core/bin/lib/pattern.cjs +122 -0
  109. package/gsd-core/bin/lib/phase-estimation.cjs +1 -1
  110. package/gsd-core/bin/lib/phase-id.cjs +507 -36
  111. package/gsd-core/bin/lib/phase-lifecycle.cjs +28 -3
  112. package/gsd-core/bin/lib/phase-locator.cjs +258 -58
  113. package/gsd-core/bin/lib/phase.cjs +891 -156
  114. package/gsd-core/bin/lib/plan-dependency-graph.cjs +303 -0
  115. package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
  116. package/gsd-core/bin/lib/plan-scan.cjs +86 -2
  117. package/gsd-core/bin/lib/planning-scope.cjs +31 -0
  118. package/gsd-core/bin/lib/planning-snapshot.cjs +890 -0
  119. package/gsd-core/bin/lib/planning-workspace.cjs +60 -6
  120. package/gsd-core/bin/lib/probe-core.cjs +1 -1
  121. package/gsd-core/bin/lib/profile-output.cjs +1 -1
  122. package/gsd-core/bin/lib/prompt-budget.cjs +128 -165
  123. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +740 -0
  124. package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +85 -0
  125. package/gsd-core/bin/lib/review-lane-descriptor.cjs +108 -0
  126. package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
  127. package/gsd-core/bin/lib/review-lane-runner.cjs +447 -68
  128. package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
  129. package/gsd-core/bin/lib/roadmap-command-router.cjs +76 -9
  130. package/gsd-core/bin/lib/roadmap-parser.cjs +1035 -194
  131. package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
  132. package/gsd-core/bin/lib/roadmap.cjs +405 -84
  133. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +795 -100
  134. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
  135. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +440 -57
  136. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
  137. package/gsd-core/bin/lib/runtime-homes.cjs +220 -41
  138. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +220 -44
  139. package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
  140. package/gsd-core/bin/lib/runtime-slash.cjs +27 -9
  141. package/gsd-core/bin/lib/section-manifest.cjs +209 -0
  142. package/gsd-core/bin/lib/security.cjs +104 -5
  143. package/gsd-core/bin/lib/shell-command-projection.cjs +388 -30
  144. package/gsd-core/bin/lib/smart-entry.cjs +154 -22
  145. package/gsd-core/bin/lib/state-command-router.cjs +5 -1
  146. package/gsd-core/bin/lib/state-document.cjs +152 -8
  147. package/gsd-core/bin/lib/state-transition.cjs +424 -105
  148. package/gsd-core/bin/lib/state.cjs +1927 -401
  149. package/gsd-core/bin/lib/surface.cjs +35 -10
  150. package/gsd-core/bin/lib/text-lines.cjs +80 -0
  151. package/gsd-core/bin/lib/token-scanner.cjs +76 -0
  152. package/gsd-core/bin/lib/uat-predicate.cjs +20 -4
  153. package/gsd-core/bin/lib/uat.cjs +706 -64
  154. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
  155. package/gsd-core/bin/lib/ui-safety-gate.cjs +14 -5
  156. package/gsd-core/bin/lib/unusable-input.cjs +33 -0
  157. package/gsd-core/bin/lib/update-context.cjs +8 -2
  158. package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
  159. package/gsd-core/bin/lib/validate.cjs +20 -6
  160. package/gsd-core/bin/lib/vendor/README.md +37 -0
  161. package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
  162. package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
  163. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  164. package/gsd-core/bin/lib/verification.cjs +287 -20
  165. package/gsd-core/bin/lib/verify.cjs +368 -880
  166. package/gsd-core/bin/lib/workflow-fragments.cjs +557 -0
  167. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +203 -19
  168. package/gsd-core/bin/lib/workstream-inventory.cjs +576 -31
  169. package/gsd-core/bin/lib/workstream.cjs +8 -2
  170. package/gsd-core/bin/lib/worktree-base-ref.cjs +50 -6
  171. package/gsd-core/bin/lib/worktree-safety.cjs +450 -125
  172. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
  173. package/gsd-core/bin/shared/config-schema.manifest.json +9 -1
  174. package/gsd-core/references/agent-contracts.md +43 -26
  175. package/gsd-core/references/artifact-types.md +10 -3
  176. package/gsd-core/references/autonomous-ui-design-contract.md +42 -0
  177. package/gsd-core/references/checkpoints.md +2 -2
  178. package/gsd-core/references/context-budget.md +1 -1
  179. package/gsd-core/references/debugger-techniques.md +255 -0
  180. package/gsd-core/references/dispatch-isolation-gate.md +138 -0
  181. package/gsd-core/references/doc-conflict-engine.md +1 -1
  182. package/gsd-core/references/execute-mvp-tdd.md +3 -3
  183. package/gsd-core/references/execute-phase-between-wave-reset.md +6 -2
  184. package/gsd-core/references/execute-phase-context-guard.md +1 -1
  185. package/gsd-core/references/execute-phase-response-language.md +1 -1
  186. package/gsd-core/references/execute-phase-wave-guard.md +6 -2
  187. package/gsd-core/references/gate-prompts.md +1 -1
  188. package/gsd-core/references/git-planning-commit.md +2 -1
  189. package/gsd-core/references/loop-hook-dispatch.md +39 -2
  190. package/gsd-core/references/model-profiles.md +12 -4
  191. package/gsd-core/references/mvp-concepts.md +9 -9
  192. package/gsd-core/references/planner-guidance.md +3 -9
  193. package/gsd-core/references/planner-preconditions.md +1 -1
  194. package/gsd-core/references/planner-reviews.md +1 -1
  195. package/gsd-core/references/planning-config.md +8 -6
  196. package/gsd-core/references/research-documentation-lookup.md +5 -3
  197. package/gsd-core/references/revision-loop.md +1 -1
  198. package/gsd-core/references/specless-probe-fallback.md +8 -7
  199. package/gsd-core/references/universal-anti-patterns.md +3 -3
  200. package/gsd-core/references/verifier-phase-gates.md +192 -0
  201. package/gsd-core/references/verifier-wiring-patterns.md +100 -0
  202. package/gsd-core/references/verify-mvp-mode.md +1 -1
  203. package/gsd-core/references/workstream-flag.md +22 -6
  204. package/gsd-core/references/worktree-branch-check.md +2 -2
  205. package/gsd-core/templates/discussion-log.md +1 -1
  206. package/gsd-core/templates/phase-prompt.md +2 -4
  207. package/gsd-core/templates/state.md +4 -4
  208. package/gsd-core/templates/summary-complex.md +2 -0
  209. package/gsd-core/templates/summary-minimal.md +2 -0
  210. package/gsd-core/templates/summary-standard.md +2 -0
  211. package/gsd-core/templates/summary.md +2 -0
  212. package/gsd-core/templates/verification-report.md +9 -1
  213. package/gsd-core/workflows/ai-integration-phase.md +9 -11
  214. package/gsd-core/workflows/audit-milestone.md +3 -0
  215. package/gsd-core/workflows/autonomous/steps/converge-banner.md +1 -0
  216. package/gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md +11 -0
  217. package/gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md +7 -0
  218. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +21 -0
  219. package/gsd-core/workflows/autonomous/steps/converge-loop.md +7 -0
  220. package/gsd-core/workflows/autonomous.md +33 -70
  221. package/gsd-core/workflows/cleanup.md +62 -3
  222. package/gsd-core/workflows/code-review/steps/dispatch-fix.md +39 -0
  223. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +93 -0
  224. package/gsd-core/workflows/code-review-fix.md +37 -10
  225. package/gsd-core/workflows/code-review.md +74 -166
  226. package/gsd-core/workflows/complete-milestone/steps/git-tag.md +29 -0
  227. package/gsd-core/workflows/complete-milestone.md +160 -95
  228. package/gsd-core/workflows/debug.md +16 -17
  229. package/gsd-core/workflows/diagnose-issues.md +56 -8
  230. package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -1
  231. package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
  232. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +15 -0
  233. package/gsd-core/workflows/discuss-phase-assumptions.md +7 -17
  234. package/gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md +51 -0
  235. package/gsd-core/workflows/docs-update.md +8 -51
  236. package/gsd-core/workflows/edit-phase.md +26 -1
  237. package/gsd-core/workflows/eval-review.md +3 -5
  238. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +64 -7
  239. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +50 -0
  240. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +31 -0
  241. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
  242. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +21 -0
  243. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +42 -0
  244. package/gsd-core/workflows/execute-phase/steps/regression-gate.md +43 -37
  245. package/gsd-core/workflows/execute-phase.md +103 -187
  246. package/gsd-core/workflows/execute-plan.md +36 -4
  247. package/gsd-core/workflows/explore.md +131 -4
  248. package/gsd-core/workflows/fast.md +10 -2
  249. package/gsd-core/workflows/health.md +73 -4
  250. package/gsd-core/workflows/help/modes/full.md +6 -1
  251. package/gsd-core/workflows/import.md +4 -4
  252. package/gsd-core/workflows/ingest-docs.md +7 -6
  253. package/gsd-core/workflows/mvp-phase.md +6 -3
  254. package/gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md +16 -0
  255. package/gsd-core/workflows/new-milestone/steps/reset-phase-safety.md +19 -0
  256. package/gsd-core/workflows/new-milestone.md +35 -47
  257. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +176 -0
  258. package/gsd-core/workflows/new-project/steps/auto-mode-detection.md +32 -0
  259. package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +18 -0
  260. package/gsd-core/workflows/new-project.md +27 -240
  261. package/gsd-core/workflows/next.md +12 -0
  262. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +15 -0
  263. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +110 -0
  264. package/gsd-core/workflows/plan-phase/steps/prd-express-gate.md +8 -0
  265. package/gsd-core/workflows/plan-phase/steps/research-only-early-exit.md +17 -0
  266. package/gsd-core/workflows/plan-phase/steps/research-only-modifiers.md +16 -0
  267. package/gsd-core/workflows/plan-phase/steps/reviews-prerequisite.md +17 -0
  268. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +149 -0
  269. package/gsd-core/workflows/plan-phase.md +89 -209
  270. package/gsd-core/workflows/plan-review-convergence.md +50 -2
  271. package/gsd-core/workflows/progress/steps/forensic-audit.md +125 -0
  272. package/gsd-core/workflows/progress/steps/mvp-display.md +18 -0
  273. package/gsd-core/workflows/progress.md +45 -159
  274. package/gsd-core/workflows/quick/steps/discussion-phase.md +124 -0
  275. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +111 -0
  276. package/gsd-core/workflows/quick/steps/quick-verification.md +67 -0
  277. package/gsd-core/workflows/quick/steps/research-phase.md +72 -0
  278. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +37 -0
  279. package/gsd-core/workflows/quick.md +55 -405
  280. package/gsd-core/workflows/resume-project.md +3 -0
  281. package/gsd-core/workflows/review/steps/reviewer-instances-note-1.md +4 -0
  282. package/gsd-core/workflows/review/steps/reviewer-instances-note-2.md +3 -0
  283. package/gsd-core/workflows/review.md +41 -13
  284. package/gsd-core/workflows/section-manifest.json +219 -0
  285. package/gsd-core/workflows/secure-phase.md +1 -1
  286. package/gsd-core/workflows/session-report.md +2 -1
  287. package/gsd-core/workflows/settings.md +66 -2
  288. package/gsd-core/workflows/ship.md +104 -44
  289. package/gsd-core/workflows/sketch.md +1 -1
  290. package/gsd-core/workflows/spec-phase.md +41 -20
  291. package/gsd-core/workflows/spike-wrap-up.md +20 -5
  292. package/gsd-core/workflows/spike.md +50 -16
  293. package/gsd-core/workflows/sync-skills.md +106 -13
  294. package/gsd-core/workflows/transition/steps/workstream-collision-check.md +17 -0
  295. package/gsd-core/workflows/transition.md +53 -31
  296. package/gsd-core/workflows/ui-phase.md +13 -12
  297. package/gsd-core/workflows/ui-review.md +2 -2
  298. package/gsd-core/workflows/update/steps/channel-banner.md +7 -0
  299. package/gsd-core/workflows/update.md +19 -8
  300. package/gsd-core/workflows/validate-phase.md +1 -1
  301. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +36 -0
  302. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +21 -0
  303. package/gsd-core/workflows/verify-work.md +17 -65
  304. package/hooks/dist/gsd-agent-isolation-guard.js +517 -0
  305. package/hooks/dist/gsd-check-update-worker.js +64 -12
  306. package/hooks/dist/gsd-check-update.js +19 -1
  307. package/hooks/dist/gsd-cursor-pre-tool.js +0 -3
  308. package/hooks/dist/gsd-cursor-subagent-start.js +607 -26
  309. package/hooks/dist/gsd-cursor-subagent-stop.js +3 -2
  310. package/hooks/dist/gsd-prompt-guard.js +21 -20
  311. package/hooks/dist/gsd-read-injection-scanner.js +45 -24
  312. package/hooks/dist/gsd-statusline.js +90 -6
  313. package/hooks/dist/gsd-update-banner.js +22 -1
  314. package/hooks/dist/gsd-workflow-guard.js +134 -36
  315. package/hooks/dist/gsd-worktree-path-guard.js +2 -1
  316. package/hooks/dist/gsd-write-guard.js +359 -0
  317. package/hooks/dist/lib/git-cmd.js +92 -59
  318. package/hooks/dist/lib/injection-patterns.js +45 -0
  319. package/hooks/dist/lib/isolation-deny-reason.js +39 -0
  320. package/hooks/dist/lib/isolation-sentinel.js +277 -0
  321. package/hooks/dist/managed-hooks-registry.cjs +2 -0
  322. package/hooks/gsd-agent-isolation-guard.js +517 -0
  323. package/hooks/gsd-check-update-worker.js +64 -12
  324. package/hooks/gsd-check-update.js +19 -1
  325. package/hooks/gsd-cursor-pre-tool.js +0 -3
  326. package/hooks/gsd-cursor-subagent-start.js +607 -26
  327. package/hooks/gsd-cursor-subagent-stop.js +3 -2
  328. package/hooks/gsd-prompt-guard.js +21 -20
  329. package/hooks/gsd-read-injection-scanner.js +45 -24
  330. package/hooks/gsd-statusline.js +90 -6
  331. package/hooks/gsd-update-banner.js +22 -1
  332. package/hooks/gsd-workflow-guard.js +134 -36
  333. package/hooks/gsd-worktree-path-guard.js +2 -1
  334. package/hooks/gsd-write-guard.js +359 -0
  335. package/hooks/hooks.json +12 -0
  336. package/hooks/lib/git-cmd.js +92 -59
  337. package/hooks/lib/injection-patterns.js +45 -0
  338. package/hooks/lib/isolation-deny-reason.js +39 -0
  339. package/hooks/lib/isolation-sentinel.js +277 -0
  340. package/hooks/managed-hooks-registry.cjs +2 -0
  341. package/package.json +31 -10
  342. package/pi/gsd.cjs +71 -12
  343. package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
  344. package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
  345. package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
  346. package/scripts/build-hooks.js +9 -0
  347. package/scripts/changeset/lint.cjs +68 -6
  348. package/scripts/changeset/serialize.cjs +5 -1
  349. package/scripts/check-alias-drift.cjs +7 -43
  350. package/scripts/check-contract-drift.cjs +297 -0
  351. package/scripts/ci-test-scope.cjs +19 -2
  352. package/scripts/command-contract-helpers.cjs +903 -1
  353. package/scripts/gen-adr-index.cjs +728 -38
  354. package/scripts/gen-capability-matrix.cjs +1 -1
  355. package/scripts/gen-capability-registry.cjs +3 -15
  356. package/scripts/gen-context-index.cjs +439 -0
  357. package/scripts/gen-health-docs.cjs +390 -0
  358. package/scripts/gen-inventory-manifest.cjs +150 -4
  359. package/scripts/gen-loop-host-contract.cjs +4 -24
  360. package/scripts/gen-prompt-budget-parity-corpus.cjs +645 -0
  361. package/scripts/gen-registry.cjs +3 -14
  362. package/scripts/gen-section-manifest.cjs +638 -0
  363. package/scripts/generate-package-identity.cjs +4 -2
  364. package/scripts/lib/alias-drift-families.cjs +46 -0
  365. package/scripts/lib/drift-scan.cjs +278 -0
  366. package/scripts/lint-allow-test-rule-refs.allowlist.json +15 -54
  367. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
  368. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
  369. package/scripts/lint-canary-version-leak.cjs +73 -0
  370. package/scripts/lint-command-contract.cjs +96 -13
  371. package/scripts/lint-compiled-artifact-sync.cjs +6 -1
  372. package/scripts/lint-completion-predicate-drift.cjs +933 -0
  373. package/scripts/lint-completion-ratio-drift.cjs +214 -0
  374. package/scripts/lint-default-flip-documentation.cjs +193 -0
  375. package/scripts/lint-docs-command-form.cjs +195 -0
  376. package/scripts/lint-docs-required.cjs +9 -1
  377. package/scripts/lint-emitted-drift-ack.cjs +215 -20
  378. package/scripts/lint-eslint-glob-coverage.allowlist.json +34 -0
  379. package/scripts/lint-eslint-glob-coverage.cjs +340 -0
  380. package/scripts/lint-example-parser-parity.cjs +395 -0
  381. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
  382. package/scripts/lint-health-diagnostic-rule-table.cjs +404 -0
  383. package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
  384. package/scripts/lint-milestone-window-drift.cjs +468 -0
  385. package/scripts/lint-phase-enumeration-drift.cjs +479 -0
  386. package/scripts/lint-plan-count-drift.cjs +318 -0
  387. package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
  388. package/scripts/lint-planning-prompt-drift.cjs +434 -0
  389. package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
  390. package/scripts/lint-regression-test-names.cjs +15 -13
  391. package/scripts/lint-removed-but-needed.cjs +320 -0
  392. package/scripts/lint-state-field-drift.cjs +805 -0
  393. package/scripts/lint-state-write-path-drift.cjs +1045 -0
  394. package/scripts/lint-test-file-count.allowlist.json +40 -3
  395. package/scripts/lint-unreachable-guard-drift.cjs +843 -0
  396. package/scripts/lint-vendored-deps.cjs +124 -0
  397. package/scripts/mutation-matrix.cjs +13 -0
  398. package/scripts/pr-changed-files.cjs +63 -0
  399. package/scripts/pr-template-policy.cjs +14 -4
  400. package/scripts/prompt-injection-scan.sh +52 -6
  401. package/scripts/require-issue-link-policy.cjs +192 -0
  402. package/scripts/state-write-path-drift-baseline.json +19 -0
  403. package/scripts/sync-runtime-launcher.cjs +2 -4
  404. package/skills/gsd-autonomous/SKILL.md +0 -1
  405. package/skills/gsd-code-review/SKILL.md +1 -1
  406. package/skills/gsd-execute-phase/SKILL.md +1 -2
  407. package/skills/gsd-map-codebase/SKILL.md +1 -1
  408. package/skills/gsd-mempalace-capture/SKILL.md +2 -2
  409. package/skills/gsd-mempalace-recall/SKILL.md +1 -1
  410. package/skills/gsd-new-milestone/SKILL.md +2 -2
  411. package/skills/gsd-next/SKILL.md +0 -1
  412. package/skills/gsd-plan-phase/SKILL.md +1 -2
  413. package/skills/gsd-progress/SKILL.md +0 -1
  414. package/skills/gsd-quick/SKILL.md +1 -1
  415. package/skills/gsd-review-backlog/SKILL.md +2 -1
  416. package/skills/gsd-stats/SKILL.md +0 -1
  417. package/skills/gsd-verify-work/SKILL.md +1 -1
  418. package/vscode/package.json +1 -1
  419. package/gsd-core/workflows/discovery-phase.md +0 -298
  420. package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
  421. package/gsd-core/workflows/verify-phase.md +0 -577
  422. package/scripts/affected-tests-lib.cjs +0 -554
  423. package/scripts/gen-emitted-baseline.cjs +0 -145
  424. package/scripts/lint-allow-test-rule-refs.cjs +0 -162
  425. package/scripts/run-affected-tests.cjs +0 -7
  426. package/scripts/run-tests.cjs +0 -1050
@@ -0,0 +1,638 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * gen-section-manifest.cjs — generates gsd-core/workflows/section-manifest.json
6
+ * from the `<!-- gsd:section -->` markers in gsd-core/workflows/*.md (ADR-1671
7
+ * epic #1671; Phase 5 / issue #2932 introduced the artifact,
8
+ * `.gsd/phase/chore-2932-init-section-manifest/40-design.md`; Phase 6.1 /
9
+ * issue #2992 generalized it from single-workflow to PER-WORKFLOW,
10
+ * `.gsd/phase/chore-2992-widen-when-vocabulary/40-design.md`).
11
+ *
12
+ * Reuses `parseWorkflowSections` from the compiled `workflow-fragments.cjs`
13
+ * (src/workflow-fragments.cts, Phase 3 / #2930) UNCHANGED — this module never
14
+ * re-implements marker parsing (design "Rejected #6"; a second parser is the
15
+ * `DEFECT.GENERATIVE-FIX` divergence class).
16
+ *
17
+ * The committed artifact is placed INSIDE the `gsd-core/` tree (not `docs/`,
18
+ * unlike `docs/CONTEXT-INDEX.json`/`docs/INVENTORY-MANIFEST.json`) because it
19
+ * must SHIP: `bin/install.js`'s `copyWithPathReplacement` only copies
20
+ * `gsd-core/`, and the init CLI's run-time selection reads this artifact
21
+ * from the INSTALLED tree, not the dev repo. `copyWithPathReplacement`
22
+ * only runs `composeWorkflow`/converters on `*.md` — a `.json` leaf falls
23
+ * through to a plain `fs.copyFileSync`, so the artifact ships byte-identical.
24
+ *
25
+ * Shape (#2992 Phase 6.1): `{ "workflows": { "<workflow-name>": [{id, when,
26
+ * read}, ...], ... } }`, where `<workflow-name>` is a source `.md` file's
27
+ * basename without extension. A workflow with zero explicit sections
28
+ * contributes NO key at all (absence, not `[]` — an init caller for that
29
+ * workflow must degrade to `null`, never be attributed some OTHER workflow's
30
+ * sections). Keys are serialized in sorted (filename) order; each workflow's
31
+ * own sections stay in document order. The pre-6.1 shape was a single flat
32
+ * `{ sections: [...] }` array with no workflow key at all — `isValidManifestShape`
33
+ * REJECTS that shape outright so a stale committed artifact can never be
34
+ * mis-attributed to whichever workflow asks first (design row C4).
35
+ *
36
+ * Per design's "Rejected" list, a workflow's section list carries NEITHER
37
+ * section content (would duplicate every section's bytes, fighting Phase 4's
38
+ * emitted-byte caps) NOR line numbers (re-drifts on any line shift, and
39
+ * per-runtime converters rewrite text so ranges would differ per runtime).
40
+ * It carries only `{id, when, read}` triples — `read` is a POSIX-normalized
41
+ * path, relative to the repo root, of the step file the section body was
42
+ * moved to.
43
+ *
44
+ * Usage:
45
+ * node scripts/gen-section-manifest.cjs # print to stdout
46
+ * node scripts/gen-section-manifest.cjs --write # write the manifest
47
+ * node scripts/gen-section-manifest.cjs --check # exit 1 if stale/invalid
48
+ * node scripts/gen-section-manifest.cjs --check --json # same, + typed report
49
+ * node scripts/gen-section-manifest.cjs --write --workflows-dir <p> --manifest-path <p>
50
+ * # override the two hardcoded
51
+ * # repo-root paths (tests point
52
+ * # the real CLI at a temp fixture
53
+ * # tree with no fs monkeypatching)
54
+ *
55
+ * Only `.md` files directly inside `--workflows-dir` are scanned (not files
56
+ * already inside a `<workflow>/steps/` subdirectory — those are MOVED-TO
57
+ * output, never source-with-markers). A workflow with zero explicit sections
58
+ * (most workflows today) contributes no key to `workflows` and is never
59
+ * orphan-checked — orphan-checking is scoped only to a workflow's OWN
60
+ * `steps/` directory, and only for workflows that declare at least one
61
+ * `gsd:section` marker.
62
+ *
63
+ * `--check` fails closed (exit 1, never a stack trace) on:
64
+ * - the compiled `workflow-fragments.cjs` dependency being unbuilt/unloadable
65
+ * (FAIL_LIB_NOT_BUILT — `npm run build:lib` has not run)
66
+ * - the live build itself failing: a marker's derived step file does not
67
+ * exist on disk (FAIL_MISSING_STEP_FILE), a step file in a managed
68
+ * workflow's `steps/` dir is referenced by no marker AND no reachable
69
+ * prose reference (FAIL_ORPHAN_STEP_FILE — see `findOrphanStepFiles`),
70
+ * or the source itself fails to parse (FAIL_SOURCE_PARSE_ERROR, wraps
71
+ * `parseWorkflowSections`' typed `WorkflowFragmentsError`)
72
+ * - the committed manifest: absent (FAIL_MANIFEST_MISSING), empty/unparseable
73
+ * JSON (FAIL_MANIFEST_UNPARSEABLE), valid JSON but the wrong shape
74
+ * (FAIL_MANIFEST_MALFORMED_SHAPE — `0`, `"s"`, `[]`, `null`, `true` all
75
+ * land here), or parseable-and-shaped but not equal to the live build
76
+ * (FAIL_STALE)
77
+ *
78
+ * `--write --json` reports the same typed envelope on failure: the compiled
79
+ * dependency being unloadable (FAIL_LIB_NOT_BUILT, same as above) or the
80
+ * atomic write itself failing (FAIL_WRITE_ERROR — see `writeManifestAtomically`).
81
+ *
82
+ * Orphan detection (`findOrphanStepFiles`) is plain substring reachability
83
+ * over prose, NOT a second marker parser: starting from the parent workflow's
84
+ * raw text, it does a fixed-point search for the literal token
85
+ * `steps/<basename>` across the parent text and every already-reached step
86
+ * file's own text (so a step file that is itself an on-demand delegation
87
+ * TARGET of another step file — e.g. `regression-gate.md`'s own "Read and
88
+ * execute `.../steps/regression-gate-run.md`" line — is correctly resolved
89
+ * as non-orphan without the generator needing to know that convention).
90
+ *
91
+ * `--write` writes atomically: content lands at a same-directory temp path
92
+ * first, then `fs.renameSync` swaps it into place. If either step throws, the
93
+ * temp path is removed (best-effort) and the target manifest is left exactly
94
+ * as it was — never truncated or partially written.
95
+ */
96
+
97
+ const fs = require('node:fs');
98
+ const path = require('node:path');
99
+
100
+ const { ExitError, runMain } = require('./lib/cli-exit.cjs');
101
+
102
+ const ROOT = path.resolve(__dirname, '..');
103
+ const WORKFLOWS_DIR = path.join(ROOT, 'gsd-core', 'workflows');
104
+ const MANIFEST_PATH = path.join(WORKFLOWS_DIR, 'section-manifest.json');
105
+
106
+ // ─── Typed reason enum (CONTRIBUTING.md "Prohibited: Raw Text Matching") ───────
107
+
108
+ /**
109
+ * Stable reason codes for `checkReport`'s `reason` field. Tests assert via
110
+ * `assert.equal(report.reason, REASON.X)` rather than regex-matching the
111
+ * human-readable prose the non-JSON `--check` mode still writes to
112
+ * stdout/stderr.
113
+ *
114
+ * Adding a new reason requires updating this map AND the test that locks
115
+ * `Object.keys(REASON).sort()` as a coordinated change.
116
+ */
117
+ const REASON = Object.freeze({
118
+ OK_UP_TO_DATE: 'ok_up_to_date',
119
+ FAIL_STALE: 'fail_stale',
120
+ FAIL_MANIFEST_MISSING: 'fail_manifest_missing',
121
+ FAIL_MANIFEST_UNPARSEABLE: 'fail_manifest_unparseable',
122
+ FAIL_MANIFEST_MALFORMED_SHAPE: 'fail_manifest_malformed_shape',
123
+ FAIL_MISSING_STEP_FILE: 'fail_missing_step_file',
124
+ FAIL_ORPHAN_STEP_FILE: 'fail_orphan_step_file',
125
+ FAIL_SOURCE_PARSE_ERROR: 'fail_source_parse_error',
126
+ FAIL_LIB_NOT_BUILT: 'fail_lib_not_built',
127
+ FAIL_WRITE_ERROR: 'fail_write_error',
128
+ });
129
+
130
+ // ─── Loaders ──────────────────────────────────────────────────────────────────
131
+
132
+ const WORKFLOW_FRAGMENTS_LIB_PATH = path.join(ROOT, 'gsd-core', 'bin', 'lib', 'workflow-fragments.cjs');
133
+
134
+ /**
135
+ * Load the compiled workflow-fragments library. The artifact is a gitignored
136
+ * tsc build output of src/workflow-fragments.cts and only exists after
137
+ * `npm run build:lib`. Throws a clean `ManifestBuildError` (REASON.FAIL_LIB_NOT_BUILT;
138
+ * never a bare MODULE_NOT_FOUND stack) naming the remedy when it is missing —
139
+ * `ManifestBuildError` extends `ExitError`, so `runMain` still prints only the
140
+ * friendly message, and `checkReport`/the `--write --json` path can still read
141
+ * `.reason`/`.subject` off it to emit the typed envelope.
142
+ *
143
+ * @returns {{ parseWorkflowSections: Function }}
144
+ */
145
+ function loadWorkflowFragmentsLib() {
146
+ try {
147
+ delete require.cache[require.resolve(WORKFLOW_FRAGMENTS_LIB_PATH)];
148
+ return require(WORKFLOW_FRAGMENTS_LIB_PATH);
149
+ } catch (err) {
150
+ const subject = relPosix(ROOT, WORKFLOW_FRAGMENTS_LIB_PATH);
151
+ throw new ManifestBuildError(
152
+ REASON.FAIL_LIB_NOT_BUILT,
153
+ subject,
154
+ `Cannot load ${subject}: ${err && err.message}\n` +
155
+ 'Run:\n npm run build:lib\n',
156
+ );
157
+ }
158
+ }
159
+
160
+ // ─── POSIX path helpers ─────────────────────────────────────────────────────
161
+
162
+ /** Unconditional backslash->forward-slash normalization (CONTEXT.md
163
+ * path-separator-normalization rule: never gate on `path.sep`). */
164
+ function toPosix(p) {
165
+ return p.replace(/\\/g, '/');
166
+ }
167
+
168
+ /**
169
+ * Repo-root-relative POSIX path for a file under `repoRoot`.
170
+ *
171
+ * @param {string} repoRoot
172
+ * @param {string} absPath
173
+ */
174
+ function relPosix(repoRoot, absPath) {
175
+ return toPosix(path.relative(repoRoot, absPath));
176
+ }
177
+
178
+ // ─── Orphan detection (plain substring reachability, not a marker parser) ────
179
+
180
+ /**
181
+ * Fixed-point reachability scan over a managed workflow's `steps/` directory:
182
+ * a step file is "reached" once the literal token `steps/<its-basename>`
183
+ * appears in the parent workflow's raw text OR in the text of any
184
+ * already-reached step file (so nested delegation — a step file that itself
185
+ * names another step file — resolves without the generator knowing that
186
+ * convention explicitly). Returns the SORTED list of `.md` basenames in
187
+ * `stepsDir` that are never reached (i.e. orphans). Returns `[]` if
188
+ * `stepsDir` does not exist.
189
+ *
190
+ * @param {string} parentText - the workflow.md's raw source text
191
+ * @param {string} stepsDir - absolute path to `<workflow>/steps/`
192
+ * @returns {string[]}
193
+ */
194
+ function findOrphanStepFiles(parentText, stepsDir) {
195
+ if (!fs.existsSync(stepsDir)) return [];
196
+ const files = fs
197
+ .readdirSync(stepsDir, { withFileTypes: true })
198
+ .filter((d) => d.isFile() && d.name.endsWith('.md'))
199
+ .map((d) => d.name)
200
+ .sort();
201
+
202
+ const textOf = new Map(files.map((f) => [f, fs.readFileSync(path.join(stepsDir, f), 'utf8')]));
203
+ const reached = new Set();
204
+ const haystacks = [parentText];
205
+
206
+ let changed = true;
207
+ while (changed) {
208
+ changed = false;
209
+ for (const f of files) {
210
+ if (reached.has(f)) continue;
211
+ const token = `steps/${f}`;
212
+ if (haystacks.some((h) => h.includes(token))) {
213
+ reached.add(f);
214
+ haystacks.push(textOf.get(f));
215
+ changed = true;
216
+ }
217
+ }
218
+ }
219
+
220
+ return files.filter((f) => !reached.has(f));
221
+ }
222
+
223
+ // ─── Live manifest build ─────────────────────────────────────────────────────
224
+
225
+ /** Thrown by `buildFreshManifest`/`loadWorkflowFragmentsLib`/`writeManifestAtomically`
226
+ * for every fail-closed condition below; carries a stable `reason` + the
227
+ * offending `subject` path so `checkReport`/the `--write --json` path never
228
+ * need to string-match a message. Extends `ExitError` (not plain `Error`) so
229
+ * `runMain` still prints only the friendly `message` — never a bare stack
230
+ * trace — for the `default`/`--write` (non-`--json`) code paths that let it
231
+ * propagate uncaught, exactly like every other `ExitError`. */
232
+ class ManifestBuildError extends ExitError {
233
+ constructor(reason, subject, message) {
234
+ super(1, message);
235
+ this.name = 'ManifestBuildError';
236
+ this.reason = reason;
237
+ this.subject = subject;
238
+ }
239
+ }
240
+
241
+ /**
242
+ * Scan `workflowsDir` for `.md` files carrying `gsd:section` markers and
243
+ * build the live (freshly-derived) manifest: `{ workflows: { <name>:
244
+ * [{id, when, read}], ... } }` (#2992 Phase 6.1). Workflow keys are
245
+ * serialized in sorted (filename) order; a workflow's own sections stay in
246
+ * document order. A workflow with zero explicit sections contributes NO key
247
+ * at all — absence, not `[]` (design row C4/C11: absence must degrade to
248
+ * `null` at the init seam, never be confused with "computed, no sections").
249
+ * Throws `ManifestBuildError` on any fail-closed condition (missing step
250
+ * file, orphan step file, unparseable source).
251
+ *
252
+ * @param {string} workflowsDir - defaults to the real repo-root gsd-core/workflows/
253
+ * @param {string} repoRoot - root `read` paths are computed relative to
254
+ * @returns {{ workflows: Record<string, Array<{id: string, when: string, read: string}>> }}
255
+ */
256
+ function buildFreshManifest(workflowsDir = WORKFLOWS_DIR, repoRoot = ROOT) {
257
+ const { parseWorkflowSections } = loadWorkflowFragmentsLib();
258
+
259
+ const workflowFiles = fs
260
+ .readdirSync(workflowsDir, { withFileTypes: true })
261
+ .filter((d) => d.isFile() && d.name.endsWith('.md'))
262
+ .map((d) => d.name)
263
+ .sort();
264
+
265
+ const workflows = {};
266
+
267
+ for (const fileName of workflowFiles) {
268
+ const filePath = path.join(workflowsDir, fileName);
269
+ const relSourcePath = relPosix(repoRoot, filePath);
270
+ let content;
271
+ try {
272
+ content = fs.readFileSync(filePath, 'utf8');
273
+ } catch (err) {
274
+ throw new ManifestBuildError(REASON.FAIL_SOURCE_PARSE_ERROR, relSourcePath, `Cannot read ${relSourcePath}: ${err && err.message}`);
275
+ }
276
+
277
+ let parsed;
278
+ try {
279
+ parsed = parseWorkflowSections(content, relSourcePath);
280
+ } catch (err) {
281
+ throw new ManifestBuildError(REASON.FAIL_SOURCE_PARSE_ERROR, relSourcePath, `${relSourcePath}: ${err && err.message}`);
282
+ }
283
+
284
+ const explicitSections = parsed.filter((s) => s.explicit);
285
+ if (explicitSections.length === 0) continue;
286
+
287
+ const workflowName = fileName.replace(/\.md$/, '');
288
+ const stepsDir = path.join(workflowsDir, workflowName, 'steps');
289
+ const sections = [];
290
+
291
+ for (const section of explicitSections) {
292
+ const stepFileAbs = path.join(stepsDir, `${section.id}.md`);
293
+ if (!fs.existsSync(stepFileAbs)) {
294
+ const relStepPath = relPosix(repoRoot, stepFileAbs);
295
+ throw new ManifestBuildError(
296
+ REASON.FAIL_MISSING_STEP_FILE,
297
+ relStepPath,
298
+ `${relSourcePath}: section "${section.id}" (when="${section.when}") names step file ${relStepPath}, which does not exist`,
299
+ );
300
+ }
301
+ sections.push({
302
+ id: section.id,
303
+ when: section.when,
304
+ read: relPosix(repoRoot, stepFileAbs),
305
+ });
306
+ }
307
+
308
+ const orphans = findOrphanStepFiles(content, stepsDir);
309
+ if (orphans.length > 0) {
310
+ const relOrphanPath = relPosix(repoRoot, path.join(stepsDir, orphans[0]));
311
+ throw new ManifestBuildError(
312
+ REASON.FAIL_ORPHAN_STEP_FILE,
313
+ relOrphanPath,
314
+ `${relOrphanPath} is not referenced by any gsd:section marker or reachable "steps/" reference in ${relSourcePath}`,
315
+ );
316
+ }
317
+
318
+ workflows[workflowName] = sections;
319
+ }
320
+
321
+ return { workflows };
322
+ }
323
+
324
+ // ─── Serialization ────────────────────────────────────────────────────────────
325
+
326
+ /**
327
+ * @param {{ workflows: Record<string, Array<{id: string, when: string, read: string}>> }} manifest
328
+ * @returns {string}
329
+ */
330
+ function serializeManifest(manifest) {
331
+ return JSON.stringify(manifest, null, 2) + '\n';
332
+ }
333
+
334
+ /**
335
+ * Workflow-count / section-count pair for a manifest's `workflows` map, used
336
+ * by the `--write` stdout summary and the `--check` OK message.
337
+ *
338
+ * @param {{ workflows: Record<string, Array<{id: string, when: string, read: string}>> }} manifest
339
+ * @returns {{ workflowCount: number, sectionCount: number }}
340
+ */
341
+ function countManifest(manifest) {
342
+ const workflowNames = Object.keys(manifest.workflows);
343
+ const sectionCount = workflowNames.reduce((sum, name) => sum + manifest.workflows[name].length, 0);
344
+ return { workflowCount: workflowNames.length, sectionCount };
345
+ }
346
+
347
+ /**
348
+ * True when `parsed` has the expected committed-manifest shape (#2992 Phase
349
+ * 6.1): a plain object (not an array, not null) carrying a `workflows` plain
350
+ * object (not an array, not null) whose every own value is an array of
351
+ * `{id, when, read}` string triples. Rejects `0`, `"s"`, `[]`, `null`, `true`
352
+ * — AND rejects the pre-6.1 flat `{sections:[...]}` shape (no `workflows`
353
+ * key ⇒ `parsed.workflows` is `undefined`, which fails the object check),
354
+ * so a stale committed artifact can never be silently mis-attributed to
355
+ * whichever workflow asks first (design row C4).
356
+ *
357
+ * @param {unknown} parsed
358
+ * @returns {boolean}
359
+ */
360
+ function isValidManifestShape(parsed) {
361
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) return false;
362
+ const workflows = parsed.workflows;
363
+ if (workflows === null || typeof workflows !== 'object' || Array.isArray(workflows)) return false;
364
+ return Object.values(workflows).every(
365
+ (sections) =>
366
+ Array.isArray(sections) &&
367
+ sections.every(
368
+ (s) => s !== null && typeof s === 'object' && typeof s.id === 'string' && typeof s.when === 'string' && typeof s.read === 'string',
369
+ ),
370
+ );
371
+ }
372
+
373
+ // ─── Atomic write ─────────────────────────────────────────────────────────────
374
+
375
+ /**
376
+ * Write `content` to `targetPath` atomically: write to a same-directory temp
377
+ * path, then `fs.renameSync` it into place (same filesystem, so the rename is
378
+ * atomic). On ANY failure (the write or the rename), the temp path is removed
379
+ * best-effort and a `ManifestBuildError` (REASON.FAIL_WRITE_ERROR, subject
380
+ * `targetPath`) is thrown — `targetPath` is left exactly as it was before the
381
+ * call, never truncated or partially written. `ManifestBuildError` extends
382
+ * `ExitError`, so a caller that lets it propagate uncaught (e.g. `--write`
383
+ * without `--json`) still gets only the friendly message, never a stack trace.
384
+ *
385
+ * @param {string} targetPath
386
+ * @param {string} content
387
+ */
388
+ function writeManifestAtomically(targetPath, content) {
389
+ const tmpPath = path.join(path.dirname(targetPath), `.${path.basename(targetPath)}.tmp-${process.pid}-${Date.now()}`);
390
+ try {
391
+ fs.writeFileSync(tmpPath, content, 'utf8');
392
+ fs.renameSync(tmpPath, targetPath);
393
+ } catch (err) {
394
+ try {
395
+ fs.unlinkSync(tmpPath);
396
+ } catch (_cleanupErr) {
397
+ // best-effort: tmpPath may never have been created (writeFileSync itself threw)
398
+ }
399
+ throw new ManifestBuildError(REASON.FAIL_WRITE_ERROR, targetPath, `Cannot write ${targetPath}: ${err && err.message}`);
400
+ }
401
+ }
402
+
403
+ // ─── Typed check report ───────────────────────────────────────────────────────
404
+
405
+ /**
406
+ * Empty-report shape shared by every early-exit branch below.
407
+ *
408
+ * @returns {{ subject: string | null }}
409
+ */
410
+ function emptyReportFields() {
411
+ return { subject: null };
412
+ }
413
+
414
+ /**
415
+ * Compute the full `--check` result as a typed, non-throwing report.
416
+ *
417
+ * @param {string} [workflowsDir]
418
+ * @param {string} [manifestPath]
419
+ * @param {string} [repoRoot]
420
+ * @returns {{ ok: boolean, reason: string, subject: string | null, message: string }}
421
+ */
422
+ function checkReport(workflowsDir = WORKFLOWS_DIR, manifestPath = MANIFEST_PATH, repoRoot = ROOT) {
423
+ let live;
424
+ try {
425
+ live = buildFreshManifest(workflowsDir, repoRoot);
426
+ } catch (err) {
427
+ if (err instanceof ManifestBuildError) {
428
+ return { ok: false, reason: err.reason, subject: err.subject, message: `${err.message}\n` };
429
+ }
430
+ throw err;
431
+ }
432
+
433
+ if (!fs.existsSync(manifestPath)) {
434
+ return {
435
+ ok: false,
436
+ reason: REASON.FAIL_MANIFEST_MISSING,
437
+ ...emptyReportFields(),
438
+ subject: manifestPath,
439
+ message: `${manifestPath} does not exist. Run:\n node scripts/gen-section-manifest.cjs --write\n`,
440
+ };
441
+ }
442
+
443
+ let committedText;
444
+ try {
445
+ committedText = fs.readFileSync(manifestPath, 'utf8');
446
+ } catch (err) {
447
+ return {
448
+ ok: false,
449
+ reason: REASON.FAIL_MANIFEST_UNPARSEABLE,
450
+ subject: manifestPath,
451
+ message: `Cannot read ${manifestPath}: ${err && err.message}\n`,
452
+ };
453
+ }
454
+
455
+ let committed;
456
+ try {
457
+ committed = JSON.parse(committedText);
458
+ } catch (err) {
459
+ return {
460
+ ok: false,
461
+ reason: REASON.FAIL_MANIFEST_UNPARSEABLE,
462
+ subject: manifestPath,
463
+ message: `${manifestPath} is not valid JSON: ${err && err.message}\n` +
464
+ 'Run:\n node scripts/gen-section-manifest.cjs --write\n',
465
+ };
466
+ }
467
+
468
+ if (!isValidManifestShape(committed)) {
469
+ return {
470
+ ok: false,
471
+ reason: REASON.FAIL_MANIFEST_MALFORMED_SHAPE,
472
+ subject: manifestPath,
473
+ message: `${manifestPath} is valid JSON but does not have the expected {workflows:{<name>:[{id,when,read}]}} shape.\n` +
474
+ 'Run:\n node scripts/gen-section-manifest.cjs --write\n',
475
+ };
476
+ }
477
+
478
+ if (JSON.stringify(committed) !== JSON.stringify(live)) {
479
+ return {
480
+ ok: false,
481
+ reason: REASON.FAIL_STALE,
482
+ subject: manifestPath,
483
+ message: `${manifestPath} is stale. Run:\n node scripts/gen-section-manifest.cjs --write\n`,
484
+ };
485
+ }
486
+
487
+ const { workflowCount, sectionCount } = countManifest(live);
488
+ return {
489
+ ok: true,
490
+ reason: REASON.OK_UP_TO_DATE,
491
+ subject: null,
492
+ message: `${manifestPath} is up to date (${workflowCount} workflow${workflowCount === 1 ? '' : 's'}, ${sectionCount} section${sectionCount === 1 ? '' : 's'}).\n`,
493
+ };
494
+ }
495
+
496
+ // ─── Argument parsing ─────────────────────────────────────────────────────────
497
+
498
+ /**
499
+ * @param {string|undefined} value
500
+ * @returns {boolean}
501
+ */
502
+ function isMissingPathValue(value) {
503
+ return value === undefined || value === '' || value.startsWith('-');
504
+ }
505
+
506
+ /**
507
+ * @param {string[]} argv
508
+ * @returns {{ mode: 'check'|'write'|'default'|'unknown', json: boolean, workflowsDir: string, manifestPath: string, repoRoot: string, unknownArg?: string, usageMessage?: string }}
509
+ */
510
+ function parseArgs(argv) {
511
+ const opts = { mode: 'default', json: false, workflowsDir: WORKFLOWS_DIR, manifestPath: MANIFEST_PATH, repoRoot: ROOT };
512
+ let sawCheck = false;
513
+ let sawWrite = false;
514
+
515
+ for (let i = 0; i < argv.length; i++) {
516
+ const arg = argv[i];
517
+ if (arg === '--check') {
518
+ sawCheck = true;
519
+ opts.mode = 'check';
520
+ } else if (arg === '--write') {
521
+ sawWrite = true;
522
+ opts.mode = 'write';
523
+ } else if (arg === '--json') {
524
+ opts.json = true;
525
+ } else if (arg === '--workflows-dir' || arg === '--manifest-path' || arg === '--repo-root') {
526
+ const value = argv[i + 1];
527
+ if (isMissingPathValue(value)) {
528
+ return {
529
+ ...opts,
530
+ mode: 'unknown',
531
+ unknownArg: arg,
532
+ usageMessage: `${arg} requires a non-empty path argument (got ${value === undefined ? 'nothing' : JSON.stringify(value)})`,
533
+ };
534
+ }
535
+ i++;
536
+ if (arg === '--workflows-dir') opts.workflowsDir = path.resolve(value);
537
+ else if (arg === '--manifest-path') opts.manifestPath = path.resolve(value);
538
+ else opts.repoRoot = path.resolve(value);
539
+ } else {
540
+ opts.mode = 'unknown';
541
+ opts.unknownArg = arg;
542
+ }
543
+ }
544
+
545
+ if (sawCheck && sawWrite) {
546
+ return { ...opts, mode: 'unknown', usageMessage: '--check and --write are mutually exclusive' };
547
+ }
548
+
549
+ return opts;
550
+ }
551
+
552
+ // ─── Main ─────────────────────────────────────────────────────────────────────
553
+
554
+ function main() {
555
+ const opts = parseArgs(process.argv.slice(2));
556
+
557
+ if (opts.mode === 'unknown') {
558
+ process.stderr.write('Usage: gen-section-manifest.cjs [--write|--check] [--json] [--workflows-dir <path>] [--manifest-path <path>] [--repo-root <path>]\n');
559
+ if (opts.usageMessage) process.stderr.write(`${opts.usageMessage}\n`);
560
+ throw new ExitError(1);
561
+ }
562
+
563
+ if (opts.mode === 'default') {
564
+ process.stdout.write(serializeManifest(buildFreshManifest(opts.workflowsDir, opts.repoRoot)));
565
+ return;
566
+ }
567
+
568
+ if (opts.mode === 'check') {
569
+ const report = checkReport(opts.workflowsDir, opts.manifestPath, opts.repoRoot);
570
+
571
+ if (opts.json) {
572
+ process.stdout.write(JSON.stringify({ ok: report.ok, reason: report.reason, subject: report.subject }) + '\n');
573
+ } else if (report.ok) {
574
+ process.stdout.write(report.message);
575
+ }
576
+
577
+ if (!report.ok) {
578
+ throw new ExitError(1, opts.json ? undefined : report.message);
579
+ }
580
+ return;
581
+ }
582
+
583
+ // opts.mode === 'write'. Same typed-envelope pattern as --check --json above:
584
+ // a ManifestBuildError (FAIL_LIB_NOT_BUILT from buildFreshManifest,
585
+ // FAIL_WRITE_ERROR from writeManifestAtomically) is caught here so --json
586
+ // still emits {ok, reason, subject} instead of letting the error propagate
587
+ // to runMain unobserved by the JSON caller.
588
+ let manifest;
589
+ let writeErr;
590
+ try {
591
+ manifest = buildFreshManifest(opts.workflowsDir, opts.repoRoot);
592
+ writeManifestAtomically(opts.manifestPath, serializeManifest(manifest));
593
+ } catch (err) {
594
+ if (!(err instanceof ManifestBuildError)) throw err;
595
+ writeErr = err;
596
+ }
597
+
598
+ if (opts.json) {
599
+ process.stdout.write(JSON.stringify(
600
+ writeErr
601
+ ? { ok: false, reason: writeErr.reason, subject: writeErr.subject }
602
+ : { ok: true, reason: REASON.OK_UP_TO_DATE, subject: null },
603
+ ) + '\n');
604
+ } else if (!writeErr) {
605
+ const { workflowCount, sectionCount } = countManifest(manifest);
606
+ process.stdout.write(`Wrote ${opts.manifestPath}\n ${workflowCount} workflow${workflowCount === 1 ? '' : 's'}, ${sectionCount} section${sectionCount === 1 ? '' : 's'}\n`);
607
+ }
608
+
609
+ if (writeErr) {
610
+ throw new ExitError(1, opts.json ? undefined : writeErr.message);
611
+ }
612
+ }
613
+
614
+ // ─── Exports (for tests) ──────────────────────────────────────────────────────
615
+
616
+ module.exports = {
617
+ loadWorkflowFragmentsLib,
618
+ findOrphanStepFiles,
619
+ buildFreshManifest,
620
+ serializeManifest,
621
+ countManifest,
622
+ isValidManifestShape,
623
+ writeManifestAtomically,
624
+ checkReport,
625
+ parseArgs,
626
+ ManifestBuildError,
627
+ REASON,
628
+ WORKFLOW_FRAGMENTS_LIB_PATH,
629
+ WORKFLOWS_DIR,
630
+ MANIFEST_PATH,
631
+ ROOT,
632
+ };
633
+
634
+ // ─── CLI entry point ──────────────────────────────────────────────────────────
635
+
636
+ if (require.main === module) {
637
+ runMain(main);
638
+ }
@@ -7,8 +7,10 @@
7
7
  * `deriveIdentity(pkg)` is the pure core: it turns a parsed package.json into
8
8
  * the coordinate record every consumer needs. The generated runtime module
9
9
  * `gsd-core/bin/lib/package-identity.cjs` bakes those values at build
10
- * time, because the installed tree carries only a synthetic
11
- * `{"type":"commonjs"}` package.json (no `.name`) — so a runtime
10
+ * time, because the installed tree carries no package.json with a `.name` —
11
+ * the only ones GSD stages are synthetic `{"type":"commonjs"}` markers, which
12
+ * since #2544 live inside GSD's own directories (`hooks/`, and the native
13
+ * plugin dir) rather than at the config root — so a runtime
12
14
  * `require('package.json').name` resolves to `undefined` (the #378 bug this
13
15
  * seam retires). Baking from package.json reconciles #378 (renames survive)
14
16
  * with #2992 (the value is never an LLM runtime choice).