@opengsd/gsd-core 1.11.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.opencode/plugins/gsd-core.js +12 -0
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-debug-session-manager.md +1 -1
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-dom-verifier.md +169 -0
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +78 -42
- package/agents/gsd-framework-selector.md +1 -3
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.md +0 -1
- package/agents/gsd-pattern-mapper.md +11 -0
- package/agents/gsd-phase-researcher.md +3 -1
- package/agents/gsd-plan-checker.md +91 -112
- package/agents/gsd-planner.md +20 -4
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.md +2 -2
- package/agents/gsd-roadmapper.md +15 -11
- package/agents/gsd-ui-checker.md +82 -7
- package/agents/gsd-ui-researcher.md +70 -3
- package/agents/gsd-verifier.md +24 -2
- package/bin/install.js +847 -200
- package/commands/gsd/discuss-phase.md +1 -1
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/import.md +1 -1
- package/commands/gsd/ns-workflow.md +2 -1
- package/commands/gsd/phase.md +1 -1
- package/commands/gsd/quick-batch.md +105 -0
- package/commands/gsd/quick.md +8 -4
- package/commands/gsd/surface.md +18 -8
- package/gsd-core/bin/gsd-tools.cjs +761 -100
- package/gsd-core/bin/lib/active-workstream-store.cjs +8 -0
- package/gsd-core/bin/lib/adr-parser.cjs +13 -7
- package/gsd-core/bin/lib/agent-install-check.cjs +162 -0
- package/gsd-core/bin/lib/api-coverage.cjs +30 -9
- package/gsd-core/bin/lib/artifacts.cjs +2 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +30 -11
- package/gsd-core/bin/lib/audit.cjs +163 -41
- package/gsd-core/bin/lib/broken-windows.cjs +306 -28
- package/gsd-core/bin/lib/capability-activation.cjs +27 -0
- package/gsd-core/bin/lib/capability-lock.cjs +10 -4
- package/gsd-core/bin/lib/capability-registry.cjs +785 -144
- package/gsd-core/bin/lib/capability-state.cjs +25 -4
- package/gsd-core/bin/lib/capability-validator.cjs +321 -18
- package/gsd-core/bin/lib/capability-writer.cjs +14 -4
- package/gsd-core/bin/lib/check-command-router.cjs +229 -6
- package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
- package/gsd-core/bin/lib/cli-exit.cjs +496 -10
- package/gsd-core/bin/lib/clusters.cjs +1 -0
- package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
- package/gsd-core/bin/lib/codex-agent-toml.cjs +410 -4
- package/gsd-core/bin/lib/command-aliases.cjs +16 -0
- package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
- package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
- package/gsd-core/bin/lib/commands.cjs +877 -54
- package/gsd-core/bin/lib/complexity-trigger.cjs +26 -6
- package/gsd-core/bin/lib/config-loader.cjs +121 -29
- package/gsd-core/bin/lib/config.cjs +92 -2
- package/gsd-core/bin/lib/configuration.cjs +129 -37
- package/gsd-core/bin/lib/core-utils.cjs +118 -14
- package/gsd-core/bin/lib/decisions.cjs +213 -1
- package/gsd-core/bin/lib/edge-probe.cjs +23 -2
- package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
- package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
- package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
- package/gsd-core/bin/lib/frontmatter.cjs +975 -326
- package/gsd-core/bin/lib/gap-checker.cjs +41 -8
- package/gsd-core/bin/lib/git-base-branch.cjs +182 -39
- package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +7 -3
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +60 -14
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +75 -22
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +22 -8
- package/gsd-core/bin/lib/health-diagnostic.cjs +23 -3
- package/gsd-core/bin/lib/host-integration.cjs +96 -11
- package/gsd-core/bin/lib/init-command-router.cjs +132 -21
- package/gsd-core/bin/lib/init.cjs +252 -56
- package/gsd-core/bin/lib/install-engine.cjs +252 -15
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +78 -1
- package/gsd-core/bin/lib/install-profiles.cjs +100 -18
- package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
- package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +10 -7
- package/gsd-core/bin/lib/intel.cjs +101 -26
- package/gsd-core/bin/lib/io.cjs +195 -15
- package/gsd-core/bin/lib/learnings.cjs +85 -14
- package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
- package/gsd-core/bin/lib/loop-resolver.cjs +14 -8
- package/gsd-core/bin/lib/markdown-table.cjs +175 -4
- package/gsd-core/bin/lib/milestone.cjs +112 -7
- package/gsd-core/bin/lib/model-catalog.cjs +177 -19
- package/gsd-core/bin/lib/model-resolver.cjs +10 -28
- package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
- package/gsd-core/bin/lib/phase-command-router.cjs +13 -6
- package/gsd-core/bin/lib/phase-estimation.cjs +17 -8
- package/gsd-core/bin/lib/phase-id.cjs +321 -13
- package/gsd-core/bin/lib/phase-lifecycle.cjs +24 -16
- package/gsd-core/bin/lib/phase-locator.cjs +138 -17
- package/gsd-core/bin/lib/phase.cjs +1175 -115
- package/gsd-core/bin/lib/plan-document.cjs +273 -0
- package/gsd-core/bin/lib/plan-scan.cjs +13 -2
- package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +165 -34
- package/gsd-core/bin/lib/planning-workspace.cjs +159 -28
- package/gsd-core/bin/lib/probe-core.cjs +4 -1
- package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
- package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
- package/gsd-core/bin/lib/quick-batch-command-router.cjs +285 -0
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
- package/gsd-core/bin/lib/quick-batch.cjs +840 -0
- package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +71 -45
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +62 -14
- package/gsd-core/bin/lib/review-lane-invocation.cjs +73 -1
- package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
- package/gsd-core/bin/lib/roadmap-command-router.cjs +45 -31
- package/gsd-core/bin/lib/roadmap-parser.cjs +577 -41
- package/gsd-core/bin/lib/roadmap.cjs +248 -64
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +329 -41
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +16 -17
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +320 -109
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +487 -83
- package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
- package/gsd-core/bin/lib/runtime-slash.cjs +72 -2
- package/gsd-core/bin/lib/shell-command-projection.cjs +75 -8
- package/gsd-core/bin/lib/smart-entry.cjs +19 -31
- package/gsd-core/bin/lib/spec-section.cjs +12 -7
- package/gsd-core/bin/lib/state-command-router.cjs +47 -18
- package/gsd-core/bin/lib/state-contract.cjs +359 -0
- package/gsd-core/bin/lib/state-document.cjs +216 -5
- package/gsd-core/bin/lib/state-md-schema.cjs +231 -0
- package/gsd-core/bin/lib/state-transition.cjs +850 -145
- package/gsd-core/bin/lib/state.cjs +1629 -287
- package/gsd-core/bin/lib/surface.cjs +33 -10
- package/gsd-core/bin/lib/task-command-router.cjs +111 -1
- package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
- package/gsd-core/bin/lib/teams-status.cjs +4 -1
- package/gsd-core/bin/lib/uat-predicate.cjs +58 -20
- package/gsd-core/bin/lib/uat.cjs +2542 -387
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
- package/gsd-core/bin/lib/ui-safety-gate.cjs +37 -7
- package/gsd-core/bin/lib/unusable-input.cjs +13 -0
- package/gsd-core/bin/lib/update-context.cjs +6 -2
- package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
- package/gsd-core/bin/lib/validate.cjs +230 -12
- package/gsd-core/bin/lib/vendor/README.md +43 -5
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +287 -13
- package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
- package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
- package/gsd-core/bin/lib/verify.cjs +441 -56
- package/gsd-core/bin/lib/workstream-inventory.cjs +20 -2
- package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
- package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
- package/gsd-core/bin/lib/worktree-safety.cjs +185 -21
- package/gsd-core/bin/shared/config-defaults.manifest.json +7 -1
- package/gsd-core/bin/shared/config-schema.manifest.json +13 -0
- package/gsd-core/bin/shared/exit-codes.json +8 -0
- package/gsd-core/bin/shared/exit-codes.sh +20 -0
- package/gsd-core/bin/shared/model-catalog.json +8 -1
- package/gsd-core/bin/verify-reapply-patches.cjs +70 -3
- package/gsd-core/references/agent-contracts.md +6 -5
- package/gsd-core/references/api-coverage.md +24 -2
- package/gsd-core/references/autonomous-smart-discuss.md +3 -3
- package/gsd-core/references/checkpoints.md +37 -19
- package/gsd-core/references/decimal-phase-calculation.md +5 -5
- package/gsd-core/references/edge-probe.md +17 -5
- package/gsd-core/references/execute-mvp-tdd.md +18 -18
- package/gsd-core/references/execute-phase-between-wave-reset.md +9 -12
- package/gsd-core/references/execute-phase-response-language.md +6 -0
- package/gsd-core/references/execute-phase-wave-guard.md +11 -9
- package/gsd-core/references/executor-examples.md +42 -0
- package/gsd-core/references/failing-direction.md +78 -0
- package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
- package/gsd-core/references/gate-prompts.md +1 -1
- package/gsd-core/references/git-integration.md +5 -5
- package/gsd-core/references/git-planning-commit.md +3 -3
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +22 -0
- package/gsd-core/references/model-profiles.md +1 -1
- package/gsd-core/references/mvp-concepts.md +2 -2
- package/gsd-core/references/nyquist-compliance.md +74 -0
- package/gsd-core/references/offer-next.md +3 -5
- package/gsd-core/references/phase-argument-parsing.md +3 -3
- package/gsd-core/references/plan-checker-examples.md +41 -0
- package/gsd-core/references/planner-antipatterns.md +25 -0
- package/gsd-core/references/planner-chunked.md +5 -1
- package/gsd-core/references/planner-coupling.md +42 -0
- package/gsd-core/references/planner-failing-direction.md +53 -0
- package/gsd-core/references/planner-human-verify-mode.md +15 -1
- package/gsd-core/references/planner-quick-batch.md +71 -0
- package/gsd-core/references/planner-reviews.md +47 -0
- package/gsd-core/references/planner-revision.md +76 -3
- package/gsd-core/references/planner-verify-command-grounding.md +17 -0
- package/gsd-core/references/planning-config.md +39 -9
- package/gsd-core/references/response-language-directive.md +9 -0
- package/gsd-core/references/reviewer-instances.md +31 -0
- package/gsd-core/references/revision-loop.md +118 -11
- package/gsd-core/references/runtime-aware-dispatch.md +1 -1
- package/gsd-core/references/tdd.md +15 -12
- package/gsd-core/references/ui-brand.md +65 -21
- package/gsd-core/references/ui-consideration-probe.md +1 -1
- package/gsd-core/references/universal-anti-patterns.md +2 -2
- package/gsd-core/references/verifier-evidence-gate.md +160 -0
- package/gsd-core/references/verify-command-path-resolvability.md +42 -0
- package/gsd-core/references/verify-mvp-mode.md +1 -1
- package/gsd-core/references/workstream-flag.md +11 -11
- package/gsd-core/templates/README.md +1 -1
- package/gsd-core/templates/SECURITY.md +3 -3
- package/gsd-core/templates/UI-SPEC.md +25 -3
- package/gsd-core/templates/VALIDATION.md +3 -3
- package/gsd-core/templates/phase-prompt.md +7 -0
- package/gsd-core/templates/state.md +7 -0
- package/gsd-core/templates/verification-report.md +5 -0
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +3 -1
- package/gsd-core/workflows/add-phase.md +5 -3
- package/gsd-core/workflows/add-tests.md +4 -9
- package/gsd-core/workflows/add-todo.md +2 -2
- package/gsd-core/workflows/ai-integration-phase.md +5 -10
- package/gsd-core/workflows/analyze-dependencies.md +2 -0
- package/gsd-core/workflows/audit-fix.md +14 -3
- package/gsd-core/workflows/audit-milestone.md +11 -9
- package/gsd-core/workflows/audit-uat.md +19 -2
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
- package/gsd-core/workflows/autonomous.md +12 -26
- package/gsd-core/workflows/check-todos.md +2 -2
- package/gsd-core/workflows/cleanup.md +3 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +16 -14
- package/gsd-core/workflows/code-review-fix.md +3 -1
- package/gsd-core/workflows/code-review.md +192 -69
- package/gsd-core/workflows/complete-milestone.md +28 -14
- package/gsd-core/workflows/debug.md +6 -4
- package/gsd-core/workflows/diagnose-issues.md +17 -7
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +3 -1
- package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -7
- package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/text.md +3 -1
- package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
- package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
- package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
- package/gsd-core/workflows/discuss-phase-assumptions.md +3 -3
- package/gsd-core/workflows/discuss-phase-power.md +2 -0
- package/gsd-core/workflows/discuss-phase.md +2 -2
- package/gsd-core/workflows/do.md +46 -19
- package/gsd-core/workflows/docs-update.md +6 -5
- package/gsd-core/workflows/edit-phase.md +3 -1
- package/gsd-core/workflows/eval-review.md +5 -10
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +3 -1
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +129 -11
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +29 -5
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +4 -2
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
- package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
- package/gsd-core/workflows/execute-phase.md +68 -66
- package/gsd-core/workflows/execute-plan.md +25 -20
- package/gsd-core/workflows/explore.md +3 -1
- package/gsd-core/workflows/extract-learnings.md +3 -1
- package/gsd-core/workflows/fast.md +8 -2
- package/gsd-core/workflows/forensics.md +3 -1
- package/gsd-core/workflows/graduation.md +6 -6
- package/gsd-core/workflows/health.md +4 -7
- package/gsd-core/workflows/help/modes/brief.md +2 -0
- package/gsd-core/workflows/help/modes/default.md +2 -0
- package/gsd-core/workflows/help/modes/full.md +12 -0
- package/gsd-core/workflows/help/modes/topic.md +2 -0
- package/gsd-core/workflows/help.md +2 -0
- package/gsd-core/workflows/import.md +17 -14
- package/gsd-core/workflows/inbox.md +5 -6
- package/gsd-core/workflows/ingest-docs.md +45 -12
- package/gsd-core/workflows/insert-phase.md +7 -5
- package/gsd-core/workflows/list-phase-assumptions.md +2 -0
- package/gsd-core/workflows/list-seeds.md +7 -3
- package/gsd-core/workflows/list-workspaces.md +3 -1
- package/gsd-core/workflows/manager.md +15 -26
- package/gsd-core/workflows/map-codebase.md +3 -1
- package/gsd-core/workflows/milestone-summary.md +3 -1
- package/gsd-core/workflows/mvp-phase.md +3 -3
- package/gsd-core/workflows/new-milestone.md +10 -22
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
- package/gsd-core/workflows/new-project.md +17 -29
- package/gsd-core/workflows/new-workspace.md +2 -2
- package/gsd-core/workflows/next.md +4 -2
- package/gsd-core/workflows/node-repair.md +2 -0
- package/gsd-core/workflows/note.md +2 -0
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +20 -5
- package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +4 -4
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +12 -3
- package/gsd-core/workflows/plan-phase.md +251 -54
- package/gsd-core/workflows/plan-review-convergence.md +148 -19
- package/gsd-core/workflows/plant-seed.md +3 -3
- package/gsd-core/workflows/pr-branch.md +195 -51
- package/gsd-core/workflows/profile-user.md +17 -15
- package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
- package/gsd-core/workflows/progress.md +52 -15
- package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +38 -5
- package/gsd-core/workflows/quick/steps/quick-verification.md +2 -4
- package/gsd-core/workflows/quick/steps/research-phase.md +5 -7
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
- package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
- package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
- package/gsd-core/workflows/quick-batch.md +203 -0
- package/gsd-core/workflows/quick.md +33 -32
- package/gsd-core/workflows/reapply-patches.md +2 -0
- package/gsd-core/workflows/remove-phase.md +6 -4
- package/gsd-core/workflows/remove-workspace.md +3 -3
- package/gsd-core/workflows/resume-project.md +14 -14
- package/gsd-core/workflows/review.md +404 -21
- package/gsd-core/workflows/scan.md +3 -1
- package/gsd-core/workflows/section-manifest.json +12 -0
- package/gsd-core/workflows/secure-phase.md +3 -3
- package/gsd-core/workflows/session-report.md +2 -0
- package/gsd-core/workflows/settings-advanced.md +9 -9
- package/gsd-core/workflows/settings-integrations.md +66 -32
- package/gsd-core/workflows/settings.md +4 -6
- package/gsd-core/workflows/ship.md +22 -16
- package/gsd-core/workflows/sketch-wrap-up.md +13 -17
- package/gsd-core/workflows/sketch.md +13 -19
- package/gsd-core/workflows/smart-entry.md +4 -6
- package/gsd-core/workflows/spec-phase.md +31 -4
- package/gsd-core/workflows/spike-wrap-up.md +9 -11
- package/gsd-core/workflows/spike.md +21 -32
- package/gsd-core/workflows/stats.md +4 -2
- package/gsd-core/workflows/sync-skills.md +13 -5
- package/gsd-core/workflows/thread.md +13 -7
- package/gsd-core/workflows/transition.md +7 -5
- package/gsd-core/workflows/ui-phase.md +36 -21
- package/gsd-core/workflows/ui-review.md +7 -11
- package/gsd-core/workflows/ultraplan-phase.md +7 -13
- package/gsd-core/workflows/undo.md +9 -17
- package/gsd-core/workflows/update.md +47 -48
- package/gsd-core/workflows/validate-phase.md +3 -3
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
- package/gsd-core/workflows/verify-work.md +106 -21
- package/hooks/dist/gsd-agent-isolation-guard.js +77 -38
- package/hooks/dist/gsd-check-update-worker.js +19 -2
- package/hooks/dist/gsd-config-reload.js +18 -12
- package/hooks/dist/gsd-context-monitor.js +302 -22
- package/hooks/dist/gsd-cursor-post-tool.js +3 -1
- package/hooks/dist/gsd-cursor-pre-tool.js +3 -1
- package/hooks/dist/gsd-cursor-session-start.js +2 -1
- package/hooks/dist/gsd-cursor-stop.js +2 -1
- package/hooks/dist/gsd-cursor-subagent-start.js +28 -23
- package/hooks/dist/gsd-cursor-subagent-stop.js +3 -1
- package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
- package/hooks/dist/gsd-graphify-update.sh +22 -18
- package/hooks/dist/gsd-node-runner.sh +77 -0
- package/hooks/dist/gsd-phase-boundary.sh +1 -0
- package/hooks/dist/gsd-prompt-guard.js +46 -12
- package/hooks/dist/gsd-read-guard.js +18 -7
- package/hooks/dist/gsd-read-injection-scanner.js +22 -13
- package/hooks/dist/gsd-secret-read-guard.js +1079 -0
- package/hooks/dist/gsd-session-state.sh +1 -0
- package/hooks/dist/gsd-statusline.js +222 -29
- package/hooks/dist/gsd-validate-commit.sh +523 -12
- package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
- package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
- package/hooks/dist/gsd-workflow-guard.js +36 -17
- package/hooks/dist/gsd-worktree-path-guard.js +36 -21
- package/hooks/dist/gsd-write-guard.js +35 -25
- package/hooks/dist/lib/cli-exit.js +560 -0
- package/hooks/dist/lib/exit-code-registry.js +98 -0
- package/hooks/dist/lib/git-cmd.js +210 -1
- package/hooks/dist/lib/git-probe.js +84 -0
- package/hooks/dist/lib/hook-exit.js +81 -0
- package/hooks/dist/lib/injection-patterns.js +36 -6
- package/hooks/dist/managed-hooks-registry.cjs +4 -0
- package/hooks/gsd-agent-isolation-guard.js +77 -38
- package/hooks/gsd-check-update-worker.js +19 -2
- package/hooks/gsd-config-reload.js +18 -12
- package/hooks/gsd-context-monitor.js +302 -22
- package/hooks/gsd-cursor-post-tool.js +3 -1
- package/hooks/gsd-cursor-pre-tool.js +3 -1
- package/hooks/gsd-cursor-session-start.js +2 -1
- package/hooks/gsd-cursor-stop.js +2 -1
- package/hooks/gsd-cursor-subagent-start.js +28 -23
- package/hooks/gsd-cursor-subagent-stop.js +3 -1
- package/hooks/gsd-ensure-canonical-path.js +2 -1
- package/hooks/gsd-graphify-update.sh +22 -18
- package/hooks/gsd-node-runner.sh +77 -0
- package/hooks/gsd-phase-boundary.sh +1 -0
- package/hooks/gsd-prompt-guard.js +46 -12
- package/hooks/gsd-read-guard.js +18 -7
- package/hooks/gsd-read-injection-scanner.js +22 -13
- package/hooks/gsd-secret-read-guard.js +1079 -0
- package/hooks/gsd-session-state.sh +1 -0
- package/hooks/gsd-statusline.js +222 -29
- package/hooks/gsd-validate-commit.sh +523 -12
- package/hooks/gsd-windsurf-pre-command.js +16 -11
- package/hooks/gsd-windsurf-pre-write.js +22 -13
- package/hooks/gsd-workflow-guard.js +36 -17
- package/hooks/gsd-worktree-path-guard.js +36 -21
- package/hooks/gsd-write-guard.js +35 -25
- package/hooks/hooks.json +6 -0
- package/hooks/lib/cli-exit.js +560 -0
- package/hooks/lib/exit-code-registry.js +98 -0
- package/hooks/lib/git-cmd.js +210 -1
- package/hooks/lib/git-probe.js +84 -0
- package/hooks/lib/hook-exit.js +81 -0
- package/hooks/lib/injection-patterns.js +36 -6
- package/hooks/managed-hooks-registry.cjs +4 -0
- package/package.json +14 -9
- package/scripts/base64-scan.sh +74 -12
- package/scripts/build-hooks.js +12 -0
- package/scripts/check-glossary-refs.cjs +77 -15
- package/scripts/check-mutation-score-ratchet.cjs +156 -0
- package/scripts/ci-check-job-near-cap.cjs +49 -0
- package/scripts/ci-pr-mergeability.cjs +262 -0
- package/scripts/ci-test-scope.cjs +52 -12
- package/scripts/ci-timeout-report.cjs +230 -0
- package/scripts/docs-guard-registry.cjs +406 -0
- package/scripts/gen-capability-registry.cjs +8 -6
- package/scripts/gen-exit-code-docs.cjs +318 -0
- package/scripts/gen-exit-code-registry.cjs +891 -0
- package/scripts/gen-features.cjs +836 -0
- package/scripts/gen-hooks-cli-exit.cjs +239 -0
- package/scripts/gen-install-tree-fixtures.cjs +2 -2
- package/scripts/gen-loop-host-contract.cjs +189 -4
- package/scripts/gen-scripts-cli-exit.cjs +185 -0
- package/scripts/gen-state-md-docs.cjs +727 -0
- package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
- package/scripts/lib/ci-job-timing.cjs +72 -0
- package/scripts/lib/cli-exit.cjs +546 -44
- package/scripts/lib/drift-scan.cjs +32 -2
- package/scripts/lib/exit-code-registry.cjs +98 -0
- package/scripts/lib/ndjson-reporter.cjs +119 -0
- package/scripts/lib/shellcheck-fetch.cjs +247 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
- package/scripts/lint-docs-guard-registration.cjs +495 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +198 -0
- package/scripts/lint-eslint-glob-coverage.allowlist.json +4 -0
- package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
- package/scripts/lint-health-diagnostic-rule-table.cjs +65 -8
- package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
- package/scripts/lint-phase-enumeration-drift.cjs +45 -14
- package/scripts/lint-phase-id-drift.cjs +133 -8
- package/scripts/lint-planning-prompt-drift.cjs +38 -1
- package/scripts/lint-portable-grep.cjs +176 -0
- package/scripts/lint-removed-but-needed.cjs +184 -16
- package/scripts/lint-response-language-coverage.cjs +524 -0
- package/scripts/lint-seam-enforcement.cjs +182 -0
- package/scripts/lint-slug-derivation-drift.cjs +921 -0
- package/scripts/lint-source-test-name-collision.cjs +241 -0
- package/scripts/lint-state-write-path-drift.cjs +337 -432
- package/scripts/lint-test-file-count.allowlist.json +124 -4
- package/scripts/lint-test-file-count.cjs +25 -3
- package/scripts/lint-unreachable-guard-drift.cjs +51 -64
- package/scripts/lint-vendored-deps.cjs +208 -35
- package/scripts/lint-workflow-shellcheck-baseline.json +1027 -0
- package/scripts/lint-workflow-shellcheck.cjs +614 -0
- package/scripts/mutation-matrix.cjs +599 -50
- package/scripts/npm-audit-baseline.cjs +376 -0
- package/scripts/prompt-injection-scan.sh +83 -14
- package/scripts/require-issue-link-policy.cjs +16 -1
- package/scripts/secret-scan.sh +75 -13
- package/scripts/select-docs-guards.cjs +56 -0
- package/scripts/sync-runtime-launcher.cjs +22 -3
- package/skills/gsd-discuss-phase/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -1
- package/skills/gsd-import/SKILL.md +1 -1
- package/skills/gsd-ns-workflow/SKILL.md +1 -0
- package/skills/gsd-phase/SKILL.md +1 -1
- package/skills/gsd-quick/SKILL.md +8 -4
- package/skills/gsd-quick-batch/SKILL.md +105 -0
- package/skills/gsd-surface/SKILL.md +18 -8
- package/vscode/package.json +1 -1
- package/bin/lib/ui-safety-gate.cjs +0 -109
- package/scripts/lint-emitted-drift-ack.cjs +0 -344
- package/scripts/state-write-path-drift-baseline.json +0 -19
|
@@ -11,6 +11,17 @@
|
|
|
11
11
|
* Dependencies:
|
|
12
12
|
* - ./pattern.cjs (escapeRegex — #3212 Phase 1 seam; this module is no
|
|
13
13
|
* longer the owner of pattern-escaping, only a consumer)
|
|
14
|
+
* - ./core-utils.cjs (generateSlugInternal — #3883/ADR-3473 §8.3: the
|
|
15
|
+
* canonical slug formula). core-utils.cjs also requires THIS module
|
|
16
|
+
* (comparePhaseNum, scopeToPhase), so a top-level require here would be
|
|
17
|
+
* circular and — per this codebase's compiled-.cjs convention of a
|
|
18
|
+
* single `module.exports = {...}` reassignment at the bottom of each
|
|
19
|
+
* file — a top-level circular require captures a stale, still-empty
|
|
20
|
+
* exports object forever (verified live: it throws
|
|
21
|
+
* "generateSlugInternal is not a function" when core-utils.cjs happens
|
|
22
|
+
* to load first). The require is deferred (lazy, inside each function
|
|
23
|
+
* body) instead, mirroring the same cycle-break already used by
|
|
24
|
+
* core-utils.cts's own getPhaseFileStats/plan-scan.cjs seam.
|
|
14
25
|
*/
|
|
15
26
|
const pattern_cjs_1 = require("./pattern.cjs");
|
|
16
27
|
// ─── Phase-id helpers ─────────────────────────────────────────────────────────
|
|
@@ -147,7 +158,156 @@ const BRACKET_PHASE_TOKEN_SOURCE = `\\d+[A-Z]?` +
|
|
|
147
158
|
// followed by a `Phase ` label) or a bare `Phase ` label; a bare number is NOT
|
|
148
159
|
// a phase-heading intro. The `[^\]]{1,200}` bound mirrors the existing
|
|
149
160
|
// roadmap-parser heading regexes (ReDoS-safe: a header is one short line).
|
|
161
|
+
//
|
|
162
|
+
// Retained as PR-1 shipped it. PR-2 does not consume it — see the gated
|
|
163
|
+
// selector below, which supersedes it — and it is left byte-identical so an
|
|
164
|
+
// already-merged epic export does not change its accepted language.
|
|
150
165
|
const PHASE_HEADING_PREFIX_SRC = '(?:\\[[^\\]]{1,200}\\]\\s*(?:Phase\\s+)?|Phase\\s+)';
|
|
166
|
+
// ── #612 PR-2: the ONE bracket identity grammar ─────────────────────────────
|
|
167
|
+
// A bracket phase-ID prefix is `{CODE}.{MM}`. Before this, three spellings of
|
|
168
|
+
// that shape lived in this file and disagreed: extractPhaseToken's bracket
|
|
169
|
+
// branch (`\d+`, case-sensitive), bracketQualifiedKey (`\d+`, mixed-case), and
|
|
170
|
+
// the dir prefix (`\d{2,}`). `GSD.2-05-feature` was simultaneously "not a phase
|
|
171
|
+
// directory" and "phase 05", depending on which one a caller reached. They are
|
|
172
|
+
// now one source.
|
|
173
|
+
//
|
|
174
|
+
// The milestone width mirrors the EMIT grammar rather than accepting any digit
|
|
175
|
+
// run: pad2() emits at least two digits, so two digits — or three-plus with no
|
|
176
|
+
// leading zero — is what toDir can produce. A bare `0` is NOT admitted: the
|
|
177
|
+
// padded `00` is the backlog sentinel's canonical identity and `\d{2}` already
|
|
178
|
+
// covers it, so nothing needs the unpadded spelling. (An earlier revision of
|
|
179
|
+
// this comment claimed the opposite; the constant below has always rejected it
|
|
180
|
+
// — #2867 review, Minor 2.) `[GSD.2] 05:` is therefore NOT a bracket id —
|
|
181
|
+
// which is the point: it is the shape that made the three spellings disagree.
|
|
182
|
+
// Reconciled with BRACKET_CANONICAL_NUMERIC_SOURCE above — the width toDir
|
|
183
|
+
// actually emits (pad2: 2 digits, or 3+ with no leading zero). The earlier
|
|
184
|
+
// `(?:\d{2,}|0)` diverged from it in both directions: it admitted `002`, which
|
|
185
|
+
// the emit validator rejects, and a bare `0` that pad2 never produces. Every
|
|
186
|
+
// bracket-milestone recognizer now derives from this one constant — the section
|
|
187
|
+
// recognizers previously spelled `0*N` or `\d+` and accepted `[GSD.2]`, which
|
|
188
|
+
// SCOPED a milestone no phase heading could then resolve into, recreating the
|
|
189
|
+
// on-disk-count fallback this PR exists to remove.
|
|
190
|
+
//
|
|
191
|
+
// An unpadded bracket is therefore MALFORMED, uniformly: it scopes nothing,
|
|
192
|
+
// bounds nothing, sections nothing, and is not a phase id. W005 on its
|
|
193
|
+
// directories is the signal that surfaces it.
|
|
194
|
+
const BRACKET_MILESTONE_NUMERIC_SRC = BRACKET_CANONICAL_NUMERIC_SOURCE;
|
|
195
|
+
// #2761 M3 (trek-e review): the bracket PROJECT-CODE class, as a named source
|
|
196
|
+
// rather than a class this file (and three readers outside it) each re-typed.
|
|
197
|
+
// The re-typed copies were the exact drift #2761's own gate forbids — "no token
|
|
198
|
+
// literal outside src/phase-id.cts" — and `check:phase-id-drift` did not see
|
|
199
|
+
// them, because its detector only knew the phase-NUMBER token grammar. The
|
|
200
|
+
// guard now carries a bracket rule too (scripts/lint-phase-id-drift.cjs).
|
|
201
|
+
const BRACKET_PROJECT_CODE_SRC = '[A-Z][A-Z0-9_]*';
|
|
202
|
+
const BRACKET_ID_SRC = `${BRACKET_PROJECT_CODE_SRC}\\.${BRACKET_MILESTONE_NUMERIC_SRC}`;
|
|
203
|
+
// The bracket MILESTONE INTRO — `[CODE.MM]` — in the two shapes its readers
|
|
204
|
+
// need. Both were re-typed verbatim outside this module before #2761 M3:
|
|
205
|
+
//
|
|
206
|
+
// * PINNED, to one already-resolved milestone integer. This owns the pad2
|
|
207
|
+
// spelling rule as well as the grammar, so "canonical spelling only, not
|
|
208
|
+
// `0*N`" (the rule that keeps `[GSD.2]` from scoping a milestone no phase
|
|
209
|
+
// heading can resolve into) lives in ONE place instead of being restated
|
|
210
|
+
// beside every regex. `roadmap-parser`'s bracket-fallback selector and
|
|
211
|
+
// `state`'s `isMilestoneBounded` both consume this.
|
|
212
|
+
//
|
|
213
|
+
// * CAPTURING, over any milestone, putting the milestone digits in a group.
|
|
214
|
+
// `validate`'s `checkBracketCoherence` consumes this.
|
|
215
|
+
//
|
|
216
|
+
// The milestone argument is expected to be a safe integer — every caller
|
|
217
|
+
// resolves it through `Number.isSafeInteger` first. A non-integer yields a
|
|
218
|
+
// regex source that is still well-formed and simply matches nothing, which is
|
|
219
|
+
// the same safe degrade the callers' own guards produce.
|
|
220
|
+
function bracketMilestoneIntroSrcFor(milestone) {
|
|
221
|
+
return `\\[${BRACKET_PROJECT_CODE_SRC}\\.${String(milestone).padStart(2, '0')}\\]`;
|
|
222
|
+
}
|
|
223
|
+
const BRACKET_MILESTONE_INTRO_CAPTURING_SRC = `\\[${BRACKET_PROJECT_CODE_SRC}\\.(${BRACKET_MILESTONE_NUMERIC_SRC})\\]`;
|
|
224
|
+
// Recognition is case-INSENSITIVE (every reader compiles `/i`), but the identity
|
|
225
|
+
// helpers this file owns — isSentinelPhaseId, getMilestoneFromPhaseId,
|
|
226
|
+
// bracketQualifiedKey — match `[A-Z]` case-SENSITIVELY. A lowercase bracket id
|
|
227
|
+
// captured by a reader and handed straight to them silently fails every identity
|
|
228
|
+
// test, which is how `### [gsd.999] 07:` leaked into phase counts as a real
|
|
229
|
+
// phase. Fold before any identity operation; never fold for display.
|
|
230
|
+
function foldBracketId(bracketId) {
|
|
231
|
+
return String(bracketId).toUpperCase();
|
|
232
|
+
}
|
|
233
|
+
// The identity recognizers every bracket helper in this file shares.
|
|
234
|
+
// `BRACKET_ID_PREFIX_RE` is applied to an ALREADY-FOLDED string, so it needs no
|
|
235
|
+
// `/i`; the other two see raw dir names and carry it.
|
|
236
|
+
// The milestone field must END at the phase separator. Without the boundary the
|
|
237
|
+
// width alternation matched a PREFIX of a malformed run — `GSD.002-01` matched
|
|
238
|
+
// its leading `00` and read as a sentinel.
|
|
239
|
+
const BRACKET_ID_PREFIX_RE = new RegExp(`^${BRACKET_PROJECT_CODE_SRC}\\.(${BRACKET_MILESTONE_NUMERIC_SRC})(?=-|$)`);
|
|
240
|
+
const BRACKET_DIR_PREFIX_SRC = `${BRACKET_ID_SRC}-`;
|
|
241
|
+
// The trailing `(?=-|$)` is what makes the recognizer and the resolver agree on
|
|
242
|
+
// REJECTED input, not just accepted input. Without it `GSD.02-12A-hotfix`
|
|
243
|
+
// resolves to token `12` here while the directory recognizer calls the name
|
|
244
|
+
// malformed — so W005 reports it malformed in the same run that the
|
|
245
|
+
// milestone-complete check treats it as a real phase directory.
|
|
246
|
+
const BRACKET_DIR_TOKEN_RE = new RegExp(`^${BRACKET_DIR_PREFIX_SRC}(\\d+(?:\\.\\d+)?)(?=-|$)`, 'i');
|
|
247
|
+
// Same width rule, same `(?=-|$)` boundary and same single-sub-phase shape as
|
|
248
|
+
// BRACKET_DIR_TOKEN_RE. Without them a qualified query `GSD.02-12` matched the
|
|
249
|
+
// directory `GSD.02-12A-hotfix` — which isPhaseDirName calls malformed — and
|
|
250
|
+
// phaseTokenMatches returns UNCONDITIONALLY on a qualified hit, so that
|
|
251
|
+
// disagreement would have been final rather than a fall-through.
|
|
252
|
+
const BRACKET_QUALIFIED_KEY_RE = new RegExp(`^(${BRACKET_PROJECT_CODE_SRC})\\.(${BRACKET_MILESTONE_NUMERIC_SRC})-(\\d+(?:\\.\\d+)?)(?=-|$)`, 'i');
|
|
253
|
+
// ── #612 PR-2: gated heading-intro selection ────────────────────────────────
|
|
254
|
+
// The two intro spellings that exist upstream TODAY, transcribed verbatim from
|
|
255
|
+
// the call sites. A repo that has not opted into the bracket convention
|
|
256
|
+
// compiles exactly these — not a superset of them, THEM — so its reads are
|
|
257
|
+
// structurally identical to the base build rather than argued equivalent.
|
|
258
|
+
// tests/adr-612-bracket-heading-selection.test.cjs asserts that byte-equality
|
|
259
|
+
// against its own independently transcribed copies of the call-site literals.
|
|
260
|
+
const BASE_ANY_BRACKET_HEADING_PREFIX_SRC = '(?:\\[[^\\]]{1,200}\\]\\s*)?Phase\\s+';
|
|
261
|
+
const BASE_PHASE_LABEL_PREFIX_SRC = 'Phase\\s+';
|
|
262
|
+
// Which of those two a site spells at base. Passed explicitly rather than
|
|
263
|
+
// inferred, because the choice is a fact about the call site's history that no
|
|
264
|
+
// amount of looking at the widened pattern can recover.
|
|
265
|
+
const PHASE_HEADING_BASELINE = Object.freeze({
|
|
266
|
+
/** Site already tolerates `[anything] Phase N` — roadmap headings, validate's heading scanner. */
|
|
267
|
+
ANY_BRACKET: 'any-bracket',
|
|
268
|
+
/** Site spells a bare `Phase N` with no bracket tolerance — checklist bullets, the counters. */
|
|
269
|
+
LABEL_ONLY: 'label-only',
|
|
270
|
+
});
|
|
271
|
+
/**
|
|
272
|
+
* The heading-intro source a site should compile, given the resolved
|
|
273
|
+
* `phase_id_convention`.
|
|
274
|
+
*
|
|
275
|
+
* NON-bracket conventions (null, undefined, 'milestone-prefixed', or any
|
|
276
|
+
* unrecognized value) return the site's BASE spelling unchanged. This is the
|
|
277
|
+
* whole design: PR-2 originally widened these reads ungated and argued the new
|
|
278
|
+
* shape "cannot occur in a legacy ROADMAP", which is false — `### [RFC.2119] 5:`,
|
|
279
|
+
* `### [v1.0] 2024:` and `### [ADR.612] 3:` are all legal legacy headings that
|
|
280
|
+
* the widened form claims as phases, moving phase_count, total_phases and W006
|
|
281
|
+
* on repos that never opted in. Selection at construction time removes the
|
|
282
|
+
* argument entirely: there is nothing to reason about, because a non-bracket
|
|
283
|
+
* repo compiles the same source string it compiled before.
|
|
284
|
+
*
|
|
285
|
+
* `capturing` adds EXACTLY ONE group, at position 1, holding the bracket id —
|
|
286
|
+
* `undefined` whenever a non-bracket alternative matched. Sites that filter
|
|
287
|
+
* sentinels need it: READING-B puts the sentinel milestone in the bracket, so
|
|
288
|
+
* testing the phase token alone is blind to `### [GSD.999] 01:`.
|
|
289
|
+
*
|
|
290
|
+
* Pure: takes the resolved convention, never reads config.
|
|
291
|
+
*/
|
|
292
|
+
function phaseHeadingPrefixSrcFor(baseline, convention, capturing = false) {
|
|
293
|
+
const base = baseline === PHASE_HEADING_BASELINE.ANY_BRACKET
|
|
294
|
+
? BASE_ANY_BRACKET_HEADING_PREFIX_SRC
|
|
295
|
+
: BASE_PHASE_LABEL_PREFIX_SRC;
|
|
296
|
+
if (convention !== 'bracket')
|
|
297
|
+
return base;
|
|
298
|
+
const id = capturing ? `(${BRACKET_ID_SRC})` : BRACKET_ID_SRC;
|
|
299
|
+
// `[ \t]*` not `\s*`: `\s` spans newlines, so a bracket-terminated heading
|
|
300
|
+
// followed by a blank line and a digit-leading prose line read as one phase.
|
|
301
|
+
// BOTH bracket forms are admitted at both baselines, and both CAPTURE. The
|
|
302
|
+
// any-bracket base already matches `[GSD.999] Phase 07:` on its own — but
|
|
303
|
+
// through the base alternative, which captures nothing, so the reader saw
|
|
304
|
+
// `bracketId === undefined`, fell back to the legacy leading-integer rule, and
|
|
305
|
+
// counted a labeled icebox heading as a real phase while the label-less form
|
|
306
|
+
// beside it was excluded. Two derivations of one ROADMAP disagreed. The
|
|
307
|
+
// bracket alternative is tried FIRST so it wins the capture.
|
|
308
|
+
const bracketAlt = `\\[${id}\\][ \\t]*(?:Phase\\s+|(?=\\d))`;
|
|
309
|
+
return `(?:${bracketAlt}|${base})`;
|
|
310
|
+
}
|
|
151
311
|
function stripProjectCodePrefix(value, caseInsensitive = true) {
|
|
152
312
|
const input = String(value);
|
|
153
313
|
const re = caseInsensitive ? PROJECT_CODE_PREFIX_STRIP_RE_I : PROJECT_CODE_PREFIX_STRIP_RE;
|
|
@@ -189,10 +349,10 @@ function getMilestoneFromPhaseId(phaseId, convention) {
|
|
|
189
349
|
// pure (no config read) and backward-compatible: every existing single-arg
|
|
190
350
|
// caller resolves to the unchanged READING-A body.
|
|
191
351
|
if (convention === 'bracket') {
|
|
192
|
-
const b =
|
|
352
|
+
const b = foldBracketId(phaseId).match(BRACKET_ID_PREFIX_RE);
|
|
193
353
|
if (!b)
|
|
194
354
|
return null;
|
|
195
|
-
const mm = parseInt(b[
|
|
355
|
+
const mm = parseInt(b[1], 10);
|
|
196
356
|
if (SENTINEL_RANGES.includes(mm))
|
|
197
357
|
return null; // sentinel milestones have no real milestone
|
|
198
358
|
return `v${mm}.0`;
|
|
@@ -214,9 +374,15 @@ function getPhaseDirFromPhaseId(phaseId, phaseName, projectCode) {
|
|
|
214
374
|
const milestone = String(parseInt(m[1], 10)).padStart(2, '0');
|
|
215
375
|
const subParts = m[2].split('-').map(p => String(parseInt(p, 10)).padStart(2, '0'));
|
|
216
376
|
const sub = subParts.join('-');
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
377
|
+
// #3883 (ADR-3473 §8.3): delegate to the canonical slug formula
|
|
378
|
+
// (generateSlugInternal, core-utils.cts) rather than re-implementing it.
|
|
379
|
+
// `maxLen: null` preserves this site's pre-migration untruncated contract —
|
|
380
|
+
// the 60-char default would silently shadow one on-disk phase dir's
|
|
381
|
+
// reported phase_slug behind another distinct >60-char phase name's.
|
|
382
|
+
// Lazy require to break the core-utils.cjs <-> phase-id.cjs cycle (see the
|
|
383
|
+
// module dependency doc comment above).
|
|
384
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
|
385
|
+
const slug = phaseName ? (require('./core-utils.cjs').generateSlugInternal(phaseName, null) ?? '') : '';
|
|
220
386
|
const parts = [milestone, sub, slug].filter(Boolean);
|
|
221
387
|
const base = parts.join('-');
|
|
222
388
|
return projectCode ? `${projectCode}-${base}` : base;
|
|
@@ -324,7 +490,22 @@ function toDir(id, slug) {
|
|
|
324
490
|
const sub = id.subphase ? `.${id.subphase}` : '';
|
|
325
491
|
// Slug guard: the slug becomes an on-disk path segment, so collapse it to a
|
|
326
492
|
// safe lowercase token — never a path separator or `..` traversal.
|
|
327
|
-
|
|
493
|
+
// #3883 (ADR-3473 §8.3): delegate the sanitize formula itself to the
|
|
494
|
+
// canonical (generateSlugInternal, core-utils.cts) — this fixes the
|
|
495
|
+
// Cyrillic-collapses-to-empty defect (#2848-class) that toDir carried
|
|
496
|
+
// before (it never transliterated). The empty-sanitize and all-digit
|
|
497
|
+
// throw guards below stay: they are a DECLARED DIFFERENCE from every
|
|
498
|
+
// other slug call site, not a bug — a slug here becomes a real directory
|
|
499
|
+
// name, and toDir protects the parsePhaseId dir↔identity bijection
|
|
500
|
+
// (see the toDir docstring above) by refusing to emit an unusable name,
|
|
501
|
+
// where every other site silently accepts "" or a re-truncated value.
|
|
502
|
+
// `maxLen: null` preserves toDir's pre-migration untruncated contract — the
|
|
503
|
+
// 60-char default let two distinct >60-char phase names collapse onto the
|
|
504
|
+
// identical directory name, one silently shadowing the other on disk.
|
|
505
|
+
// Lazy require to break the core-utils.cjs <-> phase-id.cjs cycle (see the
|
|
506
|
+
// module dependency doc comment above).
|
|
507
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
|
508
|
+
const safeSlug = require('./core-utils.cjs').generateSlugInternal(slug, null) ?? '';
|
|
328
509
|
// A slug that sanitizes to nothing (e.g. '!!!') would otherwise emit a
|
|
329
510
|
// dangling trailing hyphen.
|
|
330
511
|
if (!safeSlug) {
|
|
@@ -351,15 +532,75 @@ function isSentinelPhaseId(phaseId, convention) {
|
|
|
351
532
|
// convention-less caller uses the legacy/bare leading-int rule below, so no
|
|
352
533
|
// existing reader gains a false positive; the bracket reading is opt-in.
|
|
353
534
|
if (convention === 'bracket') {
|
|
354
|
-
|
|
535
|
+
// #612 PR-2: fold before matching. Readers recognize headings under `/i`, so
|
|
536
|
+
// a lowercase `[gsd.999]` arrives verbatim; the case-sensitive class below
|
|
537
|
+
// then failed to match and an icebox item counted as a real phase.
|
|
538
|
+
const bracket = foldBracketId(s).match(BRACKET_ID_PREFIX_RE);
|
|
355
539
|
if (bracket)
|
|
356
540
|
return SENTINEL_RANGES.includes(parseInt(bracket[1], 10));
|
|
541
|
+
// #2761 round-12: NO bracket tag matched — a bare/untagged id under
|
|
542
|
+
// bracket convention (`0-bootstrap`, a directory with no `{CODE}.{MM}-`
|
|
543
|
+
// prefix, or a legacy-spelled `### Phase 0:` heading routed here without
|
|
544
|
+
// its own bracketId guard). Falling through to the LEGACY leading-int rule
|
|
545
|
+
// below would read this bare `0` as sentinel milestone 0 — but under
|
|
546
|
+
// bracket convention milestone 0 is expressed ONLY via an explicit
|
|
547
|
+
// bracket tag, so an untagged leading `0` is a real phase token, not a
|
|
548
|
+
// sentinel. This mirrors the two HEADING-side counters that already carry
|
|
549
|
+
// this exact carve-out: state.cts's `countRoadmapPhaseHeadings` guards its
|
|
550
|
+
// bare-0 exclusion with `bracketId &&` (so an untagged `Phase 0:` heading
|
|
551
|
+
// is counted), and roadmap-parser.cts's `scanMilestonePhaseIds` composes
|
|
552
|
+
// the bare-token rule as 999-only, deliberately NOT this predicate, for
|
|
553
|
+
// the identical reason (#3185: the leading-int rule also swallows the
|
|
554
|
+
// #2554 decimal ids — `0.5-bootstrap` is a real phase, not milestone 0).
|
|
555
|
+
// The 999/icebox reading stays universal either way — an untagged `999`
|
|
556
|
+
// is still backlog under every convention — so only that half of
|
|
557
|
+
// SENTINEL_RANGES applies here.
|
|
558
|
+
//
|
|
559
|
+
// `isSentinelPhaseDir` remains deliberately convention-blind for its
|
|
560
|
+
// warning-only disk guards and may conservatively classify a bare `0`
|
|
561
|
+
// directory as sentinel. This branch has an explicit resolved convention
|
|
562
|
+
// and feeds counts/archives, so it must retain the more precise reading.
|
|
563
|
+
const bare = stripProjectCodePrefix(s).match(/^0*(\d+)/);
|
|
564
|
+
return bare !== null && parseInt(bare[1], 10) === 999;
|
|
357
565
|
}
|
|
358
566
|
const legacy = stripProjectCodePrefix(s).match(/^0*(\d+)/); // legacy/bare: leading int
|
|
359
567
|
if (!legacy)
|
|
360
568
|
return false;
|
|
361
569
|
return SENTINEL_RANGES.includes(parseInt(legacy[1], 10));
|
|
362
570
|
}
|
|
571
|
+
/**
|
|
572
|
+
* Disk-side sentinel recognizer (#3639): is this on-disk PHASE DIRECTORY a
|
|
573
|
+
* sentinel (never-on-roadmap by convention)?
|
|
574
|
+
*
|
|
575
|
+
* The disk-side guards (C001 gap numbering, W007 orphan dirs) see raw
|
|
576
|
+
* directory names and do not know the repo's naming convention — and neither
|
|
577
|
+
* convention-blind route could recognize a bracket sentinel: `isSentinelPhaseId`
|
|
578
|
+
* without the convention argument reads only the legacy leading int, while
|
|
579
|
+
* `extractPhaseToken(dirName)` (convention-aware or not) strips the MILESTONE
|
|
580
|
+
* and returns the bare phase token — bracket sentinel-ness lives in the
|
|
581
|
+
* milestone portion (`GSD.999-07-icebox` is icebox because of the 999, not
|
|
582
|
+
* the 07). This helper reads the milestone directly off the dir name.
|
|
583
|
+
*
|
|
584
|
+
* The bracket branch requires the FULL bracket dir shape — code prefix, dot,
|
|
585
|
+
* milestone digits, hyphen, PHASE DIGITS — so a #1324 letter-prefixed real
|
|
586
|
+
* dir with a LETTER slug (`P0.0-foundation`) never matches it (ADR-2121
|
|
587
|
+
* indistinguishability, same gate as extractPhaseToken below). DISCLOSED
|
|
588
|
+
* RESIDUAL (#3639 review): the #1324 family also has digit continuations
|
|
589
|
+
* (`P0.0-1-foundation`, `P0.3-2` are real shapes per derivePhaseTokenSegments),
|
|
590
|
+
* and `{code}.{0|999}-{digit}...` is string-indistinguishable from a bracket
|
|
591
|
+
* sentinel dir — no convention-free discriminator exists (ADR-2121). Such a
|
|
592
|
+
* dir reads as sentinel here, which at the disk-guard call sites suppresses
|
|
593
|
+
* a warning (conservative for a linter) rather than deleting data. The
|
|
594
|
+
* digit-continuation family with NON-sentinel first decimals (`P0.3-2`)
|
|
595
|
+
* reads milestone 3 — ordinary — exactly as the convention-gated id
|
|
596
|
+
* predicate does. Everything else falls to the legacy leading-int rule.
|
|
597
|
+
*/
|
|
598
|
+
function isSentinelPhaseDir(dirName) {
|
|
599
|
+
const bracketDir = dirName.match(/^[A-Z][A-Z0-9_]*\.(\d+)-\d/); // milestone digits + hyphen + phase DIGITS
|
|
600
|
+
if (bracketDir)
|
|
601
|
+
return SENTINEL_RANGES.includes(parseInt(bracketDir[1], 10));
|
|
602
|
+
return isSentinelPhaseId(dirName);
|
|
603
|
+
}
|
|
363
604
|
/**
|
|
364
605
|
* Render a regex source fragment matching a phase number against ROADMAP/STATE
|
|
365
606
|
* prose regardless of zero-padding on either side.
|
|
@@ -534,7 +775,10 @@ function extractPhaseToken(dirName, convention) {
|
|
|
534
775
|
// config read). The captured token is dot-only (`PP[.SS]`); the milestone↔phase
|
|
535
776
|
// hyphen and any trailing plan/slug are excluded.
|
|
536
777
|
if (convention === 'bracket') {
|
|
537
|
-
|
|
778
|
+
// #612 PR-2: built from the ONE bracket identity grammar, not a private
|
|
779
|
+
// spelling. Case-insensitive to match how the readers recognize headings and
|
|
780
|
+
// directories; the milestone width is the emit grammar's.
|
|
781
|
+
const bracketDir = dirName.match(BRACKET_DIR_TOKEN_RE);
|
|
538
782
|
if (bracketDir)
|
|
539
783
|
return bracketDir[1];
|
|
540
784
|
}
|
|
@@ -795,10 +1039,52 @@ function isPhaseArtifact(fileName, phaseDirName) {
|
|
|
795
1039
|
function scopeToPhase(fileNames, phaseDirName) {
|
|
796
1040
|
return fileNames.filter((f) => isPhaseArtifact(f, phaseDirName));
|
|
797
1041
|
}
|
|
1042
|
+
/**
|
|
1043
|
+
* Canonical comparable key for a milestone-qualified bracket id or dir name.
|
|
1044
|
+
* Lifts the milestone out of the `{CODE}.{MM}-` prefix so a flat multi-milestone
|
|
1045
|
+
* layout disambiguates: `CK.03-02` resolves to its OWN milestone's directory,
|
|
1046
|
+
* never the first same-numbered directory of another milestone.
|
|
1047
|
+
*
|
|
1048
|
+
* Returns null for UNQUALIFIED ids (`02`, `HQ-11`, `11.01`) so callers fall back
|
|
1049
|
+
* to bare-token matching unchanged, and GATED on convention === 'bracket' for
|
|
1050
|
+
* the same reason as extractPhaseToken: the qualified key is padding-
|
|
1051
|
+
* INSENSITIVE where the legacy token path is padding-SENSITIVE, so ungated it
|
|
1052
|
+
* silently widens matching on legacy repos.
|
|
1053
|
+
*/
|
|
1054
|
+
function bracketQualifiedKey(s, convention) {
|
|
1055
|
+
if (convention !== 'bracket')
|
|
1056
|
+
return null;
|
|
1057
|
+
const m = String(s).match(BRACKET_QUALIFIED_KEY_RE);
|
|
1058
|
+
if (!m)
|
|
1059
|
+
return null;
|
|
1060
|
+
const milestone = parseInt(m[2], 10);
|
|
1061
|
+
// A milestone integer past Number's exact range collapses to Infinity, and
|
|
1062
|
+
// every such id would then share one key. Refuse rather than collide.
|
|
1063
|
+
if (!Number.isSafeInteger(milestone))
|
|
1064
|
+
return null;
|
|
1065
|
+
const phase = m[3].split('.').map(n => parseInt(n, 10));
|
|
1066
|
+
if (phase.some(n => !Number.isSafeInteger(n)))
|
|
1067
|
+
return null;
|
|
1068
|
+
return `${foldBracketId(m[1])}.${milestone}-${phase.join('.')}`;
|
|
1069
|
+
}
|
|
798
1070
|
/**
|
|
799
1071
|
* Check if a directory name's phase token matches the normalized phase exactly.
|
|
1072
|
+
*
|
|
1073
|
+
* The optional `convention` is the ADR-2121 additive shape: every existing
|
|
1074
|
+
* two-argument call site resolves to the unchanged legacy body.
|
|
800
1075
|
*/
|
|
801
|
-
function phaseTokenMatches(dirName, normalized) {
|
|
1076
|
+
function phaseTokenMatches(dirName, normalized, convention) {
|
|
1077
|
+
if (convention === 'bracket') {
|
|
1078
|
+
// A milestone-qualified query compares on the full qualified key, and
|
|
1079
|
+
// returns unconditionally: falling through on a miss would re-admit the
|
|
1080
|
+
// cross-milestone match the qualification exists to prevent.
|
|
1081
|
+
const qKey = bracketQualifiedKey(normalized, convention);
|
|
1082
|
+
if (qKey)
|
|
1083
|
+
return bracketQualifiedKey(dirName, convention) === qKey;
|
|
1084
|
+
const bracketToken = extractPhaseToken(dirName, convention);
|
|
1085
|
+
if (bracketToken.toUpperCase() === normalized.toUpperCase())
|
|
1086
|
+
return true;
|
|
1087
|
+
}
|
|
802
1088
|
const token = extractPhaseToken(dirName);
|
|
803
1089
|
if (token.toUpperCase() === normalized.toUpperCase())
|
|
804
1090
|
return true;
|
|
@@ -895,8 +1181,8 @@ const unpad = (digits) => digits.replace(/^0+(?=\d)/, '');
|
|
|
895
1181
|
* the directory's leading digit run instead of `extractPhaseToken` (whose
|
|
896
1182
|
* token for these dirs is the mis-absorbed multi-segment form).
|
|
897
1183
|
*/
|
|
898
|
-
function matchPhaseDirs(dirs, normalized) {
|
|
899
|
-
const primary = dirs.filter(d => phaseTokenMatches(d, normalized));
|
|
1184
|
+
function matchPhaseDirs(dirs, normalized, convention) {
|
|
1185
|
+
const primary = dirs.filter(d => phaseTokenMatches(d, normalized, convention));
|
|
900
1186
|
if (primary.length > 0)
|
|
901
1187
|
return { matches: primary, usedBareFallback: false };
|
|
902
1188
|
const bare = String(normalized);
|
|
@@ -956,9 +1242,18 @@ function phaseKeyFromToken(token) {
|
|
|
956
1242
|
/**
|
|
957
1243
|
* Canonical key for a phase DIRECTORY name (`"05-schedule-8"` → `"05"`,
|
|
958
1244
|
* `"PROJ-5-x"` → `"05"`, `"30.1-follow-up"` → `"30.1"`).
|
|
1245
|
+
*
|
|
1246
|
+
* #612: `convention` is forwarded to `extractPhaseToken`, which needs that signal
|
|
1247
|
+
* to read a bracket directory (`"GSD.02-05-delta"` → `"05"`) — a bracket dir is
|
|
1248
|
+
* string-indistinguishable from the legacy letter-prefixed-decimal family, so the
|
|
1249
|
+
* extractor refuses to guess. Optional and defaulted-absent, so every pre-#612
|
|
1250
|
+
* call site resolves byte-identically to prior behaviour. Without it a bracket dir
|
|
1251
|
+
* yields its whole name as the key and never matches the ROADMAP entry it names —
|
|
1252
|
+
* #2562's own defect class reached from the other side: both sides of a comparison
|
|
1253
|
+
* must be derived not merely by the same function but under the same convention.
|
|
959
1254
|
*/
|
|
960
|
-
function phaseKeyFromDir(dirName) {
|
|
961
|
-
return phaseKeyFromToken(extractPhaseToken(dirName));
|
|
1255
|
+
function phaseKeyFromDir(dirName, convention) {
|
|
1256
|
+
return phaseKeyFromToken(extractPhaseToken(dirName, convention));
|
|
962
1257
|
}
|
|
963
1258
|
/**
|
|
964
1259
|
* Canonical key for a phase referenced in PROSE — a ROADMAP `## Progress` table
|
|
@@ -1111,6 +1406,18 @@ module.exports = {
|
|
|
1111
1406
|
isPhaseContinuationSegment,
|
|
1112
1407
|
BRACKET_PHASE_TOKEN_SOURCE,
|
|
1113
1408
|
PHASE_HEADING_PREFIX_SRC,
|
|
1409
|
+
BRACKET_ID_SRC,
|
|
1410
|
+
BRACKET_PROJECT_CODE_SRC,
|
|
1411
|
+
bracketMilestoneIntroSrcFor,
|
|
1412
|
+
BRACKET_MILESTONE_INTRO_CAPTURING_SRC,
|
|
1413
|
+
BRACKET_MILESTONE_NUMERIC_SRC,
|
|
1414
|
+
BRACKET_DIR_PREFIX_SRC,
|
|
1415
|
+
BASE_ANY_BRACKET_HEADING_PREFIX_SRC,
|
|
1416
|
+
BASE_PHASE_LABEL_PREFIX_SRC,
|
|
1417
|
+
PHASE_HEADING_BASELINE,
|
|
1418
|
+
phaseHeadingPrefixSrcFor,
|
|
1419
|
+
foldBracketId,
|
|
1420
|
+
bracketQualifiedKey,
|
|
1114
1421
|
stripProjectCodePrefix,
|
|
1115
1422
|
normalizePhaseName,
|
|
1116
1423
|
getMilestoneFromPhaseId,
|
|
@@ -1120,6 +1427,7 @@ module.exports = {
|
|
|
1120
1427
|
toDir,
|
|
1121
1428
|
SENTINEL_RANGES,
|
|
1122
1429
|
isSentinelPhaseId,
|
|
1430
|
+
isSentinelPhaseDir,
|
|
1123
1431
|
phaseMarkdownRegexSource,
|
|
1124
1432
|
phaseMarkdownRegexSourceExact,
|
|
1125
1433
|
comparePhaseNum,
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
* - Issue #4 (open-gsd/gsd-core)
|
|
22
22
|
*/
|
|
23
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.locateProgressTable = locateProgressTable;
|
|
24
25
|
exports.deriveProgressFromRoadmap = deriveProgressFromRoadmap;
|
|
25
26
|
exports.clampPercentFromFraction = clampPercentFromFraction;
|
|
26
27
|
exports.clampPercent = clampPercent;
|
|
@@ -29,8 +30,10 @@ const markdown_table_cjs_1 = require("./markdown-table.cjs");
|
|
|
29
30
|
const phaseIdMod = require("./phase-id.cjs");
|
|
30
31
|
const { isSentinelPhaseId } = phaseIdMod;
|
|
31
32
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
33
|
+
* #3227: the single owner of "where is this ROADMAP's Progress table".
|
|
34
|
+
* Lifted verbatim out of deriveProgressFromRoadmap so `state-contract.cts`
|
|
35
|
+
* enumerates phases from THE SAME table this module derives its counts from.
|
|
36
|
+
* A second copy of this locator is the DEFECT.GENERATIVE-FIX shape.
|
|
34
37
|
*
|
|
35
38
|
* ADR-2143 §3 ("addressed by NAME, never ordinal"): the Progress table is
|
|
36
39
|
* located via the markdown-table seam's `findTableWithColumns`, which is
|
|
@@ -49,6 +52,24 @@ const { isSentinelPhaseId } = phaseIdMod;
|
|
|
49
52
|
* to scanning the whole input, preserving the "Progress table not under a
|
|
50
53
|
* `## Progress` heading, or not the first table in the document, still
|
|
51
54
|
* resolves" behaviour.
|
|
55
|
+
*/
|
|
56
|
+
function locateProgressTable(roadmapContent) {
|
|
57
|
+
const progressMatch = roadmapContent.match(/^##[ \t]+Progress\b/im);
|
|
58
|
+
let scoped = roadmapContent;
|
|
59
|
+
if (progressMatch && progressMatch.index !== undefined) {
|
|
60
|
+
const afterHeading = roadmapContent.slice(progressMatch.index);
|
|
61
|
+
const nextHeading = afterHeading.search(/\n#{1,2}[ \t]/);
|
|
62
|
+
scoped = nextHeading >= 0 ? afterHeading.slice(0, nextHeading) : afterHeading;
|
|
63
|
+
}
|
|
64
|
+
return (0, markdown_table_cjs_1.findTableWithColumns)(scoped, ['Phase', 'Plans Complete', 'Status', 'Completed']);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Derive completed_phases, total_phases, and total_plans from ROADMAP content.
|
|
68
|
+
* Root cause fix for issue #4 — see gen-phase-lifecycle.mjs for full documentation.
|
|
69
|
+
*
|
|
70
|
+
* The Progress table itself is located by `locateProgressTable` (ADR-2143 §3,
|
|
71
|
+
* lifted out as #3227's single-owner extraction) — this function consumes
|
|
72
|
+
* that table.
|
|
52
73
|
*
|
|
53
74
|
* Cells are read by column NAME (`r['Status']`, `r['Plans Complete']`,
|
|
54
75
|
* `r['Phase']`), fixing #2137 (the old position-based regex assumed "Status"
|
|
@@ -67,20 +88,7 @@ function deriveProgressFromRoadmap(roadmapContent) {
|
|
|
67
88
|
// `{ ok: false, reason }`, not an exception — so the catch was masking
|
|
68
89
|
// nothing but dead code paths. Removed per ADR-2143 §5; the public
|
|
69
90
|
// `RoadmapProgress` contract (nulls = absent) is unchanged.
|
|
70
|
-
|
|
71
|
-
// ADR-2143 §3: read the Progress table by column NAME (order/injection-invariant),
|
|
72
|
-
// via the markdown-table seam. Scope to the `## Progress` section when present
|
|
73
|
-
// (#2012 decoy avoidance); a headingless milestone slice (#1445) falls back to the
|
|
74
|
-
// whole input. Requires the canonical Phase/Plans Complete/Status/Completed columns
|
|
75
|
-
// in any order (extra columns ignored) — supersedes findTableBySchema's exact-schema lookup.
|
|
76
|
-
const progressMatch = roadmapContent.match(/^##[ \t]+Progress\b/im);
|
|
77
|
-
let scoped = roadmapContent;
|
|
78
|
-
if (progressMatch && progressMatch.index !== undefined) {
|
|
79
|
-
const afterHeading = roadmapContent.slice(progressMatch.index);
|
|
80
|
-
const nextHeading = afterHeading.search(/\n#{1,2}[ \t]/);
|
|
81
|
-
scoped = nextHeading >= 0 ? afterHeading.slice(0, nextHeading) : afterHeading;
|
|
82
|
-
}
|
|
83
|
-
const table = (0, markdown_table_cjs_1.findTableWithColumns)(scoped, ['Phase', 'Plans Complete', 'Status', 'Completed']);
|
|
91
|
+
const table = locateProgressTable(roadmapContent);
|
|
84
92
|
if (table) {
|
|
85
93
|
const allRows = table.rows;
|
|
86
94
|
const completed = allRows.filter((r) => /^complete$/i.test((r['Status'] ?? '').trim())).length;
|