@opengsd/gsd-core 1.9.1 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (426) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -3
  3. package/.opencode/plugins/gsd-core.js +8 -1
  4. package/agents/gsd-code-fixer.md +27 -3
  5. package/agents/gsd-debug-session-manager.md +11 -0
  6. package/agents/gsd-debugger.md +12 -246
  7. package/agents/gsd-doc-synthesizer.md +2 -4
  8. package/agents/gsd-executor.md +12 -10
  9. package/agents/gsd-integration-checker.md +3 -0
  10. package/agents/gsd-mempalace-curator.md +5 -2
  11. package/agents/gsd-phase-researcher.md +20 -1
  12. package/agents/gsd-plan-checker.md +46 -0
  13. package/agents/gsd-planner.md +49 -54
  14. package/agents/gsd-roadmapper.md +21 -3
  15. package/agents/gsd-user-profiler.md +3 -0
  16. package/agents/gsd-verifier.md +26 -73
  17. package/bin/install.js +1272 -1238
  18. package/bin/lib/ui-safety-gate.cjs +2 -0
  19. package/commands/gsd/code-review.md +1 -1
  20. package/commands/gsd/execute-phase.md +1 -1
  21. package/commands/gsd/map-codebase.md +1 -1
  22. package/commands/gsd/mempalace-capture.md +2 -2
  23. package/commands/gsd/mempalace-recall.md +1 -1
  24. package/commands/gsd/new-milestone.md +2 -2
  25. package/commands/gsd/plan-phase.md +1 -1
  26. package/commands/gsd/quick.md +1 -1
  27. package/commands/gsd/review-backlog.md +2 -1
  28. package/commands/gsd/verify-work.md +1 -1
  29. package/gsd-core/bin/gsd-tools.cjs +1009 -115
  30. package/gsd-core/bin/lib/active-workstream-store.cjs +153 -12
  31. package/gsd-core/bin/lib/agent-install-check.cjs +268 -38
  32. package/gsd-core/bin/lib/api-coverage.cjs +123 -5
  33. package/gsd-core/bin/lib/artifacts.cjs +3 -0
  34. package/gsd-core/bin/lib/assumption-delta.cjs +2 -4
  35. package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
  36. package/gsd-core/bin/lib/audit.cjs +926 -202
  37. package/gsd-core/bin/lib/broken-windows.cjs +36 -6
  38. package/gsd-core/bin/lib/capability-consent.cjs +149 -15
  39. package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
  40. package/gsd-core/bin/lib/capability-registry.cjs +608 -148
  41. package/gsd-core/bin/lib/capability-source.cjs +92 -0
  42. package/gsd-core/bin/lib/capability-trust.cjs +444 -25
  43. package/gsd-core/bin/lib/capability-validator.cjs +507 -24
  44. package/gsd-core/bin/lib/capability-writer.cjs +3 -2
  45. package/gsd-core/bin/lib/check-command-router.cjs +114 -38
  46. package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
  47. package/gsd-core/bin/lib/codex-agent-toml.cjs +329 -0
  48. package/gsd-core/bin/lib/command-aliases.cjs +94 -0
  49. package/gsd-core/bin/lib/command-roster.cjs +44 -1
  50. package/gsd-core/bin/lib/commands.cjs +665 -99
  51. package/gsd-core/bin/lib/commonjs-marker.cjs +142 -0
  52. package/gsd-core/bin/lib/complexity-trigger.cjs +1172 -0
  53. package/gsd-core/bin/lib/config-loader.cjs +76 -0
  54. package/gsd-core/bin/lib/config.cjs +22 -2
  55. package/gsd-core/bin/lib/context-composer.cjs +278 -0
  56. package/gsd-core/bin/lib/context-predicates.cjs +506 -0
  57. package/gsd-core/bin/lib/core-utils.cjs +217 -40
  58. package/gsd-core/bin/lib/decisions.cjs +23 -0
  59. package/gsd-core/bin/lib/docs.cjs +3 -2
  60. package/gsd-core/bin/lib/external-job.cjs +19 -4
  61. package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
  62. package/gsd-core/bin/lib/frontmatter.cjs +239 -32
  63. package/gsd-core/bin/lib/gap-checker.cjs +68 -7
  64. package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +57 -6
  65. package/gsd-core/bin/lib/git-base-branch.cjs +160 -15
  66. package/gsd-core/bin/lib/graphify.cjs +142 -27
  67. package/gsd-core/bin/lib/gsd2-import.cjs +37 -5
  68. package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
  69. package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
  70. package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +145 -0
  71. package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
  72. package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
  73. package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
  74. package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +265 -0
  75. package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
  76. package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
  77. package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +173 -0
  78. package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
  79. package/gsd-core/bin/lib/health-diagnostic.cjs +431 -0
  80. package/gsd-core/bin/lib/host-integration.cjs +13 -1
  81. package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
  82. package/gsd-core/bin/lib/init-command-router.cjs +83 -8
  83. package/gsd-core/bin/lib/init.cjs +1325 -169
  84. package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
  85. package/gsd-core/bin/lib/install-engine.cjs +805 -264
  86. package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
  87. package/gsd-core/bin/lib/install-model-override-resolver.cjs +203 -0
  88. package/gsd-core/bin/lib/install-profiles.cjs +160 -57
  89. package/gsd-core/bin/lib/install-scope.cjs +270 -0
  90. package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
  91. package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
  92. package/gsd-core/bin/lib/installer-migration-authoring.cjs +3 -1
  93. package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
  94. package/gsd-core/bin/lib/installer-migrations/007-retire-config-root-commonjs-marker.cjs +149 -0
  95. package/gsd-core/bin/lib/installer-migrations/008-cursor-retire-commands-surface.cjs +55 -0
  96. package/gsd-core/bin/lib/installer-migrations/009-pi-retire-reserved-hooks-dir.cjs +199 -0
  97. package/gsd-core/bin/lib/installer-migrations.cjs +206 -13
  98. package/gsd-core/bin/lib/io.cjs +38 -3
  99. package/gsd-core/bin/lib/markdown-sectionizer.cjs +8 -1
  100. package/gsd-core/bin/lib/markdown-table.cjs +133 -20
  101. package/gsd-core/bin/lib/mcp-catalog.cjs +518 -0
  102. package/gsd-core/bin/lib/mcp-server.cjs +135 -3
  103. package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
  104. package/gsd-core/bin/lib/milestone.cjs +821 -109
  105. package/gsd-core/bin/lib/model-catalog.cjs +59 -1
  106. package/gsd-core/bin/lib/model-resolver.cjs +183 -40
  107. package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
  108. package/gsd-core/bin/lib/pattern.cjs +122 -0
  109. package/gsd-core/bin/lib/phase-estimation.cjs +1 -1
  110. package/gsd-core/bin/lib/phase-id.cjs +507 -36
  111. package/gsd-core/bin/lib/phase-lifecycle.cjs +28 -3
  112. package/gsd-core/bin/lib/phase-locator.cjs +258 -58
  113. package/gsd-core/bin/lib/phase.cjs +891 -156
  114. package/gsd-core/bin/lib/plan-dependency-graph.cjs +303 -0
  115. package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
  116. package/gsd-core/bin/lib/plan-scan.cjs +86 -2
  117. package/gsd-core/bin/lib/planning-scope.cjs +31 -0
  118. package/gsd-core/bin/lib/planning-snapshot.cjs +890 -0
  119. package/gsd-core/bin/lib/planning-workspace.cjs +60 -6
  120. package/gsd-core/bin/lib/probe-core.cjs +1 -1
  121. package/gsd-core/bin/lib/profile-output.cjs +1 -1
  122. package/gsd-core/bin/lib/prompt-budget.cjs +128 -165
  123. package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +740 -0
  124. package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +85 -0
  125. package/gsd-core/bin/lib/review-lane-descriptor.cjs +108 -0
  126. package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
  127. package/gsd-core/bin/lib/review-lane-runner.cjs +447 -68
  128. package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
  129. package/gsd-core/bin/lib/roadmap-command-router.cjs +76 -9
  130. package/gsd-core/bin/lib/roadmap-parser.cjs +1035 -194
  131. package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
  132. package/gsd-core/bin/lib/roadmap.cjs +405 -84
  133. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +795 -100
  134. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
  135. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +440 -57
  136. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
  137. package/gsd-core/bin/lib/runtime-homes.cjs +220 -41
  138. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +220 -44
  139. package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
  140. package/gsd-core/bin/lib/runtime-slash.cjs +27 -9
  141. package/gsd-core/bin/lib/section-manifest.cjs +209 -0
  142. package/gsd-core/bin/lib/security.cjs +104 -5
  143. package/gsd-core/bin/lib/shell-command-projection.cjs +388 -30
  144. package/gsd-core/bin/lib/smart-entry.cjs +154 -22
  145. package/gsd-core/bin/lib/state-command-router.cjs +5 -1
  146. package/gsd-core/bin/lib/state-document.cjs +152 -8
  147. package/gsd-core/bin/lib/state-transition.cjs +424 -105
  148. package/gsd-core/bin/lib/state.cjs +1927 -401
  149. package/gsd-core/bin/lib/surface.cjs +35 -10
  150. package/gsd-core/bin/lib/text-lines.cjs +80 -0
  151. package/gsd-core/bin/lib/token-scanner.cjs +76 -0
  152. package/gsd-core/bin/lib/uat-predicate.cjs +20 -4
  153. package/gsd-core/bin/lib/uat.cjs +706 -64
  154. package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
  155. package/gsd-core/bin/lib/ui-safety-gate.cjs +14 -5
  156. package/gsd-core/bin/lib/unusable-input.cjs +33 -0
  157. package/gsd-core/bin/lib/update-context.cjs +8 -2
  158. package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
  159. package/gsd-core/bin/lib/validate.cjs +20 -6
  160. package/gsd-core/bin/lib/vendor/README.md +37 -0
  161. package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
  162. package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
  163. package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
  164. package/gsd-core/bin/lib/verification.cjs +287 -20
  165. package/gsd-core/bin/lib/verify.cjs +368 -880
  166. package/gsd-core/bin/lib/workflow-fragments.cjs +557 -0
  167. package/gsd-core/bin/lib/workstream-inventory-builder.cjs +203 -19
  168. package/gsd-core/bin/lib/workstream-inventory.cjs +576 -31
  169. package/gsd-core/bin/lib/workstream.cjs +8 -2
  170. package/gsd-core/bin/lib/worktree-base-ref.cjs +50 -6
  171. package/gsd-core/bin/lib/worktree-safety.cjs +450 -125
  172. package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
  173. package/gsd-core/bin/shared/config-schema.manifest.json +9 -1
  174. package/gsd-core/references/agent-contracts.md +43 -26
  175. package/gsd-core/references/artifact-types.md +10 -3
  176. package/gsd-core/references/autonomous-ui-design-contract.md +42 -0
  177. package/gsd-core/references/checkpoints.md +2 -2
  178. package/gsd-core/references/context-budget.md +1 -1
  179. package/gsd-core/references/debugger-techniques.md +255 -0
  180. package/gsd-core/references/dispatch-isolation-gate.md +138 -0
  181. package/gsd-core/references/doc-conflict-engine.md +1 -1
  182. package/gsd-core/references/execute-mvp-tdd.md +3 -3
  183. package/gsd-core/references/execute-phase-between-wave-reset.md +6 -2
  184. package/gsd-core/references/execute-phase-context-guard.md +1 -1
  185. package/gsd-core/references/execute-phase-response-language.md +1 -1
  186. package/gsd-core/references/execute-phase-wave-guard.md +6 -2
  187. package/gsd-core/references/gate-prompts.md +1 -1
  188. package/gsd-core/references/git-planning-commit.md +2 -1
  189. package/gsd-core/references/loop-hook-dispatch.md +39 -2
  190. package/gsd-core/references/model-profiles.md +12 -4
  191. package/gsd-core/references/mvp-concepts.md +9 -9
  192. package/gsd-core/references/planner-guidance.md +3 -9
  193. package/gsd-core/references/planner-preconditions.md +1 -1
  194. package/gsd-core/references/planner-reviews.md +1 -1
  195. package/gsd-core/references/planning-config.md +8 -6
  196. package/gsd-core/references/research-documentation-lookup.md +5 -3
  197. package/gsd-core/references/revision-loop.md +1 -1
  198. package/gsd-core/references/specless-probe-fallback.md +8 -7
  199. package/gsd-core/references/universal-anti-patterns.md +3 -3
  200. package/gsd-core/references/verifier-phase-gates.md +192 -0
  201. package/gsd-core/references/verifier-wiring-patterns.md +100 -0
  202. package/gsd-core/references/verify-mvp-mode.md +1 -1
  203. package/gsd-core/references/workstream-flag.md +22 -6
  204. package/gsd-core/references/worktree-branch-check.md +2 -2
  205. package/gsd-core/templates/discussion-log.md +1 -1
  206. package/gsd-core/templates/phase-prompt.md +2 -4
  207. package/gsd-core/templates/state.md +4 -4
  208. package/gsd-core/templates/summary-complex.md +2 -0
  209. package/gsd-core/templates/summary-minimal.md +2 -0
  210. package/gsd-core/templates/summary-standard.md +2 -0
  211. package/gsd-core/templates/summary.md +2 -0
  212. package/gsd-core/templates/verification-report.md +9 -1
  213. package/gsd-core/workflows/ai-integration-phase.md +9 -11
  214. package/gsd-core/workflows/audit-milestone.md +3 -0
  215. package/gsd-core/workflows/autonomous/steps/converge-banner.md +1 -0
  216. package/gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md +11 -0
  217. package/gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md +7 -0
  218. package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +21 -0
  219. package/gsd-core/workflows/autonomous/steps/converge-loop.md +7 -0
  220. package/gsd-core/workflows/autonomous.md +33 -70
  221. package/gsd-core/workflows/cleanup.md +62 -3
  222. package/gsd-core/workflows/code-review/steps/dispatch-fix.md +39 -0
  223. package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +93 -0
  224. package/gsd-core/workflows/code-review-fix.md +37 -10
  225. package/gsd-core/workflows/code-review.md +74 -166
  226. package/gsd-core/workflows/complete-milestone/steps/git-tag.md +29 -0
  227. package/gsd-core/workflows/complete-milestone.md +160 -95
  228. package/gsd-core/workflows/debug.md +16 -17
  229. package/gsd-core/workflows/diagnose-issues.md +56 -8
  230. package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -1
  231. package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
  232. package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +15 -0
  233. package/gsd-core/workflows/discuss-phase-assumptions.md +7 -17
  234. package/gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md +51 -0
  235. package/gsd-core/workflows/docs-update.md +8 -51
  236. package/gsd-core/workflows/edit-phase.md +26 -1
  237. package/gsd-core/workflows/eval-review.md +3 -5
  238. package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +64 -7
  239. package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +50 -0
  240. package/gsd-core/workflows/execute-phase/steps/partial-wave.md +31 -0
  241. package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
  242. package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +21 -0
  243. package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +42 -0
  244. package/gsd-core/workflows/execute-phase/steps/regression-gate.md +43 -37
  245. package/gsd-core/workflows/execute-phase.md +103 -187
  246. package/gsd-core/workflows/execute-plan.md +36 -4
  247. package/gsd-core/workflows/explore.md +131 -4
  248. package/gsd-core/workflows/fast.md +10 -2
  249. package/gsd-core/workflows/health.md +73 -4
  250. package/gsd-core/workflows/help/modes/full.md +6 -1
  251. package/gsd-core/workflows/import.md +4 -4
  252. package/gsd-core/workflows/ingest-docs.md +7 -6
  253. package/gsd-core/workflows/mvp-phase.md +6 -3
  254. package/gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md +16 -0
  255. package/gsd-core/workflows/new-milestone/steps/reset-phase-safety.md +19 -0
  256. package/gsd-core/workflows/new-milestone.md +35 -47
  257. package/gsd-core/workflows/new-project/steps/auto-mode-config.md +176 -0
  258. package/gsd-core/workflows/new-project/steps/auto-mode-detection.md +32 -0
  259. package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +18 -0
  260. package/gsd-core/workflows/new-project.md +27 -240
  261. package/gsd-core/workflows/next.md +12 -0
  262. package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +15 -0
  263. package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +110 -0
  264. package/gsd-core/workflows/plan-phase/steps/prd-express-gate.md +8 -0
  265. package/gsd-core/workflows/plan-phase/steps/research-only-early-exit.md +17 -0
  266. package/gsd-core/workflows/plan-phase/steps/research-only-modifiers.md +16 -0
  267. package/gsd-core/workflows/plan-phase/steps/reviews-prerequisite.md +17 -0
  268. package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +149 -0
  269. package/gsd-core/workflows/plan-phase.md +89 -209
  270. package/gsd-core/workflows/plan-review-convergence.md +50 -2
  271. package/gsd-core/workflows/progress/steps/forensic-audit.md +125 -0
  272. package/gsd-core/workflows/progress/steps/mvp-display.md +18 -0
  273. package/gsd-core/workflows/progress.md +45 -159
  274. package/gsd-core/workflows/quick/steps/discussion-phase.md +124 -0
  275. package/gsd-core/workflows/quick/steps/plan-checker-loop.md +111 -0
  276. package/gsd-core/workflows/quick/steps/quick-verification.md +67 -0
  277. package/gsd-core/workflows/quick/steps/research-phase.md +72 -0
  278. package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +37 -0
  279. package/gsd-core/workflows/quick.md +55 -405
  280. package/gsd-core/workflows/resume-project.md +3 -0
  281. package/gsd-core/workflows/review/steps/reviewer-instances-note-1.md +4 -0
  282. package/gsd-core/workflows/review/steps/reviewer-instances-note-2.md +3 -0
  283. package/gsd-core/workflows/review.md +41 -13
  284. package/gsd-core/workflows/section-manifest.json +219 -0
  285. package/gsd-core/workflows/secure-phase.md +1 -1
  286. package/gsd-core/workflows/session-report.md +2 -1
  287. package/gsd-core/workflows/settings.md +66 -2
  288. package/gsd-core/workflows/ship.md +104 -44
  289. package/gsd-core/workflows/sketch.md +1 -1
  290. package/gsd-core/workflows/spec-phase.md +41 -20
  291. package/gsd-core/workflows/spike-wrap-up.md +20 -5
  292. package/gsd-core/workflows/spike.md +50 -16
  293. package/gsd-core/workflows/sync-skills.md +106 -13
  294. package/gsd-core/workflows/transition/steps/workstream-collision-check.md +17 -0
  295. package/gsd-core/workflows/transition.md +53 -31
  296. package/gsd-core/workflows/ui-phase.md +13 -12
  297. package/gsd-core/workflows/ui-review.md +2 -2
  298. package/gsd-core/workflows/update/steps/channel-banner.md +7 -0
  299. package/gsd-core/workflows/update.md +19 -8
  300. package/gsd-core/workflows/validate-phase.md +1 -1
  301. package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +36 -0
  302. package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +21 -0
  303. package/gsd-core/workflows/verify-work.md +17 -65
  304. package/hooks/dist/gsd-agent-isolation-guard.js +517 -0
  305. package/hooks/dist/gsd-check-update-worker.js +64 -12
  306. package/hooks/dist/gsd-check-update.js +19 -1
  307. package/hooks/dist/gsd-cursor-pre-tool.js +0 -3
  308. package/hooks/dist/gsd-cursor-subagent-start.js +607 -26
  309. package/hooks/dist/gsd-cursor-subagent-stop.js +3 -2
  310. package/hooks/dist/gsd-prompt-guard.js +21 -20
  311. package/hooks/dist/gsd-read-injection-scanner.js +45 -24
  312. package/hooks/dist/gsd-statusline.js +90 -6
  313. package/hooks/dist/gsd-update-banner.js +22 -1
  314. package/hooks/dist/gsd-workflow-guard.js +134 -36
  315. package/hooks/dist/gsd-worktree-path-guard.js +2 -1
  316. package/hooks/dist/gsd-write-guard.js +359 -0
  317. package/hooks/dist/lib/git-cmd.js +92 -59
  318. package/hooks/dist/lib/injection-patterns.js +45 -0
  319. package/hooks/dist/lib/isolation-deny-reason.js +39 -0
  320. package/hooks/dist/lib/isolation-sentinel.js +277 -0
  321. package/hooks/dist/managed-hooks-registry.cjs +2 -0
  322. package/hooks/gsd-agent-isolation-guard.js +517 -0
  323. package/hooks/gsd-check-update-worker.js +64 -12
  324. package/hooks/gsd-check-update.js +19 -1
  325. package/hooks/gsd-cursor-pre-tool.js +0 -3
  326. package/hooks/gsd-cursor-subagent-start.js +607 -26
  327. package/hooks/gsd-cursor-subagent-stop.js +3 -2
  328. package/hooks/gsd-prompt-guard.js +21 -20
  329. package/hooks/gsd-read-injection-scanner.js +45 -24
  330. package/hooks/gsd-statusline.js +90 -6
  331. package/hooks/gsd-update-banner.js +22 -1
  332. package/hooks/gsd-workflow-guard.js +134 -36
  333. package/hooks/gsd-worktree-path-guard.js +2 -1
  334. package/hooks/gsd-write-guard.js +359 -0
  335. package/hooks/hooks.json +12 -0
  336. package/hooks/lib/git-cmd.js +92 -59
  337. package/hooks/lib/injection-patterns.js +45 -0
  338. package/hooks/lib/isolation-deny-reason.js +39 -0
  339. package/hooks/lib/isolation-sentinel.js +277 -0
  340. package/hooks/managed-hooks-registry.cjs +2 -0
  341. package/package.json +31 -10
  342. package/pi/gsd.cjs +71 -12
  343. package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
  344. package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
  345. package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
  346. package/scripts/build-hooks.js +9 -0
  347. package/scripts/changeset/lint.cjs +68 -6
  348. package/scripts/changeset/serialize.cjs +5 -1
  349. package/scripts/check-alias-drift.cjs +7 -43
  350. package/scripts/check-contract-drift.cjs +297 -0
  351. package/scripts/ci-test-scope.cjs +19 -2
  352. package/scripts/command-contract-helpers.cjs +903 -1
  353. package/scripts/gen-adr-index.cjs +728 -38
  354. package/scripts/gen-capability-matrix.cjs +1 -1
  355. package/scripts/gen-capability-registry.cjs +3 -15
  356. package/scripts/gen-context-index.cjs +439 -0
  357. package/scripts/gen-health-docs.cjs +390 -0
  358. package/scripts/gen-inventory-manifest.cjs +150 -4
  359. package/scripts/gen-loop-host-contract.cjs +4 -24
  360. package/scripts/gen-prompt-budget-parity-corpus.cjs +645 -0
  361. package/scripts/gen-registry.cjs +3 -14
  362. package/scripts/gen-section-manifest.cjs +638 -0
  363. package/scripts/generate-package-identity.cjs +4 -2
  364. package/scripts/lib/alias-drift-families.cjs +46 -0
  365. package/scripts/lib/drift-scan.cjs +278 -0
  366. package/scripts/lint-allow-test-rule-refs.allowlist.json +15 -54
  367. package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
  368. package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
  369. package/scripts/lint-canary-version-leak.cjs +73 -0
  370. package/scripts/lint-command-contract.cjs +96 -13
  371. package/scripts/lint-compiled-artifact-sync.cjs +6 -1
  372. package/scripts/lint-completion-predicate-drift.cjs +933 -0
  373. package/scripts/lint-completion-ratio-drift.cjs +214 -0
  374. package/scripts/lint-default-flip-documentation.cjs +193 -0
  375. package/scripts/lint-docs-command-form.cjs +195 -0
  376. package/scripts/lint-docs-required.cjs +9 -1
  377. package/scripts/lint-emitted-drift-ack.cjs +215 -20
  378. package/scripts/lint-eslint-glob-coverage.allowlist.json +34 -0
  379. package/scripts/lint-eslint-glob-coverage.cjs +340 -0
  380. package/scripts/lint-example-parser-parity.cjs +395 -0
  381. package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
  382. package/scripts/lint-health-diagnostic-rule-table.cjs +404 -0
  383. package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
  384. package/scripts/lint-milestone-window-drift.cjs +468 -0
  385. package/scripts/lint-phase-enumeration-drift.cjs +479 -0
  386. package/scripts/lint-plan-count-drift.cjs +318 -0
  387. package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
  388. package/scripts/lint-planning-prompt-drift.cjs +434 -0
  389. package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
  390. package/scripts/lint-regression-test-names.cjs +15 -13
  391. package/scripts/lint-removed-but-needed.cjs +320 -0
  392. package/scripts/lint-state-field-drift.cjs +805 -0
  393. package/scripts/lint-state-write-path-drift.cjs +1045 -0
  394. package/scripts/lint-test-file-count.allowlist.json +40 -3
  395. package/scripts/lint-unreachable-guard-drift.cjs +843 -0
  396. package/scripts/lint-vendored-deps.cjs +124 -0
  397. package/scripts/mutation-matrix.cjs +13 -0
  398. package/scripts/pr-changed-files.cjs +63 -0
  399. package/scripts/pr-template-policy.cjs +14 -4
  400. package/scripts/prompt-injection-scan.sh +52 -6
  401. package/scripts/require-issue-link-policy.cjs +192 -0
  402. package/scripts/state-write-path-drift-baseline.json +19 -0
  403. package/scripts/sync-runtime-launcher.cjs +2 -4
  404. package/skills/gsd-autonomous/SKILL.md +0 -1
  405. package/skills/gsd-code-review/SKILL.md +1 -1
  406. package/skills/gsd-execute-phase/SKILL.md +1 -2
  407. package/skills/gsd-map-codebase/SKILL.md +1 -1
  408. package/skills/gsd-mempalace-capture/SKILL.md +2 -2
  409. package/skills/gsd-mempalace-recall/SKILL.md +1 -1
  410. package/skills/gsd-new-milestone/SKILL.md +2 -2
  411. package/skills/gsd-next/SKILL.md +0 -1
  412. package/skills/gsd-plan-phase/SKILL.md +1 -2
  413. package/skills/gsd-progress/SKILL.md +0 -1
  414. package/skills/gsd-quick/SKILL.md +1 -1
  415. package/skills/gsd-review-backlog/SKILL.md +2 -1
  416. package/skills/gsd-stats/SKILL.md +0 -1
  417. package/skills/gsd-verify-work/SKILL.md +1 -1
  418. package/vscode/package.json +1 -1
  419. package/gsd-core/workflows/discovery-phase.md +0 -298
  420. package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
  421. package/gsd-core/workflows/verify-phase.md +0 -577
  422. package/scripts/affected-tests-lib.cjs +0 -554
  423. package/scripts/gen-emitted-baseline.cjs +0 -145
  424. package/scripts/lint-allow-test-rule-refs.cjs +0 -162
  425. package/scripts/run-affected-tests.cjs +0 -7
  426. package/scripts/run-tests.cjs +0 -1050
@@ -8,12 +8,12 @@
8
8
  * boundary moved. The core.cjs re-export spine was retired in epic #1267;
9
9
  * callers import phase-id helpers from phase-id.cjs directly.
10
10
  *
11
- * Dependencies: none (pure string/regex, no Node built-ins required).
11
+ * Dependencies:
12
+ * - ./pattern.cjs (escapeRegex — #3212 Phase 1 seam; this module is no
13
+ * longer the owner of pattern-escaping, only a consumer)
12
14
  */
15
+ const pattern_cjs_1 = require("./pattern.cjs");
13
16
  // ─── Phase-id helpers ─────────────────────────────────────────────────────────
14
- function escapeRegex(value) {
15
- return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
16
- }
17
17
  // project_code values start with an uppercase letter (e.g. PROJ, APP_CODE);
18
18
  // leading underscores are not valid project codes per .planning/config.json.
19
19
  const PROJECT_CODE_PREFIX_STRIP_RE = /^[A-Z][A-Z0-9_]*-(?=\d)/;
@@ -48,6 +48,22 @@ const OPTIONAL_PHASE_TAG_SOURCE = '(?:\\s*\\([^)\\n]{0,200}\\))?';
48
48
  // (scripts/lint-phase-id-drift.cjs) fails CI if a literal re-derivation is
49
49
  // introduced outside this module without a `// phase-id-owner:` justification.
50
50
  const PHASE_NUMBER_TOKEN_SOURCE = '\\d+[A-Z]?(?:\\.\\d+)*';
51
+ // #2528 review: the CASE-FLEXIBLE renderings of the two sources above, for call
52
+ // sites that scan directory names (where a project code or a variant suffix may
53
+ // legitimately be lowercase) and therefore cannot use a case-sensitive class.
54
+ //
55
+ // They live HERE, beside the sources they widen, because the alternative in use
56
+ // was `SOURCE.replaceAll('A-Z', 'A-Za-z')` at the consuming site — a derivation
57
+ // that depends on the owner rendering that exact literal. It passes
58
+ // lint-phase-id-drift.cjs (no literal copy of the grammar), but the day this
59
+ // module expresses the same class any other way (`[[:upper:]]`, a named
60
+ // fragment, an escaped range) the replaceAll silently no-ops and the consumer
61
+ // quietly narrows to uppercase-only — the failure is a NON-match, so nothing
62
+ // throws and no test that only feeds uppercase input notices. Deriving it once,
63
+ // where the source is defined, makes that impossible: a rename here is a
64
+ // compile-visible change, not a silent behavior change three modules away.
65
+ const CASE_FLEXIBLE_PROJECT_CODE_PREFIX_SOURCE = OPTIONAL_PROJECT_CODE_PREFIX_SOURCE.replaceAll('A-Z', 'A-Za-z');
66
+ const CASE_FLEXIBLE_PHASE_NUMBER_TOKEN_SOURCE = PHASE_NUMBER_TOKEN_SOURCE.replaceAll('A-Z', 'A-Za-z');
51
67
  // #2232: the canonical CONTINUATION-segment grammar — a dash-separated segment
52
68
  // that extends a phase token (a zero-padded sub-phase or plan number, e.g. the
53
69
  // "01" in "02-01-setup"). getPhaseDirFromPhaseId writes these zero-padded to
@@ -59,7 +75,8 @@ const PHASE_NUMBER_TOKEN_SOURCE = '\\d+[A-Z]?(?:\\.\\d+)*';
59
75
  // trailing grammar (letter suffixes, dotted sub-phases, segment boundaries).
60
76
  // POLICY (locked by boundary tests): sub-phase/plan numbers ≥100 are out of the
61
77
  // dir-token grammar — the LEADING phase number stays unbounded (`\d+`), only
62
- // continuation segments are width-capped. Shared from here so the five #2043
78
+ // continuation segments begin with a two-digit run; consuming sites retain
79
+ // their established suffix and boundary grammar. Shared from here so the five #2043
63
80
  // call sites cannot drift independently (see scripts/lint-phase-id-drift.cjs).
64
81
  const PHASE_CONTINUATION_SEGMENT_SOURCE = '\\d{2}(?!\\d)';
65
82
  const PHASE_CONTINUATION_SEGMENT_PREFIX_RE = new RegExp(`^${PHASE_CONTINUATION_SEGMENT_SOURCE}`);
@@ -124,7 +141,8 @@ const BRACKET_CANONICAL_NUMERIC_SOURCE = '(?:[1-9]\\d{2,}|\\d{2})';
124
141
  const BRACKET_PHASE_TOKEN_SOURCE = `\\d+[A-Z]?` +
125
142
  `(?:-${BRACKET_CANONICAL_NUMERIC_SOURCE}(?!\\d))?` +
126
143
  `(?:\\.${BRACKET_CANONICAL_NUMERIC_SOURCE}(?!\\d))?` +
127
- `(?:-${PHASE_CONTINUATION_SEGMENT_SOURCE})?`;
144
+ `(?:-${PHASE_CONTINUATION_SEGMENT_SOURCE})?` +
145
+ `(?=-|$)`;
128
146
  // A phase HEADING intro under bracket is either a `[...]` bracket (optionally
129
147
  // followed by a `Phase ` label) or a bare `Phase ` label; a bare number is NOT
130
148
  // a phase-heading intro. The `[^\]]{1,200}` bound mirrors the existing
@@ -355,20 +373,24 @@ function phaseMarkdownRegexSource(phaseNum) {
355
373
  const subParts = milestoneSegments[2].slice(1).split('-');
356
374
  const subFragments = subParts.map(s => {
357
375
  const unpadded = s.replace(/^0+/, '') || '0';
358
- return `0*${escapeRegex(unpadded)}`;
376
+ return `0*${(0, pattern_cjs_1.escapeRegex)(unpadded)}`;
359
377
  });
360
378
  const suffix = milestoneSegments[3] || '';
361
- const suffixFragment = suffix ? escapeRegex(suffix) : '';
362
- return `0*${escapeRegex(majorUnpadded)}-${subFragments.join('-')}${suffixFragment}`;
379
+ const suffixFragment = suffix ? (0, pattern_cjs_1.escapeRegex)(suffix) : '';
380
+ return `0*${(0, pattern_cjs_1.escapeRegex)(majorUnpadded)}-${subFragments.join('-')}${suffixFragment}`;
363
381
  }
364
382
  // Plain numeric phase: 1, 01, 12A, 12.1
365
383
  const match = stripped.match(/^0*(\d+)([A-Z])?((?:\.\d+)*)$/i);
384
+ // #3212: escapeRegex now requires a string (the seam owns coercion policy,
385
+ // not this module) — String(...) here preserves this function's own
386
+ // pre-existing `unknown` acceptance for callers that pass a non-string
387
+ // phaseNum through to this fallback branch.
366
388
  if (!match)
367
- return escapeRegex(phaseNum);
389
+ return (0, pattern_cjs_1.escapeRegex)(String(phaseNum));
368
390
  const integer = match[1].replace(/^0+/, '') || '0';
369
- const letter = match[2] ? escapeRegex(match[2]) : '';
370
- const decimal = match[3] ? escapeRegex(match[3]) : '';
371
- return `0*${escapeRegex(integer)}${letter}${decimal}`;
391
+ const letter = match[2] ? (0, pattern_cjs_1.escapeRegex)(match[2]) : '';
392
+ const decimal = match[3] ? (0, pattern_cjs_1.escapeRegex)(match[3]) : '';
393
+ return `0*${(0, pattern_cjs_1.escapeRegex)(integer)}${letter}${decimal}`;
372
394
  }
373
395
  /**
374
396
  * #3599: when the caller passed a project-code-prefixed ID like `PROJ-42`,
@@ -378,7 +400,7 @@ function phaseMarkdownRegexSourceExact(phaseNum) {
378
400
  const raw = String(phaseNum);
379
401
  if (!hasProjectCodePrefix(raw))
380
402
  return null;
381
- return escapeRegex(raw);
403
+ return (0, pattern_cjs_1.escapeRegex)(raw);
382
404
  }
383
405
  function comparePhaseNum(a, b) {
384
406
  // Strip optional project_code prefix before comparing
@@ -436,27 +458,18 @@ function comparePhaseNum(a, b) {
436
458
  return 0;
437
459
  }
438
460
  /**
439
- * Extract the phase token from a directory name.
461
+ * Segmentation core shared by `extractPhaseToken` (the token VALUE) and
462
+ * `isPhaseArtifact` (the DERIVABILITY check — #3511). Factored out so the two
463
+ * questions ("what is this dir's token" and "did a real token exist at all")
464
+ * can never diverge — see CLAUDE.md's "Generative Fix Divergence" note: this
465
+ * is exactly a shared parser between two parallel surfaces.
466
+ *
467
+ * Returns `tokenSegments.length === 0` iff dirName's own leading segment
468
+ * carries no phase-number token (the `extractPhaseToken` dirName-unchanged
469
+ * fallback) — i.e. the directory name itself does not start with a digit or a
470
+ * short letter+digit prefix, so no reliable phase token can be read from it.
440
471
  */
441
- function extractPhaseToken(dirName, convention) {
442
- // #612 bracket dir form `{CODE}.{MM}-{PP}[.{SS}]-slug` → phase token `PP[.SS]`.
443
- // GATED on convention === 'bracket' (mirrors getMilestoneFromPhaseId's READING-B
444
- // decision above). A bracket dir `{CODE}.{MM}-{PP}` is string-INDISTINGUISHABLE
445
- // from the legacy #2043/#1324 letter-prefixed-decimal family (`P0.3-2`,
446
- // `P0.12-34`) whenever the project code ends in a digit, so NO string-only
447
- // discriminator can separate the two conventions — auto-detecting here silently
448
- // reinterpreted `P0.3-2` → `2` (was `P0.3-2`), a byte-identical-read regression
449
- // on this CRITICAL 6-caller helper (ADR-2121). Requiring an explicit convention
450
- // signal keeps every existing (convention-less) call site byte-identical to
451
- // prior behaviour — see the #2043 numeric-tail characterization in
452
- // tests/phase-id.test.cjs — while keeping the helper pure (optional param, no
453
- // config read). The captured token is dot-only (`PP[.SS]`); the milestone↔phase
454
- // hyphen and any trailing plan/slug are excluded.
455
- if (convention === 'bracket') {
456
- const bracketDir = dirName.match(/^[A-Z][A-Z0-9_]*\.\d+-(\d+(?:\.\d+)?)/);
457
- if (bracketDir)
458
- return bracketDir[1];
459
- }
472
+ function derivePhaseTokenSegments(dirName) {
460
473
  const codePrefixMatch = dirName.match(PROJECT_CODE_PREFIX_CAPTURE_RE_I);
461
474
  let prefix = '';
462
475
  let rest = dirName;
@@ -493,18 +506,295 @@ function extractPhaseToken(dirName, convention) {
493
506
  break;
494
507
  }
495
508
  }
496
- else if (isPhaseContinuationSegment(seg) || (firstLetterPrefixed && /^\d/.test(seg))) {
509
+ else if ((firstLetterPrefixed && /^\d/.test(seg)) ||
510
+ (!firstLetterPrefixed && isPhaseContinuationSegment(seg))) {
497
511
  tokenSegments.push(seg);
498
512
  }
499
513
  else {
500
514
  break;
501
515
  }
502
516
  }
517
+ return { prefix, tokenSegments, firstLetterPrefixed };
518
+ }
519
+ /**
520
+ * Extract the phase token from a directory name.
521
+ */
522
+ function extractPhaseToken(dirName, convention) {
523
+ // #612 bracket dir form `{CODE}.{MM}-{PP}[.{SS}]-slug` → phase token `PP[.SS]`.
524
+ // GATED on convention === 'bracket' (mirrors getMilestoneFromPhaseId's READING-B
525
+ // decision above). A bracket dir `{CODE}.{MM}-{PP}` is string-INDISTINGUISHABLE
526
+ // from the legacy #2043/#1324 letter-prefixed-decimal family (`P0.3-2`,
527
+ // `P0.12-34`) whenever the project code ends in a digit, so NO string-only
528
+ // discriminator can separate the two conventions — auto-detecting here silently
529
+ // reinterpreted `P0.3-2` → `2` (was `P0.3-2`), a byte-identical-read regression
530
+ // on this CRITICAL 6-caller helper (ADR-2121). Requiring an explicit convention
531
+ // signal keeps every existing (convention-less) call site byte-identical to
532
+ // prior behaviour — see the #2043 numeric-tail characterization in
533
+ // tests/phase-id.test.cjs — while keeping the helper pure (optional param, no
534
+ // config read). The captured token is dot-only (`PP[.SS]`); the milestone↔phase
535
+ // hyphen and any trailing plan/slug are excluded.
536
+ if (convention === 'bracket') {
537
+ const bracketDir = dirName.match(/^[A-Z][A-Z0-9_]*\.\d+-(\d+(?:\.\d+)?)/);
538
+ if (bracketDir)
539
+ return bracketDir[1];
540
+ }
541
+ const { prefix, tokenSegments, firstLetterPrefixed } = derivePhaseTokenSegments(dirName);
503
542
  if (tokenSegments.length === 0) {
504
543
  return dirName;
505
544
  }
545
+ // #2528 (re-review): the tokenizer deliberately does NOT try to tell a 2-digit
546
+ // slug word ("24" of "24/7 Autonomy") from a genuine zero-padded continuation
547
+ // ("24" of sub-phase 10.24) — by width alone they are the same string, the gap
548
+ // between #2043's 1-digit and #2232's ≥3-digit guards, and no LOCAL signal
549
+ // separates them. An earlier revision of this fix rewound the token when the
550
+ // segment that stopped the scan was a 1-digit word, which reads
551
+ // "10-24-7-autonomy" correctly but silently re-tokenizes the equally real
552
+ // "10-24-7-zip" (sub-phase 10.24 named "7-Zip …") from "10-24" to "10" — it
553
+ // trades the reported ambiguity for the symmetric one a level down, on a
554
+ // CRITICAL 15-caller chokepoint whose output feeds query-less derivations
555
+ // (STATE.md phase counts, W007, the #2562 key surface).
556
+ //
557
+ // So the token stays the LITERAL reading of the name, and disambiguation lives
558
+ // ONE layer up, in matchPhaseDirs, where a QUERY exists to disambiguate
559
+ // against: a bare-integer lookup falls back to the directory's leading digit
560
+ // run and resolves "10-24-7-autonomy" for "10" without touching what the
561
+ // directory's own token is. That is the same bounded mechanism the
562
+ // "05-80-20-cleanup" shape already uses — one rule for the whole
563
+ // digit-leading-slug family instead of two overlapping ones.
564
+ //
565
+ // A generated slug is lowercase. If the owner admitted a two-digit prefix
566
+ // from a digit+letter slug segment ("10x", "25abc"), remove only that final
567
+ // segment. Uppercase suffixes remain available to the established plan-ID
568
+ // grammar, and dotted continuations remain intact.
569
+ if (!firstLetterPrefixed &&
570
+ tokenSegments.length > 1 &&
571
+ /^\d{2}[a-z][a-z0-9]*$/.test(tokenSegments[tokenSegments.length - 1])) {
572
+ tokenSegments.pop();
573
+ }
506
574
  return prefix + tokenSegments.join('-');
507
575
  }
576
+ /**
577
+ * #3511 (reworked — adversarial review found the membership rule wrong in
578
+ * approach, not just detail): predicate for AGGREGATE phase-directory scans
579
+ * (every matching file contributes, e.g.
580
+ * uat-predicate/phase.cts/state.cts/uat.cts/audit.cts's `*-UAT.md` /
581
+ * `*-VERIFICATION.md` scans) — answers "does fileName belong to THIS phase"
582
+ * so a stray, cross-phase, or ad-hoc file (`04-VERIFICATION.md` sitting in
583
+ * phase 03's directory) cannot contribute its status to phase 03. This is
584
+ * deliberately NOT `resolveVerificationFile` (`src/verification.cts`,
585
+ * #3357/#3492) — that resolver answers a SINGLE-PICK question ("which one
586
+ * candidate is THE report") for a phase dir already known to hold one; this
587
+ * answers a per-file membership question for a scan that must fold in EVERY
588
+ * match. See "Reconciliation" below — the two do NOT fully agree.
589
+ *
590
+ * THE ORIGINAL BUG: files are named by `normalizePhaseName`
591
+ * (`cmdScaffold`, `src/commands.cts` — PADDED, project-code-STRIPPED), while
592
+ * this predicate read the directory's OWN token via the literal, unpadded,
593
+ * project-code-CARRYING `extractPhaseToken(phaseDirName)`. Two different
594
+ * normalizations of the same phase number, so a literal
595
+ * `startsWith(token + '-')` excluded a phase's own artifacts whenever they
596
+ * disagreed: `CK-01-foundation` (token `CK-01`, file `01-VERIFICATION.md`),
597
+ * `1-unpadded` (token `1`, file `01-VERIFICATION.md`), and the #2528
598
+ * digit-leading-slug family `05-80-20-cleanup` / `10-24-7-autonomy` (token
599
+ * over-absorbs past the digit run `cmdScaffold` actually writes: `05-80-20`
600
+ * vs the real `05-UAT.md`).
601
+ *
602
+ * THE FIX: build the set of every phase-number READING this directory could
603
+ * plausibly resolve to elsewhere in the module, then check fileName against
604
+ * ALL of them — reusing the exact readings `matchPhaseDirs` /
605
+ * `phaseNumberForMatch` (#2528, below) already carry for directory
606
+ * RESOLUTION, so this membership check can never diverge from what "the
607
+ * directory for phase N" means elsewhere in the module (no third
608
+ * normalization — CLAUDE.md's Generative Fix Divergence class):
609
+ * 1. the literal token (`extractPhaseToken(phaseDirName)` — still correct
610
+ * for the common case and for genuine decimal / letter-suffixed
611
+ * sub-phase dirs);
612
+ * 2. the same token read off the project-code-STRIPPED name
613
+ * (`stripProjectCodePrefix` — the exact fallback `phaseTokenMatches`
614
+ * already applies, #612/#1324);
615
+ * 3. the directory's own LEADING DIGIT RUN on the stripped name
616
+ * (`LEADING_DIGIT_RUN_RE` — the #2528 bare-integer-fallback reading,
617
+ * the one that actually matches what `cmdScaffold` writes for the
618
+ * digit-leading-slug family); and
619
+ * 4. each of (1)-(3) additionally passed through `normalizePhaseName`,
620
+ * since files always carry the PADDED form and directories often do
621
+ * not (`1-unpadded` vs `01-...`).
622
+ * A file belongs when it starts with any candidate + `-` OR any candidate +
623
+ * `.`, compared case-insensitively (matching `phaseTokenMatches`' own rule —
624
+ * review item 8: `03A-VERIFICATION.md` vs `03a-foo`). This is a PREFIX check,
625
+ * not a full-token equality — `03-01-SUMMARY.md` (phase 03, plan 01) must
626
+ * still match dir `03-foo` on candidate `03`, even though
627
+ * `extractPhaseToken('03-01-SUMMARY.md')` would (wrongly, for this purpose)
628
+ * read `03-01` as a mis-absorbed 2-digit continuation.
629
+ *
630
+ * DOTTED SUB-PHASE CONTINUATION: the dot arm of the check exists because this
631
+ * module's own token grammar (`PHASE_NUMBER_TOKEN_SOURCE`) admits a dotted
632
+ * sub-phase continuation (`(?:\.\d+)*`) alongside dash-continuations — a
633
+ * sub-phase artifact `01.1-CONTEXT.md` is `01`'s own file, written into `01`'s
634
+ * directory, not a stray from a different phase. A dash-only prefix check
635
+ * excluded it (`01.1-` does not start with `01-`), which is over-exclusion:
636
+ * the dangerous direction for an aggregate scan whose fail-safes above all
637
+ * default to inclusion when membership is unclear. Widening dash-only to
638
+ * dash-OR-dot only ever ADDS a match a candidate already earned; it cannot
639
+ * newly admit a file whose leading digits differ from `candidate`, so it
640
+ * cannot resolve a genuinely different phase's artifact (`02.1-...` still
641
+ * fails every `01`-rooted candidate).
642
+ *
643
+ * BRACKET CONVENTION (review item 7): a letter-prefixed-decimal dir
644
+ * (`P0.3-2-slug`) is string-INDISTINGUISHABLE from a bracket-dir token
645
+ * (`extractPhaseToken` above, gated on `convention === 'bracket'`) without an
646
+ * explicit convention signal — and this predicate is never given one: none
647
+ * of its 9 call sites thread `convention`/config through today. Rather than
648
+ * guess a reading it cannot know is active and risk excluding the phase's OWN
649
+ * artifact (the exact defect class this rework exists to fix), this family
650
+ * (`firstLetterPrefixed` dirs) falls into the same include-everything
651
+ * fail-safe as the zero-segment case below — a documented, deliberate
652
+ * widening (it also stops excluding a genuine stray from a DIFFERENT
653
+ * letter-prefixed-decimal phase, narrowly) accepted in trade for never
654
+ * dropping the phase's own report. Convention-aware scoping for this family
655
+ * is deferred to whenever a call site actually threads `convention` through.
656
+ *
657
+ * FAIL-SAFE (#3511, unchanged): when dirName's own leading segment carries no
658
+ * phase-number token at all (`derivePhaseTokenSegments` finds zero segments —
659
+ * the same condition `extractPhaseToken` treats as "return dirName
660
+ * unchanged"), no reliable token exists to scope against. Excluding on an
661
+ * unreliable token would make an aggregate gate silently PERMISSIVE in the
662
+ * wrong direction — dropping the phase's own real blockers — which is worse
663
+ * than the cross-phase-contamination bug this predicate exists to fix.
664
+ * Instead every file is treated as belonging to the phase (returns `true`
665
+ * unconditionally), matching pre-fix (unscoped) behaviour for that directory.
666
+ *
667
+ * FIX 2 — bare `VERIFICATION.md` / `UAT.md` (no dash, no token of its own):
668
+ * `derivePhaseTokenSegments(fileName)` also finds zero segments for these —
669
+ * the file carries no phase number to compare against anything. Directory
670
+ * containment is the only signal available for a token-less file, and it is
671
+ * sufficient: every call site passes `fs.readdirSync` results for ONE
672
+ * specific phase dir, so a token-less candidate already reaching this
673
+ * predicate (past each call site's own verification/UAT suffix pre-filter)
674
+ * is, by construction, that phase's own
675
+ * listing. Returns `true` unconditionally, same as the dir-side fail-safe.
676
+ *
677
+ * RECONCILIATION WITH resolveVerificationFile (#3357/#3492/#3511) — the two
678
+ * surfaces now AGREE. `resolveVerificationFile`'s fallback (`verification.cts`,
679
+ * "Fallback" step in its own docblock) filters its dashed candidates through
680
+ * THIS predicate — `isPhaseArtifact(f, phaseDirName)` — before picking
681
+ * alphabetically-first, via a new `phaseDirName` option every call site
682
+ * threads in (the same basename each already derives for `phaseToken`). So a
683
+ * stray cross-phase file can no longer win the single-pick fallback either:
684
+ * it is excluded there for the identical reason it is excluded from the
685
+ * aggregate scans here — membership, not canonical shape. The fail-safes stay
686
+ * aligned too: when this predicate cannot determine membership for a
687
+ * directory (returns `true` unconditionally — see FAIL-SAFE above),
688
+ * `resolveVerificationFile`'s filter is a no-op and its fallback degrades to
689
+ * the original pre-#3357 "alphabetically first of ALL dashed candidates"
690
+ * behavior, exactly as it always did for that directory shape.
691
+ */
692
+ function isPhaseArtifact(fileName, phaseDirName) {
693
+ const { tokenSegments, firstLetterPrefixed } = derivePhaseTokenSegments(phaseDirName);
694
+ if (tokenSegments.length === 0)
695
+ return true;
696
+ const literalToken = extractPhaseToken(phaseDirName);
697
+ const strippedDir = stripProjectCodePrefix(phaseDirName);
698
+ const strippedToken = strippedDir !== phaseDirName ? extractPhaseToken(strippedDir) : literalToken;
699
+ const leadingRunMatch = strippedDir.match(LEADING_DIGIT_RUN_RE);
700
+ const rawCandidates = [literalToken, strippedToken, leadingRunMatch?.[1]].filter((t) => Boolean(t));
701
+ // Each reading is compared in BOTH its padded and de-padded form: files are
702
+ // written padded by `normalizePhaseName` (`cmdScaffold`) while directories
703
+ // are often not (`1-unpadded`), and legacy trees carry the reverse pairing.
704
+ // De-padding is numeric-only — a token with a letter suffix or a dotted
705
+ // sub-phase (`03A`, `03.1`) has no meaningful de-padded form and is left
706
+ // alone, so this only ever ADDS a reading and can never drop one.
707
+ const depad = (t) => (/^\d+$/.test(t) ? String(Number(t)) : t);
708
+ const candidates = new Set(rawCandidates
709
+ .flatMap(t => [t, normalizePhaseName(t), depad(t)])
710
+ .map(t => t.toUpperCase()));
711
+ const fileUpper = fileName.toUpperCase();
712
+ for (const candidate of candidates) {
713
+ // A dotted sub-phase segment (e.g. `01.1-CONTEXT.md`) is a legitimate
714
+ // continuation of `candidate` per this module's own token grammar
715
+ // (PHASE_NUMBER_TOKEN_SOURCE admits `(?:\.\d+)*`), so it belongs to
716
+ // `candidate`'s own directory just as a dash-continuation does. Inclusion
717
+ // is the safe direction for these aggregate scans (see FAIL-SAFE above) —
718
+ // widening a dash-only check to dash-OR-dot never drops a genuine match,
719
+ // it only stops wrongly excluding one.
720
+ //
721
+ // Accepted separator class after a matched candidate: `-`, `.`, or `_`.
722
+ // The underscore was added for state.cts's `cmdStateValidate` S006/S007
723
+ // scan, whose own pre-filter is deliberately broader than the dashed
724
+ // grammar every other call site uses (`.includes('VERIFICATION')`, no
725
+ // dash required — see the WARNING-4 comment there), so it admits names
726
+ // like `03_VERIFICATION.md`. Before this predicate accepted `_` as a
727
+ // boundary, such a file failed the `-`/`.`-only check here even though
728
+ // its digits matched `candidate` exactly, and `scopeToPhase` dropped it —
729
+ // a real same-phase verification report reported as absent. Widening the
730
+ // separator class only ever EXTENDS a candidate whose digits already
731
+ // match exactly; it cannot admit a genuinely different phase's file,
732
+ // since the candidate comparison itself is unchanged.
733
+ if (fileUpper.startsWith(`${candidate}-`) ||
734
+ fileUpper.startsWith(`${candidate}.`) ||
735
+ fileUpper.startsWith(`${candidate}_`))
736
+ return true;
737
+ }
738
+ // FIX 2: token-less filename (bare "VERIFICATION.md"/"UAT.md") — containment
739
+ // in this phase's own directory listing is sufficient.
740
+ if (derivePhaseTokenSegments(fileName).tokenSegments.length === 0)
741
+ return true;
742
+ // Bracket-convention ambiguity fail-safe — see docblock above.
743
+ if (firstLetterPrefixed)
744
+ return true;
745
+ return false;
746
+ }
747
+ /**
748
+ * #3511: scope `fileNames` to the subset that passes
749
+ * `isPhaseArtifact(fileName, phaseDirName)`. The single seam every
750
+ * phase-directory scan routes through, so the membership rule has ONE owner.
751
+ *
752
+ * AN EMPTY RESULT IS A REAL ANSWER — deliberately, and this is the hard-won
753
+ * part. An earlier revision of this helper carried an extra rule ("scoping
754
+ * must never turn a non-empty set into an empty one": if the filter removed
755
+ * every file, return the unfiltered input). It was added to rescue a
756
+ * directory whose basename merely PARSES phase-shaped —
757
+ * `gsd-651-broad-grep-a1b2`, an `mkdtemp`-style fixture name that
758
+ * `extractPhaseToken` reads as project code `gsd` + phase `651` (the capture
759
+ * regex is case-INSENSITIVE) — holding only `01-bg-VERIFICATION.md`, which
760
+ * the filter then dropped, yielding an empty set indistinguishable from "no
761
+ * report exists".
762
+ *
763
+ * That rescue was wrong, and no local rule can make it right: a directory
764
+ * whose own name says phase 651 holding only a file that says phase 01 is
765
+ * STRING-INDISTINGUISHABLE from `03-foo/` holding only `04-VERIFICATION.md`
766
+ * — the exact cross-phase stray #3511 exists to exclude. Keeping the rule
767
+ * meant a real phase directory holding only a MISFILED report would resolve
768
+ * to it and publish another phase's `passed` as its own: the reported bug, in
769
+ * its single most damaging form. `missing` is the correct answer when a
770
+ * phase's own report is genuinely absent, and every caller already has a
771
+ * `missing`/`null` branch for it.
772
+ *
773
+ * The over-exclusion that rule was reaching for is instead handled where it
774
+ * is actually determinable, inside `isPhaseArtifact`: the zero-token dir
775
+ * fail-safe, the `firstLetterPrefixed` bracket-ambiguity fail-safe, the
776
+ * token-less-filename rule, and the multi-reading candidate set (literal /
777
+ * project-code-stripped / leading-digit-run, each also padded AND de-padded)
778
+ * that covers every normalization a phase directory and its files can
779
+ * legitimately disagree on. A file excluded after all of those genuinely
780
+ * names a different phase.
781
+ *
782
+ * SITE DISCIPLINE: every aggregate-scan call site (`uat.cts`,
783
+ * `uat-predicate.cts`, `phase.cts`, `audit.cts`, `state.cts`,
784
+ * `core-utils.cts`'s `getPhaseFileStats` — #3511 BLOCKER-2 — and
785
+ * `init.cts`'s two phase-info-projection sites — #3511 BLOCKER-3, both of
786
+ * which scope the raw listing once up front and reuse it for every bare
787
+ * `.find()`/`.some()` artifact predicate: context/research/UAT/reviews/
788
+ * patterns) and `resolveVerificationFile`'s single-pick fallback
789
+ * (`verification.cts`) MUST route through this helper rather than calling
790
+ * `isPhaseArtifact` in a filter position directly, so the rule cannot be
791
+ * re-derived per site (CLAUDE.md's Generative Fix Divergence class).
792
+ * `isPhaseArtifact` stays exported for single-item membership questions and
793
+ * its own unit tests.
794
+ */
795
+ function scopeToPhase(fileNames, phaseDirName) {
796
+ return fileNames.filter((f) => isPhaseArtifact(f, phaseDirName));
797
+ }
508
798
  /**
509
799
  * Check if a directory name's phase token matches the normalized phase exactly.
510
800
  */
@@ -520,6 +810,178 @@ function phaseTokenMatches(dirName, normalized) {
520
810
  }
521
811
  return false;
522
812
  }
813
+ /**
814
+ * #2528: the LEADING DIGIT RUN of a directory name — the fragment the
815
+ * bare-integer fallback selects on, and the one `phaseNumberForMatch` then
816
+ * displays. Named (per this module's convention of naming grammar fragments
817
+ * rather than inlining them) because the two sites must not drift: selecting on
818
+ * one run and displaying another would resolve a directory and then label it
819
+ * with a number that never matched.
820
+ *
821
+ * `LEADING_DIGIT_RUN_RE` anchors a trailing `-`-or-end so the run is a whole
822
+ * segment; `_PREFIX` is the same run without that boundary, for reading the run
823
+ * back off a name already known to match.
824
+ */
825
+ const LEADING_DIGIT_RUN_SOURCE = '\\d+';
826
+ const LEADING_DIGIT_RUN_RE = new RegExp(`^(${LEADING_DIGIT_RUN_SOURCE})(?:-|$)`);
827
+ const LEADING_DIGIT_RUN_PREFIX_RE = new RegExp(`^${LEADING_DIGIT_RUN_SOURCE}`);
828
+ const BARE_INTEGER_RE = new RegExp(`^${LEADING_DIGIT_RUN_SOURCE}$`);
829
+ /** Strip leading zeros for numeric-equality compare, keeping a lone "0". */
830
+ const unpad = (digits) => digits.replace(/^0+(?=\d)/, '');
831
+ /**
832
+ * #2528: the CANONICAL phase-directory match selection — the one rule every
833
+ * directory-resolution path (the shared locator plus the `find-phase` and
834
+ * `phase-plan-index` command scans) applies to a candidate dir list. Extracted
835
+ * here because the surrounding scan/ambiguity/shaping code exists per site and
836
+ * had already diverged; the selection itself must not.
837
+ *
838
+ * Two passes:
839
+ * 1. PRIMARY — exact token match (`phaseTokenMatches`), unchanged behavior.
840
+ * 2. BARE-INTEGER FALLBACK — only when the primary pass matched NOTHING and
841
+ * the query is a bare integer, re-filter by each directory's own LEADING
842
+ * digit run (zero-padded compare). This catches digit-leading slug shapes
843
+ * the tokenizer cannot disambiguate from genuine sub-phase segments
844
+ * (e.g. "05-80-20-cleanup", phase 5 named "80/20 Cleanup", whose token
845
+ * "05-80-20" is byte-identical in shape to a real deep-decomposition dir).
846
+ * The fallback can only turn a silent not-found into a resolution or into
847
+ * a surfaced ambiguity (callers keep their #2237 multi-match guards) —
848
+ * never override a primary match.
849
+ *
850
+ * SCOPE, precisely (#2528 re-review). Non-bare QUERIES ("46-6", "12A",
851
+ * "PROJ-42") never enter the fallback, so nothing changes about how a
852
+ * deep-decomposition or letter-suffix lookup is asked. What DOES change is the
853
+ * DIRECTORY side: a bare query now reaches directories the tokenizer classified
854
+ * as multi-segment, and a genuine sub-phase directory has exactly that shape.
855
+ * So `5` against a lone `05-01-auth` resolves (phase_number "05", phase_name
856
+ * "01-auth") where it previously found nothing.
857
+ *
858
+ * That widening is DELIBERATE and it is irreducible from directory names alone.
859
+ * `05-01-auth` (sub-phase 5.1) and `30-12-factor-refactor` (phase 30 named
860
+ * "12-Factor Refactor") are the same string shape — `NN-NN-<slug>` — and the
861
+ * discriminator that would separate them, "is the second segment a valid decimal
862
+ * sub-phase", accepts both (`5.1` and `30.12` are equally well-formed). Any rule
863
+ * strong enough to exclude `05-01-auth` also excludes `30-12-factor-refactor`,
864
+ * which is the defect #2528 exists to fix. The tie is therefore broken in favour
865
+ * of resolving, and the consequence is bounded on the side that matters: when
866
+ * BOTH readings have a directory (`05-01-auth` + `05-02-api`) the result is two
867
+ * matches. `tests/phase-resolution-parity.test.cjs` pins both directions: the
868
+ * lone-directory resolution and the two-directory refusal.
869
+ *
870
+ * WHAT IS SHARED IS SELECTION, NOT AMBIGUITY POLICY. This function is the one
871
+ * owner of "which directories does this query name". What a caller does with
872
+ * two of them stays the caller's own decision, and the callers split in two
873
+ * tiers on purpose:
874
+ *
875
+ * REFUSE on `matches.length > 1` — `searchPhaseInDir`, `cmdFindPhase`,
876
+ * `cmdPhasePlanIndex`, `cmdPhaseRemove`. These either act destructively or
877
+ * answer "which phase is this", so guessing is worse than reporting the
878
+ * candidates (#2237).
879
+ *
880
+ * TAKE `matches[0]` — `cmdPhasesList`, `cmdInitManager`, `cmdRoadmapAnalyze`,
881
+ * `cmdVerifySchemaDrift`, `detectVerifyFailed`. Each read a directory to
882
+ * DECORATE a row they are already emitting; each used `.find()` before this
883
+ * PR, so first-match is their prior behavior preserved verbatim, and each is
884
+ * order-stable because the directory list is sorted and this function filters
885
+ * without reordering.
886
+ *
887
+ * The honest caveat on that second tier: the bare-number fallback makes
888
+ * multi-match newly REACHABLE for inputs that previously found nothing, so those
889
+ * five can now silently pick one of several candidates where they used to report
890
+ * not-found. That is a widening of an existing first-match rule, not a new rule
891
+ * — but it is a widening, and promoting any of them to refusal is a UX decision
892
+ * about their own output, not a change to selection, so it does not belong here.
893
+ *
894
+ * `usedBareFallback` tells callers to derive the displayed phase number from
895
+ * the directory's leading digit run instead of `extractPhaseToken` (whose
896
+ * token for these dirs is the mis-absorbed multi-segment form).
897
+ */
898
+ function matchPhaseDirs(dirs, normalized) {
899
+ const primary = dirs.filter(d => phaseTokenMatches(d, normalized));
900
+ if (primary.length > 0)
901
+ return { matches: primary, usedBareFallback: false };
902
+ const bare = String(normalized);
903
+ if (!BARE_INTEGER_RE.test(bare))
904
+ return { matches: primary, usedBareFallback: false };
905
+ const want = unpad(bare);
906
+ const fallback = dirs.filter(d => {
907
+ const m = stripProjectCodePrefix(d).match(LEADING_DIGIT_RUN_RE);
908
+ return m !== null && unpad(m[1]) === want;
909
+ });
910
+ return { matches: fallback, usedBareFallback: fallback.length > 0 };
911
+ }
912
+ /**
913
+ * #2528: the display phase number for a directory selected by matchPhaseDirs.
914
+ * Primary matches keep the extracted token; bare-fallback matches use the
915
+ * directory's leading digit run (the whole point of the fallback is that the
916
+ * extracted token is wrong for these dirs).
917
+ */
918
+ function phaseNumberForMatch(dirName, usedBareFallback) {
919
+ if (!usedBareFallback)
920
+ return extractPhaseToken(dirName);
921
+ const stripped = stripProjectCodePrefix(dirName);
922
+ const prefix = dirName.slice(0, dirName.length - stripped.length);
923
+ const m = stripped.match(LEADING_DIGIT_RUN_PREFIX_RE);
924
+ return m ? prefix + m[0] : extractPhaseToken(dirName);
925
+ }
926
+ // ─── Canonical phase KEY surface (#2562) ─────────────────────────────────────
927
+ //
928
+ // A phase "key" is the padding-, case- and project-code-insensitive identity of
929
+ // a phase, for use as a Map/Set key when two independently-derived phase
930
+ // references (a ROADMAP table cell and a phase directory name, say) must be
931
+ // compared. Promoted here from a local pair in state.cts (#2445) so every
932
+ // consumer derives BOTH sides of a comparison from the SAME function — deriving
933
+ // one side with a bespoke regex is the #2562 defect class (a `01` table cell
934
+ // never matching a `1-slug` directory, silently zeroing a rollup).
935
+ /**
936
+ * Canonical key for an already-extracted phase TOKEN (`"5"`, `"05"`, `"005"`,
937
+ * `"12A"`, `"30.1"`, `"PROJ-05"`). Padding- and case-insensitive: every
938
+ * spelling of a number collapses to one key.
939
+ *
940
+ * Leading zeros are stripped per hyphen-separated segment BEFORE
941
+ * `normalizePhaseName` pads to the 2-digit convention. Padding alone is not a
942
+ * normalisation — `padStart(2)` is a no-op once the input is already ≥2
943
+ * characters, so `5` yielded `05` while `005` stayed `005` and the two never
944
+ * compared equal. The strip is deliberately confined to this key surface:
945
+ * `normalizePhaseName` itself is a RENDERING function whose verbatim treatment
946
+ * of wide IDs (`001.10`) is relied on by plan-ID capture and wave assignment.
947
+ * Arithmetic is avoided (`parseInt` would lose precision on a long digit run).
948
+ */
949
+ function phaseKeyFromToken(token) {
950
+ const stripped = String(token)
951
+ .split('-')
952
+ .map(segment => segment.replace(/^0+(?=\d)/, ''))
953
+ .join('-');
954
+ return normalizePhaseName(stripped).toUpperCase();
955
+ }
956
+ /**
957
+ * Canonical key for a phase DIRECTORY name (`"05-schedule-8"` → `"05"`,
958
+ * `"PROJ-5-x"` → `"05"`, `"30.1-follow-up"` → `"30.1"`).
959
+ */
960
+ function phaseKeyFromDir(dirName) {
961
+ return phaseKeyFromToken(extractPhaseToken(dirName));
962
+ }
963
+ /**
964
+ * Canonical key for a phase referenced in PROSE — a ROADMAP `## Progress` table
965
+ * cell (`"30. Schedule 8 rollout"`, `"**05.1 Follow-up**"`) or a STATE.md
966
+ * `Phase:` value. Markdown emphasis is stripped first so a bolded cell is not
967
+ * mistaken for a non-phase. Returns null when the value does not BEGIN with a
968
+ * phase token (`parsePhaseFromProse` anchoring, #2111).
969
+ */
970
+ function phaseKeyFromProse(value) {
971
+ if (value == null)
972
+ return null;
973
+ const { phase } = parsePhaseFromProse(String(value).replace(/[*_`~]/g, ''));
974
+ return phase === null ? null : phaseKeyFromToken(phase);
975
+ }
976
+ /**
977
+ * The PARENT phase key of a sub-phase key (`"30.1"` → `"30"`), or null for a
978
+ * top-level phase. A sub-phase directory inserted mid-milestone frequently has
979
+ * no ROADMAP row of its own and inherits its parent's milestone (#2562).
980
+ */
981
+ function parentPhaseKey(key) {
982
+ const dot = key.indexOf('.');
983
+ return dot === -1 ? null : key.slice(0, dot);
984
+ }
523
985
  // ─── #2121 canonical surface (ADR-2121) ──────────────────────────────────────
524
986
  /**
525
987
  * Parse a phase identifier from a STATE.md `Phase:` prose field VALUE — the text
@@ -640,10 +1102,11 @@ function roadmapPhaseLookupSources(phaseNum) {
640
1102
  return [...new Set(sources)];
641
1103
  }
642
1104
  module.exports = {
643
- escapeRegex,
644
1105
  OPTIONAL_PROJECT_CODE_PREFIX_SOURCE,
645
1106
  OPTIONAL_PHASE_TAG_SOURCE,
646
1107
  PHASE_NUMBER_TOKEN_SOURCE,
1108
+ CASE_FLEXIBLE_PROJECT_CODE_PREFIX_SOURCE,
1109
+ CASE_FLEXIBLE_PHASE_NUMBER_TOKEN_SOURCE,
647
1110
  PHASE_CONTINUATION_SEGMENT_SOURCE,
648
1111
  isPhaseContinuationSegment,
649
1112
  BRACKET_PHASE_TOKEN_SOURCE,
@@ -661,7 +1124,15 @@ module.exports = {
661
1124
  phaseMarkdownRegexSourceExact,
662
1125
  comparePhaseNum,
663
1126
  extractPhaseToken,
1127
+ isPhaseArtifact,
1128
+ scopeToPhase,
664
1129
  phaseTokenMatches,
1130
+ matchPhaseDirs,
1131
+ phaseNumberForMatch,
1132
+ phaseKeyFromToken,
1133
+ phaseKeyFromDir,
1134
+ phaseKeyFromProse,
1135
+ parentPhaseKey,
665
1136
  parsePhaseFromProse,
666
1137
  stripConfiguredProjectCodePrefix,
667
1138
  isForeignPrefixedPhaseQuery,