@opengsd/gsd-core 1.11.0 → 1.13.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 (498) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.opencode/plugins/gsd-core.js +12 -0
  4. package/agents/gsd-code-fixer.md +1 -1
  5. package/agents/gsd-debug-session-manager.md +1 -1
  6. package/agents/gsd-debugger.md +1 -1
  7. package/agents/gsd-dom-verifier.md +169 -0
  8. package/agents/gsd-eval-auditor.md +1 -1
  9. package/agents/gsd-executor.md +78 -42
  10. package/agents/gsd-framework-selector.md +1 -3
  11. package/agents/gsd-intel-updater.md +1 -1
  12. package/agents/gsd-mempalace-curator.md +0 -1
  13. package/agents/gsd-pattern-mapper.md +11 -0
  14. package/agents/gsd-phase-researcher.md +3 -1
  15. package/agents/gsd-plan-checker.md +91 -112
  16. package/agents/gsd-planner.md +20 -4
  17. package/agents/gsd-project-researcher.md +1 -1
  18. package/agents/gsd-research-synthesizer.md +2 -2
  19. package/agents/gsd-roadmapper.md +15 -11
  20. package/agents/gsd-ui-checker.md +82 -7
  21. package/agents/gsd-ui-researcher.md +70 -3
  22. package/agents/gsd-verifier.md +24 -2
  23. package/bin/install.js +847 -200
  24. package/commands/gsd/discuss-phase.md +1 -1
  25. package/commands/gsd/execute-phase.md +1 -1
  26. package/commands/gsd/import.md +1 -1
  27. package/commands/gsd/ns-workflow.md +2 -1
  28. package/commands/gsd/phase.md +1 -1
  29. package/commands/gsd/quick-batch.md +105 -0
  30. package/commands/gsd/quick.md +8 -4
  31. package/commands/gsd/surface.md +18 -8
  32. package/gsd-core/bin/gsd-tools.cjs +761 -100
  33. package/gsd-core/bin/lib/active-workstream-store.cjs +8 -0
  34. package/gsd-core/bin/lib/adr-parser.cjs +13 -7
  35. package/gsd-core/bin/lib/agent-install-check.cjs +162 -0
  36. package/gsd-core/bin/lib/api-coverage.cjs +30 -9
  37. package/gsd-core/bin/lib/artifacts.cjs +2 -0
  38. package/gsd-core/bin/lib/assumption-delta.cjs +30 -11
  39. package/gsd-core/bin/lib/audit.cjs +163 -41
  40. package/gsd-core/bin/lib/broken-windows.cjs +306 -28
  41. package/gsd-core/bin/lib/capability-activation.cjs +27 -0
  42. package/gsd-core/bin/lib/capability-lock.cjs +10 -4
  43. package/gsd-core/bin/lib/capability-registry.cjs +785 -144
  44. package/gsd-core/bin/lib/capability-state.cjs +25 -4
  45. package/gsd-core/bin/lib/capability-validator.cjs +321 -18
  46. package/gsd-core/bin/lib/capability-writer.cjs +14 -4
  47. package/gsd-core/bin/lib/check-command-router.cjs +229 -6
  48. package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
  49. package/gsd-core/bin/lib/cli-exit.cjs +496 -10
  50. package/gsd-core/bin/lib/clusters.cjs +1 -0
  51. package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
  52. package/gsd-core/bin/lib/codex-agent-toml.cjs +410 -4
  53. package/gsd-core/bin/lib/command-aliases.cjs +16 -0
  54. package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
  55. package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
  56. package/gsd-core/bin/lib/commands.cjs +877 -54
  57. package/gsd-core/bin/lib/complexity-trigger.cjs +26 -6
  58. package/gsd-core/bin/lib/config-loader.cjs +121 -29
  59. package/gsd-core/bin/lib/config.cjs +92 -2
  60. package/gsd-core/bin/lib/configuration.cjs +129 -37
  61. package/gsd-core/bin/lib/core-utils.cjs +118 -14
  62. package/gsd-core/bin/lib/decisions.cjs +213 -1
  63. package/gsd-core/bin/lib/edge-probe.cjs +23 -2
  64. package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
  65. package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
  66. package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
  67. package/gsd-core/bin/lib/frontmatter.cjs +975 -326
  68. package/gsd-core/bin/lib/gap-checker.cjs +41 -8
  69. package/gsd-core/bin/lib/git-base-branch.cjs +182 -39
  70. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +7 -3
  71. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
  72. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +60 -14
  73. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +75 -22
  74. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +22 -8
  75. package/gsd-core/bin/lib/health-diagnostic.cjs +23 -3
  76. package/gsd-core/bin/lib/host-integration.cjs +96 -11
  77. package/gsd-core/bin/lib/init-command-router.cjs +132 -21
  78. package/gsd-core/bin/lib/init.cjs +252 -56
  79. package/gsd-core/bin/lib/install-engine.cjs +252 -15
  80. package/gsd-core/bin/lib/install-model-override-resolver.cjs +78 -1
  81. package/gsd-core/bin/lib/install-profiles.cjs +100 -18
  82. package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
  83. package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
  84. package/gsd-core/bin/lib/installer-migrations.cjs +10 -7
  85. package/gsd-core/bin/lib/intel.cjs +101 -26
  86. package/gsd-core/bin/lib/io.cjs +195 -15
  87. package/gsd-core/bin/lib/learnings.cjs +85 -14
  88. package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
  89. package/gsd-core/bin/lib/loop-resolver.cjs +14 -8
  90. package/gsd-core/bin/lib/markdown-table.cjs +175 -4
  91. package/gsd-core/bin/lib/milestone.cjs +112 -7
  92. package/gsd-core/bin/lib/model-catalog.cjs +177 -19
  93. package/gsd-core/bin/lib/model-resolver.cjs +10 -28
  94. package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
  95. package/gsd-core/bin/lib/phase-command-router.cjs +13 -6
  96. package/gsd-core/bin/lib/phase-estimation.cjs +17 -8
  97. package/gsd-core/bin/lib/phase-id.cjs +321 -13
  98. package/gsd-core/bin/lib/phase-lifecycle.cjs +24 -16
  99. package/gsd-core/bin/lib/phase-locator.cjs +138 -17
  100. package/gsd-core/bin/lib/phase.cjs +1175 -115
  101. package/gsd-core/bin/lib/plan-document.cjs +273 -0
  102. package/gsd-core/bin/lib/plan-scan.cjs +13 -2
  103. package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
  104. package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
  105. package/gsd-core/bin/lib/planning-snapshot.cjs +165 -34
  106. package/gsd-core/bin/lib/planning-workspace.cjs +159 -28
  107. package/gsd-core/bin/lib/probe-core.cjs +4 -1
  108. package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
  109. package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
  110. package/gsd-core/bin/lib/quick-batch-command-router.cjs +285 -0
  111. package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
  112. package/gsd-core/bin/lib/quick-batch.cjs +840 -0
  113. package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
  114. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +71 -45
  115. package/gsd-core/bin/lib/review-lane-descriptor.cjs +62 -14
  116. package/gsd-core/bin/lib/review-lane-invocation.cjs +73 -1
  117. package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
  118. package/gsd-core/bin/lib/roadmap-command-router.cjs +45 -31
  119. package/gsd-core/bin/lib/roadmap-parser.cjs +577 -41
  120. package/gsd-core/bin/lib/roadmap.cjs +248 -64
  121. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +329 -41
  122. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +16 -17
  123. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +320 -109
  124. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +487 -83
  125. package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
  126. package/gsd-core/bin/lib/runtime-slash.cjs +72 -2
  127. package/gsd-core/bin/lib/shell-command-projection.cjs +75 -8
  128. package/gsd-core/bin/lib/smart-entry.cjs +19 -31
  129. package/gsd-core/bin/lib/spec-section.cjs +12 -7
  130. package/gsd-core/bin/lib/state-command-router.cjs +47 -18
  131. package/gsd-core/bin/lib/state-contract.cjs +359 -0
  132. package/gsd-core/bin/lib/state-document.cjs +216 -5
  133. package/gsd-core/bin/lib/state-md-schema.cjs +231 -0
  134. package/gsd-core/bin/lib/state-transition.cjs +850 -145
  135. package/gsd-core/bin/lib/state.cjs +1629 -287
  136. package/gsd-core/bin/lib/surface.cjs +33 -10
  137. package/gsd-core/bin/lib/task-command-router.cjs +111 -1
  138. package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
  139. package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
  140. package/gsd-core/bin/lib/teams-status.cjs +4 -1
  141. package/gsd-core/bin/lib/uat-predicate.cjs +58 -20
  142. package/gsd-core/bin/lib/uat.cjs +2542 -387
  143. package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
  144. package/gsd-core/bin/lib/ui-safety-gate.cjs +37 -7
  145. package/gsd-core/bin/lib/unusable-input.cjs +13 -0
  146. package/gsd-core/bin/lib/update-context.cjs +6 -2
  147. package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
  148. package/gsd-core/bin/lib/validate.cjs +230 -12
  149. package/gsd-core/bin/lib/vendor/README.md +43 -5
  150. package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
  151. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  152. package/gsd-core/bin/lib/verification.cjs +287 -13
  153. package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
  154. package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
  155. package/gsd-core/bin/lib/verify.cjs +441 -56
  156. package/gsd-core/bin/lib/workstream-inventory.cjs +20 -2
  157. package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
  158. package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
  159. package/gsd-core/bin/lib/worktree-safety.cjs +185 -21
  160. package/gsd-core/bin/shared/config-defaults.manifest.json +7 -1
  161. package/gsd-core/bin/shared/config-schema.manifest.json +13 -0
  162. package/gsd-core/bin/shared/exit-codes.json +8 -0
  163. package/gsd-core/bin/shared/exit-codes.sh +20 -0
  164. package/gsd-core/bin/shared/model-catalog.json +8 -1
  165. package/gsd-core/bin/verify-reapply-patches.cjs +70 -3
  166. package/gsd-core/references/agent-contracts.md +6 -5
  167. package/gsd-core/references/api-coverage.md +24 -2
  168. package/gsd-core/references/autonomous-smart-discuss.md +3 -3
  169. package/gsd-core/references/checkpoints.md +37 -19
  170. package/gsd-core/references/decimal-phase-calculation.md +5 -5
  171. package/gsd-core/references/edge-probe.md +17 -5
  172. package/gsd-core/references/execute-mvp-tdd.md +18 -18
  173. package/gsd-core/references/execute-phase-between-wave-reset.md +9 -12
  174. package/gsd-core/references/execute-phase-response-language.md +6 -0
  175. package/gsd-core/references/execute-phase-wave-guard.md +11 -9
  176. package/gsd-core/references/executor-examples.md +42 -0
  177. package/gsd-core/references/failing-direction.md +78 -0
  178. package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
  179. package/gsd-core/references/gate-prompts.md +1 -1
  180. package/gsd-core/references/git-integration.md +5 -5
  181. package/gsd-core/references/git-planning-commit.md +3 -3
  182. package/gsd-core/references/gsd-run-resolver.md +1 -1
  183. package/gsd-core/references/loop-hook-dispatch.md +22 -0
  184. package/gsd-core/references/model-profiles.md +1 -1
  185. package/gsd-core/references/mvp-concepts.md +2 -2
  186. package/gsd-core/references/nyquist-compliance.md +74 -0
  187. package/gsd-core/references/offer-next.md +3 -5
  188. package/gsd-core/references/phase-argument-parsing.md +3 -3
  189. package/gsd-core/references/plan-checker-examples.md +41 -0
  190. package/gsd-core/references/planner-antipatterns.md +25 -0
  191. package/gsd-core/references/planner-chunked.md +5 -1
  192. package/gsd-core/references/planner-coupling.md +42 -0
  193. package/gsd-core/references/planner-failing-direction.md +53 -0
  194. package/gsd-core/references/planner-human-verify-mode.md +15 -1
  195. package/gsd-core/references/planner-quick-batch.md +71 -0
  196. package/gsd-core/references/planner-reviews.md +47 -0
  197. package/gsd-core/references/planner-revision.md +76 -3
  198. package/gsd-core/references/planner-verify-command-grounding.md +17 -0
  199. package/gsd-core/references/planning-config.md +39 -9
  200. package/gsd-core/references/response-language-directive.md +9 -0
  201. package/gsd-core/references/reviewer-instances.md +31 -0
  202. package/gsd-core/references/revision-loop.md +118 -11
  203. package/gsd-core/references/runtime-aware-dispatch.md +1 -1
  204. package/gsd-core/references/tdd.md +15 -12
  205. package/gsd-core/references/ui-brand.md +65 -21
  206. package/gsd-core/references/ui-consideration-probe.md +1 -1
  207. package/gsd-core/references/universal-anti-patterns.md +2 -2
  208. package/gsd-core/references/verifier-evidence-gate.md +160 -0
  209. package/gsd-core/references/verify-command-path-resolvability.md +42 -0
  210. package/gsd-core/references/verify-mvp-mode.md +1 -1
  211. package/gsd-core/references/workstream-flag.md +11 -11
  212. package/gsd-core/templates/README.md +1 -1
  213. package/gsd-core/templates/SECURITY.md +3 -3
  214. package/gsd-core/templates/UI-SPEC.md +25 -3
  215. package/gsd-core/templates/VALIDATION.md +3 -3
  216. package/gsd-core/templates/phase-prompt.md +7 -0
  217. package/gsd-core/templates/state.md +7 -0
  218. package/gsd-core/templates/verification-report.md +5 -0
  219. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  220. package/gsd-core/workflows/add-backlog.md +3 -1
  221. package/gsd-core/workflows/add-phase.md +5 -3
  222. package/gsd-core/workflows/add-tests.md +4 -9
  223. package/gsd-core/workflows/add-todo.md +2 -2
  224. package/gsd-core/workflows/ai-integration-phase.md +5 -10
  225. package/gsd-core/workflows/analyze-dependencies.md +2 -0
  226. package/gsd-core/workflows/audit-fix.md +14 -3
  227. package/gsd-core/workflows/audit-milestone.md +11 -9
  228. package/gsd-core/workflows/audit-uat.md +19 -2
  229. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
  230. package/gsd-core/workflows/autonomous.md +12 -26
  231. package/gsd-core/workflows/check-todos.md +2 -2
  232. package/gsd-core/workflows/cleanup.md +3 -3
  233. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +16 -14
  234. package/gsd-core/workflows/code-review-fix.md +3 -1
  235. package/gsd-core/workflows/code-review.md +192 -69
  236. package/gsd-core/workflows/complete-milestone.md +28 -14
  237. package/gsd-core/workflows/debug.md +6 -4
  238. package/gsd-core/workflows/diagnose-issues.md +17 -7
  239. package/gsd-core/workflows/discuss-phase/modes/advisor.md +3 -1
  240. package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
  241. package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
  242. package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
  243. package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
  244. package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -7
  245. package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
  246. package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
  247. package/gsd-core/workflows/discuss-phase/modes/text.md +3 -1
  248. package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
  249. package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
  250. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
  251. package/gsd-core/workflows/discuss-phase-assumptions.md +3 -3
  252. package/gsd-core/workflows/discuss-phase-power.md +2 -0
  253. package/gsd-core/workflows/discuss-phase.md +2 -2
  254. package/gsd-core/workflows/do.md +46 -19
  255. package/gsd-core/workflows/docs-update.md +6 -5
  256. package/gsd-core/workflows/edit-phase.md +3 -1
  257. package/gsd-core/workflows/eval-review.md +5 -10
  258. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +3 -1
  259. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +129 -11
  260. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
  261. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
  262. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
  263. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +29 -5
  264. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
  265. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
  266. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +4 -2
  267. package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
  268. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
  269. package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
  270. package/gsd-core/workflows/execute-phase.md +68 -66
  271. package/gsd-core/workflows/execute-plan.md +25 -20
  272. package/gsd-core/workflows/explore.md +3 -1
  273. package/gsd-core/workflows/extract-learnings.md +3 -1
  274. package/gsd-core/workflows/fast.md +8 -2
  275. package/gsd-core/workflows/forensics.md +3 -1
  276. package/gsd-core/workflows/graduation.md +6 -6
  277. package/gsd-core/workflows/health.md +4 -7
  278. package/gsd-core/workflows/help/modes/brief.md +2 -0
  279. package/gsd-core/workflows/help/modes/default.md +2 -0
  280. package/gsd-core/workflows/help/modes/full.md +12 -0
  281. package/gsd-core/workflows/help/modes/topic.md +2 -0
  282. package/gsd-core/workflows/help.md +2 -0
  283. package/gsd-core/workflows/import.md +17 -14
  284. package/gsd-core/workflows/inbox.md +5 -6
  285. package/gsd-core/workflows/ingest-docs.md +45 -12
  286. package/gsd-core/workflows/insert-phase.md +7 -5
  287. package/gsd-core/workflows/list-phase-assumptions.md +2 -0
  288. package/gsd-core/workflows/list-seeds.md +7 -3
  289. package/gsd-core/workflows/list-workspaces.md +3 -1
  290. package/gsd-core/workflows/manager.md +15 -26
  291. package/gsd-core/workflows/map-codebase.md +3 -1
  292. package/gsd-core/workflows/milestone-summary.md +3 -1
  293. package/gsd-core/workflows/mvp-phase.md +3 -3
  294. package/gsd-core/workflows/new-milestone.md +10 -22
  295. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
  296. package/gsd-core/workflows/new-project.md +17 -29
  297. package/gsd-core/workflows/new-workspace.md +2 -2
  298. package/gsd-core/workflows/next.md +4 -2
  299. package/gsd-core/workflows/node-repair.md +2 -0
  300. package/gsd-core/workflows/note.md +2 -0
  301. package/gsd-core/workflows/onboard.md +1 -1
  302. package/gsd-core/workflows/pause-work.md +20 -5
  303. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
  304. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
  305. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +4 -4
  306. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +12 -3
  307. package/gsd-core/workflows/plan-phase.md +251 -54
  308. package/gsd-core/workflows/plan-review-convergence.md +148 -19
  309. package/gsd-core/workflows/plant-seed.md +3 -3
  310. package/gsd-core/workflows/pr-branch.md +195 -51
  311. package/gsd-core/workflows/profile-user.md +17 -15
  312. package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
  313. package/gsd-core/workflows/progress.md +52 -15
  314. package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
  315. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +38 -5
  316. package/gsd-core/workflows/quick/steps/quick-verification.md +2 -4
  317. package/gsd-core/workflows/quick/steps/research-phase.md +5 -7
  318. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
  319. package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
  320. package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
  321. package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
  322. package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
  323. package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
  324. package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
  325. package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
  326. package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
  327. package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
  328. package/gsd-core/workflows/quick-batch.md +203 -0
  329. package/gsd-core/workflows/quick.md +33 -32
  330. package/gsd-core/workflows/reapply-patches.md +2 -0
  331. package/gsd-core/workflows/remove-phase.md +6 -4
  332. package/gsd-core/workflows/remove-workspace.md +3 -3
  333. package/gsd-core/workflows/resume-project.md +14 -14
  334. package/gsd-core/workflows/review.md +404 -21
  335. package/gsd-core/workflows/scan.md +3 -1
  336. package/gsd-core/workflows/section-manifest.json +12 -0
  337. package/gsd-core/workflows/secure-phase.md +3 -3
  338. package/gsd-core/workflows/session-report.md +2 -0
  339. package/gsd-core/workflows/settings-advanced.md +9 -9
  340. package/gsd-core/workflows/settings-integrations.md +66 -32
  341. package/gsd-core/workflows/settings.md +4 -6
  342. package/gsd-core/workflows/ship.md +22 -16
  343. package/gsd-core/workflows/sketch-wrap-up.md +13 -17
  344. package/gsd-core/workflows/sketch.md +13 -19
  345. package/gsd-core/workflows/smart-entry.md +4 -6
  346. package/gsd-core/workflows/spec-phase.md +31 -4
  347. package/gsd-core/workflows/spike-wrap-up.md +9 -11
  348. package/gsd-core/workflows/spike.md +21 -32
  349. package/gsd-core/workflows/stats.md +4 -2
  350. package/gsd-core/workflows/sync-skills.md +13 -5
  351. package/gsd-core/workflows/thread.md +13 -7
  352. package/gsd-core/workflows/transition.md +7 -5
  353. package/gsd-core/workflows/ui-phase.md +36 -21
  354. package/gsd-core/workflows/ui-review.md +7 -11
  355. package/gsd-core/workflows/ultraplan-phase.md +7 -13
  356. package/gsd-core/workflows/undo.md +9 -17
  357. package/gsd-core/workflows/update.md +47 -48
  358. package/gsd-core/workflows/validate-phase.md +3 -3
  359. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
  360. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
  361. package/gsd-core/workflows/verify-work.md +106 -21
  362. package/hooks/dist/gsd-agent-isolation-guard.js +77 -38
  363. package/hooks/dist/gsd-check-update-worker.js +19 -2
  364. package/hooks/dist/gsd-config-reload.js +18 -12
  365. package/hooks/dist/gsd-context-monitor.js +302 -22
  366. package/hooks/dist/gsd-cursor-post-tool.js +3 -1
  367. package/hooks/dist/gsd-cursor-pre-tool.js +3 -1
  368. package/hooks/dist/gsd-cursor-session-start.js +2 -1
  369. package/hooks/dist/gsd-cursor-stop.js +2 -1
  370. package/hooks/dist/gsd-cursor-subagent-start.js +28 -23
  371. package/hooks/dist/gsd-cursor-subagent-stop.js +3 -1
  372. package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
  373. package/hooks/dist/gsd-graphify-update.sh +22 -18
  374. package/hooks/dist/gsd-node-runner.sh +77 -0
  375. package/hooks/dist/gsd-phase-boundary.sh +1 -0
  376. package/hooks/dist/gsd-prompt-guard.js +46 -12
  377. package/hooks/dist/gsd-read-guard.js +18 -7
  378. package/hooks/dist/gsd-read-injection-scanner.js +22 -13
  379. package/hooks/dist/gsd-secret-read-guard.js +1079 -0
  380. package/hooks/dist/gsd-session-state.sh +1 -0
  381. package/hooks/dist/gsd-statusline.js +222 -29
  382. package/hooks/dist/gsd-validate-commit.sh +523 -12
  383. package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
  384. package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
  385. package/hooks/dist/gsd-workflow-guard.js +36 -17
  386. package/hooks/dist/gsd-worktree-path-guard.js +36 -21
  387. package/hooks/dist/gsd-write-guard.js +35 -25
  388. package/hooks/dist/lib/cli-exit.js +560 -0
  389. package/hooks/dist/lib/exit-code-registry.js +98 -0
  390. package/hooks/dist/lib/git-cmd.js +210 -1
  391. package/hooks/dist/lib/git-probe.js +84 -0
  392. package/hooks/dist/lib/hook-exit.js +81 -0
  393. package/hooks/dist/lib/injection-patterns.js +36 -6
  394. package/hooks/dist/managed-hooks-registry.cjs +4 -0
  395. package/hooks/gsd-agent-isolation-guard.js +77 -38
  396. package/hooks/gsd-check-update-worker.js +19 -2
  397. package/hooks/gsd-config-reload.js +18 -12
  398. package/hooks/gsd-context-monitor.js +302 -22
  399. package/hooks/gsd-cursor-post-tool.js +3 -1
  400. package/hooks/gsd-cursor-pre-tool.js +3 -1
  401. package/hooks/gsd-cursor-session-start.js +2 -1
  402. package/hooks/gsd-cursor-stop.js +2 -1
  403. package/hooks/gsd-cursor-subagent-start.js +28 -23
  404. package/hooks/gsd-cursor-subagent-stop.js +3 -1
  405. package/hooks/gsd-ensure-canonical-path.js +2 -1
  406. package/hooks/gsd-graphify-update.sh +22 -18
  407. package/hooks/gsd-node-runner.sh +77 -0
  408. package/hooks/gsd-phase-boundary.sh +1 -0
  409. package/hooks/gsd-prompt-guard.js +46 -12
  410. package/hooks/gsd-read-guard.js +18 -7
  411. package/hooks/gsd-read-injection-scanner.js +22 -13
  412. package/hooks/gsd-secret-read-guard.js +1079 -0
  413. package/hooks/gsd-session-state.sh +1 -0
  414. package/hooks/gsd-statusline.js +222 -29
  415. package/hooks/gsd-validate-commit.sh +523 -12
  416. package/hooks/gsd-windsurf-pre-command.js +16 -11
  417. package/hooks/gsd-windsurf-pre-write.js +22 -13
  418. package/hooks/gsd-workflow-guard.js +36 -17
  419. package/hooks/gsd-worktree-path-guard.js +36 -21
  420. package/hooks/gsd-write-guard.js +35 -25
  421. package/hooks/hooks.json +6 -0
  422. package/hooks/lib/cli-exit.js +560 -0
  423. package/hooks/lib/exit-code-registry.js +98 -0
  424. package/hooks/lib/git-cmd.js +210 -1
  425. package/hooks/lib/git-probe.js +84 -0
  426. package/hooks/lib/hook-exit.js +81 -0
  427. package/hooks/lib/injection-patterns.js +36 -6
  428. package/hooks/managed-hooks-registry.cjs +4 -0
  429. package/package.json +14 -9
  430. package/scripts/base64-scan.sh +74 -12
  431. package/scripts/build-hooks.js +12 -0
  432. package/scripts/check-glossary-refs.cjs +77 -15
  433. package/scripts/check-mutation-score-ratchet.cjs +156 -0
  434. package/scripts/ci-check-job-near-cap.cjs +49 -0
  435. package/scripts/ci-pr-mergeability.cjs +262 -0
  436. package/scripts/ci-test-scope.cjs +52 -12
  437. package/scripts/ci-timeout-report.cjs +230 -0
  438. package/scripts/docs-guard-registry.cjs +406 -0
  439. package/scripts/gen-capability-registry.cjs +8 -6
  440. package/scripts/gen-exit-code-docs.cjs +318 -0
  441. package/scripts/gen-exit-code-registry.cjs +891 -0
  442. package/scripts/gen-features.cjs +836 -0
  443. package/scripts/gen-hooks-cli-exit.cjs +239 -0
  444. package/scripts/gen-install-tree-fixtures.cjs +2 -2
  445. package/scripts/gen-loop-host-contract.cjs +189 -4
  446. package/scripts/gen-scripts-cli-exit.cjs +185 -0
  447. package/scripts/gen-state-md-docs.cjs +727 -0
  448. package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
  449. package/scripts/lib/ci-job-timing.cjs +72 -0
  450. package/scripts/lib/cli-exit.cjs +546 -44
  451. package/scripts/lib/drift-scan.cjs +32 -2
  452. package/scripts/lib/exit-code-registry.cjs +98 -0
  453. package/scripts/lib/ndjson-reporter.cjs +119 -0
  454. package/scripts/lib/shellcheck-fetch.cjs +247 -0
  455. package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
  456. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
  457. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
  458. package/scripts/lint-docs-guard-registration.cjs +495 -0
  459. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +198 -0
  460. package/scripts/lint-eslint-glob-coverage.allowlist.json +4 -0
  461. package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
  462. package/scripts/lint-health-diagnostic-rule-table.cjs +65 -8
  463. package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
  464. package/scripts/lint-phase-enumeration-drift.cjs +45 -14
  465. package/scripts/lint-phase-id-drift.cjs +133 -8
  466. package/scripts/lint-planning-prompt-drift.cjs +38 -1
  467. package/scripts/lint-portable-grep.cjs +176 -0
  468. package/scripts/lint-removed-but-needed.cjs +184 -16
  469. package/scripts/lint-response-language-coverage.cjs +524 -0
  470. package/scripts/lint-seam-enforcement.cjs +182 -0
  471. package/scripts/lint-slug-derivation-drift.cjs +921 -0
  472. package/scripts/lint-source-test-name-collision.cjs +241 -0
  473. package/scripts/lint-state-write-path-drift.cjs +337 -432
  474. package/scripts/lint-test-file-count.allowlist.json +124 -4
  475. package/scripts/lint-test-file-count.cjs +25 -3
  476. package/scripts/lint-unreachable-guard-drift.cjs +51 -64
  477. package/scripts/lint-vendored-deps.cjs +208 -35
  478. package/scripts/lint-workflow-shellcheck-baseline.json +1027 -0
  479. package/scripts/lint-workflow-shellcheck.cjs +614 -0
  480. package/scripts/mutation-matrix.cjs +599 -50
  481. package/scripts/npm-audit-baseline.cjs +376 -0
  482. package/scripts/prompt-injection-scan.sh +83 -14
  483. package/scripts/require-issue-link-policy.cjs +16 -1
  484. package/scripts/secret-scan.sh +75 -13
  485. package/scripts/select-docs-guards.cjs +56 -0
  486. package/scripts/sync-runtime-launcher.cjs +22 -3
  487. package/skills/gsd-discuss-phase/SKILL.md +1 -1
  488. package/skills/gsd-execute-phase/SKILL.md +1 -1
  489. package/skills/gsd-import/SKILL.md +1 -1
  490. package/skills/gsd-ns-workflow/SKILL.md +1 -0
  491. package/skills/gsd-phase/SKILL.md +1 -1
  492. package/skills/gsd-quick/SKILL.md +8 -4
  493. package/skills/gsd-quick-batch/SKILL.md +105 -0
  494. package/skills/gsd-surface/SKILL.md +18 -8
  495. package/vscode/package.json +1 -1
  496. package/bin/lib/ui-safety-gate.cjs +0 -109
  497. package/scripts/lint-emitted-drift-ack.cjs +0 -344
  498. package/scripts/state-write-path-drift-baseline.json +0 -19
@@ -0,0 +1,727 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Generates the schema-derived MARKED REGIONS inside `gsd-core/templates/state.md`
6
+ * and the five `docs/{,ja-JP/,zh-CN/,ko-KR/,pt-BR/}reference/state-md.md` pages
7
+ * from `STATE_FIELD_SCHEMA` (`src/state-md-schema.cts`, ADR-3473 §8.8, #3873).
8
+ *
9
+ * WHY THIS EXISTS. Four hand-maintained declarations of "which STATE.md keys
10
+ * exist and what they carry" already disagreed (see `src/state-md-schema.cts`'s
11
+ * own docstring for the `last_activity` case). Two of the SIX places named in
12
+ * `.gsd/phase/feat-3873-state-md-schema/40-design.md`'s table are documents,
13
+ * not code: the shipped template and the reference docs. This generator is
14
+ * their half of the consolidation. `FIELD_CLASSIFICATION` /
15
+ * `FRONTMATTER_BODY_SOURCE` / `FRONTMATTER_KEY_TO_BODY_LABEL` project from the
16
+ * same schema at MODULE LOAD (`src/state-transition.cts`, `src/state.cts`);
17
+ * this script projects at BUILD TIME instead, into committed markdown.
18
+ *
19
+ * GENERATED REGIONS, NOT GENERATED FILES (design doc §4). The five reference
20
+ * pages are hand-translated prose with schema-derived tables and section
21
+ * skeletons embedded in them; a generator that rewrote the whole file would
22
+ * clobber a community translation. So — following `gen-features.cjs`, not
23
+ * `gen-context-index.cjs` (the design's own precedent choice) — this script
24
+ * owns only explicitly MARKED regions inside otherwise hand-authored files,
25
+ * and never touches a byte outside a marker pair.
26
+ *
27
+ * Two regions are declared today:
28
+ * - `frontmatter` — the initial YAML frontmatter block in
29
+ * `gsd-core/templates/state.md`, the block a new project starts from.
30
+ * - `status-lifecycle` — the `### Status lifecycle (ADR-2207)` section
31
+ * documenting the `status` field's lifecycle. This is the section
32
+ * ISSUE #3873's own design doc found missing from all four locale
33
+ * translations (verified by line/heading count) — the generator emitting
34
+ * this skeleton is what turns `tests/docs-state-md-locale-parity.test.cjs`
35
+ * green.
36
+ *
37
+ * A NOTE ON WHAT "SCHEMA-DERIVED" MEANS HERE. `STATE_FIELD_SCHEMA.status.enum`
38
+ * (`STATUS_LIFECYCLE_ENUM`) is the seven-member NORMALIZED status value set
39
+ * `normalizeStateStatus()` computes. The `### Status lifecycle (ADR-2207)`
40
+ * table documents a DIFFERENT, narrower vocabulary — the raw `Status:` body
41
+ * strings `completePhaseCore`/`milestoneCompleteCore` actually write
42
+ * (`Ready to plan`, `All phases complete`, ...) — `src/state-md-schema.cts`'s
43
+ * own docstring is explicit that these are not the same set. This generator
44
+ * therefore does not literally enumerate `schema.status.enum` into the table;
45
+ * it asserts `schema.status.enum` is declared and non-empty (so a future
46
+ * removal of the `status` row's enum fails this generator loudly, keeping the
47
+ * link real) and renders the ADR-2207 table from `STATUS_LIFECYCLE_ROWS`
48
+ * below, which the codebase does not (yet) declare in one place either. This
49
+ * is a section SKELETON, not a full re-derivation, per the design's own
50
+ * scoping: "Parsers are checked, not generated."
51
+ *
52
+ * COLUMN HEADERS ARE PER-LOCALE (design doc §4: "Column headers come from a
53
+ * per-locale string table so a translated table has translated headers").
54
+ * Table BODY content (the four `Ready to plan` / ... rows) is left in English
55
+ * across every locale deliberately: those are literal STATE.md body-field
56
+ * values a project actually writes, not prose to translate, and the
57
+ * "Meaning" column is exactly the kind of hand-translatable prose the design
58
+ * doc says a human still owns — the generator's contract there is only that
59
+ * the SECTION exists (structural, ADR-3873/#3853), never that its prose is
60
+ * translated (design doc "Not-corruption": "A locale is not 'wrong' for
61
+ * having different prose").
62
+ *
63
+ * Usage:
64
+ * node scripts/gen-state-md-docs.cjs # print every region to stdout
65
+ * node scripts/gen-state-md-docs.cjs --write # rewrite every region in place
66
+ * node scripts/gen-state-md-docs.cjs --check # exit 1 if any region is stale
67
+ * node scripts/gen-state-md-docs.cjs --json # --check semantics; JSON report
68
+ * node scripts/gen-state-md-docs.cjs --write --force # write despite violations
69
+ * node scripts/gen-state-md-docs.cjs ... --root <dir># resolve target files under <dir>
70
+ * # instead of the repo root (test-only;
71
+ * # the schema module itself is always
72
+ * # required from THIS repo's build output)
73
+ */
74
+
75
+ const fs = require('node:fs');
76
+ const path = require('node:path');
77
+
78
+ const { ExitError, runMain } = require('./lib/cli-exit.cjs');
79
+
80
+ const REPO_ROOT = path.resolve(__dirname, '..');
81
+ const SCHEMA_LIB_PATH = path.join(REPO_ROOT, 'gsd-core', 'bin', 'lib', 'state-md-schema.cjs');
82
+
83
+ const MARKER_TAG = 'STATE-MD-SCHEMA';
84
+
85
+ /** Stable reason codes for every violation this gate can emit. */
86
+ const REASON = Object.freeze({
87
+ SCHEMA_LIB_MISSING: 'schema_lib_missing',
88
+ LOCALE_STRINGS_MISSING: 'locale_strings_missing',
89
+ MARKERS_MISSING: 'markers_missing',
90
+ MARKER_UNCLOSED: 'marker_unclosed',
91
+ MARKER_ORDER_INVALID: 'marker_order_invalid',
92
+ REGION_STALE: 'region_stale',
93
+ FIELD_REFERENCE_DRIFT: 'field_reference_drift',
94
+ STATUS_VALUES_DRIFT: 'status_values_drift',
95
+ });
96
+
97
+ /**
98
+ * The five reference-doc locales plus the shipped template — each an
99
+ * independent target file, each declaring the ordered list of region names it
100
+ * carries. `en` and the four translations all carry `status-lifecycle`;
101
+ * only the template carries `frontmatter`.
102
+ */
103
+ const TARGETS = Object.freeze([
104
+ { key: 'template', relPath: path.join('gsd-core', 'templates', 'state.md'), regions: ['frontmatter'] },
105
+ { key: 'en', relPath: path.join('docs', 'reference', 'state-md.md'), locale: 'en', regions: ['status-lifecycle', 'cardinality'] },
106
+ { key: 'ja-JP', relPath: path.join('docs', 'ja-JP', 'reference', 'state-md.md'), locale: 'ja-JP', regions: ['status-lifecycle', 'cardinality'] },
107
+ { key: 'zh-CN', relPath: path.join('docs', 'zh-CN', 'reference', 'state-md.md'), locale: 'zh-CN', regions: ['status-lifecycle', 'cardinality'] },
108
+ { key: 'ko-KR', relPath: path.join('docs', 'ko-KR', 'reference', 'state-md.md'), locale: 'ko-KR', regions: ['status-lifecycle', 'cardinality'] },
109
+ { key: 'pt-BR', relPath: path.join('docs', 'pt-BR', 'reference', 'state-md.md'), locale: 'pt-BR', regions: ['status-lifecycle', 'cardinality'] },
110
+ ]);
111
+
112
+ /** `<!-- STATE-MD-SCHEMA:START:<region> — generated by ...; do not edit by hand -->` */
113
+ function startMarker(region) {
114
+ return `<!-- ${MARKER_TAG}:START:${region} — generated by scripts/gen-state-md-docs.cjs from src/state-md-schema.cts; do not edit by hand -->`;
115
+ }
116
+ /** `<!-- STATE-MD-SCHEMA:END:<region> -->` */
117
+ function endMarker(region) {
118
+ return `<!-- ${MARKER_TAG}:END:${region} -->`;
119
+ }
120
+
121
+ // ─── Frontmatter region (gsd-core/templates/state.md) ──────────────────────
122
+
123
+ /**
124
+ * The subset of `STATE_FIELD_SCHEMA` keys a FRESH project's initial STATE.md
125
+ * frontmatter declares. Validated against the schema at render time — if a
126
+ * future schema change drops one of these keys, rendering throws rather than
127
+ * silently emitting a template field the schema no longer recognizes.
128
+ */
129
+ const TEMPLATE_FRONTMATTER_FIELDS = Object.freeze(['gsd_state_version', 'status', 'progress']);
130
+
131
+ function renderFrontmatterRegion(schema) {
132
+ for (const field of TEMPLATE_FRONTMATTER_FIELDS) {
133
+ if (!(field in schema)) {
134
+ throw new ExitError(
135
+ 1,
136
+ `template frontmatter field '${field}' is not declared in STATE_FIELD_SCHEMA (src/state-md-schema.cts) — update TEMPLATE_FRONTMATTER_FIELDS or the schema`,
137
+ );
138
+ }
139
+ }
140
+ // NOTE: this body deliberately opens the SAME ```markdown fence the
141
+ // hand-authored File Template body continues in — it does NOT wrap the
142
+ // frontmatter in its own separate ```yaml fence. `tests/state-transition
143
+ // .test.cjs`'s bug #21 regression guard extracts the entire ```markdown
144
+ // ... ``` block and asserts the extracted text STARTS with '---': a
145
+ // separate preceding fence broke that contract (#3873 fixed-forward). The
146
+ // END marker therefore lands inside the still-open fence, right after the
147
+ // frontmatter's closing '---' and before the hand-authored '# Project
148
+ // State' line — that is intentional, not a rendering bug.
149
+ return [
150
+ '```markdown',
151
+ '---',
152
+ "gsd_state_version: '1.0' # placeholder; syncStateFrontmatter overwrites on first state.* call",
153
+ 'status: planning',
154
+ 'progress:',
155
+ ' total_phases: 0',
156
+ ' completed_phases: 0',
157
+ ' total_plans: 0',
158
+ ' completed_plans: 0',
159
+ ' percent: 0',
160
+ '---',
161
+ ].join('\n');
162
+ }
163
+
164
+ // ─── Status-lifecycle region (the five reference docs) ──────────────────────
165
+
166
+ /** Per-locale heading text and translated column headers (design §4). */
167
+ const STATUS_LIFECYCLE_STRINGS = Object.freeze({
168
+ en: { heading: 'Status lifecycle (ADR-2207)', cols: ['Value', 'Written by', 'Meaning'] },
169
+ 'ja-JP': { heading: 'ステータスライフサイクル (ADR-2207)', cols: ['値', '書き込み元', '意味'] },
170
+ 'zh-CN': { heading: '状态生命周期 (ADR-2207)', cols: ['值', '写入方', '含义'] },
171
+ 'ko-KR': { heading: '상태 라이프사이클 (ADR-2207)', cols: ['값', '작성자', '의미'] },
172
+ 'pt-BR': { heading: 'Ciclo de vida do status (ADR-2207)', cols: ['Valor', 'Escrito por', 'Significado'] },
173
+ });
174
+
175
+ /** Canonical, deliberately untranslated across locales — see file header note. */
176
+ const STATUS_LIFECYCLE_ROWS = Object.freeze([
177
+ ['`Ready to plan`', '`completePhaseCore` (non-last phase)', 'Next phase is ready for planning'],
178
+ ['`All phases complete`', '`completePhaseCore` (last phase)', 'All phases done; milestone awaiting formal close'],
179
+ ['`<version> milestone complete`', '`milestoneCompleteCore`', 'Milestone formally closed and archived'],
180
+ ['`Awaiting next milestone`', '`milestoneCompleteCore`', 'Terminal/archived state'],
181
+ ]);
182
+
183
+ const STATUS_LIFECYCLE_INTRO =
184
+ 'The `Status` field follows a strict lifecycle across phase and milestone boundaries:';
185
+ const STATUS_LIFECYCLE_FOOTNOTE =
186
+ 'Phase-completion verbs never write `Milestone complete` (the overloaded bare value was removed in ' +
187
+ '#2204 per ADR-2207 to decouple phase-level writes from milestone termination).';
188
+
189
+ /**
190
+ * Render the status-lifecycle region for `locale`.
191
+ *
192
+ * `schema.status` lacking a non-empty `enum` is a hard SCHEMA DEFECT (there
193
+ * is nothing coherent to render for any locale) and remains a thrown
194
+ * `ExitError` — unlike the locale-strings case below, `--force` could not
195
+ * meaningfully "override" it, because no fallback content exists.
196
+ *
197
+ * A locale absent from `STATUS_LIFECYCLE_STRINGS`, by contrast, is exactly
198
+ * the forceable-violation shape `gen-features.cjs` established (a corpus gap
199
+ * that still has a renderable fallback): pushed onto `violations` rather
200
+ * than thrown, and rendered using the English strings so `--write --force`
201
+ * can still emit something reviewable while `--check`/`--write` (no force)
202
+ * refuse.
203
+ */
204
+ function renderStatusLifecycleRegion(locale, schema, violations) {
205
+ const statusRow = schema.status;
206
+ if (!statusRow || !Array.isArray(statusRow.enum) || statusRow.enum.length === 0) {
207
+ throw new ExitError(
208
+ 1,
209
+ "STATE_FIELD_SCHEMA.status must declare a non-empty 'enum' for the status-lifecycle section to be generated",
210
+ );
211
+ }
212
+ let strings = STATUS_LIFECYCLE_STRINGS[locale];
213
+ if (!strings) {
214
+ violations.push({ reason: REASON.LOCALE_STRINGS_MISSING, file: null, region: 'status-lifecycle', locale });
215
+ strings = STATUS_LIFECYCLE_STRINGS.en;
216
+ }
217
+ const [c0, c1, c2] = strings.cols;
218
+ const lines = [
219
+ `### ${strings.heading}`,
220
+ '',
221
+ STATUS_LIFECYCLE_INTRO,
222
+ '',
223
+ `| ${c0} | ${c1} | ${c2} |`,
224
+ '|---|---|---|',
225
+ ];
226
+ for (const [value, writtenBy, meaning] of STATUS_LIFECYCLE_ROWS) {
227
+ lines.push(`| ${value} | ${writtenBy} | ${meaning} |`);
228
+ }
229
+ lines.push('', STATUS_LIFECYCLE_FOOTNOTE);
230
+ return lines.join('\n');
231
+ }
232
+
233
+ // ─── Cardinality region (the five reference docs) ───────────────────────────
234
+ //
235
+ // ADR-3473 §8.8 names three schema-derived tables: "field reference, status
236
+ // values, cardinality". No "### Field cardinality" section existed anywhere
237
+ // before this generator — unlike status-lifecycle (which REPLACED an
238
+ // existing English section and filled a gap in the four translations), this
239
+ // is wholly NEW content, so there is nothing hand-translated to lose by
240
+ // generating it. Every row is schema-derived: the field NAME and its
241
+ // `cardinality` value ('one' | 'optional' | 'many'), nothing else — no
242
+ // prose, so no translation to overwrite.
243
+ //
244
+ // EXCLUDED_FIELD_TABLE_KEYS: the bare `progress` object row. The existing
245
+ // Field-reference table (hand-authored) never lists `progress` itself as its
246
+ // own row either — only its five `progress.*` leaves — matching this repo's
247
+ // "reporting granularity is the dotted leaf path" convention (ADR-3473 §8.8,
248
+ // "Rule — reporting granularity is the dotted leaf path"). The cardinality
249
+ // table mirrors that same convention rather than introducing a new one.
250
+ const EXCLUDED_FIELD_TABLE_KEYS = Object.freeze(['progress']);
251
+
252
+ const CARDINALITY_STRINGS = Object.freeze({
253
+ en: { heading: 'Field cardinality', cols: ['Field', 'Cardinality'] },
254
+ 'ja-JP': { heading: 'フィールドの多重度', cols: ['フィールド', '多重度'] },
255
+ 'zh-CN': { heading: '字段基数', cols: ['字段', '基数'] },
256
+ 'ko-KR': { heading: '필드 카디널리티', cols: ['필드', '카디널리티'] },
257
+ 'pt-BR': { heading: 'Cardinalidade dos campos', cols: ['Campo', 'Cardinalidade'] },
258
+ });
259
+
260
+ function renderCardinalityRegion(locale, schema) {
261
+ const strings = CARDINALITY_STRINGS[locale];
262
+ if (!strings) {
263
+ throw new ExitError(1, `no localized strings registered in CARDINALITY_STRINGS for locale '${locale}'`);
264
+ }
265
+ const [c0, c1] = strings.cols;
266
+ const lines = [`### ${strings.heading}`, '', `| ${c0} | ${c1} |`, '|---|---|'];
267
+ for (const key of Object.keys(schema)) {
268
+ if (EXCLUDED_FIELD_TABLE_KEYS.includes(key)) continue;
269
+ lines.push(`| \`${key}\` | ${schema[key].cardinality} |`);
270
+ }
271
+ return lines.join('\n');
272
+ }
273
+
274
+ // ─── Field-reference / status-values KEY-SET PARITY (detection, not generation) ──
275
+ //
276
+ // The Field-reference and Status-values tables carry hand-translated PROSE
277
+ // per row (a field's "Purpose"/"When populated" description; a status
278
+ // value's "Matched text" description) that the schema does not model at all
279
+ // — `StateFieldSchema` has no descriptive-text field, and translating that
280
+ // prose into a per-locale static registry inside this generator would mean
281
+ // OVERWRITING genuinely hand-translated ja-JP/zh-CN/ko-KR/pt-BR content with
282
+ // canonical English text on every `--write`. That is a real content loss,
283
+ // not a cosmetic one, and it is the "column the schema does not model"
284
+ // case (see the module header and the PR discussion this code answers).
285
+ //
286
+ // What the schema DOES let us check, losslessly: the ROW SET. A field's
287
+ // Purpose/When-populated text can be hand-authored forever; but the fact
288
+ // that `last_activity_desc` (declared in STATE_FIELD_SCHEMA) has NO row in
289
+ // the English Field-reference table at all is exactly the "unrepresentable
290
+ // drift" ADR-3473 §8.8 exists to close, and it is real, live, and pre-dates
291
+ // this generator (found while building it; fixed alongside it in this same
292
+ // change). This check makes that class of drift DETECTABLE (fails --check)
293
+ // without touching a single byte of translated prose.
294
+ //
295
+ // KNOWN_SCHEMA_GAP_FIELDS: `active_phase`, `next_action`, `next_phases` are
296
+ // real STATE.md frontmatter keys (ADR issue #2833), documented in the
297
+ // Field-reference table today, but NOT declared in `STATE_FIELD_SCHEMA` —
298
+ // they are computed/reported outside the classification/preservation
299
+ // dispatch model `FIELD_CLASSIFICATION` projects, and deciding whether they
300
+ // belong in that schema at all is a cross-cutting call this generator does
301
+ // not make unilaterally (it would ripple into `state-transition.cts` /
302
+ // `state.cts`'s pinned projection-parity tests). Grandfathered here, by
303
+ // name, so the check is a real ratchet (a FIFTH undeclared field would still
304
+ // fail) rather than silently disabled.
305
+ const KNOWN_SCHEMA_GAP_FIELDS = Object.freeze(['active_phase', 'next_action', 'next_phases']);
306
+
307
+ /** Per-locale heading text for the two hand-authored, prose-bearing tables (parity-parsed, never spliced). */
308
+ const FIELD_REFERENCE_HEADING = Object.freeze({
309
+ en: 'Field reference',
310
+ 'ja-JP': 'フィールドリファレンス',
311
+ 'zh-CN': '字段参考',
312
+ 'ko-KR': '필드 참조',
313
+ 'pt-BR': 'Referência de campos',
314
+ });
315
+ const STATUS_VALUES_HEADING = Object.freeze({
316
+ en: 'Status values',
317
+ 'ja-JP': 'ステータス値',
318
+ 'zh-CN': '状态值',
319
+ 'ko-KR': '상태 값',
320
+ 'pt-BR': 'Valores de status',
321
+ });
322
+
323
+ /**
324
+ * The first column's cell values of the FIRST markdown table immediately
325
+ * following the `### <headingText>` heading in `normalizedText` (LF-normalized).
326
+ * Returns `null` if the heading is not found. Backtick-fenced inline code is
327
+ * stripped (`` `key` `` -> `key`) since every row in both tables opens its
328
+ * first cell that way.
329
+ */
330
+ function firstColumnAfterHeading(normalizedText, headingText) {
331
+ const lines = normalizedText.split('\n');
332
+ const headingIdx = lines.findIndex((l) => l === `### ${headingText}`);
333
+ if (headingIdx === -1) return null;
334
+ const values = [];
335
+ let sawHeaderRow = false;
336
+ for (let i = headingIdx + 1; i < lines.length; i++) {
337
+ const line = lines[i];
338
+ if (!line.startsWith('|')) {
339
+ if (sawHeaderRow) break; // table ended
340
+ continue; // still skipping the heading's intro prose
341
+ }
342
+ const cells = line.split('|');
343
+ const first = (cells[1] ?? '').trim();
344
+ if (/^:?-+:?$/.test(first)) continue; // the `|---|---|` separator row
345
+ if (!sawHeaderRow) {
346
+ sawHeaderRow = true; // this is the `| Header | Header |` row itself
347
+ continue;
348
+ }
349
+ values.push(first.replace(/`/g, ''));
350
+ }
351
+ return values;
352
+ }
353
+
354
+ /**
355
+ * Field-reference / status-values row-set violations for one target, or `[]`
356
+ * when the target has neither table (the template) or the parity holds.
357
+ */
358
+ function collectKeySetParityViolations(target, schema, normalizedText) {
359
+ if (!target.locale) return [];
360
+ const found = [];
361
+
362
+ const fieldHeading = FIELD_REFERENCE_HEADING[target.locale];
363
+ const docFieldKeys = fieldHeading ? firstColumnAfterHeading(normalizedText, fieldHeading) : null;
364
+ if (docFieldKeys !== null) {
365
+ const schemaKeys = Object.keys(schema).filter((k) => !EXCLUDED_FIELD_TABLE_KEYS.includes(k));
366
+ const docSet = new Set(docFieldKeys);
367
+ const schemaSet = new Set(schemaKeys);
368
+ const missingFromDoc = schemaKeys.filter((k) => !docSet.has(k));
369
+ const undeclaredInSchema = docFieldKeys.filter(
370
+ (k) => !schemaSet.has(k) && !KNOWN_SCHEMA_GAP_FIELDS.includes(k),
371
+ );
372
+ if (missingFromDoc.length > 0 || undeclaredInSchema.length > 0) {
373
+ found.push({
374
+ reason: REASON.FIELD_REFERENCE_DRIFT,
375
+ file: target.relPath,
376
+ region: null,
377
+ missingFromDoc,
378
+ undeclaredInSchema,
379
+ });
380
+ }
381
+ }
382
+
383
+ const statusHeading = STATUS_VALUES_HEADING[target.locale];
384
+ const docStatusValues = statusHeading ? firstColumnAfterHeading(normalizedText, statusHeading) : null;
385
+ if (docStatusValues !== null) {
386
+ const enumMembers = schema.status && Array.isArray(schema.status.enum) ? schema.status.enum : [];
387
+ const docSet = new Set(docStatusValues);
388
+ const enumSet = new Set(enumMembers);
389
+ const missingFromDoc = enumMembers.filter((v) => !docSet.has(v));
390
+ const undeclaredInSchema = docStatusValues.filter((v) => !enumSet.has(v));
391
+ if (missingFromDoc.length > 0 || undeclaredInSchema.length > 0) {
392
+ found.push({
393
+ reason: REASON.STATUS_VALUES_DRIFT,
394
+ file: target.relPath,
395
+ region: null,
396
+ missingFromDoc,
397
+ undeclaredInSchema,
398
+ });
399
+ }
400
+ }
401
+
402
+ return found;
403
+ }
404
+
405
+ const REGION_RENDERERS = Object.freeze({
406
+ frontmatter: (target, schema, _violations) => renderFrontmatterRegion(schema),
407
+ 'status-lifecycle': (target, schema, violations) => renderStatusLifecycleRegion(target.locale, schema, violations),
408
+ cardinality: (target, schema, _violations) => renderCardinalityRegion(target.locale, schema),
409
+ });
410
+
411
+ // ─── Marker splicing ─────────────────────────────────────────────────────────
412
+
413
+ /**
414
+ * Locate a region's `[start, end)` byte range (over LF-normalized text),
415
+ * INCLUSIVE of both marker lines, in `doc`. Mirrors `gen-features.cjs`'s
416
+ * `spliceIntoFeatures`: the END marker is found via `lastIndexOf` so a forged
417
+ * marker cannot shrink the region it governs.
418
+ *
419
+ * Returns `{ violation }` when the pair is missing, unclosed, or out of
420
+ * order — the caller decides whether that is fatal (splice) or merely
421
+ * reportable (check/json).
422
+ */
423
+ function findRegion(doc, file, region) {
424
+ const start = startMarker(region);
425
+ const end = endMarker(region);
426
+ const startIdx = doc.indexOf(start);
427
+ const endIdx = doc.lastIndexOf(end);
428
+
429
+ if (startIdx === -1 && endIdx === -1) {
430
+ return { violation: { reason: REASON.MARKERS_MISSING, file, region } };
431
+ }
432
+ if (startIdx === -1 || endIdx === -1) {
433
+ return { violation: { reason: REASON.MARKER_UNCLOSED, file, region } };
434
+ }
435
+ if (endIdx < startIdx) {
436
+ return { violation: { reason: REASON.MARKER_ORDER_INVALID, file, region } };
437
+ }
438
+ return { range: [startIdx, endIdx + end.length] };
439
+ }
440
+
441
+ /** Replace `region`'s marked span in `doc` with `body`, markers included. */
442
+ function spliceRegion(doc, region, body) {
443
+ const { range, violation } = findRegion(doc, '<in-memory>', region);
444
+ if (violation) {
445
+ throw new ExitError(
446
+ 1,
447
+ `region '${region}' markers are ${violation.reason} — expected:\n ${startMarker(region)}\n ${endMarker(region)}\n`,
448
+ );
449
+ }
450
+ const [start, end] = range;
451
+ const replacement = `${startMarker(region)}\n${body}\n${endMarker(region)}`;
452
+ return doc.slice(0, start) + replacement + doc.slice(end);
453
+ }
454
+
455
+ /** `true` if `text`'s line endings are predominantly CRLF. */
456
+ function isCrlf(text) {
457
+ return /\r\n/.test(text);
458
+ }
459
+
460
+ // ─── Corpus assembly ─────────────────────────────────────────────────────────
461
+
462
+ function loadSchema() {
463
+ if (!fs.existsSync(SCHEMA_LIB_PATH)) {
464
+ throw new ExitError(
465
+ 1,
466
+ `${SCHEMA_LIB_PATH} does not exist. Run 'npm run build:lib' first (STATE_FIELD_SCHEMA is compiled from src/state-md-schema.cts).`,
467
+ );
468
+ }
469
+ // SCHEMA_LIB_PATH is a fixed, repo-relative constant computed from __dirname — never user input.
470
+ const mod = require(SCHEMA_LIB_PATH);
471
+ return mod.STATE_FIELD_SCHEMA;
472
+ }
473
+
474
+ /**
475
+ * Build every target's rendered regions plus, for a given `filesRoot`, the
476
+ * violations found reading its on-disk file (missing/unclosed markers,
477
+ * staleness). `filesRoot` defaults to the real repo root; tests pass a temp
478
+ * copy so no fixture is ever planted in the real tree.
479
+ */
480
+ function buildCorpus(filesRoot) {
481
+ const schema = loadSchema();
482
+ const violations = [];
483
+ const targets = TARGETS.map((target) => {
484
+ const absPath = path.join(filesRoot, target.relPath);
485
+ const regionBodies = {};
486
+ for (const region of target.regions) {
487
+ const renderer = REGION_RENDERERS[region];
488
+ regionBodies[region] = renderer(target, schema, violations);
489
+ }
490
+ return { ...target, absPath, regionBodies };
491
+ });
492
+ return { schema, targets, violations };
493
+ }
494
+
495
+ /** Read `target.absPath`, returning `null` (with a violation appended) on ENOENT. */
496
+ function readTarget(target, violations) {
497
+ try {
498
+ return fs.readFileSync(target.absPath, 'utf8');
499
+ } catch {
500
+ violations.push({ reason: REASON.MARKERS_MISSING, file: target.relPath, region: null, detail: 'file unreadable' });
501
+ return null;
502
+ }
503
+ }
504
+
505
+ /** Splice every declared region of `target` into `original`, returning the new text (or throwing). */
506
+ function spliceTarget(target, original) {
507
+ const crlf = isCrlf(original);
508
+ let normalized = original.replace(/\r\n/g, '\n');
509
+ for (const region of target.regions) {
510
+ normalized = spliceRegion(normalized, region, target.regionBodies[region]);
511
+ }
512
+ return crlf ? normalized.replace(/\n/g, '\r\n') : normalized;
513
+ }
514
+
515
+ /** Which of `target`'s regions differ between `original` and its spliced form. */
516
+ function staleRegionsOf(target, original) {
517
+ const crlf = isCrlf(original);
518
+ const normalizedOriginal = original.replace(/\r\n/g, '\n');
519
+ const stale = [];
520
+ for (const region of target.regions) {
521
+ const { range, violation } = findRegion(normalizedOriginal, target.relPath, region);
522
+ if (violation) continue; // already reported as a hostile violation elsewhere
523
+ const [start, end] = range;
524
+ const currentSpan = normalizedOriginal.slice(start, end);
525
+ const freshSpan = `${startMarker(region)}\n${target.regionBodies[region]}\n${endMarker(region)}`;
526
+ if (currentSpan !== freshSpan) stale.push(region);
527
+ }
528
+ return { stale, crlf };
529
+ }
530
+
531
+ /** Non-throwing per-target-per-region violation scan, for --check/--json. */
532
+ function collectTargetViolations(target, original) {
533
+ const found = [];
534
+ if (original === null) return found;
535
+ const normalized = original.replace(/\r\n/g, '\n');
536
+ for (const region of target.regions) {
537
+ const { violation } = findRegion(normalized, target.relPath, region);
538
+ if (violation) {
539
+ found.push({ ...violation, file: target.relPath });
540
+ }
541
+ }
542
+ return found;
543
+ }
544
+
545
+ /** Human one-liner for a violation, keyed off its typed reason. */
546
+ function describeViolation(v) {
547
+ switch (v.reason) {
548
+ case REASON.SCHEMA_LIB_MISSING:
549
+ return `${SCHEMA_LIB_PATH} is missing — run 'npm run build:lib'`;
550
+ case REASON.MARKERS_MISSING:
551
+ return `${v.file}: region '${v.region}' markers are entirely absent — expected ${startMarker(v.region)} / ${endMarker(v.region)}`;
552
+ case REASON.MARKER_UNCLOSED:
553
+ return `${v.file}: region '${v.region}' has only one of its START/END markers — malformed or unclosed`;
554
+ case REASON.MARKER_ORDER_INVALID:
555
+ return `${v.file}: region '${v.region}' END marker precedes its START marker`;
556
+ case REASON.REGION_STALE:
557
+ return `${v.file}: region '${v.region}' is stale — run 'node scripts/gen-state-md-docs.cjs --write'`;
558
+ case REASON.LOCALE_STRINGS_MISSING:
559
+ return `no localized strings registered in STATUS_LIFECYCLE_STRINGS for locale '${v.locale}' — falls back to 'en' under --force`;
560
+ case REASON.FIELD_REFERENCE_DRIFT:
561
+ return (
562
+ `${v.file}: Field-reference table row set disagrees with STATE_FIELD_SCHEMA — ` +
563
+ `missing from doc: [${v.missingFromDoc.join(', ')}]; undeclared in schema: [${v.undeclaredInSchema.join(', ')}]`
564
+ );
565
+ case REASON.STATUS_VALUES_DRIFT:
566
+ return (
567
+ `${v.file}: Status-values table row set disagrees with STATE_FIELD_SCHEMA.status.enum — ` +
568
+ `missing from doc: [${v.missingFromDoc.join(', ')}]; undeclared in schema: [${v.undeclaredInSchema.join(', ')}]`
569
+ );
570
+ default:
571
+ return `${v.file}: ${v.reason}`;
572
+ }
573
+ }
574
+
575
+ // ─── CLI ─────────────────────────────────────────────────────────────────────
576
+
577
+ function parseArgs(argv) {
578
+ const opts = { write: false, check: false, json: false, force: false, root: REPO_ROOT };
579
+ for (let i = 0; i < argv.length; i++) {
580
+ const arg = argv[i];
581
+ if (arg === '--write') opts.write = true;
582
+ else if (arg === '--check') opts.check = true;
583
+ else if (arg === '--json') opts.json = true;
584
+ else if (arg === '--force') opts.force = true;
585
+ else if (arg === '--root') {
586
+ opts.root = argv[++i];
587
+ if (!opts.root) throw new ExitError(1, '--root requires a directory argument');
588
+ } else {
589
+ throw new ExitError(1, `unknown flag: ${arg}\nRecognized flags: --write, --check, --json, --force, --root <dir>.`);
590
+ }
591
+ }
592
+ return opts;
593
+ }
594
+
595
+ function main() {
596
+ const { write, check, json, force, root } = parseArgs(process.argv.slice(2));
597
+
598
+ const { schema, targets, violations } = buildCorpus(root);
599
+
600
+ // Collect per-target hostile-input violations (missing/malformed markers)
601
+ // up front — these are fatal for --write regardless of --force, because
602
+ // --force overrides a STALE region (a legitimate "write it anyway" ask),
603
+ // never a structurally broken target (there is nothing to splice into).
604
+ const originals = new Map();
605
+ for (const target of targets) {
606
+ const original = readTarget(target, violations);
607
+ originals.set(target.key, original);
608
+ violations.push(...collectTargetViolations(target, original));
609
+ if (original !== null) {
610
+ violations.push(...collectKeySetParityViolations(target, schema, original.replace(/\r\n/g, '\n')));
611
+ }
612
+ }
613
+
614
+ const hostileViolations = violations.filter((v) =>
615
+ [REASON.MARKERS_MISSING, REASON.MARKER_UNCLOSED, REASON.MARKER_ORDER_INVALID].includes(v.reason),
616
+ );
617
+
618
+ // Staleness (only computable for targets with clean markers), per region.
619
+ const staleTargets = [];
620
+ for (const target of targets) {
621
+ const original = originals.get(target.key);
622
+ if (original === null) continue;
623
+ const hasHostile = hostileViolations.some((v) => v.file === target.relPath);
624
+ if (hasHostile) continue;
625
+ const { stale } = staleRegionsOf(target, original);
626
+ if (stale.length > 0) {
627
+ staleTargets.push(target);
628
+ for (const region of stale) violations.push({ reason: REASON.REGION_STALE, file: target.relPath, region });
629
+ }
630
+ }
631
+
632
+ if (json) {
633
+ const ok = violations.length === 0;
634
+ process.stdout.write(
635
+ JSON.stringify({
636
+ ok,
637
+ targetCount: targets.length,
638
+ staleCount: staleTargets.length,
639
+ violations,
640
+ }) + '\n',
641
+ );
642
+ return ok ? 0 : 1;
643
+ }
644
+
645
+ if (check) {
646
+ if (violations.length > 0) {
647
+ process.stderr.write(`gen-state-md-docs: ${violations.length} violation(s).\n\n`);
648
+ for (const v of violations) process.stderr.write(` ✗ ${describeViolation(v)}\n`);
649
+ process.stderr.write('\n');
650
+ throw new ExitError(1);
651
+ }
652
+ process.stdout.write(`All ${targets.length} target(s) are up to date.\n`);
653
+ return 0;
654
+ }
655
+
656
+ if (write) {
657
+ // FAIL-CLOSED (gen-features.cjs precedent, design doc §3): a hostile
658
+ // marker violation is ALWAYS fatal — --force overrides staleness, not a
659
+ // structurally broken target with nothing to splice into.
660
+ if (hostileViolations.length > 0) {
661
+ process.stderr.write(`gen-state-md-docs: refusing to write — ${hostileViolations.length} target(s) have broken markers.\n\n`);
662
+ for (const v of hostileViolations) process.stderr.write(` ✗ ${describeViolation(v)}\n`);
663
+ process.stderr.write('\n');
664
+ throw new ExitError(1);
665
+ }
666
+ const otherViolations = violations.filter((v) => v.reason !== REASON.REGION_STALE && !hostileViolations.includes(v));
667
+ if (otherViolations.length > 0 && !force) {
668
+ process.stderr.write(`gen-state-md-docs: ${otherViolations.length} violation(s). Refusing to write; pass --force to override.\n\n`);
669
+ for (const v of otherViolations) process.stderr.write(` ✗ ${describeViolation(v)}\n`);
670
+ process.stderr.write('\n');
671
+ throw new ExitError(1);
672
+ }
673
+ let written = 0;
674
+ for (const target of targets) {
675
+ const original = originals.get(target.key);
676
+ if (original === null) continue;
677
+ const hasHostile = hostileViolations.some((v) => v.file === target.relPath);
678
+ if (hasHostile) continue;
679
+ const spliced = spliceTarget(target, original);
680
+ if (spliced !== original) {
681
+ fs.writeFileSync(target.absPath, spliced);
682
+ written++;
683
+ }
684
+ }
685
+ process.stdout.write(`Wrote ${written} of ${targets.length} target(s).\n`);
686
+ return 0;
687
+ }
688
+
689
+ // Default: print every region, labeled, to stdout.
690
+ for (const target of targets) {
691
+ for (const region of target.regions) {
692
+ process.stdout.write(`\n=== ${target.relPath} :: ${region} ===\n`);
693
+ process.stdout.write(target.regionBodies[region] + '\n');
694
+ }
695
+ }
696
+ return 0;
697
+ }
698
+
699
+ if (require.main === module) runMain(main);
700
+
701
+ module.exports = {
702
+ REASON,
703
+ TARGETS,
704
+ MARKER_TAG,
705
+ startMarker,
706
+ endMarker,
707
+ TEMPLATE_FRONTMATTER_FIELDS,
708
+ STATUS_LIFECYCLE_STRINGS,
709
+ STATUS_LIFECYCLE_ROWS,
710
+ CARDINALITY_STRINGS,
711
+ EXCLUDED_FIELD_TABLE_KEYS,
712
+ KNOWN_SCHEMA_GAP_FIELDS,
713
+ FIELD_REFERENCE_HEADING,
714
+ STATUS_VALUES_HEADING,
715
+ renderFrontmatterRegion,
716
+ renderStatusLifecycleRegion,
717
+ renderCardinalityRegion,
718
+ firstColumnAfterHeading,
719
+ collectKeySetParityViolations,
720
+ findRegion,
721
+ spliceRegion,
722
+ spliceTarget,
723
+ staleRegionsOf,
724
+ isCrlf,
725
+ buildCorpus,
726
+ describeViolation,
727
+ };