@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
|
@@ -16,13 +16,13 @@ const pattern_cjs_1 = require("./pattern.cjs");
|
|
|
16
16
|
const text_lines_cjs_1 = require("./text-lines.cjs");
|
|
17
17
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
18
18
|
const ioMod = require("./io.cjs");
|
|
19
|
-
const { output, error } = ioMod;
|
|
19
|
+
const { output, error, formatDiagnosticToken, declineNoOp } = ioMod;
|
|
20
20
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
21
21
|
const phaseIdMod = require("./phase-id.cjs");
|
|
22
|
-
const { normalizePhaseName, phaseMarkdownRegexSource, matchPhaseDirs, stripProjectCodePrefix, OPTIONAL_PHASE_TAG_SOURCE, roadmapPhaseLookupSources, isSentinelPhaseId, scopeToPhase } = phaseIdMod;
|
|
22
|
+
const { normalizePhaseName, phaseMarkdownRegexSource, matchPhaseDirs, stripProjectCodePrefix, OPTIONAL_PHASE_TAG_SOURCE, roadmapPhaseLookupSources, phaseHeadingPrefixSrcFor, PHASE_HEADING_BASELINE, isSentinelPhaseId, scopeToPhase, bracketQualifiedKey, foldBracketId } = phaseIdMod;
|
|
23
23
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
24
24
|
const phaseLocatorMod = require("./phase-locator.cjs");
|
|
25
|
-
const { findPhaseInternal, listMilestonePhaseDirs } = phaseLocatorMod;
|
|
25
|
+
const { findPhaseInternal, listMilestonePhaseDirs, listAllPhaseDirs } = phaseLocatorMod;
|
|
26
26
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
27
27
|
const planningScopeMod = require("./planning-scope.cjs");
|
|
28
28
|
const { SCOPE } = planningScopeMod;
|
|
@@ -35,7 +35,12 @@ const phase_lifecycle_cjs_1 = require("./phase-lifecycle.cjs");
|
|
|
35
35
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
36
36
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
37
37
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
38
|
-
const { planningPaths, withPlanningLock, findContextMdIn } = planningWorkspace;
|
|
38
|
+
const { planningPaths, withPlanningLock, findContextMdIn, resolvePhaseIdConvention } = planningWorkspace;
|
|
39
|
+
// #3641: milestone-scope's convention resolution reads the project config
|
|
40
|
+
// (no cycle — config-loader does not import this module).
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
42
|
+
const configLoaderForScope = require("./config-loader.cjs");
|
|
43
|
+
const { loadConfig: loadConfigForScope } = configLoaderForScope;
|
|
39
44
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
40
45
|
const scanPhasePlans = require("./plan-scan.cjs");
|
|
41
46
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
@@ -88,11 +93,20 @@ function countPhasePlansAndSummaries(phaseDir) {
|
|
|
88
93
|
const { planCount, summaryCount } = scanPhasePlans(phaseDir);
|
|
89
94
|
// hasContext and hasResearch are not plan-scan concerns — read the directory
|
|
90
95
|
// once and share the listing for all non-plan metadata that cmdRoadmapAnalyze needs.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
+
//
|
|
97
|
+
// #4014 (epic #3473 B4): the listing + unreadable-vs-empty discrimination
|
|
98
|
+
// is now owned by findContextMdIn's directory-string form, retiring this
|
|
99
|
+
// function's own readdirSync try/catch (mirrors core-utils.cts's
|
|
100
|
+
// getPhaseFileStats / phase-locator.cts's listMilestonePhaseDirs
|
|
101
|
+
// SCOPE.UNREADABLE discriminator).
|
|
102
|
+
const { files: phaseFiles, scope } = findContextMdIn(phaseDir);
|
|
103
|
+
// #3885 (ADR-3473 §8.5): `contextReadError` stays additive for the shipped
|
|
104
|
+
// `AnalyzePhase.context_read_error` JSON field — derived from `scope`
|
|
105
|
+
// rather than from its own caught error, since findContextMdIn's
|
|
106
|
+
// directory-string form reports SCOPE, not the raw errno message.
|
|
107
|
+
const contextReadError = scope === SCOPE.UNREADABLE
|
|
108
|
+
? `Could not read phase directory ${formatDiagnosticToken(phaseDir)}`
|
|
109
|
+
: null;
|
|
96
110
|
// #3511: scope the raw listing to this phase dir before the
|
|
97
111
|
// phase-numbered-artifact predicates (hasContext/hasResearch) — planCount/
|
|
98
112
|
// summaryCount above stay on scanPhasePlans's own unscoped listing since a
|
|
@@ -104,6 +118,8 @@ function countPhasePlansAndSummaries(phaseDir) {
|
|
|
104
118
|
summaryCount,
|
|
105
119
|
hasContext: findContextMdIn(scopedFiles) !== null,
|
|
106
120
|
hasResearch: scopedFiles.some(f => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md'),
|
|
121
|
+
contextReadError,
|
|
122
|
+
scope,
|
|
107
123
|
};
|
|
108
124
|
}
|
|
109
125
|
// `phaseMarkdownRegexSource` lives in phase-id.cjs (#3537) and is imported above.
|
|
@@ -114,22 +130,24 @@ function countPhasePlansAndSummaries(phaseDir) {
|
|
|
114
130
|
* exact production pattern instead of hand-duplicating it.
|
|
115
131
|
* #1729: OPTIONAL_PHASE_TAG_SOURCE after the number tolerates a pre-colon ( ) tag.
|
|
116
132
|
*/
|
|
117
|
-
function buildPhaseHeadingRegex(escapedPhase) {
|
|
118
|
-
return new RegExp(
|
|
133
|
+
function buildPhaseHeadingRegex(escapedPhase, convention) {
|
|
134
|
+
return new RegExp(`^${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.ANY_BRACKET, convention)}${escapedPhase}${OPTIONAL_PHASE_TAG_SOURCE}:\\s*(.+)$`, 'i');
|
|
119
135
|
}
|
|
120
136
|
/**
|
|
121
137
|
* Search for a phase header (and its section) within the given content string.
|
|
122
138
|
* Returns a result object if found (either a full match or a malformed_roadmap
|
|
123
139
|
* checklist-only match), or null if the phase is not present at all.
|
|
124
140
|
*/
|
|
125
|
-
function searchPhaseInContent(content, escapedPhase, phaseNum) {
|
|
126
|
-
const headingPattern = buildPhaseHeadingRegex(escapedPhase);
|
|
141
|
+
function searchPhaseInContent(content, escapedPhase, phaseNum, convention) {
|
|
142
|
+
const headingPattern = buildPhaseHeadingRegex(escapedPhase, convention);
|
|
127
143
|
const headings = (0, markdown_sectionizer_cjs_1.tokenizeHeadings)(content);
|
|
128
144
|
const headingIndex = headings.findIndex((heading) => headingPattern.test(heading.text));
|
|
129
145
|
const headerMatch = headingIndex === -1 ? null : headings[headingIndex].text.match(headingPattern);
|
|
130
146
|
if (!headerMatch) {
|
|
131
147
|
// Fallback: check if phase exists in summary list but missing detail section
|
|
132
|
-
|
|
148
|
+
// A BARE `Phase\s+` at base — takes the label-only baseline, so a bracket
|
|
149
|
+
// repo gains the bracket-ID form and nothing else.
|
|
150
|
+
const checklistPattern = new RegExp(`-\\s*\\[[ x]\\]\\s*\\*\\*${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY, convention)}${escapedPhase}${OPTIONAL_PHASE_TAG_SOURCE}:\\s*([^*]+)\\*\\*`, 'i');
|
|
133
151
|
const checklistMatch = content.match(checklistPattern);
|
|
134
152
|
if (checklistMatch) {
|
|
135
153
|
return {
|
|
@@ -215,11 +233,12 @@ function getRoadmapPhaseWithFallback(cwd, phaseNum) {
|
|
|
215
233
|
// #2121/#2114: iterate the shared lookup-source list (exact → numeric →
|
|
216
234
|
// prefix-tolerant) so this resolver matches getRoadmapPhaseInternal and a
|
|
217
235
|
// bare-number query resolves a drifted project-code-prefixed heading.
|
|
236
|
+
const convention = resolvePhaseIdConvention(cwd);
|
|
218
237
|
for (const source of roadmapPhaseLookupSources(phaseNum)) {
|
|
219
|
-
const milestoneResult = searchPhaseInContent(milestoneContent, source, phaseNum);
|
|
238
|
+
const milestoneResult = searchPhaseInContent(milestoneContent, source, phaseNum, convention);
|
|
220
239
|
if (milestoneResult && !milestoneResult.error)
|
|
221
240
|
return milestoneResult.section ?? null;
|
|
222
|
-
const fullResult = searchPhaseInContent(fullContent, source, phaseNum);
|
|
241
|
+
const fullResult = searchPhaseInContent(fullContent, source, phaseNum, convention);
|
|
223
242
|
if (fullResult && !fullResult.error)
|
|
224
243
|
return fullResult.section ?? null;
|
|
225
244
|
}
|
|
@@ -241,6 +260,7 @@ function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
|
|
|
241
260
|
const rawContent = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
|
|
242
261
|
const milestoneContent = extractCurrentMilestone(rawContent, cwd);
|
|
243
262
|
const fullContent = stripShippedMilestones(rawContent);
|
|
263
|
+
const convention = resolvePhaseIdConvention(cwd);
|
|
244
264
|
// #2121/#2114: iterate the shared lookup-source list (exact → numeric →
|
|
245
265
|
// prefix-tolerant) so all three roadmap resolvers share one contract and a
|
|
246
266
|
// bare-number query resolves a drifted `### Phase AB-29:` heading. This
|
|
@@ -251,12 +271,12 @@ function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
|
|
|
251
271
|
// heading — so a milestone checklist never blocks a full-roadmap header.
|
|
252
272
|
let malformed = null;
|
|
253
273
|
for (const source of roadmapPhaseLookupSources(phaseNum)) {
|
|
254
|
-
const milestoneResult = searchPhaseInContent(milestoneContent, source, phaseNum);
|
|
274
|
+
const milestoneResult = searchPhaseInContent(milestoneContent, source, phaseNum, convention);
|
|
255
275
|
if (milestoneResult && !milestoneResult.error) {
|
|
256
276
|
output(milestoneResult, raw, milestoneResult.section);
|
|
257
277
|
return;
|
|
258
278
|
}
|
|
259
|
-
const fullResult = searchPhaseInContent(fullContent, source, phaseNum);
|
|
279
|
+
const fullResult = searchPhaseInContent(fullContent, source, phaseNum, convention);
|
|
260
280
|
if (fullResult && !fullResult.error) {
|
|
261
281
|
output(fullResult, raw, fullResult.section);
|
|
262
282
|
return;
|
|
@@ -285,6 +305,30 @@ function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
|
|
|
285
305
|
error('Failed to read ROADMAP.md: ' + e.message);
|
|
286
306
|
}
|
|
287
307
|
}
|
|
308
|
+
// #612 composes the convention-qualified sentinel reading with upstream's
|
|
309
|
+
// canonical legacy sentinel owner. A reserved bracket milestone OR a reserved
|
|
310
|
+
// phase token excludes the occurrence.
|
|
311
|
+
const isSentinelPhase = (num, bracketId) => {
|
|
312
|
+
if (bracketId && isSentinelPhaseId(`${bracketId}-${num}`, 'bracket'))
|
|
313
|
+
return true;
|
|
314
|
+
return isSentinelPhaseId(num);
|
|
315
|
+
};
|
|
316
|
+
// #2761 M1: missing-detail identity is milestone-qualified under bracket.
|
|
317
|
+
// Prefer the canonical qualified-key owner, which case-folds accepted ids, so
|
|
318
|
+
// `[gsd.02] 01` and `[GSD.02] 01` are one occurrence. It is intentionally not
|
|
319
|
+
// padding-tolerant: the milestone grammar has one canonical spelling (pad2
|
|
320
|
+
// below 100, no leading zero above), so `[GSD.2]` is malformed rather than an
|
|
321
|
+
// alternate spelling of `[GSD.02]`. Hyphenated tokens and other shapes the
|
|
322
|
+
// qualified-key owner refuses retain a folded composite, keeping distinct
|
|
323
|
+
// bracket/token pairs from collapsing onto one missing-detail verdict.
|
|
324
|
+
const occurrenceKey = (num, bracketId) => {
|
|
325
|
+
if (!bracketId)
|
|
326
|
+
return num;
|
|
327
|
+
const qualified = num.includes('-')
|
|
328
|
+
? null
|
|
329
|
+
: bracketQualifiedKey(`${bracketId}-${num}`, 'bracket');
|
|
330
|
+
return qualified ?? `${foldBracketId(bracketId)}|${num}`;
|
|
331
|
+
};
|
|
288
332
|
/**
|
|
289
333
|
* #3165: scan `content` for phase-detail headings (`##/###/#### Phase N: Name`)
|
|
290
334
|
* and enrich each with its on-disk plan/summary/completion status and ROADMAP
|
|
@@ -294,29 +338,41 @@ function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
|
|
|
294
338
|
* `cmdRoadmapAnalyze`'s former inline loop so the fallback re-runs the EXACT
|
|
295
339
|
* same enrichment, not a second derivation.
|
|
296
340
|
*/
|
|
297
|
-
function collectAnalyzePhases(content, phasesDir, phaseDirNames) {
|
|
341
|
+
function collectAnalyzePhases(content, phasesDir, phaseDirNames, convention) {
|
|
298
342
|
// Extract all phase headings: ## Phase N: Name or ### Phase N: Name
|
|
299
343
|
// #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag (literal mirror of OPTIONAL_PHASE_TAG_SOURCE).
|
|
344
|
+
// #612: CAPTURING intro under the bracket convention — group 1 is the
|
|
345
|
+
// `[CODE.MM]` bracket id (undefined otherwise), group 2 the token, group 3 the
|
|
346
|
+
// name. The bracket id is what the sentinel filter needs: READING-B puts the
|
|
347
|
+
// sentinel milestone in the bracket, not in the token.
|
|
300
348
|
// phase-id-owner: uses the [.-] (dot-or-dash) separator variant, not the canonical dot-only token; a swap to PHASE_NUMBER_TOKEN_SOURCE would drop hyphenated phase-id matches.
|
|
301
349
|
// #3036: widen the id capture to accept non-numeric-leading ids (e.g. B7, P0.3-2)
|
|
302
350
|
// that get-phase/execute-phase already resolve. An optional leading letter prefix
|
|
303
351
|
// ([A-Za-z]?) covers letter-prefixed ids without breaking numeric-leading ones.
|
|
304
352
|
// phase-id-owner: uses the [.-] (dot-or-dash) separator variant, not the canonical dot-only token; a swap to PHASE_NUMBER_TOKEN_SOURCE would drop hyphenated phase-id matches.
|
|
305
|
-
const phasePattern =
|
|
353
|
+
const phasePattern = new RegExp(`#{2,4}\\s*${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.ANY_BRACKET, convention, true)}([A-Za-z]?\\d+[A-Z]?(?:[.-]\\d+)*)(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:\\s*([^\\n]+)`, 'gi');
|
|
354
|
+
// The capturing intro inserts the bracket id at group 1 only under the
|
|
355
|
+
// bracket convention; the token and name shift by the same offset.
|
|
356
|
+
const G = convention === 'bracket' ? 1 : 0;
|
|
306
357
|
const phases = [];
|
|
307
358
|
let match;
|
|
359
|
+
// The caller needs the exact occurrence identities from the same scan that
|
|
360
|
+
// built `phases`; returning them together also keeps fallback rescans atomic.
|
|
361
|
+
const detailKeys = new Set();
|
|
308
362
|
while ((match = phasePattern.exec(content)) !== null) {
|
|
309
|
-
const
|
|
310
|
-
|
|
363
|
+
const bracketId = G ? match[1] : undefined;
|
|
364
|
+
const phaseNum = match[1 + G];
|
|
365
|
+
if (isSentinelPhase(phaseNum, bracketId))
|
|
311
366
|
continue;
|
|
312
|
-
|
|
367
|
+
detailKeys.add(occurrenceKey(phaseNum, bracketId));
|
|
368
|
+
const phaseName = match[2 + G].replace(/\(INSERTED\)/i, '').trim();
|
|
313
369
|
// Extract goal from the section
|
|
314
370
|
const sectionStart = match.index;
|
|
315
371
|
const restOfContent = content.slice(sectionStart);
|
|
316
372
|
// #3691: `\d` → `\d[\d.]*` so decimal phase headings (e.g. `### Phase 02.3:`) are
|
|
317
373
|
// recognised as section boundaries. #3036: `[A-Za-z]?\d` so non-numeric-leading ids
|
|
318
374
|
// (e.g. B7) are also recognised.
|
|
319
|
-
const nextHeader = restOfContent.match(
|
|
375
|
+
const nextHeader = restOfContent.match(new RegExp(`\\n#{2,4}\\s+${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.ANY_BRACKET, convention)}[A-Za-z]?\\d[\\d.-]*`, 'i'));
|
|
320
376
|
const sectionEnd = nextHeader ? sectionStart + nextHeader.index : content.length;
|
|
321
377
|
const section = content.slice(sectionStart, sectionEnd);
|
|
322
378
|
const goalMatch = section.match(/\*\*Goal(?::\*\*|\*\*:)\s*([^\n]+)/i);
|
|
@@ -332,19 +388,45 @@ function collectAnalyzePhases(content, phasesDir, phaseDirNames) {
|
|
|
332
388
|
let summaryCount = 0;
|
|
333
389
|
let hasContext = false;
|
|
334
390
|
let hasResearch = false;
|
|
391
|
+
// #3885 (ADR-3473 §8.5): null unless dirMatch resolves and its readdirSync
|
|
392
|
+
// hit a non-ENOENT error — no directory at all is `disk_status:
|
|
393
|
+
// 'no_directory'`, a real (if uninteresting) answer, not a read error.
|
|
394
|
+
let contextReadError = null;
|
|
395
|
+
// #4014 (epic #3473 B4): additive sibling — SCOPE.COMPLETE by default
|
|
396
|
+
// (no directory at all is a genuine, not-unreadable answer), overwritten
|
|
397
|
+
// below only when dirMatch resolves.
|
|
398
|
+
let contextScope = SCOPE.COMPLETE;
|
|
335
399
|
// DEAD catch removed (#2245 audit): matchPhaseDirs(...) is a pure
|
|
336
400
|
// array lookup on an already-resolved string array, and
|
|
337
401
|
// countPhasePlansAndSummaries is itself fully defensive (its own
|
|
338
402
|
// readdirSync is self-guarded, and it delegates to scanPhasePlans, which
|
|
339
403
|
// never throws) — nothing in this block can throw, so the try/catch could
|
|
340
404
|
// never be triggered.
|
|
341
|
-
|
|
405
|
+
// #612: the DIRECTORY read is selected by the same `convention` the four
|
|
406
|
+
// heading/checklist patterns above already thread. Left two-argument, this
|
|
407
|
+
// one call reported EVERY canonical `{CODE}.{MM}-{PP}-slug` directory as
|
|
408
|
+
// `disk_status: "no_directory"` with `plan_count`/`summary_count` 0 —
|
|
409
|
+
// `extractPhaseToken('GSD.02-01-one')` with no convention returns the whole
|
|
410
|
+
// dir name — while the same build resolved those same directories correctly
|
|
411
|
+
// in three other places on the same repo (W006/W007 through their shared
|
|
412
|
+
// directory matcher, `state json` via the milestone filter, and the W026
|
|
413
|
+
// milestone-complete read through the same convention-aware owner). It
|
|
414
|
+
// failed ONLY for the directory shape the convention exists to name: a
|
|
415
|
+
// mid-migration bracket repo carrying legacy `01-one` dirs resolved fine.
|
|
416
|
+
// That is verbatim the asymmetry the note above the W026 rule says this PR
|
|
417
|
+
// closed — the directory read widens with the heading read, or every bracket
|
|
418
|
+
// phase resolves to nothing.
|
|
419
|
+
// Upstream centralized this choice in `matchPhaseDirs`; thread the same
|
|
420
|
+
// convention into that owner rather than reviving the primitive `.find()`.
|
|
421
|
+
const dirMatch = matchPhaseDirs(phaseDirNames, normalized, convention).matches[0];
|
|
342
422
|
if (dirMatch) {
|
|
343
423
|
const counts = countPhasePlansAndSummaries(node_path_1.default.join(phasesDir, dirMatch));
|
|
344
424
|
planCount = counts.planCount;
|
|
345
425
|
summaryCount = counts.summaryCount;
|
|
346
426
|
hasContext = counts.hasContext;
|
|
347
427
|
hasResearch = counts.hasResearch;
|
|
428
|
+
contextReadError = counts.contextReadError;
|
|
429
|
+
contextScope = counts.scope;
|
|
348
430
|
// ADR-3180 §7.4 (issue #3186, disk-strict, #3168 fix): route "is this
|
|
349
431
|
// phase complete" through the canonical owner (`isPhaseComplete`),
|
|
350
432
|
// which calls readVerificationStatus UNCONDITIONALLY — plan count is
|
|
@@ -379,7 +461,7 @@ function collectAnalyzePhases(content, phasesDir, phaseDirNames) {
|
|
|
379
461
|
// checkbox — no passing `*-VERIFICATION.md`, plans outstanding — now
|
|
380
462
|
// reports incomplete; this is the deliberate Tier-2 break (ADR-3180 §7.4
|
|
381
463
|
// Decision 3).
|
|
382
|
-
const checkboxPattern = new RegExp(`-\\s*\\[(x| )\\]\\s
|
|
464
|
+
const checkboxPattern = new RegExp(`-\\s*\\[(x| )\\]\\s*.*${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY, convention)}${phaseMarkdownRegexSource(phaseNum)}${OPTIONAL_PHASE_TAG_SOURCE}[:\\s]`, 'i');
|
|
383
465
|
const checkboxMatch = content.match(checkboxPattern);
|
|
384
466
|
const roadmapComplete = checkboxMatch ? checkboxMatch[1] === 'x' : false;
|
|
385
467
|
phases.push({
|
|
@@ -394,6 +476,8 @@ function collectAnalyzePhases(content, phasesDir, phaseDirNames) {
|
|
|
394
476
|
has_research: hasResearch,
|
|
395
477
|
disk_status: diskStatus,
|
|
396
478
|
roadmap_complete: roadmapComplete,
|
|
479
|
+
context_read_error: contextReadError,
|
|
480
|
+
context_scope: contextScope,
|
|
397
481
|
});
|
|
398
482
|
}
|
|
399
483
|
// #3577: markdown-table row declarations join the enumeration — same
|
|
@@ -403,6 +487,9 @@ function collectAnalyzePhases(content, phasesDir, phaseDirNames) {
|
|
|
403
487
|
const stripPadA = (s) => s.replace(/^0+(?=.)/, '');
|
|
404
488
|
const seen = new Set(phases.map((ph) => stripPadA(ph.number)));
|
|
405
489
|
for (const tr of collectTablePhaseRows(content)) {
|
|
490
|
+
// #3577 table declarations were part of the pre-existing detail set.
|
|
491
|
+
// Preserve that behavior while heading occurrences gain bracket identity.
|
|
492
|
+
detailKeys.add(occurrenceKey(tr.id));
|
|
406
493
|
if (seen.has(stripPadA(tr.id)))
|
|
407
494
|
continue;
|
|
408
495
|
const dirMatchA = matchPhaseDirs(phaseDirNames, normalizePhaseName(tr.id)).matches[0];
|
|
@@ -410,12 +497,22 @@ function collectAnalyzePhases(content, phasesDir, phaseDirNames) {
|
|
|
410
497
|
let tSummaryCount = 0;
|
|
411
498
|
let tHasContext = false;
|
|
412
499
|
let tHasResearch = false;
|
|
500
|
+
let tContextReadError = null;
|
|
501
|
+
// #4014 (epic #3473 B4): additive sibling, same default rule as the
|
|
502
|
+
// heading-declared branch above.
|
|
503
|
+
let tContextScope = SCOPE.COMPLETE;
|
|
413
504
|
if (dirMatchA) {
|
|
414
505
|
const counts = countPhasePlansAndSummaries(node_path_1.default.join(phasesDir, dirMatchA));
|
|
415
506
|
tPlanCount = counts.planCount;
|
|
416
507
|
tSummaryCount = counts.summaryCount;
|
|
417
508
|
tHasContext = node_fs_1.default.existsSync(node_path_1.default.join(phasesDir, dirMatchA, 'CONTEXT.md'));
|
|
418
509
|
tHasResearch = node_fs_1.default.existsSync(node_path_1.default.join(phasesDir, dirMatchA, 'RESEARCH.md'));
|
|
510
|
+
// #3885 (ADR-3473 §8.5): reuse the SAME countPhasePlansAndSummaries call's
|
|
511
|
+
// discriminator — this row's hasContext/hasResearch are read via a direct
|
|
512
|
+
// existsSync (which cannot itself distinguish EACCES from absent), but
|
|
513
|
+
// an unreadable phase directory is still surfaced via the sibling call.
|
|
514
|
+
tContextReadError = counts.contextReadError;
|
|
515
|
+
tContextScope = counts.scope;
|
|
419
516
|
}
|
|
420
517
|
phases.push({
|
|
421
518
|
number: tr.id,
|
|
@@ -429,9 +526,11 @@ function collectAnalyzePhases(content, phasesDir, phaseDirNames) {
|
|
|
429
526
|
has_research: tHasResearch,
|
|
430
527
|
disk_status: dirMatchA ? 'ok' : 'no_directory',
|
|
431
528
|
roadmap_complete: false,
|
|
529
|
+
context_read_error: tContextReadError,
|
|
530
|
+
context_scope: tContextScope,
|
|
432
531
|
});
|
|
433
532
|
}
|
|
434
|
-
return phases;
|
|
533
|
+
return { phases, detailKeys };
|
|
435
534
|
}
|
|
436
535
|
function cmdRoadmapAnalyze(cwd, raw) {
|
|
437
536
|
const roadmapPath = planningPaths(cwd).roadmap;
|
|
@@ -445,26 +544,28 @@ function cmdRoadmapAnalyze(cwd, raw) {
|
|
|
445
544
|
// indistinguishable from a genuinely empty milestone.
|
|
446
545
|
const { value: content, scope } = extractCurrentMilestoneScoped(rawContent, cwd);
|
|
447
546
|
const phasesDir = planningPaths(cwd).phases;
|
|
547
|
+
// #612: resolve once per command and thread the same reading through both
|
|
548
|
+
// the scoped scan and any fallback scan.
|
|
549
|
+
const convention = resolvePhaseIdConvention(cwd);
|
|
550
|
+
const G = convention === 'bracket' ? 1 : 0;
|
|
448
551
|
// Build phase directory lookup once (O(1) readdir instead of O(N) per phase)
|
|
449
|
-
// #3185 exemption (
|
|
450
|
-
//
|
|
451
|
-
//
|
|
452
|
-
//
|
|
453
|
-
//
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
catch {
|
|
461
|
-
return [];
|
|
462
|
-
}
|
|
463
|
-
})();
|
|
552
|
+
// #3185 exemption reason (ADR-3180 Decision 4a): this is a heading->directory
|
|
553
|
+
// LOOKUP INDEX, not a milestone enumeration. It must see the PHYSICAL set so
|
|
554
|
+
// a heading already scoped by extractCurrentMilestoneScoped above can find
|
|
555
|
+
// its directory; filtering it through listMilestonePhaseDirs would scope
|
|
556
|
+
// the same set twice. #3882 (ADR-3473 §8.2): routed through the named
|
|
557
|
+
// "physical set, sentinels included" axis instead of a hand-rolled
|
|
558
|
+
// readdirSync — every heading matched below already excludes sentinel
|
|
559
|
+
// phase numbers via isSentinelPhaseId before it ever consults this list
|
|
560
|
+
// (collectAnalyzePhases), so a sentinel directory's presence here is
|
|
561
|
+
// output-invariant; this only removes the re-derivation, not the reason.
|
|
562
|
+
const _phaseDirNames = listAllPhaseDirs(phasesDir, { includeSentinels: true }).value;
|
|
464
563
|
// Scan the scoped milestone window for phase-detail headings and enrich each
|
|
465
564
|
// with its on-disk status. Extracted into `collectAnalyzePhases` (#3165) so
|
|
466
565
|
// the SAME enrichment re-runs on the fallback below — not a second copy.
|
|
467
|
-
let
|
|
566
|
+
let collected = collectAnalyzePhases(content, phasesDir, _phaseDirNames, convention);
|
|
567
|
+
let phases = collected.phases;
|
|
568
|
+
let detailKeys = collected.detailKeys;
|
|
468
569
|
// `effectiveContent` is what the downstream checklist scan (missing_details)
|
|
469
570
|
// iterates. Defaults to the scoped window; switched to the fallback document
|
|
470
571
|
// when the recovery path below fires, so a phase found via fallback is not
|
|
@@ -486,9 +587,11 @@ function cmdRoadmapAnalyze(cwd, raw) {
|
|
|
486
587
|
// populated, flagged result.
|
|
487
588
|
if (phases.length === 0 && scope !== SCOPE.COMPLETE && _phaseDirNames.length > 0) {
|
|
488
589
|
const fallbackContent = stripShippedMilestones(rawContent);
|
|
489
|
-
const
|
|
490
|
-
if (
|
|
491
|
-
|
|
590
|
+
const fallbackCollection = collectAnalyzePhases(fallbackContent, phasesDir, _phaseDirNames, convention);
|
|
591
|
+
if (fallbackCollection.phases.length > 0) {
|
|
592
|
+
collected = fallbackCollection;
|
|
593
|
+
phases = collected.phases;
|
|
594
|
+
detailKeys = collected.detailKeys;
|
|
492
595
|
effectiveContent = fallbackContent;
|
|
493
596
|
}
|
|
494
597
|
}
|
|
@@ -511,17 +614,41 @@ function cmdRoadmapAnalyze(cwd, raw) {
|
|
|
511
614
|
// The char class must allow `-` (not just `.`) so dash-separated milestone-prefixed
|
|
512
615
|
// IDs (e.g. `1-01`) match the detail-heading scanner above; otherwise they truncate
|
|
513
616
|
// at the dash (`1-01` -> `1`) and every such phase reports a phantom missing detail.
|
|
617
|
+
// #612: CAPTURING label-only intro — the bracket id rides along so the
|
|
618
|
+
// sentinel filter below is not blind to `- [ ] **[GSD.999] 01: Icebox**`.
|
|
514
619
|
// phase-id-owner: uses the [.-] (dot-or-dash) separator variant, not the canonical dot-only token; a swap to PHASE_NUMBER_TOKEN_SOURCE would drop hyphenated phase-id matches.
|
|
515
620
|
// #3036: widen to accept non-numeric-leading ids (same widening as the detail-heading pattern above).
|
|
516
621
|
// phase-id-owner: uses the [.-] (dot-or-dash) separator variant, not the canonical dot-only token; a swap to PHASE_NUMBER_TOKEN_SOURCE would drop hyphenated phase-id matches.
|
|
517
|
-
const checklistPattern =
|
|
518
|
-
|
|
622
|
+
const checklistPattern = new RegExp(`-\\s*\\[[ x]\\]\\s*\\*\\*${phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.LABEL_ONLY, convention, true)}([A-Za-z]?\\d+[A-Z]?(?:[.-]\\d+)*)`, 'gi');
|
|
623
|
+
// #2761 M1: an OCCURRENCE list keyed by `occurrenceKey`, not a token->bracket
|
|
624
|
+
// map. The map was first-wins on the bare token, so of two checklist entries
|
|
625
|
+
// sharing a token across brackets the FIRST one's bracket id classified BOTH:
|
|
626
|
+
// `- [ ] **[GSD.999] 01: Icebox**` written above `- [ ] **[GSD.02] 01: …**`
|
|
627
|
+
// made the real phase inherit the icebox's sentinel verdict and vanish from
|
|
628
|
+
// `missing_phase_details`; written below it, the same document reported it.
|
|
629
|
+
// Dedupe still happens — it is now per PHASE rather than per token, which is
|
|
630
|
+
// what makes the classification order-independent.
|
|
631
|
+
const checklistOccurrences = [];
|
|
632
|
+
const seenChecklistKeys = new Set();
|
|
519
633
|
let checklistMatch;
|
|
520
634
|
while ((checklistMatch = checklistPattern.exec(effectiveContent)) !== null) {
|
|
521
|
-
|
|
635
|
+
const token = checklistMatch[1 + G];
|
|
636
|
+
const bracketId = G ? checklistMatch[1] : undefined;
|
|
637
|
+
const key = occurrenceKey(token, bracketId);
|
|
638
|
+
if (seenChecklistKeys.has(key))
|
|
639
|
+
continue;
|
|
640
|
+
seenChecklistKeys.add(key);
|
|
641
|
+
checklistOccurrences.push({ token, bracketId });
|
|
522
642
|
}
|
|
523
|
-
|
|
524
|
-
|
|
643
|
+
// The EMITTED value stays the bare token, unchanged: `phases[].number` is a
|
|
644
|
+
// token under every convention, and `missing_phase_details` is read against
|
|
645
|
+
// it. Only the classification moved to the qualified key — so two different
|
|
646
|
+
// brackets' `01` both missing report `01` once, rather than one of them
|
|
647
|
+
// silently covering for the other.
|
|
648
|
+
const missingDetails = [...new Set(checklistOccurrences
|
|
649
|
+
.filter(o => !detailKeys.has(occurrenceKey(o.token, o.bracketId))
|
|
650
|
+
&& !isSentinelPhase(o.token, o.bracketId))
|
|
651
|
+
.map(o => o.token))];
|
|
525
652
|
// #3217 (ADR-3180 §7.6 rules 3-4): `progress_percent` used to accumulate
|
|
526
653
|
// `totalPlans`/`totalSummaries` above — a heading-matched enumeration
|
|
527
654
|
// (`phasePattern` over the milestone-windowed `content`) paired against
|
|
@@ -615,9 +742,38 @@ function cmdRoadmapMilestoneScope(cwd, raw) {
|
|
|
615
742
|
return;
|
|
616
743
|
}
|
|
617
744
|
const rawContent = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
|
|
618
|
-
|
|
745
|
+
// #3641: resolve phase_id_convention and thread it into the scope axis, so
|
|
746
|
+
// this probe and `roadmap validate`'s V005 answer the SAME question the
|
|
747
|
+
// SAME way for a bracket-convention project — a window the classifier
|
|
748
|
+
// calls TRUNCATED in validate must never read COMPLETE here (the #3262
|
|
749
|
+
// capture/compare guard consumes this scope). Resolution mirrors the
|
|
750
|
+
// validate router's: .planning/config.json first, ROADMAP.md frontmatter
|
|
751
|
+
// as fallback.
|
|
752
|
+
let phaseIdConvention;
|
|
753
|
+
try {
|
|
754
|
+
const cfg = loadConfigForScope(cwd);
|
|
755
|
+
phaseIdConvention = cfg['phase_id_convention'];
|
|
756
|
+
}
|
|
757
|
+
catch {
|
|
758
|
+
phaseIdConvention = undefined;
|
|
759
|
+
}
|
|
760
|
+
if (phaseIdConvention === undefined || phaseIdConvention === null) {
|
|
761
|
+
// Bounded per local/no-unbounded-quantifier (#2128): frontmatter is a
|
|
762
|
+
// short header block — 4KB is orders of magnitude beyond any real one.
|
|
763
|
+
const fmMatch = rawContent.match(/^---\r?\n([\s\S]{0,4000}?)\r?\n---/);
|
|
764
|
+
if (fmMatch) {
|
|
765
|
+
const kvMatch = fmMatch[1].match(/^phase_id_convention:\s*(.*)$/m);
|
|
766
|
+
if (kvMatch) {
|
|
767
|
+
const val = kvMatch[1].trim();
|
|
768
|
+
if (val !== 'null' && val !== '') {
|
|
769
|
+
phaseIdConvention = val.replace(/^["']|["']$/g, '');
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
const { value: window, scope } = extractCurrentMilestoneScoped(rawContent, cwd, undefined, phaseIdConvention);
|
|
619
775
|
// Document order (Set insertion order) — deterministic for a given document.
|
|
620
|
-
const phases = [...scanMilestonePhaseIds(window)];
|
|
776
|
+
const phases = [...scanMilestonePhaseIds(window, phaseIdConvention)];
|
|
621
777
|
output({ scope, phases, phase_count: phases.length }, raw, undefined);
|
|
622
778
|
}
|
|
623
779
|
// ─── cmdRoadmapUpdatePlanProgress ─────────────────────────────────────────────
|
|
@@ -669,7 +825,7 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
|
|
|
669
825
|
// inflate summary_count and silently flip the phase to Complete.
|
|
670
826
|
const summaryCount = countMatchedSummaries(phaseInfo.plans, phaseInfo.summaries);
|
|
671
827
|
if (planCount === 0) {
|
|
672
|
-
|
|
828
|
+
declineNoOp(raw, 'updated', 'No plans found', 'roadmap update-plan-progress skipped — no plans found for this phase. ROADMAP.md was left unchanged.', { plan_count: 0, summary_count: 0 });
|
|
673
829
|
return;
|
|
674
830
|
}
|
|
675
831
|
// Verification gate (#2022): do NOT check the phase checkbox or stamp a
|
|
@@ -711,12 +867,19 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
|
|
|
711
867
|
const status = isComplete ? 'Complete' : summaryCount > 0 ? 'In Progress' : 'Planned';
|
|
712
868
|
const today = clock_cjs_1.realClock.localToday();
|
|
713
869
|
if (!node_fs_1.default.existsSync(roadmapPath)) {
|
|
714
|
-
|
|
870
|
+
declineNoOp(raw, 'updated', 'ROADMAP.md not found', 'roadmap update-plan-progress skipped — ROADMAP.md not found.', { plan_count: planCount, summary_count: summaryCount });
|
|
715
871
|
return;
|
|
716
872
|
}
|
|
717
873
|
// Wrap entire read-modify-write in lock to prevent concurrent corruption
|
|
874
|
+
let updated = false;
|
|
718
875
|
withPlanningLock(cwd, () => {
|
|
719
|
-
|
|
876
|
+
// #3957 (B9.4): captured BEFORE any transform runs, so the write/report
|
|
877
|
+
// decision below reflects whether the transforms actually changed
|
|
878
|
+
// anything — not just that they ran. Every transform below still runs
|
|
879
|
+
// unconditionally exactly as before; only the final write-and-report
|
|
880
|
+
// step becomes conditional on `roadmapContent !== originalContent`.
|
|
881
|
+
const originalContent = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
|
|
882
|
+
let roadmapContent = originalContent;
|
|
720
883
|
const phasePattern = phaseMarkdownRegexSource(phaseNum);
|
|
721
884
|
// Progress table row: update Plans Complete/Status/Completed columns BY
|
|
722
885
|
// COLUMN NAME (handles 4- or 5-column RoadmapProgress tables regardless of
|
|
@@ -913,17 +1076,30 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
|
|
|
913
1076
|
}
|
|
914
1077
|
}
|
|
915
1078
|
}
|
|
916
|
-
(
|
|
1079
|
+
// #3957 (B9.4): write and report an update only when the transforms
|
|
1080
|
+
// above actually produced different bytes — mirroring the sibling
|
|
1081
|
+
// `cmdRoadmapAnnotateDependencies`'s existing `nextContent !== content`
|
|
1082
|
+
// gate. Previously this wrote and reported `updated: true`
|
|
1083
|
+
// unconditionally, even on an idempotent re-run that changed nothing.
|
|
1084
|
+
if (roadmapContent !== originalContent) {
|
|
1085
|
+
(0, shell_command_projection_cjs_1.platformWriteSync)(roadmapPath, roadmapContent);
|
|
1086
|
+
updated = true;
|
|
1087
|
+
}
|
|
917
1088
|
});
|
|
918
|
-
|
|
919
|
-
updated: true,
|
|
1089
|
+
const computed = {
|
|
920
1090
|
phase: phaseNum,
|
|
921
1091
|
plan_count: planCount,
|
|
922
1092
|
summary_count: summaryCount,
|
|
923
1093
|
status,
|
|
924
1094
|
complete: isComplete,
|
|
925
1095
|
verification_stale_check_indeterminate: verificationStaleCheckIndeterminate,
|
|
926
|
-
}
|
|
1096
|
+
};
|
|
1097
|
+
if (updated) {
|
|
1098
|
+
output({ updated: true, ...computed }, raw, `${summaryCount}/${planCount} ${status}`);
|
|
1099
|
+
}
|
|
1100
|
+
else {
|
|
1101
|
+
declineNoOp(raw, 'updated', "no changes were needed — ROADMAP.md already reflects this phase's plan/summary counts and status", "roadmap update-plan-progress skipped — no changes were needed; ROADMAP.md already reflects this phase's plan/summary counts and status.", computed);
|
|
1102
|
+
}
|
|
927
1103
|
}
|
|
928
1104
|
// ─── cmdRoadmapAnnotateDependencies ───────────────────────────────────────────
|
|
929
1105
|
/**
|
|
@@ -945,12 +1121,20 @@ function cmdRoadmapAnnotateDependencies(cwd, phaseNum, raw) {
|
|
|
945
1121
|
}
|
|
946
1122
|
const roadmapPath = planningPaths(cwd).roadmap;
|
|
947
1123
|
if (!node_fs_1.default.existsSync(roadmapPath)) {
|
|
948
|
-
|
|
1124
|
+
declineNoOp(raw, 'updated', 'ROADMAP.md not found', 'roadmap annotate-dependencies skipped — ROADMAP.md not found.');
|
|
949
1125
|
return;
|
|
950
1126
|
}
|
|
951
1127
|
const phaseInfo = findPhaseInternal(cwd, phaseNum);
|
|
952
|
-
|
|
953
|
-
|
|
1128
|
+
// #3957 (B9.1): distinguish "phase does not resolve at all" from "phase
|
|
1129
|
+
// resolves but has zero plans" — previously both collapsed into the same
|
|
1130
|
+
// 'no plans found for phase' reason, which is simply false for the first
|
|
1131
|
+
// case (there IS no such phase to have plans).
|
|
1132
|
+
if (!phaseInfo) {
|
|
1133
|
+
declineNoOp(raw, 'updated', `phase ${phaseNum} not found`, `roadmap annotate-dependencies skipped — phase ${formatDiagnosticToken(String(phaseNum))} not found.`, { phase: phaseNum });
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
if (phaseInfo.plans.length === 0) {
|
|
1137
|
+
declineNoOp(raw, 'updated', `phase ${phaseNum} has no plans`, `roadmap annotate-dependencies skipped — phase ${formatDiagnosticToken(String(phaseNum))} has no plans.`, { phase: phaseNum });
|
|
954
1138
|
return;
|
|
955
1139
|
}
|
|
956
1140
|
// Read each PLAN.md and extract wave + must_haves.truths
|
|
@@ -968,7 +1152,7 @@ function cmdRoadmapAnnotateDependencies(cwd, phaseNum, raw) {
|
|
|
968
1152
|
catch { /* skip unreadable plans */ }
|
|
969
1153
|
}
|
|
970
1154
|
if (planData.length === 0) {
|
|
971
|
-
|
|
1155
|
+
declineNoOp(raw, 'updated', 'could not read plan frontmatter', 'roadmap annotate-dependencies skipped — could not read plan frontmatter for any plan in this phase.');
|
|
972
1156
|
return;
|
|
973
1157
|
}
|
|
974
1158
|
// Group plans by wave (sorted)
|