@opengsd/gsd-core 1.14.0 → 1.16.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 +85 -5
- package/README.ja-JP.md +3 -3
- package/README.ko-KR.md +3 -3
- package/README.pt-BR.md +3 -3
- package/README.zh-CN.md +3 -3
- package/agents/gsd-code-fixer.compact.md +7 -6
- package/agents/gsd-code-fixer.md +9 -8
- package/agents/gsd-code-reviewer.compact.md +5 -3
- package/agents/gsd-code-reviewer.md +8 -6
- package/agents/gsd-debug-session-manager.compact.md +17 -2
- package/agents/gsd-debug-session-manager.md +17 -2
- package/agents/gsd-debugger.md +3 -3
- package/agents/gsd-eval-auditor.compact.md +1 -1
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +17 -12
- package/agents/gsd-intel-updater.compact.md +1 -1
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.md +2 -2
- package/agents/gsd-phase-researcher.md +19 -11
- package/agents/gsd-plan-checker.md +15 -9
- package/agents/gsd-planner.md +15 -11
- package/agents/gsd-project-researcher.compact.md +1 -1
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.compact.md +1 -1
- package/agents/gsd-research-synthesizer.md +1 -1
- package/agents/gsd-ui-auditor.compact.md +21 -30
- package/agents/gsd-ui-auditor.md +166 -37
- package/agents/gsd-ui-researcher.compact.md +1 -1
- package/agents/gsd-ui-researcher.md +1 -1
- package/agents/gsd-verifier.md +37 -14
- package/bin/install.js +764 -287
- package/commands/gsd/add-tests.md +6 -1
- package/commands/gsd/ai-integration-phase.md +6 -1
- package/commands/gsd/audit-fix.md +5 -0
- package/commands/gsd/audit-milestone.md +6 -1
- package/commands/gsd/autonomous.md +7 -2
- package/commands/gsd/capture.md +9 -5
- package/commands/gsd/code-review.md +7 -2
- package/commands/gsd/complete-milestone.md +4 -0
- package/commands/gsd/config.md +7 -3
- package/commands/gsd/debug.md +11 -7
- package/commands/gsd/discuss-phase.md +7 -3
- package/commands/gsd/docs-update.md +12 -7
- package/commands/gsd/eval-review.md +6 -1
- package/commands/gsd/execute-phase.md +12 -7
- package/commands/gsd/extract-learnings.md +5 -0
- package/commands/gsd/fast.md +4 -0
- package/commands/gsd/forensics.md +5 -1
- package/commands/gsd/graphify.md +10 -6
- package/commands/gsd/health.md +5 -0
- package/commands/gsd/help.md +7 -2
- package/commands/gsd/import.md +7 -3
- package/commands/gsd/inbox.md +5 -0
- package/commands/gsd/ingest-docs.md +5 -1
- package/commands/gsd/manager.md +6 -1
- package/commands/gsd/map-codebase.md +7 -3
- package/commands/gsd/mempalace-capture.md +12 -4
- package/commands/gsd/mempalace-recall.md +5 -1
- package/commands/gsd/milestone-summary.md +5 -1
- package/commands/gsd/mvp-phase.md +8 -3
- package/commands/gsd/new-milestone.md +6 -1
- package/commands/gsd/new-project.md +5 -0
- package/commands/gsd/next.md +6 -1
- package/commands/gsd/ns-context.md +4 -0
- package/commands/gsd/ns-ideate.md +4 -0
- package/commands/gsd/ns-manage.md +4 -0
- package/commands/gsd/ns-project.md +4 -0
- package/commands/gsd/ns-review.md +4 -0
- package/commands/gsd/ns-workflow.md +4 -0
- package/commands/gsd/onboard.md +6 -1
- package/commands/gsd/pause-work.md +5 -1
- package/commands/gsd/phase.md +8 -4
- package/commands/gsd/plan-phase.md +6 -1
- package/commands/gsd/plan-review-convergence.md +11 -7
- package/commands/gsd/pr-branch.md +4 -0
- package/commands/gsd/profile-user.md +5 -1
- package/commands/gsd/progress.md +7 -2
- package/commands/gsd/quick-batch.md +21 -9
- package/commands/gsd/quick.md +12 -7
- package/commands/gsd/review.md +7 -4
- package/commands/gsd/secure-phase.md +6 -1
- package/commands/gsd/ship.md +5 -0
- package/commands/gsd/sketch.md +7 -2
- package/commands/gsd/spec-phase.md +5 -1
- package/commands/gsd/spike.md +8 -3
- package/commands/gsd/surface.md +5 -1
- package/commands/gsd/thread.md +4 -0
- package/commands/gsd/ui-phase.md +6 -1
- package/commands/gsd/ui-review.md +6 -1
- package/commands/gsd/ultraplan-phase.md +5 -1
- package/commands/gsd/undo.md +5 -1
- package/commands/gsd/update.md +6 -2
- package/commands/gsd/validate-phase.md +6 -1
- package/commands/gsd/verify-work.md +6 -1
- package/commands/gsd/workspace.md +7 -3
- package/gsd-core/bin/gsd-tools.cjs +477 -78
- package/gsd-core/bin/lib/active-workstream-store.cjs +15 -0
- package/gsd-core/bin/lib/adr-parser.cjs +3 -1
- package/gsd-core/bin/lib/agent-install-check.cjs +4 -1
- package/gsd-core/bin/lib/audit.cjs +144 -42
- package/gsd-core/bin/lib/broken-windows.cjs +13 -13
- package/gsd-core/bin/lib/capability-activation.cjs +9 -4
- package/gsd-core/bin/lib/capability-registry.cjs +197 -222
- package/gsd-core/bin/lib/capability-validator.cjs +16 -1
- package/gsd-core/bin/lib/check-auto-mode.cjs +35 -0
- package/gsd-core/bin/lib/check-command-router.cjs +164 -1625
- package/gsd-core/bin/lib/cjs-command-router-adapter.cjs +13 -2
- package/gsd-core/bin/lib/cli-exit.cjs +12 -0
- package/gsd-core/bin/lib/codex-agent-toml.cjs +32 -33
- package/gsd-core/bin/lib/command-aliases.cjs +7 -0
- package/gsd-core/bin/lib/command-routing-hub.cjs +48 -1
- package/gsd-core/bin/lib/commands.cjs +343 -207
- package/gsd-core/bin/lib/complexity-trigger.cjs +8 -7
- package/gsd-core/bin/lib/config-loader.cjs +65 -4
- package/gsd-core/bin/lib/config.cjs +76 -19
- package/gsd-core/bin/lib/core-utils.cjs +6 -1
- package/gsd-core/bin/lib/coverage.cjs +4 -8
- package/gsd-core/bin/lib/decision-coverage-support.cjs +259 -0
- package/gsd-core/bin/lib/decisions.cjs +30 -14
- package/gsd-core/bin/lib/drift.cjs +177 -42
- package/gsd-core/bin/lib/frontmatter-fence.cjs +90 -0
- package/gsd-core/bin/lib/frontmatter-splice.cjs +494 -0
- package/gsd-core/bin/lib/frontmatter.cjs +426 -234
- package/gsd-core/bin/lib/gap-checker.cjs +72 -29
- package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +381 -0
- package/gsd-core/bin/lib/gate-args.cjs +53 -0
- package/gsd-core/bin/lib/gate-codebase-drift.cjs +285 -0
- package/gsd-core/bin/lib/gate-config.cjs +46 -0
- package/gsd-core/bin/lib/gate-context-drift.cjs +141 -0
- package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +169 -0
- package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +126 -0
- package/gsd-core/bin/lib/gate-evaluation-scope.cjs +555 -0
- package/gsd-core/bin/lib/gate-evidence.cjs +138 -0
- package/gsd-core/bin/lib/gate-exit.cjs +27 -0
- package/gsd-core/bin/lib/gate-gap-analysis-plan-post.cjs +61 -0
- package/gsd-core/bin/lib/gate-phase-context.cjs +170 -0
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +1 -1
- package/gsd-core/bin/lib/gate-predicate.cjs +165 -0
- package/gsd-core/bin/lib/gate-prohibition-enforcement.cjs +94 -0
- package/gsd-core/bin/lib/gate-schema-drift.cjs +165 -0
- package/gsd-core/bin/lib/gate-tdd-red-evidence.cjs +100 -0
- package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +182 -0
- package/gsd-core/bin/lib/gate-ui-plan.cjs +86 -0
- package/gsd-core/bin/lib/gate-ui-safety.cjs +80 -0
- package/gsd-core/bin/lib/gate-verdict.cjs +64 -0
- package/gsd-core/bin/lib/gate-verify-command-paths.cjs +78 -0
- package/gsd-core/bin/lib/gate-verify-failure-directions.cjs +41 -0
- package/gsd-core/bin/lib/graphify.cjs +10 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +43 -47
- package/gsd-core/bin/lib/health-diagnostic.cjs +45 -8
- package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
- package/gsd-core/bin/lib/init.cjs +364 -132
- package/gsd-core/bin/lib/install-engine.cjs +30 -33
- package/gsd-core/bin/lib/install-profiles.cjs +7 -4
- package/gsd-core/bin/lib/installer-migrations.cjs +8 -1
- package/gsd-core/bin/lib/io.cjs +122 -3
- package/gsd-core/bin/lib/loop-resolver.cjs +95 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +75 -1
- package/gsd-core/bin/lib/milestone.cjs +37 -6
- package/gsd-core/bin/lib/model-resolver.cjs +171 -62
- package/gsd-core/bin/lib/observability/event.cjs +1 -1
- package/gsd-core/bin/lib/observability/logger.cjs +46 -1
- package/gsd-core/bin/lib/pattern.cjs +10 -0
- package/gsd-core/bin/lib/phase-command-router.cjs +20 -5
- package/gsd-core/bin/lib/phase-estimation.cjs +5 -4
- package/gsd-core/bin/lib/phase-id-card.cjs +32 -0
- package/gsd-core/bin/lib/phase-id-display.cjs +78 -0
- package/gsd-core/bin/lib/phase-id.cjs +110 -8
- package/gsd-core/bin/lib/phase-lifecycle.cjs +9 -2
- package/gsd-core/bin/lib/phase-locator.cjs +29 -10
- package/gsd-core/bin/lib/phase-status.cjs +360 -0
- package/gsd-core/bin/lib/phase.cjs +489 -88
- package/gsd-core/bin/lib/plan-document.cjs +142 -20
- package/gsd-core/bin/lib/plan-drift-guard.cjs +5 -0
- package/gsd-core/bin/lib/planning-document.cjs +692 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +60 -9
- package/gsd-core/bin/lib/planning-snapshot.cjs +18 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +83 -55
- package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
- package/gsd-core/bin/lib/pristine-baseline.cjs +10 -0
- package/gsd-core/bin/lib/probe-core.cjs +7 -1
- package/gsd-core/bin/lib/profile-output.cjs +6 -3
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +0 -55
- package/gsd-core/bin/lib/project-root.cjs +41 -2
- package/gsd-core/bin/lib/quick-batch-command-router.cjs +35 -9
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +11 -8
- package/gsd-core/bin/lib/real-home-guard.cjs +9 -1
- package/gsd-core/bin/lib/report-parser.cjs +269 -0
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +10 -30
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
- package/gsd-core/bin/lib/roadmap-command-router.cjs +25 -19
- package/gsd-core/bin/lib/roadmap-parser.cjs +242 -15
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +1653 -65
- package/gsd-core/bin/lib/roadmap.cjs +405 -88
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +373 -187
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +5 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +57 -1
- package/gsd-core/bin/lib/runtime-homes.cjs +14 -7
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +522 -624
- package/gsd-core/bin/lib/runtime-name-policy.cjs +246 -21
- package/gsd-core/bin/lib/runtime-slash.cjs +47 -30
- package/gsd-core/bin/lib/shell-command-projection.cjs +47 -8
- package/gsd-core/bin/lib/smart-entry.cjs +19 -3
- package/gsd-core/bin/lib/stale-bake-guard.cjs +32 -48
- package/gsd-core/bin/lib/state-contract.cjs +15 -18
- package/gsd-core/bin/lib/state-document.cjs +100 -22
- package/gsd-core/bin/lib/state-transition.cjs +39 -2
- package/gsd-core/bin/lib/state.cjs +256 -105
- package/gsd-core/bin/lib/surface.cjs +19 -2
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +48 -79
- package/gsd-core/bin/lib/uat-predicate.cjs +359 -38
- package/gsd-core/bin/lib/uat.cjs +432 -7
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +167 -40
- package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
- package/gsd-core/bin/lib/vendor/README.md +31 -9
- package/gsd-core/bin/lib/vendor/saxes.cjs +1934 -0
- package/gsd-core/bin/lib/vendor/saxes.cjs.LICENSE.txt +92 -0
- package/gsd-core/bin/lib/vendor/tap-parser.cjs +8927 -0
- package/gsd-core/bin/lib/vendor/tap-parser.cjs.LICENSE.txt +152 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +1378 -274
- package/gsd-core/bin/lib/verify-command-grounding.cjs +46 -2
- package/gsd-core/bin/lib/verify-command-router.cjs +18 -7
- package/gsd-core/bin/lib/verify.cjs +357 -531
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +14 -6
- package/gsd-core/bin/lib/workstream-inventory.cjs +31 -21
- package/gsd-core/bin/lib/workstream-name-policy.cjs +31 -1
- package/gsd-core/bin/lib/workstream.cjs +11 -2
- package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
- package/gsd-core/bin/lib/worktree-safety.cjs +784 -51
- package/gsd-core/bin/shared/config-defaults.manifest.json +8 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +5 -0
- package/gsd-core/references/autonomous-smart-discuss.md +2 -1
- package/gsd-core/references/autonomous-ui-design-contract.md +3 -3
- package/gsd-core/references/checkpoints.md +5 -3
- package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe.md +195 -21
- package/gsd-core/references/execute-mvp-tdd.md +5 -10
- package/gsd-core/references/execute-phase-between-wave-reset.md +10 -6
- package/gsd-core/references/execute-phase-response-language.md +1 -1
- package/gsd-core/references/execute-phase-wave-guard.md +22 -11
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +7 -1
- package/gsd-core/references/model-profiles.md +1 -1
- package/gsd-core/references/offer-next.md +1 -1
- package/gsd-core/references/phase-argument-parsing.md +9 -7
- package/gsd-core/references/phase-id-convention.md +28 -0
- package/gsd-core/references/planner-gap-closure.md +2 -0
- package/gsd-core/references/planner-load-graph-context.md +24 -13
- package/gsd-core/references/planner-verify-command-grounding.md +14 -0
- package/gsd-core/references/planning-config.md +12 -3
- package/gsd-core/references/spidr-splitting.md +1 -1
- package/gsd-core/references/tdd.md +37 -8
- package/gsd-core/references/ui-consideration-probe.md +10 -5
- package/gsd-core/references/verifier-phase-gates.md +5 -2
- package/gsd-core/references/verify-command-path-resolvability.md +10 -2
- package/gsd-core/references/verify-mvp-mode.md +2 -2
- package/gsd-core/references/workstream-flag.md +33 -3
- package/gsd-core/references/worktree-path-safety.md +321 -0
- package/gsd-core/templates/README.md +1 -1
- package/gsd-core/templates/UAT.md +17 -1
- package/gsd-core/templates/config.json +2 -11
- package/gsd-core/templates/verification-report.md +1 -1
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +1 -1
- package/gsd-core/workflows/add-phase.md +8 -7
- package/gsd-core/workflows/add-tests.md +4 -3
- package/gsd-core/workflows/add-todo.md +6 -5
- package/gsd-core/workflows/ai-integration-phase.md +13 -4
- package/gsd-core/workflows/audit-fix.md +1 -1
- package/gsd-core/workflows/audit-milestone.md +4 -3
- package/gsd-core/workflows/audit-uat.md +1 -1
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +9 -18
- package/gsd-core/workflows/autonomous.md +43 -23
- package/gsd-core/workflows/check-todos.md +7 -6
- package/gsd-core/workflows/cleanup.md +2 -2
- package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +20 -13
- package/gsd-core/workflows/code-review-fix.md +112 -25
- package/gsd-core/workflows/code-review.md +146 -135
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +4 -3
- package/gsd-core/workflows/complete-milestone.md +13 -8
- package/gsd-core/workflows/debug.md +32 -7
- package/gsd-core/workflows/diagnose-issues.md +3 -2
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
- package/gsd-core/workflows/discuss-phase.md +4 -3
- package/gsd-core/workflows/do.md +2 -2
- package/gsd-core/workflows/docs-update.md +6 -5
- package/gsd-core/workflows/edit-phase.md +4 -3
- package/gsd-core/workflows/eval-review.md +14 -5
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1019 -0
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +15 -4
- package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +10 -7
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +37 -3
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +3 -1
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +2 -2
- 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 +1 -1
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +46 -9
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/ready-wave-gate.md +37 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +2 -0
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/threat-id-gate.md +28 -0
- package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +187 -0
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +2 -3
- package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
- package/gsd-core/workflows/execute-phase.md +96 -178
- package/gsd-core/workflows/execute-plan.md +18 -23
- package/gsd-core/workflows/explore.md +4 -4
- package/gsd-core/workflows/extract-learnings.md +4 -2
- package/gsd-core/workflows/fast.md +1 -1
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +3 -2
- package/gsd-core/workflows/help/modes/full.compact.md +3 -3
- package/gsd-core/workflows/help/modes/full.md +5 -5
- package/gsd-core/workflows/help/modes/topic.md +15 -5
- package/gsd-core/workflows/import.md +4 -3
- package/gsd-core/workflows/inbox.md +2 -2
- package/gsd-core/workflows/ingest-docs.md +3 -3
- package/gsd-core/workflows/insert-phase.md +4 -3
- package/gsd-core/workflows/list-seeds.md +1 -1
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +6 -4
- package/gsd-core/workflows/map-codebase.md +5 -4
- package/gsd-core/workflows/milestone-summary.md +3 -2
- package/gsd-core/workflows/mvp-phase.md +14 -14
- package/gsd-core/workflows/new-milestone.md +11 -11
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +3 -3
- package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +1 -1
- package/gsd-core/workflows/new-project.md +7 -7
- package/gsd-core/workflows/new-workspace.md +2 -2
- package/gsd-core/workflows/next.md +1 -1
- package/gsd-core/workflows/note.md +1 -1
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +2 -2
- package/gsd-core/workflows/plan-phase/detail/elaboration.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
- package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +23 -5
- package/gsd-core/workflows/plan-phase.md +50 -24
- package/gsd-core/workflows/plan-review-convergence.md +21 -5
- package/gsd-core/workflows/plant-seed.md +62 -20
- package/gsd-core/workflows/pr-branch.md +113 -13
- package/gsd-core/workflows/profile-user.md +2 -2
- package/gsd-core/workflows/progress.md +19 -49
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +27 -0
- package/gsd-core/workflows/quick/steps/quick-verification.md +4 -4
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +30 -11
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +9 -3
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
- package/gsd-core/workflows/quick-batch.md +15 -10
- package/gsd-core/workflows/quick.md +62 -39
- package/gsd-core/workflows/reapply-patches.md +9 -3
- package/gsd-core/workflows/remove-phase.md +3 -2
- package/gsd-core/workflows/remove-workspace.md +2 -2
- package/gsd-core/workflows/resume-project.md +3 -2
- package/gsd-core/workflows/review.md +33 -17
- package/gsd-core/workflows/scan.md +3 -2
- package/gsd-core/workflows/secure-phase.md +13 -13
- package/gsd-core/workflows/settings-advanced.md +30 -10
- package/gsd-core/workflows/settings-integrations.md +2 -3
- package/gsd-core/workflows/settings.md +4 -4
- package/gsd-core/workflows/ship.md +14 -13
- package/gsd-core/workflows/sketch-wrap-up.md +1 -1
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/smart-entry.md +2 -2
- package/gsd-core/workflows/spec-phase.md +15 -5
- package/gsd-core/workflows/spike-wrap-up.md +1 -1
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +1 -1
- package/gsd-core/workflows/sync-skills.md +5 -5
- package/gsd-core/workflows/thread.md +2 -2
- package/gsd-core/workflows/transition.md +13 -23
- package/gsd-core/workflows/ui-phase.md +48 -11
- package/gsd-core/workflows/ui-review.md +21 -6
- package/gsd-core/workflows/ultraplan-phase.md +3 -2
- package/gsd-core/workflows/undo.md +339 -20
- package/gsd-core/workflows/update.md +7 -7
- package/gsd-core/workflows/validate-phase.md +12 -13
- package/gsd-core/workflows/verify-work/detail/elaboration.md +43 -3
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -3
- package/gsd-core/workflows/verify-work.md +129 -55
- package/hooks/dist/gsd-agent-isolation-guard.js +32 -0
- package/hooks/dist/gsd-check-update-worker.js +8 -0
- package/hooks/dist/gsd-check-update.js +8 -0
- package/hooks/dist/gsd-context-monitor.js +31 -8
- package/hooks/dist/gsd-cursor-subagent-start.js +8 -0
- package/hooks/dist/gsd-secret-read-guard.js +161 -4
- package/hooks/dist/gsd-statusline.js +85 -20
- package/hooks/dist/gsd-update-banner.js +8 -0
- package/hooks/dist/gsd-validate-commit.sh +63 -4
- package/hooks/dist/gsd-windsurf-pre-write.js +11 -2
- package/hooks/dist/gsd-workflow-guard.js +5 -4
- package/hooks/dist/gsd-worktree-path-guard.js +6 -2
- package/hooks/dist/lib/cli-exit.js +12 -0
- package/hooks/dist/lib/git-probe.js +17 -1
- package/hooks/dist/lib/isolation-sentinel.js +2 -2
- package/hooks/gsd-agent-isolation-guard.js +32 -0
- package/hooks/gsd-check-update-worker.js +8 -0
- package/hooks/gsd-check-update.js +8 -0
- package/hooks/gsd-context-monitor.js +31 -8
- package/hooks/gsd-cursor-subagent-start.js +8 -0
- package/hooks/gsd-secret-read-guard.js +161 -4
- package/hooks/gsd-statusline.js +85 -20
- package/hooks/gsd-update-banner.js +8 -0
- package/hooks/gsd-validate-commit.sh +63 -4
- package/hooks/gsd-windsurf-pre-write.js +11 -2
- package/hooks/gsd-workflow-guard.js +5 -4
- package/hooks/gsd-worktree-path-guard.js +6 -2
- package/hooks/hooks.json +5 -5
- package/hooks/lib/cli-exit.js +12 -0
- package/hooks/lib/git-probe.js +17 -1
- package/hooks/lib/isolation-sentinel.js +2 -2
- package/package.json +22 -4
- package/scripts/build-hooks.js +15 -6
- package/scripts/changeset/parse.cjs +52 -4
- package/scripts/check-contract-drift.cjs +127 -11
- package/scripts/ci-timeout-report.cjs +770 -4
- package/scripts/command-contract-helpers.cjs +15 -8
- package/scripts/docs-guard-registry.cjs +34 -0
- package/scripts/gen-features.cjs +13 -8
- package/scripts/gen-hooks-cli-exit.cjs +12 -28
- package/scripts/gen-loop-host-contract.cjs +79 -1
- package/scripts/gen-platform-conformance-tier.cjs +187 -1
- package/scripts/gen-plugin-skills.cjs +87 -1
- package/scripts/gen-research-agents.cjs +24 -31
- package/scripts/gen-scripts-cli-exit.cjs +30 -3
- package/scripts/gen-test-timings.cjs +32 -7
- package/scripts/lib/cli-exit.cjs +12 -0
- package/scripts/lib/macos-conformance-tier.generated.cjs +34 -2
- package/scripts/lib/ndjson-reporter.cjs +31 -5
- package/scripts/lib/platform-conformance-tier.generated.cjs +45 -5
- package/scripts/lib/registration-ledger-preload.cjs +155 -0
- package/scripts/lib/vendor-bundle.cjs +59 -0
- package/scripts/lib/vendor-licenses/saxes-6.0.0.txt +64 -0
- 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-completion-predicate-drift.cjs +18 -19
- package/scripts/lint-descriptions.cjs +7 -3
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +38 -1
- package/scripts/lint-eslint-glob-coverage.allowlist.json +20 -0
- package/scripts/lint-frontmatter-fence-drift.cjs +313 -0
- package/scripts/lint-frontmatter-scalar-broad-grep.cjs +122 -16
- package/scripts/lint-phase-arg-assignment.cjs +257 -0
- package/scripts/lint-phase-enumeration-drift.cjs +12 -8
- package/scripts/lint-phase-id-drift.cjs +319 -5
- package/scripts/lint-planning-document-positive-control.cjs +329 -0
- package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
- package/scripts/lint-response-language-coverage.cjs +3 -0
- package/scripts/lint-retired-runtime-name.cjs +619 -0
- package/scripts/lint-skill-deps.cjs +7 -3
- package/scripts/lint-state-write-path-drift.cjs +93 -0
- package/scripts/lint-test-file-count.allowlist.json +38 -9
- package/scripts/lint-test-file-count.cjs +34 -1
- package/scripts/lint-vendored-deps.cjs +41 -5
- package/scripts/lint-workflow-shellcheck-baseline.json +25 -10
- package/scripts/mutation-matrix.cjs +50 -5
- package/scripts/prompt-injection-scan.sh +4 -0
- package/scripts/release-tarball-smoke.cjs +194 -1
- package/scripts/require-issue-link-policy.cjs +6 -2
- package/scripts/sync-runtime-launcher.cjs +184 -2
- package/scripts/verify-npm-publish.cjs +76 -20
- package/skills/gsd-add-tests/SKILL.md +6 -1
- package/skills/gsd-ai-integration-phase/SKILL.md +6 -1
- package/skills/gsd-audit-fix/SKILL.md +5 -0
- package/skills/gsd-audit-milestone/SKILL.md +6 -1
- package/skills/gsd-autonomous/SKILL.md +7 -2
- package/skills/gsd-capture/SKILL.md +9 -5
- package/skills/gsd-code-review/SKILL.md +7 -2
- package/skills/gsd-complete-milestone/SKILL.md +4 -0
- package/skills/gsd-config/SKILL.md +8 -4
- package/skills/gsd-debug/SKILL.md +11 -7
- package/skills/gsd-discuss-phase/SKILL.md +7 -3
- package/skills/gsd-docs-update/SKILL.md +12 -7
- package/skills/gsd-eval-review/SKILL.md +6 -1
- package/skills/gsd-execute-phase/SKILL.md +12 -7
- package/skills/gsd-extract-learnings/SKILL.md +5 -0
- package/skills/gsd-fast/SKILL.md +4 -0
- package/skills/gsd-forensics/SKILL.md +5 -1
- package/skills/gsd-graphify/SKILL.md +10 -6
- package/skills/gsd-health/SKILL.md +5 -0
- package/skills/gsd-help/SKILL.md +7 -2
- package/skills/gsd-import/SKILL.md +7 -3
- package/skills/gsd-inbox/SKILL.md +5 -0
- package/skills/gsd-ingest-docs/SKILL.md +5 -1
- package/skills/gsd-manager/SKILL.md +6 -1
- package/skills/gsd-map-codebase/SKILL.md +7 -3
- package/skills/gsd-mempalace-capture/SKILL.md +12 -4
- package/skills/gsd-mempalace-recall/SKILL.md +5 -1
- package/skills/gsd-milestone-summary/SKILL.md +5 -1
- package/skills/gsd-mvp-phase/SKILL.md +8 -3
- package/skills/gsd-new-milestone/SKILL.md +6 -1
- package/skills/gsd-new-project/SKILL.md +5 -0
- package/skills/gsd-next/SKILL.md +6 -1
- package/skills/gsd-ns-context/SKILL.md +4 -0
- package/skills/gsd-ns-ideate/SKILL.md +4 -0
- package/skills/gsd-ns-manage/SKILL.md +4 -0
- package/skills/gsd-ns-project/SKILL.md +4 -0
- package/skills/gsd-ns-review/SKILL.md +4 -0
- package/skills/gsd-ns-workflow/SKILL.md +4 -0
- package/skills/gsd-onboard/SKILL.md +6 -1
- package/skills/gsd-pause-work/SKILL.md +5 -1
- package/skills/gsd-phase/SKILL.md +8 -4
- package/skills/gsd-plan-phase/SKILL.md +6 -1
- package/skills/gsd-plan-review-convergence/SKILL.md +10 -6
- package/skills/gsd-pr-branch/SKILL.md +4 -0
- package/skills/gsd-profile-user/SKILL.md +5 -1
- package/skills/gsd-progress/SKILL.md +7 -2
- package/skills/gsd-quick/SKILL.md +16 -10
- package/skills/gsd-quick-batch/SKILL.md +21 -9
- package/skills/gsd-review/SKILL.md +7 -4
- package/skills/gsd-review-backlog/SKILL.md +3 -2
- package/skills/gsd-secure-phase/SKILL.md +6 -1
- package/skills/gsd-ship/SKILL.md +5 -0
- package/skills/gsd-sketch/SKILL.md +7 -2
- package/skills/gsd-spec-phase/SKILL.md +5 -1
- package/skills/gsd-spike/SKILL.md +8 -3
- package/skills/gsd-surface/SKILL.md +5 -1
- package/skills/gsd-thread/SKILL.md +4 -0
- package/skills/gsd-ui-phase/SKILL.md +6 -1
- package/skills/gsd-ui-review/SKILL.md +6 -1
- package/skills/gsd-ultraplan-phase/SKILL.md +5 -1
- package/skills/gsd-undo/SKILL.md +5 -1
- package/skills/gsd-update/SKILL.md +6 -2
- package/skills/gsd-validate-phase/SKILL.md +6 -1
- package/skills/gsd-verify-work/SKILL.md +6 -1
- package/skills/gsd-workspace/SKILL.md +7 -3
- package/skills/gsd-workstreams/SKILL.md +6 -6
- package/vscode/package.json +1 -1
|
@@ -22,6 +22,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
22
22
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
23
23
|
const node_path_1 = __importDefault(require("node:path"));
|
|
24
24
|
const node_child_process_1 = require("node:child_process");
|
|
25
|
+
const frontmatter_fence_cjs_1 = require("./frontmatter-fence.cjs");
|
|
25
26
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- io.cjs is an export= CommonJS module
|
|
26
27
|
const ioMod = require("./io.cjs");
|
|
27
28
|
const { output, error, ERROR_REASON, formatDiagnosticToken } = ioMod;
|
|
@@ -38,11 +39,13 @@ const coreUtilsMod = require("./core-utils.cjs");
|
|
|
38
39
|
// generative-fix divergence CLAUDE.md warns about. Collapsed onto core-utils'
|
|
39
40
|
// copy, which was already the leaf owner, so there is no second surface left to
|
|
40
41
|
// drift and no parity test needed to police one.
|
|
41
|
-
const { toPosixPath, generateSlugInternal, readSubdirectories, extractCanonicalPlanId, findUnsummarizedPlans,
|
|
42
|
+
const { toPosixPath, generateSlugInternal, readSubdirectories, extractCanonicalPlanId, findUnsummarizedPlans, } = coreUtilsMod;
|
|
42
43
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- phase-id.cjs is an export= CommonJS module
|
|
43
44
|
const phaseIdMod = require("./phase-id.cjs");
|
|
44
|
-
const { normalizePhaseName, phaseMarkdownRegexSource, comparePhaseNum, matchPhaseDirs, isSentinelPhaseId, scopeToPhase, OPTIONAL_PROJECT_CODE_PREFIX_SOURCE, OPTIONAL_PHASE_TAG_SOURCE, PHASE_NUMBER_TOKEN_SOURCE, } = phaseIdMod;
|
|
45
|
+
const { normalizePhaseName, phaseMarkdownRegexSource, comparePhaseNum, matchPhaseDirs, isSentinelPhaseId, scopeToPhase, OPTIONAL_PROJECT_CODE_PREFIX_SOURCE, OPTIONAL_PHASE_TAG_SOURCE, PHASE_NUMBER_TOKEN_SOURCE, phaseHeadingPrefixSrcFor, PHASE_HEADING_BASELINE, buildPhaseHeadingScanRegex, } = phaseIdMod;
|
|
45
46
|
const pattern_cjs_1 = require("./pattern.cjs");
|
|
47
|
+
const gate_exit_cjs_1 = require("./gate-exit.cjs");
|
|
48
|
+
const gate_verdict_cjs_1 = require("./gate-verdict.cjs");
|
|
46
49
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- phase-locator.cjs is an export= CommonJS module
|
|
47
50
|
const phaseLocatorMod = require("./phase-locator.cjs");
|
|
48
51
|
const { findPhaseInternal, getArchivedPhaseDirs, listMilestonePhaseDirs, listAllPhaseDirs } = phaseLocatorMod;
|
|
@@ -65,11 +68,17 @@ const frontmatterMod = require("./frontmatter.cjs");
|
|
|
65
68
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- state.cjs is an export= CommonJS module
|
|
66
69
|
const stateMod = require("./state.cjs");
|
|
67
70
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
71
|
+
const planning_document_cjs_1 = require("./planning-document.cjs");
|
|
68
72
|
const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
|
|
69
73
|
const clock_cjs_1 = require("./clock.cjs");
|
|
70
74
|
const state_transition_cjs_1 = require("./state-transition.cjs");
|
|
71
75
|
const markdown_table_cjs_1 = require("./markdown-table.cjs");
|
|
76
|
+
// #5060: the Phase Status Module owns the ROADMAP Status-cell token vocabulary.
|
|
77
|
+
const phase_status_cjs_1 = require("./phase-status.cjs");
|
|
72
78
|
const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
|
|
79
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- roadmap.cjs is an export= CommonJS module
|
|
80
|
+
const roadmapMod = require("./roadmap.cjs");
|
|
81
|
+
const { buildPhaseHeadingRegex } = roadmapMod;
|
|
73
82
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- uat-predicate.cjs is an export= CommonJS module
|
|
74
83
|
const uatPredicate = require("./uat-predicate.cjs");
|
|
75
84
|
const { evaluateUatPassed } = uatPredicate;
|
|
@@ -80,20 +89,19 @@ const verificationMod = require("./verification.cjs");
|
|
|
80
89
|
// cycle (the reverse edge, `state.cts → verify.cjs`, would).
|
|
81
90
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- verify.cjs is an export= CommonJS module
|
|
82
91
|
const verifyMod = require("./verify.cjs");
|
|
83
|
-
const { readVerificationStatus } = verificationMod;
|
|
92
|
+
const { readVerificationStatus, VERIFICATION_STATUS, findVerificationStatusError } = verificationMod;
|
|
84
93
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- plan-dependency-graph.cjs is an export= CommonJS module
|
|
85
94
|
const planDependencyGraphMod = require("./plan-dependency-graph.cjs");
|
|
86
95
|
const { computeHaltPropagation, buildSummaryFileIndex, isSummaryFileHalted, isSummaryFileBlocked } = planDependencyGraphMod;
|
|
87
96
|
// #612: `resolvePhaseIdConvention` selects the write-time milestone-scope
|
|
88
97
|
// guard's terminator vocabulary (see assertDescriptionPreservesMilestoneScope).
|
|
89
|
-
const { planningDir, withPlanningLock, listAvailableWorkstreams, peekActiveWorkstream, diagnoseUnresolvedActiveWorkstream, describeUnresolvedWorkstreamReason, resolvePhaseIdConvention, } = planningWorkspace;
|
|
98
|
+
const { planningDir, withPlanningLock, listAvailableWorkstreams, peekActiveWorkstream, diagnoseUnresolvedActiveWorkstream, describeUnresolvedWorkstreamReason, resolveEnvWorkstream, resolvePhaseIdConvention, } = planningWorkspace;
|
|
90
99
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- milestone-lock.cjs is an export= CommonJS module
|
|
91
100
|
const milestoneLockMod = require("./milestone-lock.cjs");
|
|
92
101
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
93
102
|
const planDocumentMod = require("./plan-document.cjs");
|
|
94
103
|
const { parsePlanDocument, planIdFromFile } = planDocumentMod;
|
|
95
|
-
const {
|
|
96
|
-
const { readModifyWriteStateMd, stateExtractField, stateReplaceField, syncAndPreserveStateMd, withStateLock, updatePerformanceMetricsSection, } = stateMod;
|
|
104
|
+
const { readModifyWriteStateMd, stateExtractField, stateReplaceField, syncAndPreserveStateMd, assertVerificationReportsReadable, withStateLock, updatePerformanceMetricsSection, } = stateMod;
|
|
97
105
|
// Any .md file with PLAN anywhere in the basename — diagnostic net
|
|
98
106
|
const PLAN_OUTLINE_RE = /-PLAN-OUTLINE\.md$/i;
|
|
99
107
|
const PLAN_PRE_BOUNCE_RE = /-PLAN.*\.pre-bounce\.md$/i;
|
|
@@ -348,16 +356,25 @@ function getRoadmapModeForPhase(cwd, phaseNum) {
|
|
|
348
356
|
const milestoneContent = extractCurrentMilestone(rawContent, cwd);
|
|
349
357
|
const fullContent = stripShippedMilestones(rawContent);
|
|
350
358
|
const escapedPhase = phaseMarkdownRegexSource(phaseNum);
|
|
351
|
-
|
|
359
|
+
// #5007 (Phase 6 / ADR-4910 §8): buildPhaseHeadingRegex (src/roadmap.cts) is
|
|
360
|
+
// anchored `^...$` with no 'm' flag — it is designed to test ONE
|
|
361
|
+
// already-tokenized heading LINE (mirroring searchPhaseInContent's own
|
|
362
|
+
// usage), not to `.match()` against multi-line content directly. Routing
|
|
363
|
+
// through tokenizeHeadings first is therefore required here, not a pure
|
|
364
|
+
// regex-literal swap (the design doc's "direct swap" plan does not hold
|
|
365
|
+
// once the anchor is accounted for).
|
|
366
|
+
const phaseHeader = buildPhaseHeadingRegex(escapedPhase);
|
|
352
367
|
for (const content of [milestoneContent, fullContent]) {
|
|
353
|
-
const
|
|
354
|
-
if (!
|
|
368
|
+
const heading = (0, markdown_sectionizer_cjs_1.tokenizeHeadings)(content).find((h) => phaseHeader.test(h.text));
|
|
369
|
+
if (!heading)
|
|
355
370
|
continue;
|
|
356
|
-
const
|
|
371
|
+
const headingLineEnd = content.indexOf('\n', heading.offset);
|
|
372
|
+
const headerMatchLength = (headingLineEnd === -1 ? content.length : headingLineEnd) - heading.offset;
|
|
373
|
+
const sectionStart = heading.offset;
|
|
357
374
|
const rest = content.slice(sectionStart);
|
|
358
|
-
const nextHeader = rest.slice(
|
|
375
|
+
const nextHeader = rest.slice(headerMatchLength).match(/\n#{2,4}\s+Phase\s+\S/i);
|
|
359
376
|
const sectionEnd = nextHeader
|
|
360
|
-
? sectionStart +
|
|
377
|
+
? sectionStart + headerMatchLength + nextHeader.index
|
|
361
378
|
: content.length;
|
|
362
379
|
const section = content.slice(sectionStart, sectionEnd);
|
|
363
380
|
const modeMatch = section.match(/\*\*Mode(?::\*\*|\*\*:)\s*([^\n]+)/i);
|
|
@@ -592,8 +609,16 @@ function cmdFindPhase(cwd, phase, raw) {
|
|
|
592
609
|
* none left in this file) degrades to the pre-#3897 two-tier behavior rather
|
|
593
610
|
* than throwing on a missing argument.
|
|
594
611
|
*/
|
|
612
|
+
/**
|
|
613
|
+
* The dependency resolver's one comparison normalization. Callers that must
|
|
614
|
+
* predict whether a token names a plan reuse this seam instead of copying its
|
|
615
|
+
* case-folding rule.
|
|
616
|
+
*/
|
|
617
|
+
function normalizeDependencyToken(token) {
|
|
618
|
+
return String(token).toLowerCase();
|
|
619
|
+
}
|
|
595
620
|
function resolveDependencyId(dep, planMap, canonicalToId, shortFormToId) {
|
|
596
|
-
const lower = dep
|
|
621
|
+
const lower = normalizeDependencyToken(dep);
|
|
597
622
|
if (planMap.has(lower))
|
|
598
623
|
return planMap.get(lower).id;
|
|
599
624
|
if (canonicalToId.has(lower))
|
|
@@ -634,7 +659,7 @@ function buildShortFormToId(rawPlans) {
|
|
|
634
659
|
const canonical = extractCanonicalPlanId(p.id);
|
|
635
660
|
const lastDash = canonical.lastIndexOf('-');
|
|
636
661
|
if (lastDash > 0 && lastDash < canonical.length - 1) {
|
|
637
|
-
const shortForm = canonical.slice(lastDash + 1)
|
|
662
|
+
const shortForm = normalizeDependencyToken(canonical.slice(lastDash + 1));
|
|
638
663
|
if (/^\d+$/.test(shortForm) && !shortFormToId.has(shortForm)) {
|
|
639
664
|
shortFormToId.set(shortForm, p.id);
|
|
640
665
|
}
|
|
@@ -744,12 +769,12 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
|
|
|
744
769
|
phase: normalized,
|
|
745
770
|
error: `Phase ${normalized} is ambiguous: ${ambiguousMatches.length} directories match (${ambiguousMatches.map((m) => `"${m}"`).join(', ')}).`,
|
|
746
771
|
ambiguous_matches: ambiguousMatches,
|
|
747
|
-
plans: [], waves: {}, incomplete: [], has_checkpoints: false,
|
|
772
|
+
plans: [], waves: {}, incomplete: [], runnable: [], ready_plans: [], has_checkpoints: false,
|
|
748
773
|
}, raw);
|
|
749
774
|
return;
|
|
750
775
|
}
|
|
751
776
|
if (!phaseDir) {
|
|
752
|
-
output({ phase: normalized, error: 'Phase not found', plans: [], waves: {}, incomplete: [], runnable: [], has_checkpoints: false }, raw);
|
|
777
|
+
output({ phase: normalized, error: 'Phase not found', plans: [], waves: {}, incomplete: [], runnable: [], ready_plans: [], has_checkpoints: false }, raw);
|
|
753
778
|
return;
|
|
754
779
|
}
|
|
755
780
|
void phaseDirName; // used only to set phaseDir above
|
|
@@ -836,6 +861,7 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
|
|
|
836
861
|
filesModified: planDoc.filesModified,
|
|
837
862
|
filesDeleted: planDoc.filesDeleted,
|
|
838
863
|
agentHint: planDoc.agentHint,
|
|
864
|
+
gapClosure: planDoc.gapClosure,
|
|
839
865
|
taskCount: planDoc.taskCount,
|
|
840
866
|
hasSummary,
|
|
841
867
|
halted,
|
|
@@ -844,7 +870,7 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
|
|
|
844
870
|
// ── Pass 2: topological level assignment via depends_on DAG ──────────────
|
|
845
871
|
const seenLower = new Map();
|
|
846
872
|
for (const p of rawPlans) {
|
|
847
|
-
const lower = p.id
|
|
873
|
+
const lower = normalizeDependencyToken(p.id);
|
|
848
874
|
const existing = seenLower.get(lower);
|
|
849
875
|
if (existing !== undefined) {
|
|
850
876
|
error(`depends_on index collision in phase ${normalized}: plan IDs '${existing}' and '${p.id}' are identical when case-folded. Rename one file to avoid ambiguous dependency resolution.`);
|
|
@@ -852,8 +878,8 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
|
|
|
852
878
|
}
|
|
853
879
|
seenLower.set(lower, p.id);
|
|
854
880
|
}
|
|
855
|
-
const planMap = new Map(rawPlans.map((p) => [p.id
|
|
856
|
-
const canonicalToId = new Map(rawPlans.map((p) => [extractCanonicalPlanId(p.id)
|
|
881
|
+
const planMap = new Map(rawPlans.map((p) => [normalizeDependencyToken(p.id), p]));
|
|
882
|
+
const canonicalToId = new Map(rawPlans.map((p) => [normalizeDependencyToken(extractCanonicalPlanId(p.id)), p.id]));
|
|
857
883
|
// #3897 rung 4 (ADR-3473 §8.9) — the third depends_on resolution tier.
|
|
858
884
|
// Resolves a bare in-phase plan-number short form (e.g. "01") to its owning
|
|
859
885
|
// plan id. In-phase only by construction (T49): the map is built from THIS
|
|
@@ -888,6 +914,10 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
|
|
|
888
914
|
const waves = {};
|
|
889
915
|
const incomplete = [];
|
|
890
916
|
const runnable = [];
|
|
917
|
+
// #4628: DAG-ready view — see the per-plan emission below. Keyed by id so
|
|
918
|
+
// the per-plan pass can resolve each plan's dependency edges.
|
|
919
|
+
const resolvedDepsByPlan = new Map(haltNodes.map((n) => [n.id, n.resolvedDependsOn]));
|
|
920
|
+
const readyPlans = [];
|
|
891
921
|
let hasCheckpoints = false;
|
|
892
922
|
const warnings = [];
|
|
893
923
|
// #3427 / ADR-3473 §8.5: name every dropped depends_on edge (plan AND
|
|
@@ -897,8 +927,12 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
|
|
|
897
927
|
// plan with no dropped edges and a genuinely wrong `wave:` still warns
|
|
898
928
|
// (N3, D6, T25).
|
|
899
929
|
const plansWithUnresolvedTokens = new Set();
|
|
930
|
+
const unresolvedTokensByPlan = new Map();
|
|
900
931
|
for (const { plan, token } of unresolved) {
|
|
901
932
|
plansWithUnresolvedTokens.add(plan);
|
|
933
|
+
const tokens = unresolvedTokensByPlan.get(plan) ?? [];
|
|
934
|
+
tokens.push(formatDiagnosticToken(token));
|
|
935
|
+
unresolvedTokensByPlan.set(plan, tokens);
|
|
902
936
|
warnings.push(`Plan ${plan}: depends_on token ${formatDiagnosticToken(token)} does not resolve to any plan in this phase — edge dropped, wave placement for this plan may be unreliable`);
|
|
903
937
|
}
|
|
904
938
|
for (const rawPlan of rawPlans) {
|
|
@@ -906,14 +940,34 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
|
|
|
906
940
|
hasCheckpoints = true;
|
|
907
941
|
}
|
|
908
942
|
const blockedByIds = blockedBy.get(rawPlan.id) ?? [];
|
|
943
|
+
// #4628: readiness fails closed — every resolved dependency must have
|
|
944
|
+
// completion evidence (has_summary), and a depends_on edge that never
|
|
945
|
+
// resolved (#3427 — a dropped edge) carries no evidence to check. A
|
|
946
|
+
// single evaluation feeds both the ready_plans list and the per-plan
|
|
947
|
+
// `ready` / `unresolved_dependencies` fields below.
|
|
948
|
+
// Case-fold the dep before the planMap lookup: planMap is lowercase-keyed
|
|
949
|
+
// (#2237) while resolveDependencyId returns the raw id — a mixed-case id
|
|
950
|
+
// must not read as 'no evidence' (over-blocking a ready plan).
|
|
951
|
+
const missingEvidence = (resolvedDepsByPlan.get(rawPlan.id) ?? [])
|
|
952
|
+
.filter((dep) => planMap.get(dep.toLowerCase())?.hasSummary !== true);
|
|
953
|
+
if (plansWithUnresolvedTokens.has(rawPlan.id)) {
|
|
954
|
+
missingEvidence.push(...(unresolvedTokensByPlan.get(rawPlan.id) ?? []));
|
|
955
|
+
}
|
|
956
|
+
const isReady = blockedByIds.length === 0 && missingEvidence.length === 0;
|
|
909
957
|
if (!rawPlan.hasSummary) {
|
|
910
958
|
incomplete.push(rawPlan.id);
|
|
911
959
|
// #2830: the runnable-only view — incomplete AND not transitively
|
|
912
960
|
// blocked by a halted upstream plan. Additive alongside `incomplete`,
|
|
913
961
|
// which keeps its existing "no SUMMARY yet" meaning unchanged.
|
|
962
|
+
// #4628: runnable says NOTHING about completion evidence — a runnable
|
|
963
|
+
// plan whose dependencies lack a SUMMARY is not DAG-ready. Consumers
|
|
964
|
+
// dispatch from `ready_plans`.
|
|
914
965
|
if (blockedByIds.length === 0) {
|
|
915
966
|
runnable.push(rawPlan.id);
|
|
916
967
|
}
|
|
968
|
+
if (isReady) {
|
|
969
|
+
readyPlans.push(rawPlan.id);
|
|
970
|
+
}
|
|
917
971
|
}
|
|
918
972
|
const computedWave = (level.get(rawPlan.id) ?? 0) + levelOffset;
|
|
919
973
|
const effectiveWave = computedWave;
|
|
@@ -943,6 +997,10 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
|
|
|
943
997
|
return planMap.has(lower) ? planMap.get(lower).id : dep;
|
|
944
998
|
}),
|
|
945
999
|
autonomous: rawPlan.autonomous,
|
|
1000
|
+
// #4924: execute-phase's `--gaps-only` filter reads this field off the
|
|
1001
|
+
// index. Before it was emitted, every plan read as non-gap-closure and the
|
|
1002
|
+
// filter selected nothing — a silent, successful-looking empty run.
|
|
1003
|
+
gap_closure: rawPlan.gapClosure,
|
|
946
1004
|
objective: rawPlan.objective,
|
|
947
1005
|
files_modified: rawPlan.filesModified,
|
|
948
1006
|
files_deleted: rawPlan.filesDeleted,
|
|
@@ -955,6 +1013,15 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
|
|
|
955
1013
|
halted: rawPlan.halted,
|
|
956
1014
|
blocked_by: blockedByIds,
|
|
957
1015
|
};
|
|
1016
|
+
if (!rawPlan.hasSummary) {
|
|
1017
|
+
// #4628: readiness is a property of INCOMPLETE plans (a summarized plan
|
|
1018
|
+
// is filtered by has_summary before readiness is ever consulted). The
|
|
1019
|
+
// unresolved_dependencies list names exactly which predecessors lack
|
|
1020
|
+
// completion evidence, for the named-skip report.
|
|
1021
|
+
plan['ready'] = isReady;
|
|
1022
|
+
if (missingEvidence.length > 0)
|
|
1023
|
+
plan['unresolved_dependencies'] = missingEvidence;
|
|
1024
|
+
}
|
|
958
1025
|
plans.push(plan);
|
|
959
1026
|
const waveKey = String(effectiveWave);
|
|
960
1027
|
if (!waves[waveKey]) {
|
|
@@ -968,6 +1035,7 @@ function cmdPhasePlanIndex(cwd, phase, raw) {
|
|
|
968
1035
|
waves,
|
|
969
1036
|
incomplete,
|
|
970
1037
|
runnable,
|
|
1038
|
+
ready_plans: readyPlans,
|
|
971
1039
|
has_checkpoints: hasCheckpoints,
|
|
972
1040
|
};
|
|
973
1041
|
if (planNamingWarning)
|
|
@@ -1115,7 +1183,14 @@ function collectSiblingWorktreePhaseNums(cwd, used) {
|
|
|
1115
1183
|
const siblingPlanningDir = (wt) => planningDir(wt, ws);
|
|
1116
1184
|
const dirNumPattern = /^(?:[A-Z][A-Z0-9]*-)?(\d+)-/;
|
|
1117
1185
|
// Same header shape the allocators scan locally (#1729 tag tolerance).
|
|
1118
|
-
|
|
1186
|
+
// #5007 (Phase 6 / ADR-4910 §8): migrated onto buildPhaseHeadingScanRegex
|
|
1187
|
+
// (src/phase-id.cjs), the shared "which phases exist in this content" scan
|
|
1188
|
+
// owner. LABEL_ONLY baseline with no convention argument reproduces the
|
|
1189
|
+
// prior literal `Phase\s+` (no bracket tolerance) byte-for-byte — this is a
|
|
1190
|
+
// counter, matching the LABEL_ONLY docstring guidance exactly, so it is
|
|
1191
|
+
// deliberately NOT widened onto the bracket grammar the way the real-heading
|
|
1192
|
+
// readers (getRoadmapModeForPhase, phaseDisplayNameFromRoadmap) are.
|
|
1193
|
+
const { regex: headerPattern, phaseNumGroup } = buildPhaseHeadingScanRegex(PHASE_HEADING_BASELINE.LABEL_ONLY);
|
|
1119
1194
|
for (const line of porcelain.split('\n')) {
|
|
1120
1195
|
if (!line.startsWith('worktree '))
|
|
1121
1196
|
continue;
|
|
@@ -1140,7 +1215,7 @@ function collectSiblingWorktreePhaseNums(cwd, used) {
|
|
|
1140
1215
|
let m;
|
|
1141
1216
|
headerPattern.lastIndex = 0;
|
|
1142
1217
|
while ((m = headerPattern.exec(content)) !== null) {
|
|
1143
|
-
const num = parseInt(m[
|
|
1218
|
+
const num = parseInt(m[phaseNumGroup], 10);
|
|
1144
1219
|
if (!isSentinelPhaseId(num))
|
|
1145
1220
|
used.add(num);
|
|
1146
1221
|
}
|
|
@@ -1180,7 +1255,13 @@ function cmdPhaseAdd(cwd, description, raw, customId) {
|
|
|
1180
1255
|
// (section header, roadmap bullet, or on-disk directory) is counted:
|
|
1181
1256
|
// 1) Section headers: ### Phase N: / ## Phase N: / #### Phase N:
|
|
1182
1257
|
// #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag (literal mirror of OPTIONAL_PHASE_TAG_SOURCE).
|
|
1183
|
-
|
|
1258
|
+
// #5007 (Phase 6 / ADR-4910 §8): migrated onto buildPhaseHeadingScanRegex
|
|
1259
|
+
// (src/phase-id.cjs) with the LABEL_ONLY baseline and no convention
|
|
1260
|
+
// argument — a counter site, matching phase-id.cts's own LABEL_ONLY
|
|
1261
|
+
// docstring guidance, reproducing the prior `Phase\s+` literal (no
|
|
1262
|
+
// bracket tolerance) byte-for-byte. See collectSiblingWorktreePhaseNums
|
|
1263
|
+
// (above) for the identical migration on the same regex shape.
|
|
1264
|
+
const { regex: headerPattern, phaseNumGroup } = buildPhaseHeadingScanRegex(PHASE_HEADING_BASELINE.LABEL_ONLY);
|
|
1184
1265
|
// 2) Roadmap bullet entries: - [ ] **Phase N: ...** (all checkbox variants)
|
|
1185
1266
|
// The lookahead accepts colon, decimal-dot, whitespace, bold-close asterisk,
|
|
1186
1267
|
// or end-of-line so titleless forms ("- [ ] **Phase 11**", "- [ ] Phase 11")
|
|
@@ -1189,7 +1270,7 @@ function cmdPhaseAdd(cwd, description, raw, customId) {
|
|
|
1189
1270
|
const usedPhaseNums = new Set();
|
|
1190
1271
|
let m;
|
|
1191
1272
|
while ((m = headerPattern.exec(content)) !== null) {
|
|
1192
|
-
const num = parseInt(m[
|
|
1273
|
+
const num = parseInt(m[phaseNumGroup], 10);
|
|
1193
1274
|
// #3185: canonical sentinel predicate (SENTINEL_RANGES [0,999]) — this was a local 999-only literal that admitted Phase 0.
|
|
1194
1275
|
if (!isSentinelPhaseId(num))
|
|
1195
1276
|
usedPhaseNums.add(num);
|
|
@@ -1289,11 +1370,15 @@ function cmdPhaseAddBatch(cwd, descriptions, raw) {
|
|
|
1289
1370
|
// bullets, on-disk dirs. The bullet scan was missing here — a bullet-only
|
|
1290
1371
|
// `Phase N` row was invisible to batch allocation (#3849 secondary).
|
|
1291
1372
|
// #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag (literal mirror of OPTIONAL_PHASE_TAG_SOURCE).
|
|
1292
|
-
|
|
1373
|
+
// #5007 (Phase 6 / ADR-4910 §8): migrated onto buildPhaseHeadingScanRegex
|
|
1374
|
+
// (src/phase-id.cjs) with the LABEL_ONLY baseline and no convention
|
|
1375
|
+
// argument — same counter-site migration as cmdPhaseAdd's identical
|
|
1376
|
+
// regex above.
|
|
1377
|
+
const { regex: phasePattern, phaseNumGroup } = buildPhaseHeadingScanRegex(PHASE_HEADING_BASELINE.LABEL_ONLY);
|
|
1293
1378
|
const bulletPattern = /^[ \t]*-[ \t]*\[[^\]]{0,200}\][ \t]*\*{0,2}Phase[ \t]+(\d+)(?=[:.\s*]|$)/gim;
|
|
1294
1379
|
let m;
|
|
1295
1380
|
while ((m = phasePattern.exec(content)) !== null) {
|
|
1296
|
-
const num = parseInt(m[
|
|
1381
|
+
const num = parseInt(m[phaseNumGroup], 10);
|
|
1297
1382
|
// #3185: canonical sentinel predicate (SENTINEL_RANGES [0,999]) — this was a local 999-only literal that admitted Phase 0.
|
|
1298
1383
|
if (isSentinelPhaseId(num))
|
|
1299
1384
|
continue;
|
|
@@ -1416,7 +1501,19 @@ function scanExistingDecimalPhaseNumbers(phasesDir, rawContent, base) {
|
|
|
1416
1501
|
decimalSet.add(parseInt(dm[1], 10));
|
|
1417
1502
|
}
|
|
1418
1503
|
}
|
|
1419
|
-
|
|
1504
|
+
// #5007 (Phase 6 / ADR-4910 §8): composes phaseHeadingPrefixSrcFor directly
|
|
1505
|
+
// rather than routing through buildPhaseHeadingScanRegex — the owner has no
|
|
1506
|
+
// decimal-subphase capture group. The design doc's suggested composition
|
|
1507
|
+
// passed `capturing=true` to phaseHeadingPrefixSrcFor, but this site has no
|
|
1508
|
+
// use for the bracket-id capture it would add, and doing so would silently
|
|
1509
|
+
// shift `rmMatch[1]` (the decimal digits) to group 2 — a real correctness
|
|
1510
|
+
// hazard. `capturing` is left at its default (false), and the `#{2,4}\s*`
|
|
1511
|
+
// heading marker (also omitted from the design's snippet) is kept explicit,
|
|
1512
|
+
// since phaseHeadingPrefixSrcFor never includes it — callers always add it
|
|
1513
|
+
// themselves (see buildPhaseHeadingScanRegex's own usage). No convention
|
|
1514
|
+
// argument: LABEL_ONLY with convention=undefined reproduces the prior
|
|
1515
|
+
// `Phase\s+` literal byte-for-byte.
|
|
1516
|
+
const rmPhasePattern = new RegExp(`#{2,4}\\s*${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY)}${phaseMarkdownRegexSource(base)}\\.(\\d+)${OPTIONAL_PHASE_TAG_SOURCE}\\s*:`, 'gi');
|
|
1420
1517
|
let rmMatch;
|
|
1421
1518
|
while ((rmMatch = rmPhasePattern.exec(rawContent)) !== null) {
|
|
1422
1519
|
decimalSet.add(parseInt(rmMatch[1], 10));
|
|
@@ -1443,10 +1540,27 @@ function cmdPhaseInsert(cwd, afterPhase, description, raw, allocation = 'nested'
|
|
|
1443
1540
|
const content = extractCurrentMilestone(rawContent, cwd);
|
|
1444
1541
|
const normalizedAfter = normalizePhaseName(afterPhase);
|
|
1445
1542
|
const afterPhaseEscaped = phaseMarkdownRegexSource(normalizedAfter);
|
|
1446
|
-
const
|
|
1447
|
-
|
|
1543
|
+
const insertConvention = resolvePhaseIdConvention(cwd);
|
|
1544
|
+
// #5007 (Phase 6 / ADR-4910 §8): buildPhaseHeadingRegex (src/roadmap.cts)
|
|
1545
|
+
// is anchored `^...$` with no 'm' flag — designed to test ONE
|
|
1546
|
+
// already-tokenized heading LINE, not to `.test()` against multi-line
|
|
1547
|
+
// `content` directly (the design doc's "no anchoring change needed"
|
|
1548
|
+
// claim for this site does not hold once the anchor is accounted for —
|
|
1549
|
+
// same discrepancy as getRoadmapModeForPhase and phaseDisplayNameFromRoadmap
|
|
1550
|
+
// above/below). Routed through tokenizeHeadings, mirroring
|
|
1551
|
+
// searchPhaseInContent's own usage.
|
|
1552
|
+
const targetPattern = buildPhaseHeadingRegex(afterPhaseEscaped, insertConvention);
|
|
1553
|
+
const headingMatch = (0, markdown_sectionizer_cjs_1.tokenizeHeadings)(content).some((h) => targetPattern.test(h.text));
|
|
1448
1554
|
const bulletPattern = new RegExp(`-\\s*\\[[ x]\\]\\s*(?:\\*\\*)?Phase\\s+${afterPhaseEscaped}${OPTIONAL_PHASE_TAG_SOURCE}[:\\s]`, 'i');
|
|
1449
|
-
|
|
1555
|
+
// #5007 (Phase 6 / ADR-4910 §8): a pure "does ANY phase heading exist"
|
|
1556
|
+
// boolean — neither shared owner exposes this shape (buildPhaseHeadingRegex
|
|
1557
|
+
// is phase-N-specific; buildPhaseHeadingScanRegex always captures a phase
|
|
1558
|
+
// number). Composed directly from phaseHeadingPrefixSrcFor with the
|
|
1559
|
+
// `#{2,4}\s*` marker kept explicit (phaseHeadingPrefixSrcFor never
|
|
1560
|
+
// includes it). Unanchored `.test()` on multi-line content, same as the
|
|
1561
|
+
// pre-migration literal — no tokenize-first refactor needed here since
|
|
1562
|
+
// there is no anchor in this source.
|
|
1563
|
+
const anyHeadingPattern = new RegExp(`#{2,4}\\s*${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY, insertConvention)}\\d`, 'i');
|
|
1450
1564
|
const roadmapHasHeadingPhases = anyHeadingPattern.test(content);
|
|
1451
1565
|
const isBulletStyle = !headingMatch && bulletPattern.test(content) && !roadmapHasHeadingPhases;
|
|
1452
1566
|
if (!headingMatch && !isBulletStyle) {
|
|
@@ -1522,14 +1636,42 @@ function cmdPhaseInsert(cwd, afterPhase, description, raw, allocation = 'nested'
|
|
|
1522
1636
|
}
|
|
1523
1637
|
else {
|
|
1524
1638
|
const phaseEntry = `\n### Phase ${_decimalPhase}: ${description} (INSERTED)\n\n**Goal:** [Urgent work - to be planned]\n**Requirements**: TBD\n**Depends on:** Phase ${afterPhase}\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run ${(0, runtime_slash_cjs_1.formatGsdSlash)('plan-phase', (0, runtime_slash_cjs_1.resolveRuntime)(cwd))} ${_decimalPhase} to break down)\n`;
|
|
1525
|
-
|
|
1639
|
+
// #5007 (Phase 6 / ADR-4910 §8): this site needs the WHOLE matched
|
|
1640
|
+
// heading line plus its trailing newline (for splicing the new entry
|
|
1641
|
+
// in immediately after it) — neither shared owner returns that shape
|
|
1642
|
+
// (buildPhaseHeadingRegex captures only the title). Composed directly
|
|
1643
|
+
// from phaseHeadingPrefixSrcFor, keeping this site's own full-line
|
|
1644
|
+
// capture; audited together with the targetPattern/anyHeadingPattern
|
|
1645
|
+
// migration above (same describe block, same afterPhaseEscaped/
|
|
1646
|
+
// insertConvention inputs).
|
|
1647
|
+
const headerPattern = new RegExp(`(#{2,4}\\s*${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY, insertConvention)}${afterPhaseEscaped}${OPTIONAL_PHASE_TAG_SOURCE}:[^\\n]*\\n)`, 'i');
|
|
1526
1648
|
const headerMatch = rawContent.match(headerPattern);
|
|
1527
1649
|
if (!headerMatch) {
|
|
1528
1650
|
error(`Could not find Phase ${afterPhase} header`);
|
|
1529
1651
|
}
|
|
1530
1652
|
const headerIdx = rawContent.indexOf(headerMatch[0]);
|
|
1531
1653
|
const afterHeader = rawContent.slice(headerIdx + headerMatch[0].length);
|
|
1532
|
-
|
|
1654
|
+
// #5007 (Phase 6 / ADR-4910 §8): this next-phase-boundary lookup was NOT
|
|
1655
|
+
// one of the 15 originally-marked grandfathered sites, but it hand-rolls
|
|
1656
|
+
// the identical `#{2,4}...Phase\s+` heading-marker shape and sits right
|
|
1657
|
+
// next to headerPattern above, which this same commit series just
|
|
1658
|
+
// widened (via insertConvention) to recognize a bracket-tagged target
|
|
1659
|
+
// heading. Left bracket-blind, this regex would silently defeat that
|
|
1660
|
+
// widening: under the bracket convention a freshly-recognized
|
|
1661
|
+
// bracket-tagged target could still fail to find the NEXT heading as a
|
|
1662
|
+
// boundary (`[CODE.MM] Phase N+1:` never matches a bare `Phase\s+`
|
|
1663
|
+
// literal), so the newly inserted entry falls through to the
|
|
1664
|
+
// rawContent.length branch and lands appended at end-of-file instead of
|
|
1665
|
+
// directly after the target section. Found while migrating the
|
|
1666
|
+
// neighboring sites, not deferred: this is the same defect class the
|
|
1667
|
+
// whole Phase 6 effort exists to drain, just an unmarked instance of it.
|
|
1668
|
+
// Composed with the SAME baseline/convention as headerPattern above so
|
|
1669
|
+
// target-discovery and next-boundary-discovery agree. The base
|
|
1670
|
+
// `#{2,4}\s+` (mandatory whitespace after the hashes, not `\s*`) is
|
|
1671
|
+
// preserved exactly as the prior literal had it — a pre-existing,
|
|
1672
|
+
// deliberately-unchanged asymmetry with the other sites in this file.
|
|
1673
|
+
const nextPhaseHeadingPrefix = `#{2,4}\\s+${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY, insertConvention)}`;
|
|
1674
|
+
const nextPhaseMatch = afterHeader.match(new RegExp(`\\r?\\n${nextPhaseHeadingPrefix}\\d[\\d.]*`, 'i'));
|
|
1533
1675
|
let insertIdx;
|
|
1534
1676
|
if (nextPhaseMatch) {
|
|
1535
1677
|
insertIdx = headerIdx + headerMatch[0].length + nextPhaseMatch.index;
|
|
@@ -1844,7 +1986,30 @@ function updateRoadmapAfterPhaseRemoval(roadmapPath, targetPhase, isDecimal, rem
|
|
|
1844
1986
|
if (!isDecimal) {
|
|
1845
1987
|
// #1729: fold an optional pre-colon ( ) tag into the suffix capture so it
|
|
1846
1988
|
// is re-emitted verbatim — a tagged later phase still gets renumbered.
|
|
1847
|
-
|
|
1989
|
+
// #5007 (Phase 6 / ADR-4910 §8): composes phaseHeadingPrefixSrcFor
|
|
1990
|
+
// directly (needs prefix/number/suffix as three separate capture groups
|
|
1991
|
+
// for a verbatim in-place rewrite — neither shared owner returns that
|
|
1992
|
+
// shape). Two deliberate departures from the design doc's suggested
|
|
1993
|
+
// composition, both kept for zero behavior change on this
|
|
1994
|
+
// highest-blast-radius site (feeds phase remove's renumber path):
|
|
1995
|
+
// 1. The `#{2,4}\s*` heading marker is kept explicit and folded INTO
|
|
1996
|
+
// the prefix capture group — the design's snippet omitted it
|
|
1997
|
+
// entirely (phaseHeadingPrefixSrcFor never includes it; every
|
|
1998
|
+
// caller adds it, e.g. buildPhaseHeadingScanRegex), and the
|
|
1999
|
+
// original group 1 here captured the marker too.
|
|
2000
|
+
// 2. No convention argument is threaded (LABEL_ONLY with
|
|
2001
|
+
// convention=undefined), even though phaseHeadingPrefixSrcFor
|
|
2002
|
+
// COULD accept one — this keeps the composed source byte-identical
|
|
2003
|
+
// to the prior `Phase\s+` literal, a deliberate non-widening for
|
|
2004
|
+
// this specific site (unlike cmdPhaseInsert's targetPattern).
|
|
2005
|
+
// The number group widens from `\d+(?:\.\d+)?` to the full
|
|
2006
|
+
// PHASE_NUMBER_TOKEN_SOURCE (letter suffix + multi-segment decimals) —
|
|
2007
|
+
// this is SAFE, not a behavior change: decrementRoadmapPhaseToken's own
|
|
2008
|
+
// regex (`^(\d+)(\.\d+)?$`) rejects anything wider and returns the
|
|
2009
|
+
// token unchanged, so a heading this widening newly matches (e.g.
|
|
2010
|
+
// "Phase 12A:") is written back byte-for-byte unchanged, identical to
|
|
2011
|
+
// the pre-migration outcome of the whole regex simply not matching it.
|
|
2012
|
+
content = content.replace(new RegExp(`(#{2,4}\\s*${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY)})(${PHASE_NUMBER_TOKEN_SOURCE})(${OPTIONAL_PHASE_TAG_SOURCE}\\s*:)`, 'gi'), (_match, prefix, num, suffix) => `${prefix}${decrementRoadmapPhaseToken(num, removedInt)}${suffix}`);
|
|
1848
2013
|
content = content.replace(/(-\s*\[[ x]\]\s*.*?Phase\s+)(\d+)(\s*:|\s+)/gi, (_match, prefix, num, suffix) => `${prefix}${decrementRoadmapPhaseNumber(num, removedInt)}${suffix}`);
|
|
1849
2014
|
// ORDINAL-RENUMBER — CELL EDIT (not row-deletion) — migrated onto
|
|
1850
2015
|
// updateTableCell (ADR-2143 §7, sibling of the deleteTableRow scoping
|
|
@@ -1933,8 +2098,52 @@ function updateRoadmapAfterPhaseRemoval(roadmapPath, targetPhase, isDecimal, rem
|
|
|
1933
2098
|
content = ordinalBefore + ordinalSection + ordinalRest;
|
|
1934
2099
|
}
|
|
1935
2100
|
content = content.replace(/(?<![0-9-])(\d{2})-(\d{2})(?=(?:(?:-[A-Za-z][A-Za-z0-9-]*)?-(?:PLAN|SUMMARY)\.md)|(?![0-9-]))/g, (_match, phaseNum, planNum) => `${decrementRoadmapPaddedPhaseNumber(phaseNum, removedInt)}-${planNum}`);
|
|
1936
|
-
|
|
1937
|
-
|
|
2101
|
+
// #5007 (Phase 6 / ADR-4910): migrated the "Depends on" bold-field
|
|
2102
|
+
// decrement off two hand-rolled regexes — one per bold-placement
|
|
2103
|
+
// variant, `**Depends on**: Phase N` and `**Depends on:** Phase N` —
|
|
2104
|
+
// onto the PlanningDoc `boldField` read/write seam (`writePlansField`
|
|
2105
|
+
// above is the established precedent for this migration shape).
|
|
2106
|
+
// `BOLD_FIELD_RE` (planning-document.cts) recognizes BOTH placements
|
|
2107
|
+
// as the SAME field label ("Depends on") — `parseBoldFieldLine` strips
|
|
2108
|
+
// the token down to its inner text regardless of which side of `**`
|
|
2109
|
+
// the colon lands on — so this single loop naturally replaces both
|
|
2110
|
+
// regexes with one call site; no variant-specific branching remains.
|
|
2111
|
+
// Unlike `writePlansField`'s single `findField` lookup (scoped to one
|
|
2112
|
+
// already-isolated phase section), a whole ROADMAP.md can carry a
|
|
2113
|
+
// "Depends on" field in EVERY phase's detail section, so this walks
|
|
2114
|
+
// every `boldField` node in the whole parsed document whose label is
|
|
2115
|
+
// "Depends on" and rewrites each one independently — a
|
|
2116
|
+
// representability refusal or absent match on one field leaves that
|
|
2117
|
+
// field untouched and never blocks any other field, mirroring the
|
|
2118
|
+
// prior regexes' per-occurrence independence. A parse failure or an
|
|
2119
|
+
// unreadable-nodes refusal on `serialize` leaves `content` byte-for-
|
|
2120
|
+
// byte untouched, the same silent no-op the prior regexes had for any
|
|
2121
|
+
// input they didn't match (neither ever threw or warned).
|
|
2122
|
+
const dependsOnParsed = (0, planning_document_cjs_1.parsePlanningDoc)(content, 'ROADMAP.md');
|
|
2123
|
+
if (dependsOnParsed.ok) {
|
|
2124
|
+
let dependsOnDoc = dependsOnParsed.value;
|
|
2125
|
+
const dependsOnFieldIds = dependsOnDoc.nodes
|
|
2126
|
+
.filter((n) => n.kind === 'boldField' && n.label === 'Depends on')
|
|
2127
|
+
.map((n) => n.id);
|
|
2128
|
+
for (const fieldId of dependsOnFieldIds) {
|
|
2129
|
+
const currentRead = (0, planning_document_cjs_1.readNode)(dependsOnDoc, fieldId);
|
|
2130
|
+
if (!currentRead.ok)
|
|
2131
|
+
continue;
|
|
2132
|
+
const depMatch = /^(Phase\s+)(\d+(?:\.\d+)?)\b/i.exec(currentRead.value);
|
|
2133
|
+
if (!depMatch)
|
|
2134
|
+
continue;
|
|
2135
|
+
const [wholeMatch, prefix, num] = depMatch;
|
|
2136
|
+
const newValue = `${prefix}${decrementRoadmapPhaseToken(num, removedInt)}` + currentRead.value.slice(wholeMatch.length);
|
|
2137
|
+
if (newValue === currentRead.value)
|
|
2138
|
+
continue;
|
|
2139
|
+
const staged = (0, planning_document_cjs_1.setFieldValue)(dependsOnDoc, fieldId, newValue);
|
|
2140
|
+
if (staged.ok)
|
|
2141
|
+
dependsOnDoc = staged.value;
|
|
2142
|
+
}
|
|
2143
|
+
const dependsOnOut = (0, planning_document_cjs_1.serialize)(dependsOnDoc);
|
|
2144
|
+
if (dependsOnOut.ok)
|
|
2145
|
+
content = dependsOnOut.value;
|
|
2146
|
+
}
|
|
1938
2147
|
}
|
|
1939
2148
|
(0, shell_command_projection_cjs_1.platformWriteSync)(roadmapPath, content);
|
|
1940
2149
|
// #3685 / #3691: compare NORMALIZED bytes (what platformWriteSync actually
|
|
@@ -1958,21 +2167,22 @@ function updateRoadmapAfterPhaseRemoval(roadmapPath, targetPhase, isDecimal, rem
|
|
|
1958
2167
|
* phase, a stray 'Total Phases: 0' between fences). A file with no leading
|
|
1959
2168
|
* frontmatter is all body: the field goes to content start, preserving the
|
|
1960
2169
|
* former behavior for that shape.
|
|
2170
|
+
*
|
|
2171
|
+
* The block is the one `locateFrontmatterFence` finds (the one fence owner), so this writer
|
|
2172
|
+
* and every STATE.md reader agree on where the body starts. The blank line and the field go
|
|
2173
|
+
* right after the closing fence line, ended by that line's own line ending — so a CRLF file
|
|
2174
|
+
* gains CRLF lines only (#3572 review: a blanket re-join on '\r\n' doubled every carriage
|
|
2175
|
+
* return; joining the new lines on a bare '\n' mixed line endings).
|
|
1961
2176
|
*/
|
|
1962
2177
|
function insertStateBodyFieldAtTop(content, fieldLine) {
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
lines.splice(closeIdx + 1, 0, '', fieldLine);
|
|
1972
|
-
return lines.join('\n');
|
|
1973
|
-
}
|
|
1974
|
-
}
|
|
1975
|
-
return fieldLine + '\n' + content;
|
|
2178
|
+
const fence = (0, frontmatter_fence_cjs_1.locateFrontmatterFence)(content);
|
|
2179
|
+
if (fence?.closed) {
|
|
2180
|
+
const nl = content[fence.closingFenceEnd] === '\r' ? '\r\n' : '\n';
|
|
2181
|
+
return `${content.slice(0, fence.closingFenceEnd)}${nl}${nl}${fieldLine}${content.slice(fence.closingFenceEnd)}`;
|
|
2182
|
+
}
|
|
2183
|
+
// All body: the field is the new first line, ended like the document's first line.
|
|
2184
|
+
const firstNewline = content.indexOf('\n');
|
|
2185
|
+
return fieldLine + (firstNewline > 0 && content[firstNewline - 1] === '\r' ? '\r\n' : '\n') + content;
|
|
1976
2186
|
}
|
|
1977
2187
|
function cmdPhaseRemove(cwd, targetPhase, options, raw) {
|
|
1978
2188
|
if (!targetPhase)
|
|
@@ -2017,6 +2227,21 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
|
|
|
2017
2227
|
error(`Phase ${targetPhase} has ${summaryCount} executed plan(s). Use --force to remove anyway.`);
|
|
2018
2228
|
}
|
|
2019
2229
|
}
|
|
2230
|
+
// #5118 (no write before the error): the STATE.md rewrite below rebuilds the
|
|
2231
|
+
// frontmatter from EVERY surviving phase's report (buildStateFrontmatter →
|
|
2232
|
+
// isPhaseComplete), and it runs AFTER the directory removal, the sibling
|
|
2233
|
+
// renames and the ROADMAP rewrite. Validate every surviving phase's report
|
|
2234
|
+
// here, BEFORE the first write, so a report whose `status` is outside the
|
|
2235
|
+
// closed set fails this command having written nothing (the removed phase's
|
|
2236
|
+
// own report is not read afterwards — it is excluded). The set is the one the
|
|
2237
|
+
// rebuild scans (`statePhaseDirsToScan`: milestone-scoped, deduped), so a
|
|
2238
|
+
// survivor OUTSIDE that set — another milestone's phase directory — does not
|
|
2239
|
+
// block the remove.
|
|
2240
|
+
if (node_fs_1.default.existsSync(node_path_1.default.join(planningDir(cwd), 'STATE.md'))) {
|
|
2241
|
+
const survivorStatusError = findVerificationStatusError(stateMod.statePhaseDirsToScan(cwd).filter((d) => d !== targetDir).map((d) => node_path_1.default.join(phasesDir, d)), { convention: resolvePhaseIdConvention(cwd) });
|
|
2242
|
+
if (survivorStatusError)
|
|
2243
|
+
throw survivorStatusError;
|
|
2244
|
+
}
|
|
2020
2245
|
if (targetDir)
|
|
2021
2246
|
node_fs_1.default.rmSync(node_path_1.default.join(phasesDir, targetDir), { recursive: true, force: true });
|
|
2022
2247
|
let renamedDirs = [];
|
|
@@ -2177,7 +2402,20 @@ function phaseDisplayNameFromRoadmap(roadmapContent, phaseNum) {
|
|
|
2177
2402
|
if (!roadmapContent || !phaseNum)
|
|
2178
2403
|
return null;
|
|
2179
2404
|
const phaseEscaped = phaseMarkdownRegexSource(phaseNum);
|
|
2180
|
-
|
|
2405
|
+
// #5007 (Phase 6 / ADR-4910 §8): buildPhaseHeadingRegex (src/roadmap.cts) is
|
|
2406
|
+
// anchored `^...$` with no 'm' flag by design — it expects one
|
|
2407
|
+
// already-tokenized heading LINE, not a `^`/`m` scan against multi-line
|
|
2408
|
+
// content directly. Routed through tokenizeHeadings first (the same real
|
|
2409
|
+
// refactor searchPhaseInContent's own usage already requires), not a pure
|
|
2410
|
+
// regex-literal swap. Adopts buildPhaseHeadingRegex's ANY_BRACKET baseline —
|
|
2411
|
+
// a deliberate widening, matching getRoadmapModeForPhase's identical choice
|
|
2412
|
+
// above: this is a real-heading reader, so it inherits the same bracket-tag
|
|
2413
|
+
// tolerance searchPhaseInContent already applies everywhere else.
|
|
2414
|
+
const headingPattern = buildPhaseHeadingRegex(phaseEscaped);
|
|
2415
|
+
const matchedHeading = (0, markdown_sectionizer_cjs_1.tokenizeHeadings)(roadmapContent).find((h) => headingPattern.test(h.text));
|
|
2416
|
+
if (!matchedHeading)
|
|
2417
|
+
return null;
|
|
2418
|
+
const heading = matchedHeading.text.match(headingPattern);
|
|
2181
2419
|
if (!heading)
|
|
2182
2420
|
return null;
|
|
2183
2421
|
const name = heading[1].replace(/\(INSERTED\)/i, '').trim();
|
|
@@ -2838,7 +3076,7 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
|
2838
3076
|
// non-mutating peek so an unresolvable pointer isn't self-healed (cleared)
|
|
2839
3077
|
// here and then found "absent" by diagnoseUnresolvedActiveWorkstream below,
|
|
2840
3078
|
// which would misreport a present-but-bad marker as no marker at all.
|
|
2841
|
-
const resolvedWorkstream =
|
|
3079
|
+
const resolvedWorkstream = resolveEnvWorkstream() ?? peekActiveWorkstream(cwd);
|
|
2842
3080
|
if (availableWorkstreams.length > 0 && !resolvedWorkstream) {
|
|
2843
3081
|
// #3579: getActiveWorkstream now inherits a pointer-less session's read
|
|
2844
3082
|
// from the shared .planning/active-workstream marker, so reaching this
|
|
@@ -2985,27 +3223,12 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
|
2985
3223
|
if (/status: diagnosed/.test(content))
|
|
2986
3224
|
warnings.push(`${file}: has diagnosed gaps`);
|
|
2987
3225
|
}
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
// dropping the human_needed/gaps_found advisory warning below.
|
|
2995
|
-
const content = normalizeLineEndings(node_fs_1.default.readFileSync(verificationFilePath, 'utf-8'));
|
|
2996
|
-
// #1159 (Defect A): read ONLY the frontmatter `status` key to avoid false positives
|
|
2997
|
-
// from historical metadata in the file body (e.g. `previous_status: gaps_found`).
|
|
2998
|
-
// A full-text regex like /status: gaps_found/ matches the substring inside
|
|
2999
|
-
// `previous_status: gaps_found`, producing spurious warnings even when the
|
|
3000
|
-
// current frontmatter status is `passed`.
|
|
3001
|
-
const verFm = extractFrontmatter(content, verificationFilePath);
|
|
3002
|
-
// Normalise to lower-case so `status: Passed` (title-case) is not missed.
|
|
3003
|
-
const verStatus = typeof verFm['status'] === 'string' ? verFm['status'].trim().toLowerCase() : '';
|
|
3004
|
-
if (verStatus === 'human_needed')
|
|
3005
|
-
warnings.push(`${file}: needs human verification`);
|
|
3006
|
-
if (verStatus === 'gaps_found')
|
|
3007
|
-
warnings.push(`${file}: has unresolved gaps`);
|
|
3008
|
-
}
|
|
3226
|
+
// #5118 (ADR-5057 Phase 4): the VERIFICATION report's `status` is no
|
|
3227
|
+
// longer read here. This pre-scan used to read each report's frontmatter
|
|
3228
|
+
// itself (case-folded, bypassing the owner) to warn on human_needed /
|
|
3229
|
+
// gaps_found — but the completion GATE below (readVerificationStatus)
|
|
3230
|
+
// already refuses those statuses and names the route, so the advisory
|
|
3231
|
+
// could only ever duplicate the gate's own answer. One reader: the gate.
|
|
3009
3232
|
}
|
|
3010
3233
|
catch {
|
|
3011
3234
|
/* best-effort (#2245 audit): this is an ADVISORY pre-scan of UAT/
|
|
@@ -3084,6 +3307,9 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
|
3084
3307
|
// #2617: pass the project's runtime so the blocked-completion error below
|
|
3085
3308
|
// suggests the command surface this runtime actually installs
|
|
3086
3309
|
// ($gsd-… on Codex) rather than a hard-coded Claude-style string.
|
|
3310
|
+
// #5118: an out-of-set report status THROWS VerificationStatusError out
|
|
3311
|
+
// of here; withPlanningLock releases the lock on the way out and the CLI
|
|
3312
|
+
// seam reports `verification_status_invalid`.
|
|
3087
3313
|
const verificationStatus = readVerificationStatus(phaseFullDir, {
|
|
3088
3314
|
runtime: (0, runtime_slash_cjs_1.resolveRuntime)(cwd),
|
|
3089
3315
|
convention: resolvePhaseIdConvention(cwd),
|
|
@@ -3101,9 +3327,22 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
|
3101
3327
|
staleCheckIndeterminate = true;
|
|
3102
3328
|
warnings.push(`verification staleness check could not complete for phase ${phaseNum} — routed as not-stale, but this was not actually verified (#3057)`);
|
|
3103
3329
|
}
|
|
3104
|
-
if (verificationStatus.status !==
|
|
3330
|
+
if (verificationStatus.status !== VERIFICATION_STATUS.PASSED) {
|
|
3105
3331
|
return verificationStatus;
|
|
3106
3332
|
}
|
|
3333
|
+
// #5118 (no write before the error): the transaction below writes ROADMAP,
|
|
3334
|
+
// REQUIREMENTS and STATE, and the STATE frontmatter rebuild reads EVERY
|
|
3335
|
+
// phase's report (buildStateFrontmatter → isPhaseComplete). Validate it
|
|
3336
|
+
// here, BEFORE the first write, over EXACTLY the set that rebuild scans
|
|
3337
|
+
// (the milestone-scoped, deduped phase set — the owner runs
|
|
3338
|
+
// buildStateFrontmatter itself, so `state sync` and this command refuse
|
|
3339
|
+
// for the same phases), so a report whose `status` is outside the closed
|
|
3340
|
+
// set fails this command having written nothing (withPlanningLock
|
|
3341
|
+
// releases the lock).
|
|
3342
|
+
const preflightStatePath = node_path_1.default.join(planningDir(cwd), 'STATE.md');
|
|
3343
|
+
if (node_fs_1.default.existsSync(preflightStatePath)) {
|
|
3344
|
+
assertVerificationReportsReadable(node_fs_1.default.readFileSync(preflightStatePath, 'utf-8'), cwd);
|
|
3345
|
+
}
|
|
3107
3346
|
const runPhaseCompleteTransaction = () => {
|
|
3108
3347
|
const writes = [];
|
|
3109
3348
|
let roadmapContent = null;
|
|
@@ -3130,7 +3369,7 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
|
3130
3369
|
// whole-slice `.replace()` onto the seam. Applied per single physical
|
|
3131
3370
|
// line by updateBullet, so the pattern no longer needs the `m` flag
|
|
3132
3371
|
// (it never sees more than one line at a time); see
|
|
3133
|
-
//
|
|
3372
|
+
// writePlansField below for the sites that were migrated onto
|
|
3134
3373
|
// withPhaseSection instead.
|
|
3135
3374
|
//
|
|
3136
3375
|
// #2245 review Fix 6: this is behaviour-preserving for GSD-GENERATED
|
|
@@ -3183,12 +3422,105 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
|
3183
3422
|
};
|
|
3184
3423
|
// ADR-2143 §4: the plan-count write is now routed through
|
|
3185
3424
|
// withPhaseSection (see mutateMilestonePhase below), which hands this
|
|
3186
|
-
//
|
|
3187
|
-
//
|
|
3188
|
-
//
|
|
3189
|
-
//
|
|
3190
|
-
//
|
|
3191
|
-
|
|
3425
|
+
// seam call ONLY phase N's own detail-section body — the section
|
|
3426
|
+
// boundary itself confines the write (the #2067/#2200 boundary-
|
|
3427
|
+
// crossing class is structurally impossible for this site).
|
|
3428
|
+
//
|
|
3429
|
+
// #4906 Phase 2 (#4917/ADR-4910): migrated off the one-capture-group
|
|
3430
|
+
// regex that replaced to end of line, dropping any hand-written
|
|
3431
|
+
// trailing prose after the count (#4852) — onto the PlanningDoc
|
|
3432
|
+
// `boldField` write seam, whose `valueSpan`/`trailingSpan` split
|
|
3433
|
+
// never touches the trailing annotation.
|
|
3434
|
+
const writePlansField = (body) => {
|
|
3435
|
+
const parsed = (0, planning_document_cjs_1.parsePlanningDoc)(body, 'ROADMAP.md');
|
|
3436
|
+
if (!parsed.ok) {
|
|
3437
|
+
preservationWarnings.push({ field: 'Plans', reason: parsed.reason });
|
|
3438
|
+
return body;
|
|
3439
|
+
}
|
|
3440
|
+
const fieldId = (0, planning_document_cjs_1.findField)(parsed.value, 'Plans');
|
|
3441
|
+
if (!fieldId) {
|
|
3442
|
+
// #4906 regression (#1163 parity, caught by gsd-test against
|
|
3443
|
+
// roadmap.cts's sibling site): a hand-edited or pre-template
|
|
3444
|
+
// ROADMAP.md may carry a PLAIN (non-bold) `Plans:` line rather
|
|
3445
|
+
// than the canonical `**Plans**:`/`**Plans:**` bold field.
|
|
3446
|
+
// BOLD_FIELD_RE stays bold-only (widening it would register
|
|
3447
|
+
// ordinary prose as a spurious field seam-wide) — this fallback
|
|
3448
|
+
// mirrors roadmap.cts's identical one, kept in parity per
|
|
3449
|
+
// Decision 2 rather than letting the two sites diverge on which
|
|
3450
|
+
// legacy shapes they tolerate.
|
|
3451
|
+
const plainMatch = body.match(/^([ \t]*)Plans:([ \t]*)([^\r\n]*)$/m);
|
|
3452
|
+
if (!plainMatch) {
|
|
3453
|
+
// No `**Plans:**`/`**Plans**:`/plain `Plans:` line in this
|
|
3454
|
+
// phase's section — nothing to write; not a failure (mirrors
|
|
3455
|
+
// the old regex's silent no-match no-op).
|
|
3456
|
+
return body;
|
|
3457
|
+
}
|
|
3458
|
+
const [whole, indent, spacing, plainValue] = plainMatch;
|
|
3459
|
+
const plainCountPrefixMatch = plainValue.match(/^(?:\d+\s*\/\s*\d+\s+plans(?:\s+(?:complete|executed))?|\d+\s+plans?)/i);
|
|
3460
|
+
const plainIsTemplatePlaceholder = /^\[\s*Number of plans\b[\s\S]*\]$/i.test(plainValue.trim());
|
|
3461
|
+
if (!plainCountPrefixMatch && !plainIsTemplatePlaceholder) {
|
|
3462
|
+
// Arm 3: freeform prose, TBD, a bracketed human annotation, or
|
|
3463
|
+
// an empty value — leave the field exactly as it was.
|
|
3464
|
+
return body;
|
|
3465
|
+
}
|
|
3466
|
+
const plainNewCountText = `${summaryCount}/${planCount} plans complete`;
|
|
3467
|
+
const plainSuffix = plainCountPrefixMatch ? plainValue.slice(plainCountPrefixMatch[0].length) : '';
|
|
3468
|
+
const newPlainLine = `${indent}Plans:${spacing}${plainNewCountText}${plainSuffix}`;
|
|
3469
|
+
const start = plainMatch.index ?? body.indexOf(whole);
|
|
3470
|
+
return body.slice(0, start) + newPlainLine + body.slice(start + whole.length);
|
|
3471
|
+
}
|
|
3472
|
+
// #4906 regression fix: PREFIX-match the existing value's count
|
|
3473
|
+
// token and re-glue whatever follows it VERBATIM — a glued-on
|
|
3474
|
+
// annotation with no ` — ` separator (e.g. a parenthetical like
|
|
3475
|
+
// `0/1 plans executed (11-16 are gap closure from VERIFICATION)`)
|
|
3476
|
+
// lives entirely inside `value` (`TRAILING_SEPARATOR_RE` in
|
|
3477
|
+
// planning-document.cts only splits on ` — `, unchanged/correct),
|
|
3478
|
+
// so overwriting `value` outright previously destroyed it.
|
|
3479
|
+
//
|
|
3480
|
+
// #4906 review finding (isolated adversarial pass): the prior
|
|
3481
|
+
// version of this migration preserved this site's OLD
|
|
3482
|
+
// unconditional-overwrite behavior for the no-count-prefix case,
|
|
3483
|
+
// which clobbers arm 3 (freeform prose / TBD / a bracketed human
|
|
3484
|
+
// annotation like `[Deferred pending re-scope]`) — a real
|
|
3485
|
+
// regression against the design doc's own Behavior table row 4,
|
|
3486
|
+
// not an accepted trade-off. Fixed here by adopting the SAME
|
|
3487
|
+
// template-placeholder / arm-3-untouched classification
|
|
3488
|
+
// roadmap.cts's sibling site already uses (isTemplatePlaceholder +
|
|
3489
|
+
// "no count prefix and not a placeholder => leave untouched"),
|
|
3490
|
+
// rather than letting the two migrated sites diverge on this.
|
|
3491
|
+
const newCountText = `${summaryCount}/${planCount} plans complete`;
|
|
3492
|
+
const current = (0, planning_document_cjs_1.readNode)(parsed.value, fieldId);
|
|
3493
|
+
if (!current.ok) {
|
|
3494
|
+
return body;
|
|
3495
|
+
}
|
|
3496
|
+
const currentValue = current.value;
|
|
3497
|
+
const countPrefixMatch = currentValue.match(/^(?:\d+\s*\/\s*\d+\s+plans(?:\s+(?:complete|executed))?|\d+\s+plans?)/i);
|
|
3498
|
+
const isTemplatePlaceholder = /^\[\s*Number of plans\b[\s\S]*\]$/i.test(currentValue.trim());
|
|
3499
|
+
if (!countPrefixMatch && !isTemplatePlaceholder) {
|
|
3500
|
+
// Arm 3: freeform prose, TBD, a bracketed human annotation, or an
|
|
3501
|
+
// empty value — leave the field exactly as it was.
|
|
3502
|
+
return body;
|
|
3503
|
+
}
|
|
3504
|
+
const newValueToWrite = countPrefixMatch
|
|
3505
|
+
? newCountText + currentValue.slice(countPrefixMatch[0].length)
|
|
3506
|
+
: newCountText;
|
|
3507
|
+
const staged = (0, planning_document_cjs_1.setFieldValue)(parsed.value, fieldId, newValueToWrite);
|
|
3508
|
+
if (!staged.ok) {
|
|
3509
|
+
preservationWarnings.push({ field: 'Plans', reason: staged.reason });
|
|
3510
|
+
return body;
|
|
3511
|
+
}
|
|
3512
|
+
const out = (0, planning_document_cjs_1.serialize)(staged.value);
|
|
3513
|
+
if (!out.ok) {
|
|
3514
|
+
// `hasUnreadableNodes` refusal (ADR-4910 amendment) — a ragged
|
|
3515
|
+
// SIBLING node elsewhere in this same section refuses the whole
|
|
3516
|
+
// splice. Never throw / crash the phase-complete transaction over
|
|
3517
|
+
// a node unrelated to this write; surface it and leave `body`
|
|
3518
|
+
// unchanged, same as any other preservation warning.
|
|
3519
|
+
preservationWarnings.push({ field: 'Plans', reason: out.reason });
|
|
3520
|
+
return body;
|
|
3521
|
+
}
|
|
3522
|
+
return out.value;
|
|
3523
|
+
};
|
|
3192
3524
|
const phaseInfoSummaries = phaseInfo['summaries'];
|
|
3193
3525
|
// #2200: apply the phase-checkbox flip, the plan-count write, and the
|
|
3194
3526
|
// per-plan checkbox flips ONLY within the current milestone's region(s)
|
|
@@ -3204,7 +3536,7 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
|
3204
3536
|
const plansResult = (0, markdown_table_cjs_1.updateTableCell)(text, rowMatch, 'Plans Complete', ` ${summaryCount}/${planCount} `);
|
|
3205
3537
|
if (plansResult.ok)
|
|
3206
3538
|
text = plansResult.value;
|
|
3207
|
-
const statusResult = (0, markdown_table_cjs_1.updateTableCell)(text, rowMatch, 'Status',
|
|
3539
|
+
const statusResult = (0, markdown_table_cjs_1.updateTableCell)(text, rowMatch, 'Status', ` ${(0, phase_status_cjs_1.toRoadmapStatusCell)(phase_status_cjs_1.PHASE_STATUS.COMPLETE).padEnd(11)} `);
|
|
3208
3540
|
if (statusResult.ok)
|
|
3209
3541
|
text = statusResult.value;
|
|
3210
3542
|
// Preserve only a valid ISO date (#1161: idempotent; self-heal
|
|
@@ -3225,14 +3557,18 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
|
3225
3557
|
// section's body, so neither regex can escape into a sibling
|
|
3226
3558
|
// phase's section, a shipped milestone, or a Backlog entry.
|
|
3227
3559
|
s = withPhaseSection(s, phaseNum, (body) => {
|
|
3228
|
-
let b = body
|
|
3560
|
+
let b = writePlansField(body);
|
|
3229
3561
|
for (const summaryFile of phaseInfoSummaries) {
|
|
3230
3562
|
const planId = summaryFile.replace('-SUMMARY.md', '').replace('SUMMARY.md', '');
|
|
3231
3563
|
if (!planId)
|
|
3232
3564
|
continue;
|
|
3233
3565
|
const planEscaped = (0, pattern_cjs_1.escapeRegex)(planId);
|
|
3234
|
-
|
|
3235
|
-
|
|
3566
|
+
// ADR-5057 §6 (Phase 13, #5217): the per-plan flip is a bullet
|
|
3567
|
+
// mutation, so it goes through `updateBullet` like the phase
|
|
3568
|
+
// checkbox flip above — first matching bullet only, never a
|
|
3569
|
+
// fenced line, never a mid-line occurrence.
|
|
3570
|
+
const planCheckboxPattern = new RegExp(`^(\\s*-\\s*\\[) (\\]\\s*(?:\\*\\*)?${planEscaped}(?:\\*\\*)?)`, 'i');
|
|
3571
|
+
b = (0, markdown_sectionizer_cjs_1.updateBullet)(b, (_bulletText, rawLine) => planCheckboxPattern.test(rawLine), (rawLine) => rawLine.replace(planCheckboxPattern, '$1x$2'));
|
|
3236
3572
|
}
|
|
3237
3573
|
return b;
|
|
3238
3574
|
});
|
|
@@ -3270,14 +3606,38 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
|
3270
3606
|
if (node_fs_1.default.existsSync(reqPath)) {
|
|
3271
3607
|
const phaseEsc = phaseMarkdownRegexSource(phaseNum);
|
|
3272
3608
|
const currentMilestoneRoadmap = extractCurrentMilestone(roadmapContent, cwd);
|
|
3273
|
-
|
|
3609
|
+
// #5007 (Phase 6 / ADR-4910 §8): this site captures the entire
|
|
3610
|
+
// section BODY through to the next heading (via its own
|
|
3611
|
+
// `[\s\S]*?` lookahead-to-next-heading structure) — neither shared
|
|
3612
|
+
// owner returns that shape, so phaseHeadingPrefixSrcFor is composed
|
|
3613
|
+
// directly, keeping this site's own lookahead. The prior hand-rolled
|
|
3614
|
+
// heading-marker-plus-label literal appeared TWICE in this source
|
|
3615
|
+
// (the main capture prefix AND the lookahead) — both occurrences
|
|
3616
|
+
// were the literal-drift detector's target
|
|
3617
|
+
// (findPhaseHeadingScanLiteralDrift is a pure literal-text match,
|
|
3618
|
+
// blind to what surrounds it), so both are replaced with the same
|
|
3619
|
+
// composed prefix, built once via phaseHeadingPrefixSrcFor and
|
|
3620
|
+
// reused, so the two halves cannot drift from each other. No
|
|
3621
|
+
// convention argument: LABEL_ONLY with convention=undefined
|
|
3622
|
+
// reproduces the prior `Phase\s+` literal byte-for-byte — this
|
|
3623
|
+
// feeds cmdPhaseComplete, so no widening was introduced.
|
|
3624
|
+
const phaseSectionHeadingPrefix = `#{2,4}\\s*${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY)}`;
|
|
3625
|
+
const phaseSectionMatch = currentMilestoneRoadmap.match(new RegExp(`(${phaseSectionHeadingPrefix}${phaseEsc}${OPTIONAL_PHASE_TAG_SOURCE}[:\\s][\\s\\S]*?)(?=${phaseSectionHeadingPrefix}|$)`, 'i'));
|
|
3274
3626
|
const sectionText = phaseSectionMatch ? phaseSectionMatch[1] : '';
|
|
3275
|
-
|
|
3627
|
+
// #4731: multiline-aware — hard-wrapped Requirements read past the
|
|
3628
|
+
// line break before the ID scan. The shared extractor also stops at
|
|
3629
|
+
// headings and table rows, so a Requirements field followed by the
|
|
3630
|
+
// Traceability table cannot bleed other phases' REQ-IDs into the
|
|
3631
|
+
// citation scan (isolated-review MEDIUM on the inline lookahead,
|
|
3632
|
+
// whose lazy capture swallowed everything to section end).
|
|
3633
|
+
const reqLine = sectionText
|
|
3634
|
+
? roadmapParserMod.extractPhaseFieldMultiline(sectionText, 'Requirements')
|
|
3635
|
+
: null;
|
|
3276
3636
|
const originalReqContent = node_fs_1.default.readFileSync(reqPath, 'utf-8');
|
|
3277
3637
|
let reqContent = originalReqContent;
|
|
3278
3638
|
// #2316: `citedReqIds` — the REQ-IDs ROADMAP's own **Requirements:**
|
|
3279
3639
|
// line for this phase actually cites — is hoisted out of the
|
|
3280
|
-
// `if (
|
|
3640
|
+
// `if (reqLine)` block (previously scoped only inside it) so the
|
|
3281
3641
|
// ghost-ID cross-check below (~#2316-1) can consult it. `TBD` is the
|
|
3282
3642
|
// literal placeholder `phase.add`/`-batch`/`-insert` seed
|
|
3283
3643
|
// (`**Requirements**: TBD`, src/phase.cts:833,920,1078) — never a
|
|
@@ -3289,16 +3649,16 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
|
3289
3649
|
// otherwise) — the `if (reqUpdate.ok)` below previously had no
|
|
3290
3650
|
// `else`, discarding this fact silently instead of surfacing it.
|
|
3291
3651
|
const traceabilityWriteMisses = [];
|
|
3292
|
-
if (
|
|
3652
|
+
if (reqLine) {
|
|
3293
3653
|
// #2334 HIGH 3 + #3697: selection and under-selection detection both
|
|
3294
3654
|
// live in `analyzeRequirementsLine` (module scope, above), extracted in
|
|
3295
3655
|
// round 3 so the parser is directly testable — a closure in here is
|
|
3296
3656
|
// reachable only by spawning the CLI, which no fast-check property test
|
|
3297
3657
|
// can do. `citedReqIds` is byte-identical to the expression that stood
|
|
3298
3658
|
// here; nothing about what phase-complete MARKS has changed.
|
|
3299
|
-
const reqLineAnalysis = analyzeRequirementsLine(
|
|
3659
|
+
const reqLineAnalysis = analyzeRequirementsLine(reqLine);
|
|
3300
3660
|
citedReqIds = reqLineAnalysis.citedReqIds;
|
|
3301
|
-
const reqLineWarning = formatRequirementsLineWarning(phaseNum,
|
|
3661
|
+
const reqLineWarning = formatRequirementsLineWarning(phaseNum, reqLine, reqLineAnalysis);
|
|
3302
3662
|
if (reqLineWarning) {
|
|
3303
3663
|
warnings.push(reqLineWarning.message);
|
|
3304
3664
|
// Carried out to the JSON result as its own field — see
|
|
@@ -3584,6 +3944,34 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
|
3584
3944
|
let diskNextName = null;
|
|
3585
3945
|
let roadmapNextNum = null;
|
|
3586
3946
|
let roadmapNextName = null;
|
|
3947
|
+
// #4699: a phase whose roadmap checkbox is `[x]` is already complete and
|
|
3948
|
+
// must never be selected as next_phase — out-of-order completion (a
|
|
3949
|
+
// reopened phase finished after later phases shipped) otherwise persists
|
|
3950
|
+
// the already-done phase as STATE.md current_phase. Collected from the
|
|
3951
|
+
// same milestone-scoped text the roadmap scan walks; membership is
|
|
3952
|
+
// comparePhaseNum-based so `02` and `2` dedupe. With no ROADMAP.md (or no
|
|
3953
|
+
// parseable rows) the set is empty and the scans behave exactly as
|
|
3954
|
+
// before.
|
|
3955
|
+
const roadmapCompleteNums = [];
|
|
3956
|
+
if (roadmapContent !== null) {
|
|
3957
|
+
try {
|
|
3958
|
+
const milestoneForComplete = extractCurrentMilestone(roadmapContent, cwd);
|
|
3959
|
+
const completePattern = new RegExp(`-\\s*\\[[xX]\\]\\s*(?:\\*\\*|__)?\\s*Phase\\s+(${PHASE_NUMBER_TOKEN_SOURCE})`, 'gi');
|
|
3960
|
+
let cm;
|
|
3961
|
+
while ((cm = completePattern.exec(milestoneForComplete)) !== null) {
|
|
3962
|
+
if (isSentinelPhaseId(cm[1]))
|
|
3963
|
+
continue;
|
|
3964
|
+
if (!roadmapCompleteNums.some((n) => comparePhaseNum(cm[1], n) === 0)) {
|
|
3965
|
+
roadmapCompleteNums.push(cm[1]);
|
|
3966
|
+
}
|
|
3967
|
+
}
|
|
3968
|
+
}
|
|
3969
|
+
catch {
|
|
3970
|
+
/* best-effort: an unreadable milestone section leaves the complete
|
|
3971
|
+
* set empty — the scans then behave exactly as they did pre-#4699. */
|
|
3972
|
+
}
|
|
3973
|
+
}
|
|
3974
|
+
const isCompletePhaseNum = (num) => roadmapCompleteNums.some((n) => comparePhaseNum(num, n) === 0);
|
|
3587
3975
|
try {
|
|
3588
3976
|
// #3185 (ADR-3180 Decision 1): "which phase directories belong to
|
|
3589
3977
|
// the CURRENT milestone" — routed through the canonical owner
|
|
@@ -3599,6 +3987,10 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
|
3599
3987
|
// #3185: canonical sentinel predicate (SENTINEL_RANGES [0,999]) — this was a local 999-only literal that admitted Phase 0.
|
|
3600
3988
|
if (isSentinelPhaseId(dm[1]))
|
|
3601
3989
|
continue;
|
|
3990
|
+
// #4699: an already-complete phase (roadmap checkbox [x]) is never
|
|
3991
|
+
// a next_phase candidate — out-of-order completion must skip it.
|
|
3992
|
+
if (roadmapContent !== null && isCompletePhaseNum(dm[1]))
|
|
3993
|
+
continue;
|
|
3602
3994
|
// Numeric MINIMUM above N, not "first encountered". `listMilestonePhaseDirs`
|
|
3603
3995
|
// does sort by `comparePhaseNum`, so a `break` on the first hit happens to be
|
|
3604
3996
|
// correct today — but that makes this scan's correctness depend on an
|
|
@@ -3666,6 +4058,11 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
|
3666
4058
|
// stage 2's heading scan must not advance into backlog headings either.
|
|
3667
4059
|
if (isSentinelPhaseId(pmNum))
|
|
3668
4060
|
continue;
|
|
4061
|
+
// #4699: skip complete phases — a `[x]` checkbox row and the
|
|
4062
|
+
// `## Phase Details` heading of an already-done phase both name a
|
|
4063
|
+
// phase that must never be next_phase.
|
|
4064
|
+
if (roadmapContent !== null && isCompletePhaseNum(pmNum))
|
|
4065
|
+
continue;
|
|
3669
4066
|
// #3701 review: the numeric MINIMUM above N, not the first row above N in
|
|
3670
4067
|
// DOCUMENT order. This scan walks raw roadmap text, and one global regex
|
|
3671
4068
|
// sweeps both the `## Phases` checklist and the `## Phase Details`
|
|
@@ -4009,6 +4406,9 @@ function cmdPhaseUatPassed(cwd, phaseNum, raw, opts = {}) {
|
|
|
4009
4406
|
const phaseFullDir = node_path_1.default.join(cwd, phaseInfo['directory']);
|
|
4010
4407
|
const report = evaluateUatPassed(phaseFullDir, { policy: opts.policy });
|
|
4011
4408
|
output({ phase: phaseNum, ...report }, raw);
|
|
4409
|
+
// #5170: the exit status follows the verdict (status mode: a failing verdict is exit 1). Declared
|
|
4410
|
+
// AFTER output(), which rewrites the pending-outcome cell. The JSON above is unchanged.
|
|
4411
|
+
(0, gate_exit_cjs_1.declareGateExit)((0, gate_verdict_cjs_1.gateVerdict)(report.passed ? 'pass' : 'block', !report.passed, { phase: phaseNum, ...report }), 'status');
|
|
4012
4412
|
}
|
|
4013
4413
|
// #1437 — phase.list-plans: list plan files for a given phase number.
|
|
4014
4414
|
// Returns the full scan result from scanPhasePlans so callers can read plan
|
|
@@ -4057,4 +4457,5 @@ module.exports = {
|
|
|
4057
4457
|
cmdPhaseListPlans,
|
|
4058
4458
|
computeDependencyLevels,
|
|
4059
4459
|
buildShortFormToId,
|
|
4460
|
+
normalizeDependencyToken,
|
|
4060
4461
|
};
|