@opengsd/gsd-core 1.11.0 → 1.12.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 (395) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/agents/gsd-code-fixer.md +1 -1
  4. package/agents/gsd-debug-session-manager.md +1 -1
  5. package/agents/gsd-debugger.md +1 -1
  6. package/agents/gsd-dom-verifier.md +169 -0
  7. package/agents/gsd-eval-auditor.md +1 -1
  8. package/agents/gsd-executor.md +17 -9
  9. package/agents/gsd-framework-selector.md +1 -3
  10. package/agents/gsd-intel-updater.md +1 -1
  11. package/agents/gsd-mempalace-curator.md +0 -1
  12. package/agents/gsd-pattern-mapper.md +11 -0
  13. package/agents/gsd-phase-researcher.md +3 -1
  14. package/agents/gsd-plan-checker.md +15 -55
  15. package/agents/gsd-planner.md +6 -4
  16. package/agents/gsd-project-researcher.md +1 -1
  17. package/agents/gsd-research-synthesizer.md +2 -2
  18. package/agents/gsd-roadmapper.md +15 -11
  19. package/agents/gsd-ui-checker.md +63 -4
  20. package/agents/gsd-ui-researcher.md +41 -3
  21. package/agents/gsd-verifier.md +1 -1
  22. package/bin/install.js +609 -134
  23. package/commands/gsd/discuss-phase.md +1 -1
  24. package/commands/gsd/import.md +1 -1
  25. package/commands/gsd/quick.md +8 -4
  26. package/gsd-core/bin/gsd-tools.cjs +567 -51
  27. package/gsd-core/bin/lib/active-workstream-store.cjs +8 -0
  28. package/gsd-core/bin/lib/adr-parser.cjs +13 -7
  29. package/gsd-core/bin/lib/agent-install-check.cjs +162 -0
  30. package/gsd-core/bin/lib/api-coverage.cjs +30 -9
  31. package/gsd-core/bin/lib/artifacts.cjs +2 -0
  32. package/gsd-core/bin/lib/assumption-delta.cjs +30 -11
  33. package/gsd-core/bin/lib/audit.cjs +163 -41
  34. package/gsd-core/bin/lib/broken-windows.cjs +306 -28
  35. package/gsd-core/bin/lib/capability-lock.cjs +10 -4
  36. package/gsd-core/bin/lib/capability-registry.cjs +336 -95
  37. package/gsd-core/bin/lib/capability-state.cjs +18 -3
  38. package/gsd-core/bin/lib/capability-validator.cjs +205 -18
  39. package/gsd-core/bin/lib/check-command-router.cjs +145 -5
  40. package/gsd-core/bin/lib/cli-exit.cjs +496 -10
  41. package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
  42. package/gsd-core/bin/lib/codex-agent-toml.cjs +410 -4
  43. package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
  44. package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
  45. package/gsd-core/bin/lib/commands.cjs +543 -44
  46. package/gsd-core/bin/lib/complexity-trigger.cjs +26 -6
  47. package/gsd-core/bin/lib/config-loader.cjs +118 -29
  48. package/gsd-core/bin/lib/config.cjs +92 -2
  49. package/gsd-core/bin/lib/configuration.cjs +129 -37
  50. package/gsd-core/bin/lib/core-utils.cjs +84 -7
  51. package/gsd-core/bin/lib/edge-probe.cjs +9 -1
  52. package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
  53. package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
  54. package/gsd-core/bin/lib/frontmatter.cjs +840 -305
  55. package/gsd-core/bin/lib/gap-checker.cjs +27 -3
  56. package/gsd-core/bin/lib/git-base-branch.cjs +174 -39
  57. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +7 -3
  58. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +6 -3
  59. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +22 -8
  60. package/gsd-core/bin/lib/health-diagnostic.cjs +23 -3
  61. package/gsd-core/bin/lib/host-integration.cjs +39 -6
  62. package/gsd-core/bin/lib/init-command-router.cjs +118 -21
  63. package/gsd-core/bin/lib/init.cjs +120 -41
  64. package/gsd-core/bin/lib/install-engine.cjs +68 -3
  65. package/gsd-core/bin/lib/install-model-override-resolver.cjs +33 -1
  66. package/gsd-core/bin/lib/install-profiles.cjs +78 -4
  67. package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
  68. package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
  69. package/gsd-core/bin/lib/installer-migrations.cjs +10 -7
  70. package/gsd-core/bin/lib/intel.cjs +101 -26
  71. package/gsd-core/bin/lib/io.cjs +160 -15
  72. package/gsd-core/bin/lib/learnings.cjs +85 -14
  73. package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
  74. package/gsd-core/bin/lib/markdown-table.cjs +52 -4
  75. package/gsd-core/bin/lib/milestone.cjs +90 -5
  76. package/gsd-core/bin/lib/model-catalog.cjs +177 -19
  77. package/gsd-core/bin/lib/model-resolver.cjs +10 -28
  78. package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
  79. package/gsd-core/bin/lib/phase-estimation.cjs +17 -8
  80. package/gsd-core/bin/lib/phase-id.cjs +70 -4
  81. package/gsd-core/bin/lib/phase-lifecycle.cjs +24 -16
  82. package/gsd-core/bin/lib/phase-locator.cjs +138 -17
  83. package/gsd-core/bin/lib/phase.cjs +405 -84
  84. package/gsd-core/bin/lib/plan-document.cjs +263 -0
  85. package/gsd-core/bin/lib/plan-scan.cjs +13 -2
  86. package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
  87. package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
  88. package/gsd-core/bin/lib/planning-snapshot.cjs +18 -14
  89. package/gsd-core/bin/lib/planning-workspace.cjs +56 -0
  90. package/gsd-core/bin/lib/probe-core.cjs +4 -1
  91. package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
  92. package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
  93. package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
  94. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +71 -45
  95. package/gsd-core/bin/lib/review-lane-descriptor.cjs +9 -9
  96. package/gsd-core/bin/lib/roadmap-command-router.cjs +45 -31
  97. package/gsd-core/bin/lib/roadmap-parser.cjs +79 -16
  98. package/gsd-core/bin/lib/roadmap.cjs +74 -19
  99. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +96 -8
  100. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +34 -1
  101. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +287 -55
  102. package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
  103. package/gsd-core/bin/lib/runtime-slash.cjs +72 -2
  104. package/gsd-core/bin/lib/shell-command-projection.cjs +71 -8
  105. package/gsd-core/bin/lib/smart-entry.cjs +12 -22
  106. package/gsd-core/bin/lib/spec-section.cjs +12 -7
  107. package/gsd-core/bin/lib/state-command-router.cjs +47 -18
  108. package/gsd-core/bin/lib/state-contract.cjs +359 -0
  109. package/gsd-core/bin/lib/state-document.cjs +186 -0
  110. package/gsd-core/bin/lib/state-md-schema.cjs +221 -0
  111. package/gsd-core/bin/lib/state-transition.cjs +517 -101
  112. package/gsd-core/bin/lib/state.cjs +946 -163
  113. package/gsd-core/bin/lib/surface.cjs +10 -2
  114. package/gsd-core/bin/lib/task-command-router.cjs +111 -1
  115. package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
  116. package/gsd-core/bin/lib/teams-status.cjs +4 -1
  117. package/gsd-core/bin/lib/uat-predicate.cjs +58 -20
  118. package/gsd-core/bin/lib/uat.cjs +1376 -125
  119. package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
  120. package/gsd-core/bin/lib/ui-safety-gate.cjs +37 -7
  121. package/gsd-core/bin/lib/unusable-input.cjs +13 -0
  122. package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
  123. package/gsd-core/bin/lib/vendor/README.md +43 -5
  124. package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
  125. package/gsd-core/bin/lib/verification.cjs +14 -1
  126. package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
  127. package/gsd-core/bin/lib/verify.cjs +95 -40
  128. package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
  129. package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
  130. package/gsd-core/bin/lib/worktree-safety.cjs +177 -21
  131. package/gsd-core/bin/shared/config-defaults.manifest.json +7 -1
  132. package/gsd-core/bin/shared/config-schema.manifest.json +5 -0
  133. package/gsd-core/bin/shared/exit-codes.json +8 -0
  134. package/gsd-core/bin/shared/exit-codes.sh +20 -0
  135. package/gsd-core/bin/shared/model-catalog.json +8 -1
  136. package/gsd-core/references/agent-contracts.md +3 -2
  137. package/gsd-core/references/api-coverage.md +24 -2
  138. package/gsd-core/references/autonomous-smart-discuss.md +3 -3
  139. package/gsd-core/references/checkpoints.md +37 -19
  140. package/gsd-core/references/decimal-phase-calculation.md +5 -5
  141. package/gsd-core/references/edge-probe.md +8 -0
  142. package/gsd-core/references/execute-mvp-tdd.md +1 -3
  143. package/gsd-core/references/execute-phase-between-wave-reset.md +9 -12
  144. package/gsd-core/references/execute-phase-wave-guard.md +11 -9
  145. package/gsd-core/references/failing-direction.md +78 -0
  146. package/gsd-core/references/gate-prompts.md +1 -1
  147. package/gsd-core/references/git-integration.md +5 -5
  148. package/gsd-core/references/git-planning-commit.md +3 -3
  149. package/gsd-core/references/gsd-run-resolver.md +1 -1
  150. package/gsd-core/references/loop-hook-dispatch.md +22 -0
  151. package/gsd-core/references/model-profiles.md +1 -1
  152. package/gsd-core/references/nyquist-compliance.md +74 -0
  153. package/gsd-core/references/offer-next.md +3 -5
  154. package/gsd-core/references/phase-argument-parsing.md +3 -3
  155. package/gsd-core/references/planner-failing-direction.md +53 -0
  156. package/gsd-core/references/planner-human-verify-mode.md +15 -1
  157. package/gsd-core/references/planner-revision.md +1 -1
  158. package/gsd-core/references/planner-verify-command-grounding.md +17 -0
  159. package/gsd-core/references/planning-config.md +37 -8
  160. package/gsd-core/references/reviewer-instances.md +31 -0
  161. package/gsd-core/references/runtime-aware-dispatch.md +1 -1
  162. package/gsd-core/references/tdd.md +1 -3
  163. package/gsd-core/references/ui-brand.md +65 -21
  164. package/gsd-core/references/ui-consideration-probe.md +1 -1
  165. package/gsd-core/references/universal-anti-patterns.md +2 -2
  166. package/gsd-core/references/verify-command-path-resolvability.md +42 -0
  167. package/gsd-core/references/verify-mvp-mode.md +1 -1
  168. package/gsd-core/references/workstream-flag.md +11 -11
  169. package/gsd-core/templates/README.md +1 -1
  170. package/gsd-core/templates/SECURITY.md +3 -3
  171. package/gsd-core/templates/UI-SPEC.md +25 -3
  172. package/gsd-core/templates/VALIDATION.md +3 -3
  173. package/gsd-core/templates/phase-prompt.md +3 -0
  174. package/gsd-core/templates/state.md +7 -0
  175. package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
  176. package/gsd-core/workflows/add-backlog.md +1 -1
  177. package/gsd-core/workflows/add-phase.md +3 -3
  178. package/gsd-core/workflows/add-tests.md +3 -8
  179. package/gsd-core/workflows/add-todo.md +1 -1
  180. package/gsd-core/workflows/ai-integration-phase.md +4 -9
  181. package/gsd-core/workflows/audit-fix.md +12 -3
  182. package/gsd-core/workflows/audit-milestone.md +9 -9
  183. package/gsd-core/workflows/audit-uat.md +17 -2
  184. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
  185. package/gsd-core/workflows/autonomous.md +10 -26
  186. package/gsd-core/workflows/check-todos.md +1 -1
  187. package/gsd-core/workflows/cleanup.md +2 -2
  188. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +1 -1
  189. package/gsd-core/workflows/code-review-fix.md +1 -1
  190. package/gsd-core/workflows/code-review.md +121 -40
  191. package/gsd-core/workflows/complete-milestone.md +15 -10
  192. package/gsd-core/workflows/debug.md +5 -3
  193. package/gsd-core/workflows/diagnose-issues.md +12 -6
  194. package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
  195. package/gsd-core/workflows/discuss-phase/modes/chain.md +3 -7
  196. package/gsd-core/workflows/discuss-phase/modes/text.md +1 -1
  197. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
  198. package/gsd-core/workflows/discuss-phase-assumptions.md +2 -2
  199. package/gsd-core/workflows/discuss-phase.md +1 -1
  200. package/gsd-core/workflows/do.md +3 -6
  201. package/gsd-core/workflows/docs-update.md +5 -4
  202. package/gsd-core/workflows/edit-phase.md +1 -1
  203. package/gsd-core/workflows/eval-review.md +4 -9
  204. package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
  205. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +113 -11
  206. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
  207. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
  208. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
  209. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +22 -4
  210. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
  211. package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
  212. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -2
  213. package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
  214. package/gsd-core/workflows/execute-phase.md +38 -54
  215. package/gsd-core/workflows/execute-plan.md +17 -12
  216. package/gsd-core/workflows/explore.md +1 -1
  217. package/gsd-core/workflows/extract-learnings.md +1 -1
  218. package/gsd-core/workflows/fast.md +2 -2
  219. package/gsd-core/workflows/forensics.md +1 -1
  220. package/gsd-core/workflows/graduation.md +5 -5
  221. package/gsd-core/workflows/health.md +3 -6
  222. package/gsd-core/workflows/import.md +14 -11
  223. package/gsd-core/workflows/inbox.md +4 -5
  224. package/gsd-core/workflows/ingest-docs.md +44 -11
  225. package/gsd-core/workflows/insert-phase.md +5 -5
  226. package/gsd-core/workflows/list-seeds.md +5 -3
  227. package/gsd-core/workflows/list-workspaces.md +1 -1
  228. package/gsd-core/workflows/manager.md +12 -23
  229. package/gsd-core/workflows/map-codebase.md +1 -1
  230. package/gsd-core/workflows/milestone-summary.md +1 -1
  231. package/gsd-core/workflows/mvp-phase.md +2 -2
  232. package/gsd-core/workflows/new-milestone.md +9 -21
  233. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
  234. package/gsd-core/workflows/new-project.md +12 -26
  235. package/gsd-core/workflows/new-workspace.md +1 -1
  236. package/gsd-core/workflows/next.md +2 -2
  237. package/gsd-core/workflows/pause-work.md +1 -1
  238. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
  239. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +1 -1
  240. package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -4
  241. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +3 -3
  242. package/gsd-core/workflows/plan-phase.md +121 -42
  243. package/gsd-core/workflows/plan-review-convergence.md +46 -9
  244. package/gsd-core/workflows/plant-seed.md +2 -2
  245. package/gsd-core/workflows/pr-branch.md +187 -51
  246. package/gsd-core/workflows/profile-user.md +16 -14
  247. package/gsd-core/workflows/progress.md +27 -12
  248. package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
  249. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +1 -3
  250. package/gsd-core/workflows/quick/steps/quick-verification.md +2 -4
  251. package/gsd-core/workflows/quick/steps/research-phase.md +2 -4
  252. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
  253. package/gsd-core/workflows/quick.md +20 -29
  254. package/gsd-core/workflows/remove-phase.md +4 -4
  255. package/gsd-core/workflows/remove-workspace.md +2 -2
  256. package/gsd-core/workflows/resume-project.md +8 -12
  257. package/gsd-core/workflows/review.md +193 -15
  258. package/gsd-core/workflows/scan.md +1 -1
  259. package/gsd-core/workflows/secure-phase.md +2 -2
  260. package/gsd-core/workflows/settings-advanced.md +7 -9
  261. package/gsd-core/workflows/settings-integrations.md +64 -31
  262. package/gsd-core/workflows/settings.md +3 -5
  263. package/gsd-core/workflows/ship.md +12 -6
  264. package/gsd-core/workflows/sketch-wrap-up.md +11 -17
  265. package/gsd-core/workflows/sketch.md +12 -18
  266. package/gsd-core/workflows/smart-entry.md +3 -5
  267. package/gsd-core/workflows/spec-phase.md +23 -1
  268. package/gsd-core/workflows/spike-wrap-up.md +7 -11
  269. package/gsd-core/workflows/spike.md +20 -31
  270. package/gsd-core/workflows/stats.md +2 -2
  271. package/gsd-core/workflows/sync-skills.md +1 -1
  272. package/gsd-core/workflows/thread.md +11 -7
  273. package/gsd-core/workflows/transition.md +5 -5
  274. package/gsd-core/workflows/ui-phase.md +10 -16
  275. package/gsd-core/workflows/ui-review.md +6 -10
  276. package/gsd-core/workflows/ultraplan-phase.md +5 -13
  277. package/gsd-core/workflows/undo.md +8 -16
  278. package/gsd-core/workflows/update.md +6 -10
  279. package/gsd-core/workflows/validate-phase.md +2 -2
  280. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
  281. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
  282. package/gsd-core/workflows/verify-work.md +57 -18
  283. package/hooks/dist/gsd-agent-isolation-guard.js +77 -38
  284. package/hooks/dist/gsd-config-reload.js +18 -12
  285. package/hooks/dist/gsd-context-monitor.js +19 -10
  286. package/hooks/dist/gsd-cursor-post-tool.js +3 -1
  287. package/hooks/dist/gsd-cursor-pre-tool.js +3 -1
  288. package/hooks/dist/gsd-cursor-session-start.js +2 -1
  289. package/hooks/dist/gsd-cursor-stop.js +2 -1
  290. package/hooks/dist/gsd-cursor-subagent-start.js +28 -23
  291. package/hooks/dist/gsd-cursor-subagent-stop.js +3 -1
  292. package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
  293. package/hooks/dist/gsd-graphify-update.sh +22 -18
  294. package/hooks/dist/gsd-node-runner.sh +76 -0
  295. package/hooks/dist/gsd-phase-boundary.sh +1 -0
  296. package/hooks/dist/gsd-prompt-guard.js +16 -7
  297. package/hooks/dist/gsd-read-guard.js +16 -7
  298. package/hooks/dist/gsd-read-injection-scanner.js +17 -8
  299. package/hooks/dist/gsd-session-state.sh +1 -0
  300. package/hooks/dist/gsd-statusline.js +215 -26
  301. package/hooks/dist/gsd-validate-commit.sh +80 -6
  302. package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
  303. package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
  304. package/hooks/dist/gsd-workflow-guard.js +34 -16
  305. package/hooks/dist/gsd-worktree-path-guard.js +36 -21
  306. package/hooks/dist/gsd-write-guard.js +35 -25
  307. package/hooks/dist/lib/cli-exit.js +560 -0
  308. package/hooks/dist/lib/exit-code-registry.js +98 -0
  309. package/hooks/dist/lib/git-probe.js +84 -0
  310. package/hooks/dist/lib/hook-exit.js +81 -0
  311. package/hooks/dist/managed-hooks-registry.cjs +3 -0
  312. package/hooks/gsd-agent-isolation-guard.js +77 -38
  313. package/hooks/gsd-config-reload.js +18 -12
  314. package/hooks/gsd-context-monitor.js +19 -10
  315. package/hooks/gsd-cursor-post-tool.js +3 -1
  316. package/hooks/gsd-cursor-pre-tool.js +3 -1
  317. package/hooks/gsd-cursor-session-start.js +2 -1
  318. package/hooks/gsd-cursor-stop.js +2 -1
  319. package/hooks/gsd-cursor-subagent-start.js +28 -23
  320. package/hooks/gsd-cursor-subagent-stop.js +3 -1
  321. package/hooks/gsd-ensure-canonical-path.js +2 -1
  322. package/hooks/gsd-graphify-update.sh +22 -18
  323. package/hooks/gsd-node-runner.sh +76 -0
  324. package/hooks/gsd-phase-boundary.sh +1 -0
  325. package/hooks/gsd-prompt-guard.js +16 -7
  326. package/hooks/gsd-read-guard.js +16 -7
  327. package/hooks/gsd-read-injection-scanner.js +17 -8
  328. package/hooks/gsd-session-state.sh +1 -0
  329. package/hooks/gsd-statusline.js +215 -26
  330. package/hooks/gsd-validate-commit.sh +80 -6
  331. package/hooks/gsd-windsurf-pre-command.js +16 -11
  332. package/hooks/gsd-windsurf-pre-write.js +22 -13
  333. package/hooks/gsd-workflow-guard.js +34 -16
  334. package/hooks/gsd-worktree-path-guard.js +36 -21
  335. package/hooks/gsd-write-guard.js +35 -25
  336. package/hooks/lib/cli-exit.js +560 -0
  337. package/hooks/lib/exit-code-registry.js +98 -0
  338. package/hooks/lib/git-probe.js +84 -0
  339. package/hooks/lib/hook-exit.js +81 -0
  340. package/hooks/managed-hooks-registry.cjs +3 -0
  341. package/package.json +12 -7
  342. package/scripts/base64-scan.sh +74 -12
  343. package/scripts/build-hooks.js +5 -0
  344. package/scripts/check-glossary-refs.cjs +77 -15
  345. package/scripts/check-mutation-score-ratchet.cjs +156 -0
  346. package/scripts/ci-check-job-near-cap.cjs +49 -0
  347. package/scripts/ci-pr-mergeability.cjs +262 -0
  348. package/scripts/ci-test-scope.cjs +45 -12
  349. package/scripts/ci-timeout-report.cjs +230 -0
  350. package/scripts/docs-guard-registry.cjs +396 -0
  351. package/scripts/gen-capability-registry.cjs +8 -6
  352. package/scripts/gen-exit-code-docs.cjs +318 -0
  353. package/scripts/gen-exit-code-registry.cjs +891 -0
  354. package/scripts/gen-features.cjs +836 -0
  355. package/scripts/gen-hooks-cli-exit.cjs +239 -0
  356. package/scripts/gen-install-tree-fixtures.cjs +2 -2
  357. package/scripts/gen-loop-host-contract.cjs +134 -1
  358. package/scripts/gen-scripts-cli-exit.cjs +185 -0
  359. package/scripts/gen-state-md-docs.cjs +727 -0
  360. package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
  361. package/scripts/lib/ci-job-timing.cjs +72 -0
  362. package/scripts/lib/cli-exit.cjs +546 -44
  363. package/scripts/lib/drift-scan.cjs +32 -2
  364. package/scripts/lib/exit-code-registry.cjs +98 -0
  365. package/scripts/lib/ndjson-reporter.cjs +119 -0
  366. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
  367. package/scripts/lint-docs-guard-registration.cjs +495 -0
  368. package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +193 -0
  369. package/scripts/lint-eslint-glob-coverage.allowlist.json +4 -0
  370. package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
  371. package/scripts/lint-health-diagnostic-rule-table.cjs +65 -8
  372. package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
  373. package/scripts/lint-phase-enumeration-drift.cjs +21 -8
  374. package/scripts/lint-planning-prompt-drift.cjs +38 -1
  375. package/scripts/lint-removed-but-needed.cjs +184 -16
  376. package/scripts/lint-seam-enforcement.cjs +182 -0
  377. package/scripts/lint-slug-derivation-drift.cjs +921 -0
  378. package/scripts/lint-source-test-name-collision.cjs +241 -0
  379. package/scripts/lint-state-write-path-drift.cjs +337 -432
  380. package/scripts/lint-test-file-count.allowlist.json +122 -4
  381. package/scripts/lint-test-file-count.cjs +25 -3
  382. package/scripts/lint-unreachable-guard-drift.cjs +51 -64
  383. package/scripts/lint-vendored-deps.cjs +208 -35
  384. package/scripts/mutation-matrix.cjs +599 -50
  385. package/scripts/prompt-injection-scan.sh +75 -14
  386. package/scripts/secret-scan.sh +75 -13
  387. package/scripts/select-docs-guards.cjs +56 -0
  388. package/scripts/sync-runtime-launcher.cjs +22 -3
  389. package/skills/gsd-discuss-phase/SKILL.md +1 -1
  390. package/skills/gsd-import/SKILL.md +1 -1
  391. package/skills/gsd-quick/SKILL.md +8 -4
  392. package/vscode/package.json +1 -1
  393. package/bin/lib/ui-safety-gate.cjs +0 -109
  394. package/scripts/lint-emitted-drift-ack.cjs +0 -344
  395. package/scripts/state-write-path-drift-baseline.json +0 -19
@@ -21,6 +21,9 @@ const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs")
21
21
  const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
22
22
  const text_lines_cjs_1 = require("./text-lines.cjs");
23
23
  // eslint-disable-next-line @typescript-eslint/no-require-imports
24
+ const coreUtils = require("./core-utils.cjs");
25
+ const { normalizeLineEndings } = coreUtils;
26
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
24
27
  const planningWorkspace = require("./planning-workspace.cjs");
25
28
  const { planningDir, quickDirFrom } = planningWorkspace;
26
29
  // eslint-disable-next-line @typescript-eslint/no-require-imports
@@ -31,7 +34,7 @@ const phaseIdMod = require("./phase-id.cjs");
31
34
  const { PHASE_NUMBER_TOKEN_SOURCE, scopeToPhase } = phaseIdMod;
32
35
  // eslint-disable-next-line @typescript-eslint/no-require-imports
33
36
  const phaseLocator = require("./phase-locator.cjs");
34
- const { getArchivedPhaseDirs } = phaseLocator;
37
+ const { getAllArchivedPhaseDirs } = phaseLocator;
35
38
  const security_cjs_1 = require("./security.cjs");
36
39
  const shell_command_projection_cjs_2 = require("./shell-command-projection.cjs");
37
40
  // eslint-disable-next-line @typescript-eslint/no-require-imports
@@ -279,9 +282,17 @@ function scanDebugSessions(planDir) {
279
282
  catch {
280
283
  continue;
281
284
  }
282
- const content = (0, shell_command_projection_cjs_1.platformReadSync)(safeFilePath);
283
- if (content === null)
285
+ // #3078-CR MEDIUM 2 (security review follow-up): normalize a lone-CR
286
+ // document at this read boundary, same seam as `src/uat.cts`'s
287
+ // `readNormalizedDocument` — `platformReadSync` performs no line-ending
288
+ // normalization itself, and extractFrontmatter/status-derivation below
289
+ // degrade a lone-CR file's frontmatter to `unknown`, which every scan
290
+ // in this module treats as "not open" (fail-open, the permissive
291
+ // direction) rather than a real parse gap.
292
+ const rawContent = (0, shell_command_projection_cjs_1.platformReadSync)(safeFilePath);
293
+ if (rawContent === null)
284
294
  continue;
295
+ const content = normalizeLineEndings(rawContent);
285
296
  const fm = extractFrontmatter(content, safeFilePath);
286
297
  const status = (fm.status || 'unknown').toLowerCase();
287
298
  if (status === 'resolved' || status === 'complete')
@@ -393,11 +404,15 @@ function scanQuickTasks(planDir) {
393
404
  catch {
394
405
  continue;
395
406
  }
396
- const content = (0, shell_command_projection_cjs_1.platformReadSync)(safeSum);
397
- if (content === null) {
407
+ // #3078-CR MEDIUM 2: same normalize-at-read-boundary fix as the other
408
+ // scans in this module — see the comment above `scanDebugSessions`'s
409
+ // read.
410
+ const rawContent = (0, shell_command_projection_cjs_1.platformReadSync)(safeSum);
411
+ if (rawContent === null) {
398
412
  status = 'unreadable';
399
413
  }
400
414
  else {
415
+ const content = normalizeLineEndings(rawContent);
401
416
  fm = extractFrontmatter(content, safeSum);
402
417
  status = (fm.status || 'unknown').toLowerCase();
403
418
  }
@@ -465,9 +480,17 @@ function scanThreads(planDir) {
465
480
  catch {
466
481
  continue;
467
482
  }
468
- const content = (0, shell_command_projection_cjs_1.platformReadSync)(safeFilePath);
469
- if (content === null)
483
+ // #3078-CR MEDIUM 2 (security review follow-up): normalize a lone-CR
484
+ // document at this read boundary, same seam as `src/uat.cts`'s
485
+ // `readNormalizedDocument` — `platformReadSync` performs no line-ending
486
+ // normalization itself, and extractFrontmatter/status-derivation below
487
+ // degrade a lone-CR file's frontmatter to `unknown`, which every scan
488
+ // in this module treats as "not open" (fail-open, the permissive
489
+ // direction) rather than a real parse gap.
490
+ const rawContent = (0, shell_command_projection_cjs_1.platformReadSync)(safeFilePath);
491
+ if (rawContent === null)
470
492
  continue;
493
+ const content = normalizeLineEndings(rawContent);
471
494
  const fm = extractFrontmatter(content, safeFilePath);
472
495
  const status = deriveThreadStatus(fm, content);
473
496
  if (!openStatuses.has(status))
@@ -532,9 +555,17 @@ function scanTodos(planDir) {
532
555
  catch {
533
556
  continue;
534
557
  }
535
- const content = (0, shell_command_projection_cjs_1.platformReadSync)(safeFilePath);
536
- if (content === null)
558
+ // #3078-CR MEDIUM 2 (security review follow-up): normalize a lone-CR
559
+ // document at this read boundary, same seam as `src/uat.cts`'s
560
+ // `readNormalizedDocument` — `platformReadSync` performs no line-ending
561
+ // normalization itself, and extractFrontmatter/status-derivation below
562
+ // degrade a lone-CR file's frontmatter to `unknown`, which every scan
563
+ // in this module treats as "not open" (fail-open, the permissive
564
+ // direction) rather than a real parse gap.
565
+ const rawContent = (0, shell_command_projection_cjs_1.platformReadSync)(safeFilePath);
566
+ if (rawContent === null)
537
567
  continue;
568
+ const content = normalizeLineEndings(rawContent);
538
569
  const fm = extractFrontmatter(content, safeFilePath);
539
570
  // Todos carry no natural status field — presence in pending/ IS "open" by
540
571
  // definition (a resolved todo is moved out, not status-flagged). So the
@@ -596,9 +627,17 @@ function scanSeeds(planDir) {
596
627
  catch {
597
628
  continue;
598
629
  }
599
- const content = (0, shell_command_projection_cjs_1.platformReadSync)(safeFilePath);
600
- if (content === null)
630
+ // #3078-CR MEDIUM 2 (security review follow-up): normalize a lone-CR
631
+ // document at this read boundary, same seam as `src/uat.cts`'s
632
+ // `readNormalizedDocument` — `platformReadSync` performs no line-ending
633
+ // normalization itself, and extractFrontmatter/status-derivation below
634
+ // degrade a lone-CR file's frontmatter to `unknown`, which every scan
635
+ // in this module treats as "not open" (fail-open, the permissive
636
+ // direction) rather than a real parse gap.
637
+ const rawContent = (0, shell_command_projection_cjs_1.platformReadSync)(safeFilePath);
638
+ if (rawContent === null)
601
639
  continue;
640
+ const content = normalizeLineEndings(rawContent);
602
641
  const fm = extractFrontmatter(content, safeFilePath);
603
642
  const status = (fm.status || 'dormant').toLowerCase();
604
643
  if (!unimplementedStatuses.has(status))
@@ -703,8 +742,11 @@ function listAuditPhaseTargets(planDir, cwd) {
703
742
  activeUnreadable = true;
704
743
  }
705
744
  }
745
+ // #3804: the audit is cross-workstream by design — the shared
746
+ // getAllArchivedPhaseDirs helper (root + every workstream, distinct
747
+ // '<ws>/<version>' labels) owns that walk.
706
748
  try {
707
- for (const archived of getArchivedPhaseDirs(cwd)) {
749
+ for (const archived of getAllArchivedPhaseDirs(cwd)) {
708
750
  targets.push({ dir: archived.name, fullPath: archived.fullPath, milestone: archived.milestone });
709
751
  }
710
752
  }
@@ -748,9 +790,17 @@ function scanUatGaps(planDir, cwd) {
748
790
  catch {
749
791
  continue;
750
792
  }
751
- const content = (0, shell_command_projection_cjs_1.platformReadSync)(safeFilePath);
752
- if (content === null)
793
+ // #3078-CR MEDIUM 2 (security review follow-up): normalize a lone-CR
794
+ // document at this read boundary, same seam as `src/uat.cts`'s
795
+ // `readNormalizedDocument` — `platformReadSync` performs no line-ending
796
+ // normalization itself, and extractFrontmatter/status-derivation below
797
+ // degrade a lone-CR file's frontmatter to `unknown`, which every scan
798
+ // in this module treats as "not open" (fail-open, the permissive
799
+ // direction) rather than a real parse gap.
800
+ const rawContent = (0, shell_command_projection_cjs_1.platformReadSync)(safeFilePath);
801
+ if (rawContent === null)
753
802
  continue;
803
+ const content = normalizeLineEndings(rawContent);
754
804
  const fm = extractFrontmatter(content, safeFilePath);
755
805
  const status = (fm.status || 'unknown').toLowerCase();
756
806
  const result = (fm.result || '').toLowerCase();
@@ -813,9 +863,17 @@ function scanVerificationGaps(planDir, cwd) {
813
863
  catch {
814
864
  continue;
815
865
  }
816
- const content = (0, shell_command_projection_cjs_1.platformReadSync)(safeFilePath);
817
- if (content === null)
866
+ // #3078-CR MEDIUM 2 (security review follow-up): normalize a lone-CR
867
+ // document at this read boundary, same seam as `src/uat.cts`'s
868
+ // `readNormalizedDocument` — `platformReadSync` performs no line-ending
869
+ // normalization itself, and extractFrontmatter/status-derivation below
870
+ // degrade a lone-CR file's frontmatter to `unknown`, which every scan
871
+ // in this module treats as "not open" (fail-open, the permissive
872
+ // direction) rather than a real parse gap.
873
+ const rawContent = (0, shell_command_projection_cjs_1.platformReadSync)(safeFilePath);
874
+ if (rawContent === null)
818
875
  continue;
876
+ const content = normalizeLineEndings(rawContent);
819
877
  const fm = extractFrontmatter(content, safeFilePath);
820
878
  const status = (fm.status || 'unknown').toLowerCase();
821
879
  if (status !== 'gaps_found' && status !== 'human_needed')
@@ -867,9 +925,17 @@ function scanContextQuestions(planDir, cwd) {
867
925
  catch {
868
926
  continue;
869
927
  }
870
- const content = (0, shell_command_projection_cjs_1.platformReadSync)(safeFilePath);
871
- if (content === null)
928
+ // #3078-CR MEDIUM 2 (security review follow-up): normalize a lone-CR
929
+ // document at this read boundary, same seam as `src/uat.cts`'s
930
+ // `readNormalizedDocument` — `platformReadSync` performs no line-ending
931
+ // normalization itself, and extractFrontmatter/status-derivation below
932
+ // degrade a lone-CR file's frontmatter to `unknown`, which every scan
933
+ // in this module treats as "not open" (fail-open, the permissive
934
+ // direction) rather than a real parse gap.
935
+ const rawContent = (0, shell_command_projection_cjs_1.platformReadSync)(safeFilePath);
936
+ if (rawContent === null)
872
937
  continue;
938
+ const content = normalizeLineEndings(rawContent);
873
939
  const fm = extractFrontmatter(content, safeFilePath);
874
940
  const questions = deriveOpenQuestions(content, fm);
875
941
  if (questions.length === 0)
@@ -947,9 +1013,15 @@ function scanDeferredItems(planDir, cwd) {
947
1013
  catch {
948
1014
  continue;
949
1015
  }
950
- const content = (0, shell_command_projection_cjs_1.platformReadSync)(safeFilePath);
951
- if (content === null)
1016
+ // #3078-CR MEDIUM 2: normalize at this read boundary too —
1017
+ // `parseDeferredItemsWithStatus` performs no normalization of its own
1018
+ // (unlike `src/uat.cts`'s callers, which route through
1019
+ // `readNormalizedDocument`), so a lone-CR `deferred-items.md` was read as
1020
+ // one unbroken line and every entry in it silently vanished.
1021
+ const rawContent = (0, shell_command_projection_cjs_1.platformReadSync)(safeFilePath);
1022
+ if (rawContent === null)
952
1023
  continue;
1024
+ const content = normalizeLineEndings(rawContent);
953
1025
  for (const item of uat.parseDeferredItemsWithStatus(content)) {
954
1026
  const rawStatus = (item.status || '').toLowerCase();
955
1027
  if (rawStatus === 'resolved')
@@ -1051,8 +1123,18 @@ function auditOpenArtifacts(cwd) {
1051
1123
  return { items: [{ scan_error: true, phase: '', file: '', text: '' }], acknowledged: 0 };
1052
1124
  }
1053
1125
  })();
1054
- // Count real items (not scan_error sentinels)
1055
- const countReal = (arr) => arr.filter(i => !i.scan_error && !i._remainder_count).length;
1126
+ // Count real items (not scan_error sentinels). #3817: a `_remainder_count`
1127
+ // marker is not a phantom — it RECORDS real items the detail list truncated
1128
+ // away for display, so its value counts toward the total. Truncation limits
1129
+ // display, never counting; only scan_error (a read failure, not an item)
1130
+ // contributes zero.
1131
+ const countReal = (arr) => arr.reduce((sum, i) => {
1132
+ if (i.scan_error)
1133
+ return sum;
1134
+ if (typeof i._remainder_count === 'number')
1135
+ return sum + i._remainder_count;
1136
+ return sum + 1;
1137
+ }, 0);
1056
1138
  const counts = {
1057
1139
  debug_sessions: countReal(debugSessions.items),
1058
1140
  quick_tasks: countReal(quickTasks.items),
@@ -1110,10 +1192,7 @@ function auditOpenArtifacts(cwd) {
1110
1192
  function formatAuditReport(auditResult) {
1111
1193
  const { counts, items, has_open_items, acknowledged } = auditResult;
1112
1194
  const lines = [];
1113
- const hr = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━';
1114
- lines.push(hr);
1115
- lines.push(' Milestone Close: Open Artifact Audit');
1116
- lines.push(hr);
1195
+ lines.push('### Milestone Close: Open Artifact Audit');
1117
1196
  // WARNING 3 (#3458 follow-up review): the acknowledged tally previously
1118
1197
  // existed only in `--json` output — the human report could not tell
1119
1198
  // "clean because fixed" apart from "clean because silenced", which is the
@@ -1121,13 +1200,13 @@ function formatAuditReport(auditResult) {
1121
1200
  if (!has_open_items) {
1122
1201
  lines.push('');
1123
1202
  if (acknowledged.total > 0) {
1124
- lines.push(` All artifact types clear (${acknowledged.total} previously acknowledged item${acknowledged.total !== 1 ? 's' : ''} still suppressed).`);
1203
+ lines.push(`All artifact types clear (${acknowledged.total} previously acknowledged item${acknowledged.total !== 1 ? 's' : ''} still suppressed).`);
1125
1204
  }
1126
1205
  else {
1127
- lines.push(' All artifact types clear. Safe to proceed.');
1206
+ lines.push('All artifact types clear. Safe to proceed.');
1128
1207
  }
1129
1208
  lines.push('');
1130
- lines.push(hr);
1209
+ lines.push('---');
1131
1210
  return lines.join('\n');
1132
1211
  }
1133
1212
  // WARNING 3: per-category "N previously acknowledged" suffix, so the
@@ -1228,12 +1307,12 @@ function formatAuditReport(auditResult) {
1228
1307
  }
1229
1308
  }
1230
1309
  lines.push('');
1231
- lines.push(hr);
1232
- lines.push(` ${counts.total} item${counts.total !== 1 ? 's' : ''} require decisions before close.`);
1310
+ lines.push('---');
1311
+ lines.push('');
1312
+ lines.push(`**${counts.total} item${counts.total !== 1 ? 's' : ''} require decisions before close.**`);
1233
1313
  if (acknowledged.total > 0) {
1234
- lines.push(` ${acknowledged.total} previously acknowledged item${acknowledged.total !== 1 ? 's' : ''} also suppressed above the ${counts.total} open item${counts.total !== 1 ? 's' : ''}.`);
1314
+ lines.push(`${acknowledged.total} previously acknowledged item${acknowledged.total !== 1 ? 's' : ''} also suppressed above the ${counts.total} open item${counts.total !== 1 ? 's' : ''}.`);
1235
1315
  }
1236
- lines.push(hr);
1237
1316
  return lines.join('\n');
1238
1317
  }
1239
1318
  // ─── resolvePhaseTargetDir ─────────────────────────────────────────────────────
@@ -1294,18 +1373,42 @@ function resolvePhaseTargetDir(planDir, cwd, phase, archivedMilestone) {
1294
1373
  * any read or write is attempted.
1295
1374
  */
1296
1375
  function cmdAuditAcknowledge(cwd, args, raw) {
1297
- const { category, milestone, at: atFlag, phase, file, 'archived-milestone': archivedMilestone, slug, 'seed-id': seedId, dir: quickDir, filename, text, } = (0, command_arg_projection_cjs_1.parseNamedArgs)(args, [
1298
- 'category', 'milestone', 'at',
1299
- 'phase', 'file', 'archived-milestone',
1300
- 'slug', 'seed-id', 'dir', 'filename', 'text',
1301
- ]);
1376
+ // args already has the family + subcommand tokens stripped by the caller
1377
+ // (audit-command-router.cts:147 passes `hubArgs.slice(2)`), so validation
1378
+ // begins at index 0 — there is no positional this handler owns itself.
1379
+ const { category, milestone, at: atFlag, phase, file, 'archived-milestone': archivedMilestone, slug, 'seed-id': seedId, dir: quickDir, filename, text, } = (0, command_arg_projection_cjs_1.parseNamedArgsOrExit)(args, {
1380
+ valueFlags: [
1381
+ 'category', 'milestone', 'at',
1382
+ 'phase', 'file', 'archived-milestone',
1383
+ 'slug', 'seed-id', 'dir', 'filename', 'text',
1384
+ ],
1385
+ positionals: 0,
1386
+ }, ioError);
1302
1387
  if (!category)
1303
1388
  ioError('--category is required');
1304
1389
  if (!milestone)
1305
1390
  ioError('--milestone is required');
1391
+ // All declared flags above are value flags, so each resolves to `string |
1392
+ // null` at runtime; the cast narrows away the `boolean` arm of
1393
+ // ParsedNamedArgs's value type that this call site never produces.
1306
1394
  const at = atFlag || new Date().toISOString().slice(0, 10);
1307
1395
  const planDir = planningDir(cwd);
1308
1396
  const markerBase = { milestone: milestone, at };
1397
+ // #3078-CR MEDIUM 2: every `fs.readFileSync` in this function (below, and
1398
+ // in the flat-category branch further down) is DELIBERATELY left raw,
1399
+ // unlike `auditOpenArtifacts`'s scan reads (which now route through
1400
+ // `normalizeLineEndings`). This function splices frontmatter into the
1401
+ // EXISTING content and writes the result back via `platformWriteSync` /
1402
+ // `uat.acknowledgeDeferredItem` — both `spliceFrontmatter` and
1403
+ // `acknowledgeDeferredItem` locate and rewrite a specific byte span
1404
+ // (frontmatter block / matched deferred-item text) in the file exactly as
1405
+ // it exists on disk. Normalizing first would rewrite the file's line
1406
+ // endings as a side effect of an unrelated acknowledge operation, and a
1407
+ // splice computed against normalized text can land at the wrong offset
1408
+ // when written back over the RAW (un-normalized) original. The snapshot
1409
+ // VALUE computed below IS normalized (on a separate in-memory copy, never
1410
+ // the spliced one) so it agrees with the scanner's frame — see the comment
1411
+ // at `normalizedContent` further down.
1309
1412
  // ── The four phase-scoped categories: --phase --file [--archived-milestone] ──
1310
1413
  const PHASE_SCOPED = new Set(['uat_gaps', 'verification_gaps', 'context_questions', 'deferred_items']);
1311
1414
  if (PHASE_SCOPED.has(category)) {
@@ -1335,7 +1438,10 @@ function cmdAuditAcknowledge(cwd, args, raw) {
1335
1438
  if (result.status === 'already_resolved')
1336
1439
  ioError(`deferred item is already "status: resolved" — acknowledging a resolved item is a no-op`);
1337
1440
  if (result.status === 'unsupported_heading_shape') {
1338
- ioError('this deferred-items.md uses the heading-delimited (#3457) entry shape, which the CLI writer does not yet support — edit the file directly');
1441
+ // #3781: heading-shaped entries are supported; the remaining refusal
1442
+ // cause is a GFM table row embedded in the entry's span (non-contiguous
1443
+ // — a write cannot be anchored safely).
1444
+ ioError('this deferred item\'s span embeds a GFM table row, so the CLI writer cannot anchor a safe write to it — edit the file directly');
1339
1445
  }
1340
1446
  if (result.status === 'match_verification_failed') {
1341
1447
  ioError(`internal error: matched span for --text "${text}" did not re-verify before write — refused rather than risk writing the wrong entry`);
@@ -1346,6 +1452,15 @@ function cmdAuditAcknowledge(cwd, args, raw) {
1346
1452
  }
1347
1453
  const content = node_fs_1.default.readFileSync(safeFilePath, 'utf-8');
1348
1454
  const fm = extractFrontmatter(content, safeFilePath);
1455
+ // Mixed-frame fix (security review, 4th instance on this branch): the
1456
+ // splice above and below stays keyed to RAW `content` (raw byte offsets
1457
+ // must not shift), but `scanUatGaps`/`scanContextQuestions` now derive
1458
+ // their comparison values from `normalizeLineEndings`d content. Deriving
1459
+ // the snapshot here from raw `content` would make a lone-CR file's
1460
+ // stored value permanently disagree with what the scanner recomputes —
1461
+ // `audit acknowledge` would be a silent no-op for lone-CR artifacts. Feed
1462
+ // the derive functions a normalized COPY; never splice from it.
1463
+ const normalizedContent = normalizeLineEndings(content);
1349
1464
  let snapshotKey;
1350
1465
  let currentValue;
1351
1466
  if (category === 'uat_gaps') {
@@ -1353,7 +1468,7 @@ function cmdAuditAcknowledge(cwd, args, raw) {
1353
1468
  // pending scenarios added under the same status — snapshot the
1354
1469
  // composite `deriveUatGapSnapshotValue` instead (see its doc comment).
1355
1470
  snapshotKey = 'gap_snapshot';
1356
- currentValue = deriveUatGapSnapshotValue((fm.status || 'unknown').toLowerCase(), content);
1471
+ currentValue = deriveUatGapSnapshotValue((fm.status || 'unknown').toLowerCase(), normalizedContent);
1357
1472
  }
1358
1473
  else if (category === 'verification_gaps') {
1359
1474
  snapshotKey = 'status';
@@ -1364,7 +1479,7 @@ function cmdAuditAcknowledge(cwd, args, raw) {
1364
1479
  // question set, not just its count (see `deriveOpenQuestionsDigest`'s
1365
1480
  // doc comment).
1366
1481
  snapshotKey = 'questions_digest';
1367
- currentValue = deriveOpenQuestionsDigest(deriveOpenQuestions(content, fm));
1482
+ currentValue = deriveOpenQuestionsDigest(deriveOpenQuestions(normalizedContent, fm));
1368
1483
  }
1369
1484
  fm.audit_acknowledged = { ...markerBase, [snapshotKey]: currentValue };
1370
1485
  const newContent = spliceFrontmatter(content, fm);
@@ -1465,6 +1580,13 @@ module.exports = {
1465
1580
  formatAuditReport,
1466
1581
  listAuditPhaseTargets,
1467
1582
  cmdAuditAcknowledge,
1583
+ // #3805: exported so uat.cts's cmdAuditUat routes the SAME artifacts'
1584
+ // suppression through the ONE predicate instead of hand-rolling a tenth
1585
+ // copy outside this file's visibility (the exact defect class the
1586
+ // predicate's own header warns about). The snapshot derivations ride
1587
+ // along so the snapshotKeys cannot drift between the two consumers.
1588
+ isAuditItemAcknowledged,
1589
+ deriveUatGapSnapshotValue,
1468
1590
  // #2142: exported so src/milestone.cts's archiveQuickTaskDirectories README
1469
1591
  // index generator shares this ONE discovery rule rather than re-deriving it.
1470
1592
  resolveQuickTaskSummaryFile,