@opengsd/gsd-core 1.11.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.opencode/plugins/gsd-core.js +12 -0
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-debug-session-manager.md +1 -1
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-dom-verifier.md +169 -0
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +78 -42
- package/agents/gsd-framework-selector.md +1 -3
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.md +0 -1
- package/agents/gsd-pattern-mapper.md +11 -0
- package/agents/gsd-phase-researcher.md +3 -1
- package/agents/gsd-plan-checker.md +91 -112
- package/agents/gsd-planner.md +20 -4
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.md +2 -2
- package/agents/gsd-roadmapper.md +15 -11
- package/agents/gsd-ui-checker.md +82 -7
- package/agents/gsd-ui-researcher.md +70 -3
- package/agents/gsd-verifier.md +24 -2
- package/bin/install.js +847 -200
- package/commands/gsd/discuss-phase.md +1 -1
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/import.md +1 -1
- package/commands/gsd/ns-workflow.md +2 -1
- package/commands/gsd/phase.md +1 -1
- package/commands/gsd/quick-batch.md +105 -0
- package/commands/gsd/quick.md +8 -4
- package/commands/gsd/surface.md +18 -8
- package/gsd-core/bin/gsd-tools.cjs +761 -100
- package/gsd-core/bin/lib/active-workstream-store.cjs +8 -0
- package/gsd-core/bin/lib/adr-parser.cjs +13 -7
- package/gsd-core/bin/lib/agent-install-check.cjs +162 -0
- package/gsd-core/bin/lib/api-coverage.cjs +30 -9
- package/gsd-core/bin/lib/artifacts.cjs +2 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +30 -11
- package/gsd-core/bin/lib/audit.cjs +163 -41
- package/gsd-core/bin/lib/broken-windows.cjs +306 -28
- package/gsd-core/bin/lib/capability-activation.cjs +27 -0
- package/gsd-core/bin/lib/capability-lock.cjs +10 -4
- package/gsd-core/bin/lib/capability-registry.cjs +785 -144
- package/gsd-core/bin/lib/capability-state.cjs +25 -4
- package/gsd-core/bin/lib/capability-validator.cjs +321 -18
- package/gsd-core/bin/lib/capability-writer.cjs +14 -4
- package/gsd-core/bin/lib/check-command-router.cjs +229 -6
- package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
- package/gsd-core/bin/lib/cli-exit.cjs +496 -10
- package/gsd-core/bin/lib/clusters.cjs +1 -0
- package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
- package/gsd-core/bin/lib/codex-agent-toml.cjs +410 -4
- package/gsd-core/bin/lib/command-aliases.cjs +16 -0
- package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
- package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
- package/gsd-core/bin/lib/commands.cjs +877 -54
- package/gsd-core/bin/lib/complexity-trigger.cjs +26 -6
- package/gsd-core/bin/lib/config-loader.cjs +121 -29
- package/gsd-core/bin/lib/config.cjs +92 -2
- package/gsd-core/bin/lib/configuration.cjs +129 -37
- package/gsd-core/bin/lib/core-utils.cjs +118 -14
- package/gsd-core/bin/lib/decisions.cjs +213 -1
- package/gsd-core/bin/lib/edge-probe.cjs +23 -2
- package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
- package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
- package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
- package/gsd-core/bin/lib/frontmatter.cjs +975 -326
- package/gsd-core/bin/lib/gap-checker.cjs +41 -8
- package/gsd-core/bin/lib/git-base-branch.cjs +182 -39
- package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +7 -3
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +60 -14
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +75 -22
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +22 -8
- package/gsd-core/bin/lib/health-diagnostic.cjs +23 -3
- package/gsd-core/bin/lib/host-integration.cjs +96 -11
- package/gsd-core/bin/lib/init-command-router.cjs +132 -21
- package/gsd-core/bin/lib/init.cjs +252 -56
- package/gsd-core/bin/lib/install-engine.cjs +252 -15
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +78 -1
- package/gsd-core/bin/lib/install-profiles.cjs +100 -18
- package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
- package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +10 -7
- package/gsd-core/bin/lib/intel.cjs +101 -26
- package/gsd-core/bin/lib/io.cjs +195 -15
- package/gsd-core/bin/lib/learnings.cjs +85 -14
- package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
- package/gsd-core/bin/lib/loop-resolver.cjs +14 -8
- package/gsd-core/bin/lib/markdown-table.cjs +175 -4
- package/gsd-core/bin/lib/milestone.cjs +112 -7
- package/gsd-core/bin/lib/model-catalog.cjs +177 -19
- package/gsd-core/bin/lib/model-resolver.cjs +10 -28
- package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
- package/gsd-core/bin/lib/phase-command-router.cjs +13 -6
- package/gsd-core/bin/lib/phase-estimation.cjs +17 -8
- package/gsd-core/bin/lib/phase-id.cjs +321 -13
- package/gsd-core/bin/lib/phase-lifecycle.cjs +24 -16
- package/gsd-core/bin/lib/phase-locator.cjs +138 -17
- package/gsd-core/bin/lib/phase.cjs +1175 -115
- package/gsd-core/bin/lib/plan-document.cjs +273 -0
- package/gsd-core/bin/lib/plan-scan.cjs +13 -2
- package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +165 -34
- package/gsd-core/bin/lib/planning-workspace.cjs +159 -28
- package/gsd-core/bin/lib/probe-core.cjs +4 -1
- package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
- package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
- package/gsd-core/bin/lib/quick-batch-command-router.cjs +285 -0
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
- package/gsd-core/bin/lib/quick-batch.cjs +840 -0
- package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +71 -45
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +62 -14
- package/gsd-core/bin/lib/review-lane-invocation.cjs +73 -1
- package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
- package/gsd-core/bin/lib/roadmap-command-router.cjs +45 -31
- package/gsd-core/bin/lib/roadmap-parser.cjs +577 -41
- package/gsd-core/bin/lib/roadmap.cjs +248 -64
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +329 -41
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +16 -17
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +320 -109
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +487 -83
- package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
- package/gsd-core/bin/lib/runtime-slash.cjs +72 -2
- package/gsd-core/bin/lib/shell-command-projection.cjs +75 -8
- package/gsd-core/bin/lib/smart-entry.cjs +19 -31
- package/gsd-core/bin/lib/spec-section.cjs +12 -7
- package/gsd-core/bin/lib/state-command-router.cjs +47 -18
- package/gsd-core/bin/lib/state-contract.cjs +359 -0
- package/gsd-core/bin/lib/state-document.cjs +216 -5
- package/gsd-core/bin/lib/state-md-schema.cjs +231 -0
- package/gsd-core/bin/lib/state-transition.cjs +850 -145
- package/gsd-core/bin/lib/state.cjs +1629 -287
- package/gsd-core/bin/lib/surface.cjs +33 -10
- package/gsd-core/bin/lib/task-command-router.cjs +111 -1
- package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
- package/gsd-core/bin/lib/teams-status.cjs +4 -1
- package/gsd-core/bin/lib/uat-predicate.cjs +58 -20
- package/gsd-core/bin/lib/uat.cjs +2542 -387
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
- package/gsd-core/bin/lib/ui-safety-gate.cjs +37 -7
- package/gsd-core/bin/lib/unusable-input.cjs +13 -0
- package/gsd-core/bin/lib/update-context.cjs +6 -2
- package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
- package/gsd-core/bin/lib/validate.cjs +230 -12
- package/gsd-core/bin/lib/vendor/README.md +43 -5
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +287 -13
- package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
- package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
- package/gsd-core/bin/lib/verify.cjs +441 -56
- package/gsd-core/bin/lib/workstream-inventory.cjs +20 -2
- package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
- package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
- package/gsd-core/bin/lib/worktree-safety.cjs +185 -21
- package/gsd-core/bin/shared/config-defaults.manifest.json +7 -1
- package/gsd-core/bin/shared/config-schema.manifest.json +13 -0
- package/gsd-core/bin/shared/exit-codes.json +8 -0
- package/gsd-core/bin/shared/exit-codes.sh +20 -0
- package/gsd-core/bin/shared/model-catalog.json +8 -1
- package/gsd-core/bin/verify-reapply-patches.cjs +70 -3
- package/gsd-core/references/agent-contracts.md +6 -5
- package/gsd-core/references/api-coverage.md +24 -2
- package/gsd-core/references/autonomous-smart-discuss.md +3 -3
- package/gsd-core/references/checkpoints.md +37 -19
- package/gsd-core/references/decimal-phase-calculation.md +5 -5
- package/gsd-core/references/edge-probe.md +17 -5
- package/gsd-core/references/execute-mvp-tdd.md +18 -18
- package/gsd-core/references/execute-phase-between-wave-reset.md +9 -12
- package/gsd-core/references/execute-phase-response-language.md +6 -0
- package/gsd-core/references/execute-phase-wave-guard.md +11 -9
- package/gsd-core/references/executor-examples.md +42 -0
- package/gsd-core/references/failing-direction.md +78 -0
- package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
- package/gsd-core/references/gate-prompts.md +1 -1
- package/gsd-core/references/git-integration.md +5 -5
- package/gsd-core/references/git-planning-commit.md +3 -3
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +22 -0
- package/gsd-core/references/model-profiles.md +1 -1
- package/gsd-core/references/mvp-concepts.md +2 -2
- package/gsd-core/references/nyquist-compliance.md +74 -0
- package/gsd-core/references/offer-next.md +3 -5
- package/gsd-core/references/phase-argument-parsing.md +3 -3
- package/gsd-core/references/plan-checker-examples.md +41 -0
- package/gsd-core/references/planner-antipatterns.md +25 -0
- package/gsd-core/references/planner-chunked.md +5 -1
- package/gsd-core/references/planner-coupling.md +42 -0
- package/gsd-core/references/planner-failing-direction.md +53 -0
- package/gsd-core/references/planner-human-verify-mode.md +15 -1
- package/gsd-core/references/planner-quick-batch.md +71 -0
- package/gsd-core/references/planner-reviews.md +47 -0
- package/gsd-core/references/planner-revision.md +76 -3
- package/gsd-core/references/planner-verify-command-grounding.md +17 -0
- package/gsd-core/references/planning-config.md +39 -9
- package/gsd-core/references/response-language-directive.md +9 -0
- package/gsd-core/references/reviewer-instances.md +31 -0
- package/gsd-core/references/revision-loop.md +118 -11
- package/gsd-core/references/runtime-aware-dispatch.md +1 -1
- package/gsd-core/references/tdd.md +15 -12
- package/gsd-core/references/ui-brand.md +65 -21
- package/gsd-core/references/ui-consideration-probe.md +1 -1
- package/gsd-core/references/universal-anti-patterns.md +2 -2
- package/gsd-core/references/verifier-evidence-gate.md +160 -0
- package/gsd-core/references/verify-command-path-resolvability.md +42 -0
- package/gsd-core/references/verify-mvp-mode.md +1 -1
- package/gsd-core/references/workstream-flag.md +11 -11
- package/gsd-core/templates/README.md +1 -1
- package/gsd-core/templates/SECURITY.md +3 -3
- package/gsd-core/templates/UI-SPEC.md +25 -3
- package/gsd-core/templates/VALIDATION.md +3 -3
- package/gsd-core/templates/phase-prompt.md +7 -0
- package/gsd-core/templates/state.md +7 -0
- package/gsd-core/templates/verification-report.md +5 -0
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +3 -1
- package/gsd-core/workflows/add-phase.md +5 -3
- package/gsd-core/workflows/add-tests.md +4 -9
- package/gsd-core/workflows/add-todo.md +2 -2
- package/gsd-core/workflows/ai-integration-phase.md +5 -10
- package/gsd-core/workflows/analyze-dependencies.md +2 -0
- package/gsd-core/workflows/audit-fix.md +14 -3
- package/gsd-core/workflows/audit-milestone.md +11 -9
- package/gsd-core/workflows/audit-uat.md +19 -2
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
- package/gsd-core/workflows/autonomous.md +12 -26
- package/gsd-core/workflows/check-todos.md +2 -2
- package/gsd-core/workflows/cleanup.md +3 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +16 -14
- package/gsd-core/workflows/code-review-fix.md +3 -1
- package/gsd-core/workflows/code-review.md +192 -69
- package/gsd-core/workflows/complete-milestone.md +28 -14
- package/gsd-core/workflows/debug.md +6 -4
- package/gsd-core/workflows/diagnose-issues.md +17 -7
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +3 -1
- package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -7
- package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/text.md +3 -1
- package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
- package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
- package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
- package/gsd-core/workflows/discuss-phase-assumptions.md +3 -3
- package/gsd-core/workflows/discuss-phase-power.md +2 -0
- package/gsd-core/workflows/discuss-phase.md +2 -2
- package/gsd-core/workflows/do.md +46 -19
- package/gsd-core/workflows/docs-update.md +6 -5
- package/gsd-core/workflows/edit-phase.md +3 -1
- package/gsd-core/workflows/eval-review.md +5 -10
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +3 -1
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +129 -11
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +29 -5
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +4 -2
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
- package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
- package/gsd-core/workflows/execute-phase.md +68 -66
- package/gsd-core/workflows/execute-plan.md +25 -20
- package/gsd-core/workflows/explore.md +3 -1
- package/gsd-core/workflows/extract-learnings.md +3 -1
- package/gsd-core/workflows/fast.md +8 -2
- package/gsd-core/workflows/forensics.md +3 -1
- package/gsd-core/workflows/graduation.md +6 -6
- package/gsd-core/workflows/health.md +4 -7
- package/gsd-core/workflows/help/modes/brief.md +2 -0
- package/gsd-core/workflows/help/modes/default.md +2 -0
- package/gsd-core/workflows/help/modes/full.md +12 -0
- package/gsd-core/workflows/help/modes/topic.md +2 -0
- package/gsd-core/workflows/help.md +2 -0
- package/gsd-core/workflows/import.md +17 -14
- package/gsd-core/workflows/inbox.md +5 -6
- package/gsd-core/workflows/ingest-docs.md +45 -12
- package/gsd-core/workflows/insert-phase.md +7 -5
- package/gsd-core/workflows/list-phase-assumptions.md +2 -0
- package/gsd-core/workflows/list-seeds.md +7 -3
- package/gsd-core/workflows/list-workspaces.md +3 -1
- package/gsd-core/workflows/manager.md +15 -26
- package/gsd-core/workflows/map-codebase.md +3 -1
- package/gsd-core/workflows/milestone-summary.md +3 -1
- package/gsd-core/workflows/mvp-phase.md +3 -3
- package/gsd-core/workflows/new-milestone.md +10 -22
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
- package/gsd-core/workflows/new-project.md +17 -29
- package/gsd-core/workflows/new-workspace.md +2 -2
- package/gsd-core/workflows/next.md +4 -2
- package/gsd-core/workflows/node-repair.md +2 -0
- package/gsd-core/workflows/note.md +2 -0
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +20 -5
- package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +4 -4
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +12 -3
- package/gsd-core/workflows/plan-phase.md +251 -54
- package/gsd-core/workflows/plan-review-convergence.md +148 -19
- package/gsd-core/workflows/plant-seed.md +3 -3
- package/gsd-core/workflows/pr-branch.md +195 -51
- package/gsd-core/workflows/profile-user.md +17 -15
- package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
- package/gsd-core/workflows/progress.md +52 -15
- package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +38 -5
- package/gsd-core/workflows/quick/steps/quick-verification.md +2 -4
- package/gsd-core/workflows/quick/steps/research-phase.md +5 -7
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
- package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
- package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
- package/gsd-core/workflows/quick-batch.md +203 -0
- package/gsd-core/workflows/quick.md +33 -32
- package/gsd-core/workflows/reapply-patches.md +2 -0
- package/gsd-core/workflows/remove-phase.md +6 -4
- package/gsd-core/workflows/remove-workspace.md +3 -3
- package/gsd-core/workflows/resume-project.md +14 -14
- package/gsd-core/workflows/review.md +404 -21
- package/gsd-core/workflows/scan.md +3 -1
- package/gsd-core/workflows/section-manifest.json +12 -0
- package/gsd-core/workflows/secure-phase.md +3 -3
- package/gsd-core/workflows/session-report.md +2 -0
- package/gsd-core/workflows/settings-advanced.md +9 -9
- package/gsd-core/workflows/settings-integrations.md +66 -32
- package/gsd-core/workflows/settings.md +4 -6
- package/gsd-core/workflows/ship.md +22 -16
- package/gsd-core/workflows/sketch-wrap-up.md +13 -17
- package/gsd-core/workflows/sketch.md +13 -19
- package/gsd-core/workflows/smart-entry.md +4 -6
- package/gsd-core/workflows/spec-phase.md +31 -4
- package/gsd-core/workflows/spike-wrap-up.md +9 -11
- package/gsd-core/workflows/spike.md +21 -32
- package/gsd-core/workflows/stats.md +4 -2
- package/gsd-core/workflows/sync-skills.md +13 -5
- package/gsd-core/workflows/thread.md +13 -7
- package/gsd-core/workflows/transition.md +7 -5
- package/gsd-core/workflows/ui-phase.md +36 -21
- package/gsd-core/workflows/ui-review.md +7 -11
- package/gsd-core/workflows/ultraplan-phase.md +7 -13
- package/gsd-core/workflows/undo.md +9 -17
- package/gsd-core/workflows/update.md +47 -48
- package/gsd-core/workflows/validate-phase.md +3 -3
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
- package/gsd-core/workflows/verify-work.md +106 -21
- package/hooks/dist/gsd-agent-isolation-guard.js +77 -38
- package/hooks/dist/gsd-check-update-worker.js +19 -2
- package/hooks/dist/gsd-config-reload.js +18 -12
- package/hooks/dist/gsd-context-monitor.js +302 -22
- package/hooks/dist/gsd-cursor-post-tool.js +3 -1
- package/hooks/dist/gsd-cursor-pre-tool.js +3 -1
- package/hooks/dist/gsd-cursor-session-start.js +2 -1
- package/hooks/dist/gsd-cursor-stop.js +2 -1
- package/hooks/dist/gsd-cursor-subagent-start.js +28 -23
- package/hooks/dist/gsd-cursor-subagent-stop.js +3 -1
- package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
- package/hooks/dist/gsd-graphify-update.sh +22 -18
- package/hooks/dist/gsd-node-runner.sh +77 -0
- package/hooks/dist/gsd-phase-boundary.sh +1 -0
- package/hooks/dist/gsd-prompt-guard.js +46 -12
- package/hooks/dist/gsd-read-guard.js +18 -7
- package/hooks/dist/gsd-read-injection-scanner.js +22 -13
- package/hooks/dist/gsd-secret-read-guard.js +1079 -0
- package/hooks/dist/gsd-session-state.sh +1 -0
- package/hooks/dist/gsd-statusline.js +222 -29
- package/hooks/dist/gsd-validate-commit.sh +523 -12
- package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
- package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
- package/hooks/dist/gsd-workflow-guard.js +36 -17
- package/hooks/dist/gsd-worktree-path-guard.js +36 -21
- package/hooks/dist/gsd-write-guard.js +35 -25
- package/hooks/dist/lib/cli-exit.js +560 -0
- package/hooks/dist/lib/exit-code-registry.js +98 -0
- package/hooks/dist/lib/git-cmd.js +210 -1
- package/hooks/dist/lib/git-probe.js +84 -0
- package/hooks/dist/lib/hook-exit.js +81 -0
- package/hooks/dist/lib/injection-patterns.js +36 -6
- package/hooks/dist/managed-hooks-registry.cjs +4 -0
- package/hooks/gsd-agent-isolation-guard.js +77 -38
- package/hooks/gsd-check-update-worker.js +19 -2
- package/hooks/gsd-config-reload.js +18 -12
- package/hooks/gsd-context-monitor.js +302 -22
- package/hooks/gsd-cursor-post-tool.js +3 -1
- package/hooks/gsd-cursor-pre-tool.js +3 -1
- package/hooks/gsd-cursor-session-start.js +2 -1
- package/hooks/gsd-cursor-stop.js +2 -1
- package/hooks/gsd-cursor-subagent-start.js +28 -23
- package/hooks/gsd-cursor-subagent-stop.js +3 -1
- package/hooks/gsd-ensure-canonical-path.js +2 -1
- package/hooks/gsd-graphify-update.sh +22 -18
- package/hooks/gsd-node-runner.sh +77 -0
- package/hooks/gsd-phase-boundary.sh +1 -0
- package/hooks/gsd-prompt-guard.js +46 -12
- package/hooks/gsd-read-guard.js +18 -7
- package/hooks/gsd-read-injection-scanner.js +22 -13
- package/hooks/gsd-secret-read-guard.js +1079 -0
- package/hooks/gsd-session-state.sh +1 -0
- package/hooks/gsd-statusline.js +222 -29
- package/hooks/gsd-validate-commit.sh +523 -12
- package/hooks/gsd-windsurf-pre-command.js +16 -11
- package/hooks/gsd-windsurf-pre-write.js +22 -13
- package/hooks/gsd-workflow-guard.js +36 -17
- package/hooks/gsd-worktree-path-guard.js +36 -21
- package/hooks/gsd-write-guard.js +35 -25
- package/hooks/hooks.json +6 -0
- package/hooks/lib/cli-exit.js +560 -0
- package/hooks/lib/exit-code-registry.js +98 -0
- package/hooks/lib/git-cmd.js +210 -1
- package/hooks/lib/git-probe.js +84 -0
- package/hooks/lib/hook-exit.js +81 -0
- package/hooks/lib/injection-patterns.js +36 -6
- package/hooks/managed-hooks-registry.cjs +4 -0
- package/package.json +14 -9
- package/scripts/base64-scan.sh +74 -12
- package/scripts/build-hooks.js +12 -0
- package/scripts/check-glossary-refs.cjs +77 -15
- package/scripts/check-mutation-score-ratchet.cjs +156 -0
- package/scripts/ci-check-job-near-cap.cjs +49 -0
- package/scripts/ci-pr-mergeability.cjs +262 -0
- package/scripts/ci-test-scope.cjs +52 -12
- package/scripts/ci-timeout-report.cjs +230 -0
- package/scripts/docs-guard-registry.cjs +406 -0
- package/scripts/gen-capability-registry.cjs +8 -6
- package/scripts/gen-exit-code-docs.cjs +318 -0
- package/scripts/gen-exit-code-registry.cjs +891 -0
- package/scripts/gen-features.cjs +836 -0
- package/scripts/gen-hooks-cli-exit.cjs +239 -0
- package/scripts/gen-install-tree-fixtures.cjs +2 -2
- package/scripts/gen-loop-host-contract.cjs +189 -4
- package/scripts/gen-scripts-cli-exit.cjs +185 -0
- package/scripts/gen-state-md-docs.cjs +727 -0
- package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
- package/scripts/lib/ci-job-timing.cjs +72 -0
- package/scripts/lib/cli-exit.cjs +546 -44
- package/scripts/lib/drift-scan.cjs +32 -2
- package/scripts/lib/exit-code-registry.cjs +98 -0
- package/scripts/lib/ndjson-reporter.cjs +119 -0
- package/scripts/lib/shellcheck-fetch.cjs +247 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
- package/scripts/lint-docs-guard-registration.cjs +495 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +198 -0
- package/scripts/lint-eslint-glob-coverage.allowlist.json +4 -0
- package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
- package/scripts/lint-health-diagnostic-rule-table.cjs +65 -8
- package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
- package/scripts/lint-phase-enumeration-drift.cjs +45 -14
- package/scripts/lint-phase-id-drift.cjs +133 -8
- package/scripts/lint-planning-prompt-drift.cjs +38 -1
- package/scripts/lint-portable-grep.cjs +176 -0
- package/scripts/lint-removed-but-needed.cjs +184 -16
- package/scripts/lint-response-language-coverage.cjs +524 -0
- package/scripts/lint-seam-enforcement.cjs +182 -0
- package/scripts/lint-slug-derivation-drift.cjs +921 -0
- package/scripts/lint-source-test-name-collision.cjs +241 -0
- package/scripts/lint-state-write-path-drift.cjs +337 -432
- package/scripts/lint-test-file-count.allowlist.json +124 -4
- package/scripts/lint-test-file-count.cjs +25 -3
- package/scripts/lint-unreachable-guard-drift.cjs +51 -64
- package/scripts/lint-vendored-deps.cjs +208 -35
- package/scripts/lint-workflow-shellcheck-baseline.json +1027 -0
- package/scripts/lint-workflow-shellcheck.cjs +614 -0
- package/scripts/mutation-matrix.cjs +599 -50
- package/scripts/npm-audit-baseline.cjs +376 -0
- package/scripts/prompt-injection-scan.sh +83 -14
- package/scripts/require-issue-link-policy.cjs +16 -1
- package/scripts/secret-scan.sh +75 -13
- package/scripts/select-docs-guards.cjs +56 -0
- package/scripts/sync-runtime-launcher.cjs +22 -3
- package/skills/gsd-discuss-phase/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -1
- package/skills/gsd-import/SKILL.md +1 -1
- package/skills/gsd-ns-workflow/SKILL.md +1 -0
- package/skills/gsd-phase/SKILL.md +1 -1
- package/skills/gsd-quick/SKILL.md +8 -4
- package/skills/gsd-quick-batch/SKILL.md +105 -0
- package/skills/gsd-surface/SKILL.md +18 -8
- package/vscode/package.json +1 -1
- package/bin/lib/ui-safety-gate.cjs +0 -109
- package/scripts/lint-emitted-drift-ack.cjs +0 -344
- package/scripts/state-write-path-drift-baseline.json +0 -19
|
@@ -29,10 +29,28 @@ const phaseIdModule = require("./phase-id.cjs");
|
|
|
29
29
|
const { phaseMarkdownRegexSource, stripProjectCodePrefix, OPTIONAL_PHASE_TAG_SOURCE,
|
|
30
30
|
// #2121: roadmapPhaseLookupSources now lives in phase-id.cjs (single owner of
|
|
31
31
|
// the lookup-source ordering); imported here rather than defined locally.
|
|
32
|
-
roadmapPhaseLookupSources, extractPhaseToken, isSentinelPhaseId,
|
|
32
|
+
roadmapPhaseLookupSources, extractPhaseToken, isSentinelPhaseId,
|
|
33
|
+
// #3641: the single-owner heading-intro and digit-token grammar sources —
|
|
34
|
+
// see BRACKET_PHASE_ENTRY_HEADING_RE below.
|
|
35
|
+
PHASE_HEADING_PREFIX_SRC, PHASE_NUMBER_TOKEN_SOURCE, phaseHeadingPrefixSrcFor, PHASE_HEADING_BASELINE,
|
|
36
|
+
// #612: the disk-side milestone filter resolves bracket directories through
|
|
37
|
+
// the owner's gated helpers rather than spelling the grammar a second time.
|
|
38
|
+
phaseTokenMatches,
|
|
39
|
+
// #2761 B1: the version-less bracket milestone boundary (computeSectionEnd /
|
|
40
|
+
// preambleCutoff, below) is built from this single-owner source rather than a
|
|
41
|
+
// re-typed bracket-id literal.
|
|
42
|
+
BRACKET_ID_SRC,
|
|
43
|
+
// #2761 M3: the PINNED bracket milestone intro, owning the pad2 spelling rule
|
|
44
|
+
// as well as the grammar — consumed by the bracket-fallback selector below,
|
|
45
|
+
// which re-typed the project-code class and restated the padding rule.
|
|
46
|
+
bracketMilestoneIntroSrcFor,
|
|
47
|
+
// #2761 B1 (round-2 fix): fold-before-identity for the SAME-MILESTONE
|
|
48
|
+
// continuation check in isBracketMilestoneBoundary, below — the branch's own
|
|
49
|
+
// convention (matches bracketQualifiedKey/isSentinelPhaseId).
|
|
50
|
+
foldBracketId, } = phaseIdModule;
|
|
33
51
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
34
52
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
35
|
-
const { planningDir } = planningWorkspace;
|
|
53
|
+
const { planningDir, resolvePhaseIdConvention } = planningWorkspace;
|
|
36
54
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
37
55
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
38
56
|
const unusableInputMod = require("./unusable-input.cjs");
|
|
@@ -60,6 +78,21 @@ function isClosedMilestoneHeading(headingText) {
|
|
|
60
78
|
function stripShippedMilestones(content) {
|
|
61
79
|
return (0, markdown_sectionizer_cjs_1.stripTaggedBlocks)(content, 'details');
|
|
62
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* #3982: strip only <details> blocks whose <summary> marks a CLOSED milestone
|
|
83
|
+
* (ARCHIVED/SHIPPED/✅/… without an active marker) — the narrow form of
|
|
84
|
+
* stripShippedMilestones the current-milestone window needs. A blanket strip
|
|
85
|
+
* would delete the ACTIVE milestone's own collapsed blocks (#1341) and
|
|
86
|
+
* reproduce the phase_count: 0 class of #557/#2947.
|
|
87
|
+
*/
|
|
88
|
+
function stripClosedMilestoneDetails(content) {
|
|
89
|
+
return content.replace(/<details\b[^>]*>[\s\S]*?<\/details>/gi, (block) => {
|
|
90
|
+
const summaryMatch = block.match(/<summary[^>]*>([^<]*)<\/summary>/i);
|
|
91
|
+
if (!summaryMatch)
|
|
92
|
+
return block;
|
|
93
|
+
return isClosedMilestoneHeading(summaryMatch[1]) ? '' : block;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
63
96
|
/**
|
|
64
97
|
* #2562: is the milestone `version` marked SHIPPED by the ROADMAP itself?
|
|
65
98
|
*
|
|
@@ -96,6 +129,129 @@ function isMilestoneShippedInRoadmap(content, version) {
|
|
|
96
129
|
}
|
|
97
130
|
return false;
|
|
98
131
|
}
|
|
132
|
+
// #2761 B1: matches a bracket MILESTONE heading's intro (`[GSD.02]`) at the
|
|
133
|
+
// START of a heading's text, capturing the bracket id in group 1. Shared by
|
|
134
|
+
// isBracketMilestoneBoundary below — the ONE recognizer for "is this heading
|
|
135
|
+
// bracket-shaped", so computeSectionEnd and the preambleCutoff scan cannot
|
|
136
|
+
// independently drift on what counts as bracket-shaped (the drift that
|
|
137
|
+
// produced Blocker 3 in the round-2 review).
|
|
138
|
+
const BRACKET_HEADING_INTRO_RE = new RegExp(`^\\[(${BRACKET_ID_SRC})\\]`, 'i');
|
|
139
|
+
// #2761 B2 (round-2 fix, Blocker 2): ADR-612 Decision 1's own discriminator
|
|
140
|
+
// (docs/adr/612-bracket-phase-id-convention.md:56) — "a phase heading is a
|
|
141
|
+
// bracket followed by a digit-then-colon (`[GSD.02] 05:`); a milestone
|
|
142
|
+
// heading is a bracket followed by a name" — is CONTENT, not heading level.
|
|
143
|
+
// The prior `h.level <= 2` level cap broke on a level-3 bracket milestone
|
|
144
|
+
// heading (`### [GSD.02] Foundation`): its own level-3 phase children
|
|
145
|
+
// (`#### [GSD.02] 01: One`) never reached this check at all (excluded
|
|
146
|
+
// upstream by the `h.level > level` sibling-depth filter), but a level-3
|
|
147
|
+
// SIBLING milestone heading (`### [GSD.03] Later`) was ALSO excluded by the
|
|
148
|
+
// level cap, so the section ran to EOF instead of stopping there — trek-e's
|
|
149
|
+
// original #612 defect, reopened on any milestone heading below level 2.
|
|
150
|
+
//
|
|
151
|
+
// Built by interpolating phase-id.cts's single-owner
|
|
152
|
+
// phaseHeadingPrefixSrcFor (the SAME intro grammar getMilestonePhaseFilter's
|
|
153
|
+
// heading counter and extractRetiredPhaseNumbers already compile) plus the
|
|
154
|
+
// digit + optional-tag + colon tail every phase-heading counter in this file
|
|
155
|
+
// already spells (mirrors the `([\w][\w.-]*)(?:\s*\([^)\n]{0,200}\))?\s*:`
|
|
156
|
+
// shape at :nnn below) — not a re-typed grammar. Covers the dotted sub-phase
|
|
157
|
+
// heading form too (`[GSD.02] 05.03:`) via the same `[\w][\w.-]*` token,
|
|
158
|
+
// which admits an embedded `.`.
|
|
159
|
+
const BRACKET_PHASE_TAIL_RE = new RegExp(`^${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.ANY_BRACKET, 'bracket', false)}[\\w][\\w.-]*(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:`, 'i');
|
|
160
|
+
/**
|
|
161
|
+
* #2761 B1/B2 (round-2 fixes): is `headingText` (hashes STRIPPED — the
|
|
162
|
+
* `tokenizeHeadings` `HeadingToken.text` shape, and the shape the
|
|
163
|
+
* preambleCutoff scan below is made to match) a BRACKET MILESTONE boundary —
|
|
164
|
+
* as opposed to (a) a bracket PHASE heading (at any level, including the
|
|
165
|
+
* dotted sub-phase form), which must never terminate a milestone's own
|
|
166
|
+
* section, or (b) a heading that names the SAME milestone already selected,
|
|
167
|
+
* which is a CONTINUATION of the current milestone's own section (a
|
|
168
|
+
* version-less split like `## [GSD.02] Foundation (Phase Details)`), not the
|
|
169
|
+
* boundary to a DIFFERENT one?
|
|
170
|
+
*
|
|
171
|
+
* `level` is the CANDIDATE heading's own depth (`h.level`), capped at 3 — a
|
|
172
|
+
* depth-SANITY ceiling, not a phase/milestone discriminator (that job is
|
|
173
|
+
* BRACKET_PHASE_TAIL_RE, below). The cap mirrors the bracket-fallback
|
|
174
|
+
* selector's own `#{1,3}` ceiling (this file's SELECTION branch above) and
|
|
175
|
+
* `isMilestoneBounded`'s (`state.cts`) — a bracket-shaped heading deeper than
|
|
176
|
+
* either of those will ever select as a CURRENT milestone is outside the
|
|
177
|
+
* shape this function needs to discriminate at all.
|
|
178
|
+
*
|
|
179
|
+
* `selectedBracketId` is the SELECTED milestone's own bracket id, already
|
|
180
|
+
* case-folded by the caller — `null` when scoping is not bracket-gated, the
|
|
181
|
+
* selected heading is not itself bracket-shaped, or (preambleCutoff) the
|
|
182
|
+
* same-milestone check does not apply at this call site (see its own comment
|
|
183
|
+
* there) — in which case the same-milestone check below simply never fires.
|
|
184
|
+
*/
|
|
185
|
+
function isBracketMilestoneBoundary(headingText, level, selectedBracketId) {
|
|
186
|
+
if (level > 3)
|
|
187
|
+
return false;
|
|
188
|
+
const introMatch = BRACKET_HEADING_INTRO_RE.exec(headingText);
|
|
189
|
+
if (!introMatch)
|
|
190
|
+
return false;
|
|
191
|
+
// #2761 B2: a bracket PHASE heading (`[GSD.02] 05:`, or the dotted
|
|
192
|
+
// sub-phase form `[GSD.02] 05.03:`) is never a milestone boundary,
|
|
193
|
+
// regardless of level.
|
|
194
|
+
if (BRACKET_PHASE_TAIL_RE.test(headingText))
|
|
195
|
+
return false;
|
|
196
|
+
// #2761 B1: fold-before-identity — this branch's own convention
|
|
197
|
+
// (bracketQualifiedKey / isSentinelPhaseId apply the same rule).
|
|
198
|
+
if (selectedBracketId && foldBracketId(introMatch[1]) === selectedBracketId)
|
|
199
|
+
return false;
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* #2761 B1 (round-3 fix, Blocker 1 case D; hardened post-round-3; round-4
|
|
204
|
+
* fix: requires a same-id PHASE child, not merely a same-id child): does
|
|
205
|
+
* `headings[index]`'s SUBTREE — every heading strictly deeper than it, up to
|
|
206
|
+
* (not including) the next heading at or above its own level — contain a
|
|
207
|
+
* bracket-shaped, PHASE-TAIL-shaped heading with the SAME id as
|
|
208
|
+
* `headings[index]`'s own?
|
|
209
|
+
*
|
|
210
|
+
* Used ONLY at the preambleCutoff scan below, to distinguish a genuine prior
|
|
211
|
+
* or later sibling milestone — whose subtree contains a real phase heading
|
|
212
|
+
* carrying its bracket id (`## [GSD.01] Setup` / `### [GSD.01] 01: …`)
|
|
213
|
+
* — from an unrelated bracket-shaped PROSE heading sitting above the current
|
|
214
|
+
* milestone's own content. Same-id-ness alone is insufficient: round-4 F1's
|
|
215
|
+
* `[ADR.612] Heading convention` owns `[ADR.612] Examples`, but that child is
|
|
216
|
+
* milestone-shaped, not a digit-colon phase. Reuse `BRACKET_PHASE_TAIL_RE`
|
|
217
|
+
* for the phase-vs-milestone distinction instead of re-deriving it.
|
|
218
|
+
*
|
|
219
|
+
* Scan the whole subtree, not just the immediate child: a genuine milestone
|
|
220
|
+
* may open with `### Notes` before its first matching-id phase. Stopping at
|
|
221
|
+
* that first non-match leaked the prior milestone's qualified phase into the
|
|
222
|
+
* current filter (3/2/67 instead of 2/1/50 in rv2-amend1).
|
|
223
|
+
*
|
|
224
|
+
* A subtree that closes with no same-id phase hit — including a childless
|
|
225
|
+
* heading or one with only milestone-shaped same-id children — degrades to
|
|
226
|
+
* not-a-boundary. That is deliberately over-inclusive: the unmatched text
|
|
227
|
+
* remains in the preamble rather than silently discarding current content.
|
|
228
|
+
*
|
|
229
|
+
* `headings` is the same fence-aware token list the caller iterates; `index`
|
|
230
|
+
* is the candidate's own position in it.
|
|
231
|
+
*/
|
|
232
|
+
function bracketHeadingHasMatchingChild(headings, index) {
|
|
233
|
+
const candidate = headings[index];
|
|
234
|
+
const ownMatch = BRACKET_HEADING_INTRO_RE.exec(candidate.text);
|
|
235
|
+
if (!ownMatch)
|
|
236
|
+
return false;
|
|
237
|
+
const ownId = foldBracketId(ownMatch[1]);
|
|
238
|
+
for (let i = index + 1; i < headings.length; i++) {
|
|
239
|
+
const next = headings[i];
|
|
240
|
+
if (next.level <= candidate.level)
|
|
241
|
+
return false;
|
|
242
|
+
const childMatch = BRACKET_HEADING_INTRO_RE.exec(next.text);
|
|
243
|
+
// #2761 round-4: a same-id child is not enough — it must also be a PHASE
|
|
244
|
+
// (BRACKET_PHASE_TAIL_RE), or an unrelated PROSE heading whose own
|
|
245
|
+
// sub-heading merely happens to share its bracket id (F1: [ADR.612]
|
|
246
|
+
// Heading convention / [ADR.612] Examples) satisfies this rule.
|
|
247
|
+
if (childMatch && foldBracketId(childMatch[1]) === ownId && BRACKET_PHASE_TAIL_RE.test(next.text))
|
|
248
|
+
return true;
|
|
249
|
+
// Not a same-id PHASE match — keep scanning DEEPER into the subtree
|
|
250
|
+
// instead of giving up on this one heading; only a same-or-shallower
|
|
251
|
+
// heading (above) actually closes the subtree.
|
|
252
|
+
}
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
99
255
|
/**
|
|
100
256
|
* #3184 (epic #3180 Phase 2): the sole owner of "where does this milestone
|
|
101
257
|
* heading's section end". Lifted from `currentMilestoneRawRanges`'s prior
|
|
@@ -105,12 +261,12 @@ function isMilestoneShippedInRoadmap(content, version) {
|
|
|
105
261
|
* `getMilestonePhaseFilter`'s versionOverride branch all call this instead of
|
|
106
262
|
* re-deriving it.
|
|
107
263
|
*/
|
|
108
|
-
function computeMilestoneSectionEnd(content, headingText, headingStart) {
|
|
264
|
+
function computeMilestoneSectionEnd(content, headingText, headingStart, additionalBoundary, headingTokens) {
|
|
109
265
|
const level = (headingText.match(/^(#{1,3})\s/) ?? ['', '#'])[1].length;
|
|
110
266
|
const afterHeading = headingStart + headingText.length;
|
|
111
267
|
// Use tokenizeHeadings (fence-aware, offsets into original content) to find
|
|
112
268
|
// the next stop boundary without re-implementing fence detection. T4 seam migration.
|
|
113
|
-
const headings = (0, markdown_sectionizer_cjs_1.tokenizeHeadings)(content);
|
|
269
|
+
const headings = headingTokens ?? (0, markdown_sectionizer_cjs_1.tokenizeHeadings)(content);
|
|
114
270
|
for (const h of headings) {
|
|
115
271
|
if (h.offset <= headingStart)
|
|
116
272
|
continue;
|
|
@@ -121,9 +277,10 @@ function computeMilestoneSectionEnd(content, headingText, headingStart) {
|
|
|
121
277
|
// Mirrors old stopPattern: level-bounded, not a Phase heading, milestone marker
|
|
122
278
|
if (/^Phase\s+\S/i.test(h.text))
|
|
123
279
|
continue;
|
|
124
|
-
if (
|
|
125
|
-
|
|
126
|
-
|
|
280
|
+
if (/v\d+\.\d+|✅|📋|🚧/i.test(h.text))
|
|
281
|
+
return h.offset;
|
|
282
|
+
if (additionalBoundary?.(h))
|
|
283
|
+
return h.offset;
|
|
127
284
|
}
|
|
128
285
|
return content.length;
|
|
129
286
|
}
|
|
@@ -340,7 +497,7 @@ const MILESTONE_HEADING_SIGNAL_PATTERN = /v\d+\.\d+|✅|📋|🚧|\bMilestone\b/
|
|
|
340
497
|
* template or the #3204/#1761/#3185 reports exercises that shape; it is
|
|
341
498
|
* recorded here rather than hidden.
|
|
342
499
|
*/
|
|
343
|
-
function
|
|
500
|
+
function countMilestoneHeadings(content) {
|
|
344
501
|
const isPhaseHeading = (text) => /^Phase\s+\S/i.test(text);
|
|
345
502
|
let milestoneHeadingCount = 0;
|
|
346
503
|
for (const heading of (0, markdown_sectionizer_cjs_1.tokenizeHeadings)(content)) {
|
|
@@ -351,10 +508,29 @@ function hasMilestoneSectioning(content) {
|
|
|
351
508
|
if (!MILESTONE_HEADING_SIGNAL_PATTERN.test(heading.text))
|
|
352
509
|
continue;
|
|
353
510
|
milestoneHeadingCount++;
|
|
354
|
-
if (milestoneHeadingCount >= 2)
|
|
355
|
-
return true;
|
|
356
511
|
}
|
|
357
|
-
return
|
|
512
|
+
return milestoneHeadingCount;
|
|
513
|
+
}
|
|
514
|
+
function hasMilestoneSectioning(content) {
|
|
515
|
+
// The >=2 short-circuit the inline walk used to have is gone — a ROADMAP's
|
|
516
|
+
// heading count is small and tokenizeHeadings materializes the full token
|
|
517
|
+
// array regardless, so the shared walk pays nothing for it.
|
|
518
|
+
return countMilestoneHeadings(content) >= 2;
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* #3642: the >=1 sibling of `hasMilestoneSectioning`. The >=2 predicate
|
|
522
|
+
* answers SIBLING-conflation ("could two sections' phases mix") and is
|
|
523
|
+
* unchanged; but `buildStateFrontmatter`'s unbounded branch asks a question
|
|
524
|
+
* >=2 under-answers: "is there ANY milestone section whose phases a
|
|
525
|
+
* whole-document count would attribute to a milestone that matches no
|
|
526
|
+
* heading?" With exactly ONE section and an asserted milestone absent from
|
|
527
|
+
* the ROADMAP, >=2 said "flat" and the single section's phases leaked into
|
|
528
|
+
* the asserted milestone's total_phases (silent clobber of the stored
|
|
529
|
+
* value). Same walk, same vocabulary, threshold 1 — exported for that
|
|
530
|
+
* consumer only; every other consumer keeps the >=2 semantics.
|
|
531
|
+
*/
|
|
532
|
+
function hasAnyMilestoneSection(content) {
|
|
533
|
+
return countMilestoneHeadings(content) >= 1;
|
|
358
534
|
}
|
|
359
535
|
/**
|
|
360
536
|
* #3184: the sole "which heading is this milestone's" rule — locate the version's
|
|
@@ -405,14 +581,39 @@ function sliceMilestoneWindow(content, version) {
|
|
|
405
581
|
* exist" — a window containing only sentinel phases still reached the
|
|
406
582
|
* region and must read COMPLETE, not TRUNCATED.
|
|
407
583
|
*/
|
|
408
|
-
|
|
584
|
+
// #3641: the bracket-convention phase-ENTRY heading shape — ADR-612 Decision
|
|
585
|
+
// 1's own discriminator: a phase heading is a bracket followed by a
|
|
586
|
+
// DIGIT-then-colon (`### [GSD.04] 01: Name`); a bracket followed by a NAME
|
|
587
|
+
// is a milestone heading and must never count. Every fragment interpolates a
|
|
588
|
+
// single-owner export from phase-id.cts — the heading intro
|
|
589
|
+
// (PHASE_HEADING_PREFIX_SRC: a `[...]` bracket optionally followed by a
|
|
590
|
+
// `Phase ` label, or a bare `Phase ` label), the digit-bearing token
|
|
591
|
+
// (PHASE_NUMBER_TOKEN_SOURCE, which also covers the dotted sub-phase form
|
|
592
|
+
// `[GSD.02] 05.03:`), and the optional pre-colon tag
|
|
593
|
+
// (OPTIONAL_PHASE_TAG_SOURCE) — never a re-typed grammar. Tested IN
|
|
594
|
+
// ADDITION to the legacy pattern below, so bracket mode is a strict
|
|
595
|
+
// superset: mid-migration legacy-labeled headings (`Phase AUTH-101:`-style
|
|
596
|
+
// custom ids included) keep their existing recognition. Review finding: an
|
|
597
|
+
// earlier single-alternative form with a `[\\w]` token admitted
|
|
598
|
+
// `[bracket] Word:` shapes — a colon-bearing MILESTONE heading inside the
|
|
599
|
+
// window read as an entry (defeating V005 outright for that spelling) and a
|
|
600
|
+
// decoy `### [GSD.04] Notes:` outside the window manufactured a false V005
|
|
601
|
+
// while suppressing the correct V004. The digit anchor forecloses both.
|
|
602
|
+
const BRACKET_PHASE_ENTRY_HEADING_RE = new RegExp(`^${PHASE_HEADING_PREFIX_SRC}${PHASE_NUMBER_TOKEN_SOURCE}${OPTIONAL_PHASE_TAG_SOURCE}\\s*:`, 'i');
|
|
603
|
+
function hasPhaseEntries(markdown, phaseIdConvention) {
|
|
409
604
|
// #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag (literal mirror of OPTIONAL_PHASE_TAG_SOURCE).
|
|
605
|
+
// #3641: the widened grammar engages ONLY when the resolved convention is
|
|
606
|
+
// 'bracket' — a project that has not opted in runs the legacy pattern
|
|
607
|
+
// alone, byte-identically.
|
|
410
608
|
const phaseHeadingPattern = /^(?:\[[^\]]{1,200}\]\s*)?Phase\s+([\w][\w.-]*)(?:\s*\([^)\n]{0,200}\))?\s*:/i;
|
|
609
|
+
const bracketMode = phaseIdConvention === 'bracket';
|
|
411
610
|
for (const h of (0, markdown_sectionizer_cjs_1.tokenizeHeadings)(markdown)) {
|
|
412
611
|
if (h.level < 2 || h.level > 4)
|
|
413
612
|
continue;
|
|
414
613
|
if (phaseHeadingPattern.test(h.text))
|
|
415
614
|
return true;
|
|
615
|
+
if (bracketMode && BRACKET_PHASE_ENTRY_HEADING_RE.test(h.text))
|
|
616
|
+
return true;
|
|
416
617
|
}
|
|
417
618
|
// #3184 review finding: the bullet fallback must be fence-aware too, or a
|
|
418
619
|
// FENCED markdown EXAMPLE of the `- [ ] **Phase N — Name**` syntax (e.g. a
|
|
@@ -485,18 +686,35 @@ function collectTablePhaseRows(window) {
|
|
|
485
686
|
* narrower question — "which phase ids does this window declare" — where only
|
|
486
687
|
* the 999 icebox range is excluded.
|
|
487
688
|
*/
|
|
488
|
-
function
|
|
689
|
+
function scanMilestonePhaseIdSets(window, convention) {
|
|
489
690
|
const ids = new Set();
|
|
691
|
+
const qualifiedIds = new Set();
|
|
490
692
|
// Use tokenizeHeadings (fence-aware) instead of stripFencedLines + regex.
|
|
491
693
|
// T4 seam migration: phase headings inside fences are excluded automatically.
|
|
492
694
|
// #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag (literal mirror of OPTIONAL_PHASE_TAG_SOURCE).
|
|
493
|
-
|
|
695
|
+
// #612: select the heading grammar from the resolved convention and retain
|
|
696
|
+
// the bracket id so the disk-side filter can distinguish equal phase tokens
|
|
697
|
+
// belonging to different milestones.
|
|
698
|
+
const capturing = convention === 'bracket';
|
|
699
|
+
const bracketGroups = capturing ? 1 : 0;
|
|
700
|
+
const phaseHeadingPattern = new RegExp(`^${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.ANY_BRACKET, convention, capturing)}([\\w][\\w.-]*)(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:`, 'i');
|
|
494
701
|
for (const h of (0, markdown_sectionizer_cjs_1.tokenizeHeadings)(window)) {
|
|
495
702
|
if (h.level < 2 || h.level > 4)
|
|
496
703
|
continue;
|
|
497
704
|
const pm = phaseHeadingPattern.exec(h.text);
|
|
498
|
-
if (pm
|
|
499
|
-
|
|
705
|
+
if (!pm)
|
|
706
|
+
continue;
|
|
707
|
+
const bracketId = bracketGroups ? pm[1] : undefined;
|
|
708
|
+
const token = pm[1 + bracketGroups];
|
|
709
|
+
if (bracketId && isSentinelPhaseId(`${bracketId}-${token}`, 'bracket'))
|
|
710
|
+
continue;
|
|
711
|
+
// This scan's legacy contract excludes only the 999 icebox range. Keep the
|
|
712
|
+
// narrower rule so real decimal phase 00.1 is not swallowed as milestone 0.
|
|
713
|
+
if (/^999\b/.test(token))
|
|
714
|
+
continue;
|
|
715
|
+
ids.add(token);
|
|
716
|
+
if (bracketId && !token.includes('-'))
|
|
717
|
+
qualifiedIds.add(`${bracketId}-${token}`);
|
|
500
718
|
}
|
|
501
719
|
// #2199: also count bullet/checkbox phase entries (`- [ ] **Phase N — name**`)
|
|
502
720
|
// so a bullet-house-style ROADMAP populates the milestone phase set instead of
|
|
@@ -512,7 +730,16 @@ function scanMilestonePhaseIds(window) {
|
|
|
512
730
|
// filter must not collapse a table-house-style window to zero-count.
|
|
513
731
|
for (const tr of collectTablePhaseRows(window))
|
|
514
732
|
ids.add(tr.id);
|
|
515
|
-
return ids;
|
|
733
|
+
return { ids, qualifiedIds };
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Public #3262 owner contract: the declared-id Set remains directly iterable.
|
|
737
|
+
* #612's qualified bracket ids are an internal companion used only by the
|
|
738
|
+
* milestone directory filter, so extending that internal read must not break
|
|
739
|
+
* existing consumers of this exported Set (including #3577's table scan).
|
|
740
|
+
*/
|
|
741
|
+
function scanMilestonePhaseIds(window, convention) {
|
|
742
|
+
return scanMilestonePhaseIdSets(window, convention).ids;
|
|
516
743
|
}
|
|
517
744
|
/**
|
|
518
745
|
* #3262 (write-time milestone-scope guard): does this free-text value contain
|
|
@@ -543,8 +770,26 @@ function scanMilestonePhaseIds(window) {
|
|
|
543
770
|
* Fence-aware via `tokenizeHeadings`: a FENCED example of a milestone heading
|
|
544
771
|
* inside a field value does not terminate the real window, so it must not be
|
|
545
772
|
* a violation either — the parser and this guard must agree on fences.
|
|
773
|
+
*
|
|
774
|
+
* #612: `convention` is the resolved `phase_id_convention`. This predicate is
|
|
775
|
+
* defined as a MIRROR of the parser's terminator vocabulary, and on this
|
|
776
|
+
* branch that vocabulary is convention-SELECTED: `computeBracketSectionEnd`
|
|
777
|
+
* adds `isBracketMilestoneBoundary` as a terminator arm, so the ADR-canonical
|
|
778
|
+
* `## [GSD.09] Hidden` — no version token, no status emoji, not the word
|
|
779
|
+
* "Milestone" — terminates the window on an opted-in bracket repo while
|
|
780
|
+
* matching NONE of the signals above. Left unmirrored, the guard accepts
|
|
781
|
+
* exactly the description that narrows the window on the one convention this
|
|
782
|
+
* branch teaches the parser to read, which is the failure the guard exists to
|
|
783
|
+
* prevent. A non-bracket (or unresolvable) value takes the pre-existing path
|
|
784
|
+
* byte-identically.
|
|
785
|
+
*
|
|
786
|
+
* REQUIRED, not optional — the same tripwire `scanMilestonePhaseIds` carries,
|
|
787
|
+
* and for the sharper reason: a blind call here fails OPEN (the guard quietly
|
|
788
|
+
* ACCEPTS a window-narrowing description) rather than merely miscounting, so a
|
|
789
|
+
* future call site must fail to COMPILE. The census today is one caller,
|
|
790
|
+
* `assertDescriptionPreservesMilestoneScope`, which pays nothing for it.
|
|
546
791
|
*/
|
|
547
|
-
function findMilestoneScopeHeadingLines(text) {
|
|
792
|
+
function findMilestoneScopeHeadingLines(text, convention) {
|
|
548
793
|
const out = [];
|
|
549
794
|
for (const h of (0, markdown_sectionizer_cjs_1.tokenizeHeadings)(text)) {
|
|
550
795
|
if (h.level > 3)
|
|
@@ -553,6 +798,22 @@ function findMilestoneScopeHeadingLines(text) {
|
|
|
553
798
|
continue;
|
|
554
799
|
if (MILESTONE_HEADING_SIGNAL_PATTERN.test(h.text) || /🔄/.test(h.text)) {
|
|
555
800
|
out.push(h.text.trim());
|
|
801
|
+
continue;
|
|
802
|
+
}
|
|
803
|
+
// #612: the bracket arm, routed through the SAME single-owner
|
|
804
|
+
// phase-vs-milestone discriminator `computeBracketSectionEnd` consults —
|
|
805
|
+
// never a second bracket-heading grammar here.
|
|
806
|
+
//
|
|
807
|
+
// `selectedBracketId` is deliberately `null`, so the same-milestone
|
|
808
|
+
// CONTINUATION exemption never fires and a value naming the ACTIVE
|
|
809
|
+
// milestone (`## [GSD.02] Foundation (Phase Details)`) is flagged even
|
|
810
|
+
// though the real parser would treat it as a continuation. That is the
|
|
811
|
+
// third instance of this function's stated conservatism and rests on the
|
|
812
|
+
// same argument as the other two: WHICH milestone is active is a property
|
|
813
|
+
// of the document at write time, not of the text being validated, and
|
|
814
|
+
// over-rejecting is one-directional (reject more, never less).
|
|
815
|
+
if (convention === 'bracket' && isBracketMilestoneBoundary(h.text, h.level, null)) {
|
|
816
|
+
out.push(h.text.trim());
|
|
556
817
|
}
|
|
557
818
|
}
|
|
558
819
|
return out;
|
|
@@ -592,8 +853,18 @@ function classifyMilestoneWindow(input) {
|
|
|
592
853
|
* symbols, 20 direct callers) means its signature and return type do not
|
|
593
854
|
* change. This is the real owner; `extractCurrentMilestone` becomes a
|
|
594
855
|
* one-line wrapper returning `.value` so every existing caller is untouched.
|
|
856
|
+
*
|
|
857
|
+
* @param phaseIdConvention - #3641: the RESOLVED `phase_id_convention`
|
|
858
|
+
* config value, threaded to `hasPhaseEntries` so the scope axis's row-8
|
|
859
|
+
* comparison recognizes bracket-convention phase entries
|
|
860
|
+
* (`### [GSD.04] 01: Name`). Optional: absent, or any value other than
|
|
861
|
+
* `'bracket'`, compiles the legacy entry grammar byte-identically — the
|
|
862
|
+
* widening engages only for a project that resolved the convention
|
|
863
|
+
* explicitly. `extractCurrentMilestone`'s wrapper deliberately does NOT
|
|
864
|
+
* expose it (its 20 callers are not the scope-axis consumers; V005's
|
|
865
|
+
* router site and `getMilestonePhaseFilter` resolve and thread it).
|
|
595
866
|
*/
|
|
596
|
-
function extractCurrentMilestoneScoped(content, cwd, ws) {
|
|
867
|
+
function extractCurrentMilestoneScoped(content, cwd, ws, phaseIdConvention) {
|
|
597
868
|
if (!cwd) {
|
|
598
869
|
// Row 1: a deliberate unscoped read (no cwd supplied) is a real answer —
|
|
599
870
|
// the caller asked for no scoping, so whole-document is COMPLETE.
|
|
@@ -631,14 +902,49 @@ function extractCurrentMilestoneScoped(content, cwd, ws) {
|
|
|
631
902
|
versionResolved,
|
|
632
903
|
hasVersionedMilestones: versionedMilestonesPresent,
|
|
633
904
|
headingFound: false,
|
|
634
|
-
windowHasPhaseEntries: hasPhaseEntries(value),
|
|
635
|
-
documentHasPhaseEntries: hasPhaseEntries(value),
|
|
905
|
+
windowHasPhaseEntries: hasPhaseEntries(value, phaseIdConvention),
|
|
906
|
+
documentHasPhaseEntries: hasPhaseEntries(value, phaseIdConvention),
|
|
636
907
|
}),
|
|
637
908
|
};
|
|
638
909
|
}
|
|
639
|
-
const documentHasPhaseEntries = hasPhaseEntries(stripShippedMilestones(content));
|
|
910
|
+
const documentHasPhaseEntries = hasPhaseEntries(stripShippedMilestones(content), phaseIdConvention);
|
|
640
911
|
const summaryPattern = new RegExp(`<summary[^>]*>([^<]*${(0, pattern_cjs_1.escapeRegex)(version)}[^<]*)<\\/summary>`, 'i');
|
|
641
|
-
|
|
912
|
+
// #3184 keeps the version-heading locator single-owned. #612 adds a gated
|
|
913
|
+
// bracket candidate source only when that owner finds no version-bearing
|
|
914
|
+
// heading; it never replaces or re-derives the legacy lookup.
|
|
915
|
+
let headingMatches = locateMilestoneHeadings(content, version);
|
|
916
|
+
// #2761 B3: resolve the boundary convention independently of whether the
|
|
917
|
+
// version-heading owner already selected a match. Preserve an explicitly
|
|
918
|
+
// threaded convention; otherwise resolve from the same workstream whose
|
|
919
|
+
// STATE/ROADMAP this call reads (#2761 B1).
|
|
920
|
+
let bracketScopeConvention = phaseIdConvention ?? null;
|
|
921
|
+
if (phaseIdConvention === undefined) {
|
|
922
|
+
try {
|
|
923
|
+
bracketScopeConvention = resolvePhaseIdConvention(cwd, ws);
|
|
924
|
+
}
|
|
925
|
+
catch { /* unresolvable convention → preserve the legacy fallback */ }
|
|
926
|
+
}
|
|
927
|
+
if (headingMatches.length === 0 && bracketScopeConvention === 'bracket') {
|
|
928
|
+
const vMatch = version.match(/^v(\d+)/i);
|
|
929
|
+
const milestoneInt = vMatch ? parseInt(vMatch[1], 10) : NaN;
|
|
930
|
+
if (Number.isSafeInteger(milestoneInt)) {
|
|
931
|
+
// #2761 M3: the phase-id owner supplies both the bracket intro grammar
|
|
932
|
+
// and canonical pad2 spelling; `[CODE.2]` therefore cannot bound a
|
|
933
|
+
// section whose phase headings the bracket grammar rejects.
|
|
934
|
+
// #612 round-5: HeadingToken.offset is the line start, so require the
|
|
935
|
+
// `#` there to preserve the old line-start anchor's indentation parity.
|
|
936
|
+
// For every survivor, rebuilding [fullLine, fullLine] plus `.index`
|
|
937
|
+
// retains the match shape and first-match order expected downstream.
|
|
938
|
+
const bracketMilestoneHeadingRe = new RegExp(`^${bracketMilestoneIntroSrcFor(milestoneInt)}`, 'i');
|
|
939
|
+
headingMatches = (0, markdown_sectionizer_cjs_1.tokenizeHeadings)(content)
|
|
940
|
+
.filter((h) => h.level <= 3 && content[h.offset] === '#' && bracketMilestoneHeadingRe.test(h.text))
|
|
941
|
+
.map((h) => {
|
|
942
|
+
const lineEnd = content.indexOf('\n', h.offset);
|
|
943
|
+
const fullLine = content.slice(h.offset, lineEnd === -1 ? content.length : lineEnd);
|
|
944
|
+
return Object.assign([fullLine, fullLine], { index: h.offset });
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
}
|
|
642
948
|
if (headingMatches.length === 0) {
|
|
643
949
|
const summaryMatch = content.match(summaryPattern);
|
|
644
950
|
if (summaryMatch) {
|
|
@@ -666,7 +972,7 @@ function extractCurrentMilestoneScoped(content, cwd, ws) {
|
|
|
666
972
|
versionResolved,
|
|
667
973
|
hasVersionedMilestones: versionedMilestonesPresent,
|
|
668
974
|
headingFound: true,
|
|
669
|
-
windowHasPhaseEntries: hasPhaseEntries(value),
|
|
975
|
+
windowHasPhaseEntries: hasPhaseEntries(value, phaseIdConvention),
|
|
670
976
|
documentHasPhaseEntries,
|
|
671
977
|
}),
|
|
672
978
|
};
|
|
@@ -680,7 +986,7 @@ function extractCurrentMilestoneScoped(content, cwd, ws) {
|
|
|
680
986
|
versionResolved,
|
|
681
987
|
hasVersionedMilestones: versionedMilestonesPresent,
|
|
682
988
|
headingFound: false,
|
|
683
|
-
windowHasPhaseEntries: hasPhaseEntries(value),
|
|
989
|
+
windowHasPhaseEntries: hasPhaseEntries(value, phaseIdConvention),
|
|
684
990
|
documentHasPhaseEntries,
|
|
685
991
|
}),
|
|
686
992
|
};
|
|
@@ -691,16 +997,174 @@ function extractCurrentMilestoneScoped(content, cwd, ws) {
|
|
|
691
997
|
// #3184: selection collapses to the sole owner; `allMatches` is still needed
|
|
692
998
|
// below (offsets, detailsMatch search), so only the selection itself routes
|
|
693
999
|
// through `selectMilestoneHeading` rather than the whole block.
|
|
694
|
-
|
|
1000
|
+
// Preserve the canonical legacy selector, then fall back to the gated
|
|
1001
|
+
// bracket candidates above when a name-only bracket milestone carries no
|
|
1002
|
+
// version token for that selector to find.
|
|
1003
|
+
const selected = selectMilestoneHeading(content, version)
|
|
1004
|
+
?? allMatches.find((m) => !isClosed(m[1]))
|
|
1005
|
+
?? firstMatch;
|
|
695
1006
|
const sectionStart = selected.index;
|
|
696
|
-
|
|
1007
|
+
// #2761 B1: a bracket heading bearing the selected milestone's own id is
|
|
1008
|
+
// a continuation, not a boundary. Derive the selected id once and compose
|
|
1009
|
+
// the shared discriminator with upstream's centralized section-end walk.
|
|
1010
|
+
const bracketBoundaryActive = bracketScopeConvention === 'bracket';
|
|
1011
|
+
const selectedBracketMatch = bracketBoundaryActive
|
|
1012
|
+
? selected[0].match(new RegExp(`^#{1,3}\\s+\\[(${BRACKET_ID_SRC})\\]`, 'i'))
|
|
1013
|
+
: null;
|
|
1014
|
+
const selectedBracketId = selectedBracketMatch ? foldBracketId(selectedBracketMatch[1]) : null;
|
|
1015
|
+
// #2761 B3: tokenize once for both the centralized section-end owner and
|
|
1016
|
+
// the bracket preamble scan below. This keeps both boundary decisions
|
|
1017
|
+
// fence-aware without restoring the local section walker retired by #3184.
|
|
1018
|
+
const currentMilestoneHeadings = (0, markdown_sectionizer_cjs_1.tokenizeHeadings)(content);
|
|
1019
|
+
const bracketBoundary = bracketBoundaryActive
|
|
1020
|
+
? (heading) => isBracketMilestoneBoundary(heading.text, heading.level, selectedBracketId)
|
|
1021
|
+
: undefined;
|
|
1022
|
+
const sectionEnd = computeMilestoneSectionEnd(content, selected[0], sectionStart, bracketBoundary, currentMilestoneHeadings);
|
|
697
1023
|
const anyMilestonePattern = /^#{1,3}\s+(?!Phase\s+\S)(?:.*v\d+\.\d+|✅|📋|🚧)/im;
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
1024
|
+
let earliestMilestoneIndex;
|
|
1025
|
+
if (!bracketBoundaryActive) {
|
|
1026
|
+
// #2761 B3: the LEGACY (non-bracket-shaped) path stays a raw
|
|
1027
|
+
// `content.match` — byte-identical to before this fix, including its
|
|
1028
|
+
// fence-blindness. That hazard is real (a fenced `## Milestone v9.0`
|
|
1029
|
+
// example in the preamble reads identically wrong at base, round-1, and
|
|
1030
|
+
// HEAD — repro12's LEGACY control) but is PRE-EXISTING and shared with
|
|
1031
|
+
// the ORIGINAL (pre-#612) code path, not introduced by this branch —
|
|
1032
|
+
// fixing it is explicitly out of scope (round-2 review's own
|
|
1033
|
+
// minimal-fix note).
|
|
1034
|
+
const versionMilestoneMatch = content.match(anyMilestonePattern);
|
|
1035
|
+
earliestMilestoneIndex = versionMilestoneMatch ? versionMilestoneMatch.index : null;
|
|
1036
|
+
}
|
|
1037
|
+
else {
|
|
1038
|
+
// #2761 Major 1 (round-3 fix): on the BRACKET branch, derive the
|
|
1039
|
+
// version/emoji half of "earliest milestone-shaped heading" from the
|
|
1040
|
+
// SAME fence-aware `currentMilestoneHeadings` token list too, instead of
|
|
1041
|
+
// the raw `content.match` above. Round-2 (ff6bf0a8) fixed the BRACKET
|
|
1042
|
+
// half's fence-blindness but left THIS half a raw regex even on this
|
|
1043
|
+
// branch: a fenced VERSION-BEARING example heading in a bracket repo's
|
|
1044
|
+
// preamble (`` ```markdown\n## Milestone v9.0: Example\n``` ``, ADR-612's
|
|
1045
|
+
// own docs illustrate the LEGACY heading shape exactly this way) was
|
|
1046
|
+
// still textually the earliest match for the raw regex, winning the old
|
|
1047
|
+
// min() and un-suppressing a wrong persisted 75% that base correctly
|
|
1048
|
+
// suppressed (rv-attack3c fixture C1) — B3 fixed only the half of the
|
|
1049
|
+
// asymmetry it introduced, not this pre-existing half once it also
|
|
1050
|
+
// started reaching the bracket branch. The predicate below (`h.text`
|
|
1051
|
+
// against the same `/^Phase\s+\S/i` / `/v\d+\.\d+|✅|📋|🚧/i` pair
|
|
1052
|
+
// `computeSectionEnd` already uses) never sees a fenced heading at all,
|
|
1053
|
+
// because tokenizeHeadings never produces a token for one.
|
|
1054
|
+
//
|
|
1055
|
+
// Hardened post-round-3: the raw `content.match(anyMilestonePattern)`
|
|
1056
|
+
// this replaced was anchored `^#{1,3}\s+…` — a level cap the token loop
|
|
1057
|
+
// dropped entirely. A level-4+ version-bearing heading in the preamble
|
|
1058
|
+
// (`#### v2.0 notes`) would win this scan where the raw pattern on the
|
|
1059
|
+
// legacy path ignores it outright, cutting the preamble at a heading
|
|
1060
|
+
// neither the selector nor `isMilestoneBounded` would ever treat as a
|
|
1061
|
+
// milestone marker. Mirrors the depth-sanity cap
|
|
1062
|
+
// `isBracketMilestoneBoundary` already applies to the bracket half.
|
|
1063
|
+
earliestMilestoneIndex = null;
|
|
1064
|
+
for (const h of currentMilestoneHeadings) {
|
|
1065
|
+
if (h.level > 3)
|
|
1066
|
+
continue;
|
|
1067
|
+
if (/^Phase\s+\S/i.test(h.text))
|
|
1068
|
+
continue;
|
|
1069
|
+
if (/v\d+\.\d+|✅|📋|🚧/i.test(h.text)) {
|
|
1070
|
+
earliestMilestoneIndex = h.offset;
|
|
1071
|
+
break;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
if (bracketBoundaryActive) {
|
|
1076
|
+
// #2761 B3: scans the SAME fence-aware `currentMilestoneHeadings` token
|
|
1077
|
+
// list computeSectionEnd consumes, instead of a raw `content.matchAll` —
|
|
1078
|
+
// closes the asymmetry between the two halves of one boundary semantic.
|
|
1079
|
+
// Before this fix, a fenced markdown example containing a bracket
|
|
1080
|
+
// heading (ADR-612's own docs do exactly this) was textually the
|
|
1081
|
+
// earliest `#{1,3} [CODE.MM]` match, so `preambleCutoff` landed INSIDE
|
|
1082
|
+
// the fence, `preamble` ended with an unclosed opener, and
|
|
1083
|
+
// `getMilestonePhaseFilter`'s tokenizeHeadings(scope) call then saw an
|
|
1084
|
+
// unbalanced fence and swallowed every real heading, degrading to a
|
|
1085
|
+
// pass-all filter (repro11). tokenizeHeadings already strips fenced
|
|
1086
|
+
// lines before a heading candidate is ever produced, so a heading INSIDE
|
|
1087
|
+
// a fence is never a candidate here at all.
|
|
1088
|
+
//
|
|
1089
|
+
// `h.text` is ALREADY hash-stripped and trimmed (HeadingToken's own
|
|
1090
|
+
// shape) — isBracketMilestoneBoundary is built to consume exactly that,
|
|
1091
|
+
// so no `^#{1,3}\s+` re-derivation is needed (that spelling would not
|
|
1092
|
+
// match `h.text` — it still carries the hashes in a raw regex match).
|
|
1093
|
+
for (let i = 0; i < currentMilestoneHeadings.length; i++) {
|
|
1094
|
+
const h = currentMilestoneHeadings[i];
|
|
1095
|
+
let isBoundary;
|
|
1096
|
+
if (h.offset === sectionStart) {
|
|
1097
|
+
// #2761 B1 (round-3 fix): the SELECTED heading's own occurrence is
|
|
1098
|
+
// ALWAYS a correct earliest answer to "where does milestone content
|
|
1099
|
+
// begin" — bypass BOTH the same-milestone check inside
|
|
1100
|
+
// isBracketMilestoneBoundary (which would otherwise reject this
|
|
1101
|
+
// heading against ITSELF, since `selectedBracketId` is its own id)
|
|
1102
|
+
// and the same-id-child rule below (which would reject a genuinely
|
|
1103
|
+
// childless CURRENT milestone, e.g. one with no phases populated
|
|
1104
|
+
// yet). Without this, a same-milestone heading EARLIER than the
|
|
1105
|
+
// selected one (a version-less checklist/overview split, or the
|
|
1106
|
+
// version-bearing heading landing on the LATER half of such a
|
|
1107
|
+
// split — Blocker 1 round-3 cases A/B) would incorrectly win via the
|
|
1108
|
+
// OLD `null`-everywhere behaviour, or (with the same-milestone
|
|
1109
|
+
// check alone reinstated) the selected heading would incorrectly
|
|
1110
|
+
// reject itself and fall through to a stray, unrelated LATER
|
|
1111
|
+
// heading.
|
|
1112
|
+
isBoundary = true;
|
|
1113
|
+
}
|
|
1114
|
+
else if (isBracketMilestoneBoundary(h.text, h.level, selectedBracketId)) {
|
|
1115
|
+
// #2761 B1 (round-3 fix, Blocker 1 case D): bracket-shaped, not
|
|
1116
|
+
// phase-tail-shaped, and not the SAME id as the selected milestone
|
|
1117
|
+
// is not enough — an unrelated bracket-shaped PROSE heading
|
|
1118
|
+
// (`## [ADR.612] Heading convention used by this roadmap`) reads as
|
|
1119
|
+
// a genuine boundary by those rules alone. Require its own next
|
|
1120
|
+
// DEEPER heading to carry ITS bracket id too — the property every
|
|
1121
|
+
// genuine sibling MILESTONE has (its own phase children) and no
|
|
1122
|
+
// unrelated prose heading does.
|
|
1123
|
+
//
|
|
1124
|
+
// #2761 round-4 Minor 1 (docstring correction — no code change): a
|
|
1125
|
+
// heading rejected here (no same-id PHASE child — see
|
|
1126
|
+
// bracketHeadingHasMatchingChild's own comment) leaves its WHOLE
|
|
1127
|
+
// SUBTREE in the preamble, not merely its own inert heading text.
|
|
1128
|
+
// That subtree can still contain a DIFFERENT-id bracket PHASE
|
|
1129
|
+
// heading, which DOES form a qualified key and CAN admit a foreign
|
|
1130
|
+
// directory (F7: `## [GSD.01] Setup` / `### [GSD.07] 01: Foreign` —
|
|
1131
|
+
// no same-id child, so `[GSD.01] Setup` is not a boundary, and
|
|
1132
|
+
// `GSD.07-01-foreign`'s directory is admitted into the CURRENT
|
|
1133
|
+
// milestone's filter, reading 3/2/67%). This is NOT a regression —
|
|
1134
|
+
// base, round-1 and HEAD all read 3/2/67% on F7 (base via its own
|
|
1135
|
+
// pass-all degrade) — and it remains the declared OVER-inclusive,
|
|
1136
|
+
// never under-inclusive, safe direction; it is a narrower and more
|
|
1137
|
+
// honest claim than "contributes nothing to any phase count", which
|
|
1138
|
+
// is true of the candidate's OWN text but not of what its subtree
|
|
1139
|
+
// can carry.
|
|
1140
|
+
isBoundary = bracketHeadingHasMatchingChild(currentMilestoneHeadings, i);
|
|
1141
|
+
}
|
|
1142
|
+
else {
|
|
1143
|
+
isBoundary = false;
|
|
1144
|
+
}
|
|
1145
|
+
if (!isBoundary)
|
|
1146
|
+
continue;
|
|
1147
|
+
if (earliestMilestoneIndex === null || h.offset < earliestMilestoneIndex) {
|
|
1148
|
+
earliestMilestoneIndex = h.offset;
|
|
1149
|
+
}
|
|
1150
|
+
break;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
const preambleCutoff = earliestMilestoneIndex !== null
|
|
1154
|
+
? earliestMilestoneIndex
|
|
701
1155
|
: firstMatch.index;
|
|
702
1156
|
const beforeMilestones = content.slice(0, preambleCutoff);
|
|
703
|
-
|
|
1157
|
+
// #3982: newest-first roadmaps collapse their archives into <details>
|
|
1158
|
+
// blocks BELOW the active milestone, whose titles live in <summary> tags —
|
|
1159
|
+
// not headings — so no milestone-shaped heading bounds the section walk and
|
|
1160
|
+
// the raw slice runs to end-of-document. Strip CLOSED milestone details
|
|
1161
|
+
// blocks here so the window never feeds archived phases to the
|
|
1162
|
+
// lowest-outstanding scan. Deliberately NOT a blanket strip: the active
|
|
1163
|
+
// milestone may legitimately hold its own collapsed <details> (#1341), and
|
|
1164
|
+
// removing that would trade this bug for the phase_count: 0 class (#557).
|
|
1165
|
+
// Gated on the same isClosedMilestoneHeading the file already applies to
|
|
1166
|
+
// <summary> lines, per the issue's prescribed narrower fix.
|
|
1167
|
+
const currentSection = stripClosedMilestoneDetails(content.slice(sectionStart, sectionEnd));
|
|
704
1168
|
// Multi-milestone roadmaps split each added milestone across two version-bearing
|
|
705
1169
|
// headings: a `## Phases` checklist subsection (early) and a dedicated
|
|
706
1170
|
// `## Milestone … (Phase Details)` section (late) holding the `### Phase N:`
|
|
@@ -722,7 +1186,7 @@ function extractCurrentMilestoneScoped(content, cwd, ws) {
|
|
|
722
1186
|
(m.index ?? 0) >= sectionEnd);
|
|
723
1187
|
if (detailsMatch) {
|
|
724
1188
|
const detailsStart = detailsMatch.index ?? 0;
|
|
725
|
-
detailsSection = content.slice(detailsStart, computeMilestoneSectionEnd(content, detailsMatch[0], detailsStart));
|
|
1189
|
+
detailsSection = content.slice(detailsStart, computeMilestoneSectionEnd(content, detailsMatch[0], detailsStart, bracketBoundary, currentMilestoneHeadings));
|
|
726
1190
|
}
|
|
727
1191
|
// #2947: the preamble strip removes `### Phase N:` detail headings from the
|
|
728
1192
|
// pre-milestone region so they don't duplicate the ones inside the selected
|
|
@@ -756,7 +1220,7 @@ function extractCurrentMilestoneScoped(content, cwd, ws) {
|
|
|
756
1220
|
versionResolved,
|
|
757
1221
|
hasVersionedMilestones: versionedMilestonesPresent,
|
|
758
1222
|
headingFound: true,
|
|
759
|
-
windowHasPhaseEntries: hasPhaseEntries(value),
|
|
1223
|
+
windowHasPhaseEntries: hasPhaseEntries(value, phaseIdConvention),
|
|
760
1224
|
documentHasPhaseEntries,
|
|
761
1225
|
}),
|
|
762
1226
|
};
|
|
@@ -1248,6 +1712,23 @@ function getMilestoneInfo(cwd) {
|
|
|
1248
1712
|
*/
|
|
1249
1713
|
function getMilestonePhaseFilter(cwd, versionOverride, phaseIdConvention, ws) {
|
|
1250
1714
|
const milestonePhaseNums = new Set();
|
|
1715
|
+
// #612: the milestone-QUALIFIED form (`{CODE}.{MM}-{PP}`) of each in-scope
|
|
1716
|
+
// bracket heading, kept in its OWN set — deliberately not in
|
|
1717
|
+
// milestonePhaseNums. A qualified id ALWAYS contains a hyphen, so putting one
|
|
1718
|
+
// there would flip `roadmapUsesHyphenedIds` below on EVERY bracket repo, which
|
|
1719
|
+
// swaps `numericRe` to the continuation-segment variant and silently moves the
|
|
1720
|
+
// LEGACY dir path. Separate set; `phaseCount` is unchanged.
|
|
1721
|
+
//
|
|
1722
|
+
// Stated exactly, because the narrower claim is the true one: this keeps
|
|
1723
|
+
// QUALIFIED IDS out of that flag's input, not hyphens in general. A heading
|
|
1724
|
+
// whose TOKEN carries its own hyphen (`### [GSD.02] Phase 02-01:`) still flips
|
|
1725
|
+
// it through milestonePhaseNums — as it also does at base, which matches that
|
|
1726
|
+
// spelling through the un-widened intro. See the token-hyphen guard below.
|
|
1727
|
+
const milestoneQualifiedIds = new Set();
|
|
1728
|
+
// Hoisted out of the try so the DIR side can select the same grammar the
|
|
1729
|
+
// HEADING side selected. The two halves of this one filter reading different
|
|
1730
|
+
// conventions is exactly the defect the bracket branch below closes.
|
|
1731
|
+
let headingConvention;
|
|
1251
1732
|
let missingExplicitVersion = false;
|
|
1252
1733
|
let versionScoped = false;
|
|
1253
1734
|
let versionSectionFound = false;
|
|
@@ -1257,7 +1738,7 @@ function getMilestonePhaseFilter(cwd, versionOverride, phaseIdConvention, ws) {
|
|
|
1257
1738
|
const roadmapContent = (0, shell_command_projection_cjs_1.platformReadSync)(roadmapPath);
|
|
1258
1739
|
if (roadmapContent === null)
|
|
1259
1740
|
throw new Error('missing');
|
|
1260
|
-
const scopedResult = extractCurrentMilestoneScoped(roadmapContent, cwd, ws);
|
|
1741
|
+
const scopedResult = extractCurrentMilestoneScoped(roadmapContent, cwd, ws, phaseIdConvention);
|
|
1261
1742
|
let roadmap = scopedResult.value;
|
|
1262
1743
|
// Default: the filter's window IS extractCurrentMilestoneScoped's own
|
|
1263
1744
|
// window (reused verbatim, not re-derived — ADR-3180 Decision 4c).
|
|
@@ -1289,7 +1770,7 @@ function getMilestonePhaseFilter(cwd, versionOverride, phaseIdConvention, ws) {
|
|
|
1289
1770
|
// via `currentVersion`), and `buildStateFrontmatter` (state.cts:1700,
|
|
1290
1771
|
// via `storedMilestone`).
|
|
1291
1772
|
const sliced = sliceMilestoneWindow(roadmapContent, versionOverride);
|
|
1292
|
-
const documentHasPhaseEntries = hasPhaseEntries(stripShippedMilestones(roadmapContent));
|
|
1773
|
+
const documentHasPhaseEntries = hasPhaseEntries(stripShippedMilestones(roadmapContent), phaseIdConvention);
|
|
1293
1774
|
if (sliced !== null) {
|
|
1294
1775
|
versionScoped = true;
|
|
1295
1776
|
versionSectionFound = true;
|
|
@@ -1312,16 +1793,25 @@ function getMilestonePhaseFilter(cwd, versionOverride, phaseIdConvention, ws) {
|
|
|
1312
1793
|
versionResolved: true,
|
|
1313
1794
|
hasVersionedMilestones: hasVersionedMilestonesGlobal,
|
|
1314
1795
|
headingFound: sliced !== null,
|
|
1315
|
-
windowHasPhaseEntries: hasPhaseEntries(roadmap),
|
|
1796
|
+
windowHasPhaseEntries: hasPhaseEntries(roadmap, phaseIdConvention),
|
|
1316
1797
|
documentHasPhaseEntries,
|
|
1317
1798
|
});
|
|
1318
1799
|
}
|
|
1319
|
-
//
|
|
1320
|
-
//
|
|
1321
|
-
//
|
|
1322
|
-
|
|
1800
|
+
// Resolve once, then thread the same answer through the shared scan and
|
|
1801
|
+
// directory matcher. Resolve an omitted value from this call's `ws`;
|
|
1802
|
+
// explicit null still means "resolved and non-bracket." A split convention
|
|
1803
|
+
// would widen the ROADMAP side while leaving bracket directories unmatched.
|
|
1804
|
+
headingConvention = phaseIdConvention === undefined
|
|
1805
|
+
? resolvePhaseIdConvention(cwd, ws)
|
|
1806
|
+
: phaseIdConvention;
|
|
1807
|
+
// #3262 remains the single owner of the phase-set scan. #612 extends its
|
|
1808
|
+
// return with qualified bracket ids rather than restoring the superseded
|
|
1809
|
+
// inline duplicate this commit was originally written against.
|
|
1810
|
+
const scanned = scanMilestonePhaseIdSets(roadmap, headingConvention);
|
|
1811
|
+
for (const id of scanned.ids)
|
|
1323
1812
|
milestonePhaseNums.add(id);
|
|
1324
|
-
|
|
1813
|
+
for (const qualified of scanned.qualifiedIds)
|
|
1814
|
+
milestoneQualifiedIds.add(qualified);
|
|
1325
1815
|
}
|
|
1326
1816
|
catch {
|
|
1327
1817
|
/* best-effort (#2245 audit): the real throw source is platformReadSync
|
|
@@ -1372,6 +1862,28 @@ function getMilestonePhaseFilter(cwd, versionOverride, phaseIdConvention, ws) {
|
|
|
1372
1862
|
// phase-id-owner: the [A-Za-z] letter class does real case handling here — this regex carries NO /i flag; kept literal, not source-byte-equal to the canonical PHASE_NUMBER_TOKEN_SOURCE.
|
|
1373
1863
|
: /^0*(\d+[A-Za-z]?(?:\.\d+)*)/;
|
|
1374
1864
|
function isDirInMilestone(dirName) {
|
|
1865
|
+
// #612: the DIR side of this filter, selected by the same convention the
|
|
1866
|
+
// heading side selected. Without it the heading scan's new bracket reach was
|
|
1867
|
+
// half a fix: `milestonePhaseNums` became non-empty, so the pass-all degrade
|
|
1868
|
+
// stopped firing, but no bracket directory could satisfy the three legacy
|
|
1869
|
+
// checks below (numericRe fails on `GSD.02-05-five`, the custom-id match
|
|
1870
|
+
// captures the project code `GSD`, and stripProjectCodePrefix does not strip
|
|
1871
|
+
// a dotted prefix) — so EVERY bracket directory was rejected and
|
|
1872
|
+
// completed_phases / total_plans / completed_plans / percent collapsed to 0
|
|
1873
|
+
// while `state sync` went on writing a percent off the unfiltered disk.
|
|
1874
|
+
//
|
|
1875
|
+
// Matching is delegated to phaseTokenMatches against the milestone-QUALIFIED
|
|
1876
|
+
// id, not the bare token: READING-B puts the milestone in the bracket, so
|
|
1877
|
+
// `GSD.01-01-old-one` and `GSD.02-01-one` share the token `01` and only the
|
|
1878
|
+
// qualified key separates them. That is the scoping this filter exists to do.
|
|
1879
|
+
// ADDITIVE: on a miss we fall through to the three legacy checks, so a
|
|
1880
|
+
// bracket repo carrying legacy-shaped directories reads exactly as before.
|
|
1881
|
+
if (headingConvention === 'bracket') {
|
|
1882
|
+
for (const qualified of milestoneQualifiedIds) {
|
|
1883
|
+
if (phaseTokenMatches(dirName, qualified, 'bracket'))
|
|
1884
|
+
return true;
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1375
1887
|
const m2 = dirName.match(numericRe);
|
|
1376
1888
|
if (m2 && normalized.has(normalizePhaseIdSegments(m2[1]).toLowerCase()))
|
|
1377
1889
|
return true;
|
|
@@ -1439,6 +1951,24 @@ function getMilestonePhaseFilter(cwd, versionOverride, phaseIdConvention, ws) {
|
|
|
1439
1951
|
* `computeMilestoneSectionEnd` owner (#3184), so there is no separate copy to
|
|
1440
1952
|
* keep in sync. Returns null when there is no versioned active milestone;
|
|
1441
1953
|
* callers then fall back to whole-content mutation (the prior behaviour).
|
|
1954
|
+
*
|
|
1955
|
+
* #2761 (round-2 review, Minor 3 — latent, currently harmless): this function
|
|
1956
|
+
* consumes #3184's shared owners, but it does not pass the bracket-specific
|
|
1957
|
+
* B1/B2 boundary predicate and still has no bracket-fallback SELECTION branch:
|
|
1958
|
+
* it returns null when the version-string owner finds nothing, unlike
|
|
1959
|
+
* `extractCurrentMilestoneScoped`. The read owner can therefore scope a
|
|
1960
|
+
* bracket ROADMAP this write-range consumer still calls unscoped. Probed and
|
|
1961
|
+
* confirmed harmless TODAY: this
|
|
1962
|
+
* function's single consumer (`mutateMilestonePhase`, src/phase.cts) falls
|
|
1963
|
+
* back to whole-content mutation when it returns null, and every mutation
|
|
1964
|
+
* inside that caller is still `Phase`-labelled-only (not bracket-widened) per
|
|
1965
|
+
* the changeset's own "READ-path opt-in until the migrator and write path
|
|
1966
|
+
* land" — so a bracket ROADMAP's checkbox/heading patterns never match inside
|
|
1967
|
+
* that fallback and nothing is mutated cross-milestone. The moment the write
|
|
1968
|
+
* path is widened (PR-3+), this divergence becomes live: the whole-content
|
|
1969
|
+
* fallback would become a cross-milestone writer, which is exactly what this
|
|
1970
|
+
* note exists to prevent. Bracket-widen this function in lockstep with the
|
|
1971
|
+
* write path landing, not before.
|
|
1442
1972
|
*/
|
|
1443
1973
|
function currentMilestoneRawRanges(content, cwd) {
|
|
1444
1974
|
if (!cwd)
|
|
@@ -1508,6 +2038,8 @@ module.exports = {
|
|
|
1508
2038
|
sliceMilestoneWindow,
|
|
1509
2039
|
hasVersionedMilestones,
|
|
1510
2040
|
hasMilestoneSectioning,
|
|
2041
|
+
// #3642: the >=1 sibling buildStateFrontmatter's unbounded branch consumes.
|
|
2042
|
+
hasAnyMilestoneSection,
|
|
1511
2043
|
// #1956: sole owner of the #2012 decoy-avoidance scope for the
|
|
1512
2044
|
// `drift-guard phase-status` CLI seam.
|
|
1513
2045
|
findRoadmapProgressTable,
|
|
@@ -1518,4 +2050,8 @@ module.exports = {
|
|
|
1518
2050
|
scanMilestonePhaseIds,
|
|
1519
2051
|
collectTablePhaseRows,
|
|
1520
2052
|
findMilestoneScopeHeadingLines,
|
|
2053
|
+
// #3641: the scope axis's phase-ENTRY predicate, exported so roadmap
|
|
2054
|
+
// validate's V004 document-level check routes through the same single
|
|
2055
|
+
// owner (and its convention gate) instead of a private inline copy.
|
|
2056
|
+
hasPhaseEntries,
|
|
1521
2057
|
};
|