@opengsd/gsd-core 1.11.0 → 1.12.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/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 +17 -9
- 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 +15 -55
- package/agents/gsd-planner.md +6 -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 +63 -4
- package/agents/gsd-ui-researcher.md +41 -3
- package/agents/gsd-verifier.md +1 -1
- package/bin/install.js +609 -134
- package/commands/gsd/discuss-phase.md +1 -1
- package/commands/gsd/import.md +1 -1
- package/commands/gsd/quick.md +8 -4
- package/gsd-core/bin/gsd-tools.cjs +567 -51
- 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-lock.cjs +10 -4
- package/gsd-core/bin/lib/capability-registry.cjs +336 -95
- package/gsd-core/bin/lib/capability-state.cjs +18 -3
- package/gsd-core/bin/lib/capability-validator.cjs +205 -18
- package/gsd-core/bin/lib/check-command-router.cjs +145 -5
- package/gsd-core/bin/lib/cli-exit.cjs +496 -10
- 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-arg-projection.cjs +144 -14
- package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
- package/gsd-core/bin/lib/commands.cjs +543 -44
- package/gsd-core/bin/lib/complexity-trigger.cjs +26 -6
- package/gsd-core/bin/lib/config-loader.cjs +118 -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 +84 -7
- package/gsd-core/bin/lib/edge-probe.cjs +9 -1
- 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/frontmatter.cjs +840 -305
- package/gsd-core/bin/lib/gap-checker.cjs +27 -3
- package/gsd-core/bin/lib/git-base-branch.cjs +174 -39
- package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +7 -3
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +6 -3
- 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 +39 -6
- package/gsd-core/bin/lib/init-command-router.cjs +118 -21
- package/gsd-core/bin/lib/init.cjs +120 -41
- package/gsd-core/bin/lib/install-engine.cjs +68 -3
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +33 -1
- package/gsd-core/bin/lib/install-profiles.cjs +78 -4
- package/gsd-core/bin/lib/installer-migration-report.cjs +3 -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 +160 -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/markdown-table.cjs +52 -4
- package/gsd-core/bin/lib/milestone.cjs +90 -5
- 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-estimation.cjs +17 -8
- package/gsd-core/bin/lib/phase-id.cjs +70 -4
- 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 +405 -84
- package/gsd-core/bin/lib/plan-document.cjs +263 -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 +18 -14
- package/gsd-core/bin/lib/planning-workspace.cjs +56 -0
- 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/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 +9 -9
- package/gsd-core/bin/lib/roadmap-command-router.cjs +45 -31
- package/gsd-core/bin/lib/roadmap-parser.cjs +79 -16
- package/gsd-core/bin/lib/roadmap.cjs +74 -19
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +96 -8
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +34 -1
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +287 -55
- 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 +71 -8
- package/gsd-core/bin/lib/smart-entry.cjs +12 -22
- 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 +186 -0
- package/gsd-core/bin/lib/state-md-schema.cjs +221 -0
- package/gsd-core/bin/lib/state-transition.cjs +517 -101
- package/gsd-core/bin/lib/state.cjs +946 -163
- package/gsd-core/bin/lib/surface.cjs +10 -2
- 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/teams-status.cjs +4 -1
- package/gsd-core/bin/lib/uat-predicate.cjs +58 -20
- package/gsd-core/bin/lib/uat.cjs +1376 -125
- 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/validate-command-router.cjs +2 -2
- 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.cjs +14 -1
- package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
- package/gsd-core/bin/lib/verify.cjs +95 -40
- 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 +177 -21
- package/gsd-core/bin/shared/config-defaults.manifest.json +7 -1
- package/gsd-core/bin/shared/config-schema.manifest.json +5 -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/references/agent-contracts.md +3 -2
- 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 +8 -0
- package/gsd-core/references/execute-mvp-tdd.md +1 -3
- package/gsd-core/references/execute-phase-between-wave-reset.md +9 -12
- package/gsd-core/references/execute-phase-wave-guard.md +11 -9
- package/gsd-core/references/failing-direction.md +78 -0
- 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/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/planner-failing-direction.md +53 -0
- package/gsd-core/references/planner-human-verify-mode.md +15 -1
- package/gsd-core/references/planner-revision.md +1 -1
- package/gsd-core/references/planner-verify-command-grounding.md +17 -0
- package/gsd-core/references/planning-config.md +37 -8
- package/gsd-core/references/reviewer-instances.md +31 -0
- package/gsd-core/references/runtime-aware-dispatch.md +1 -1
- package/gsd-core/references/tdd.md +1 -3
- 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/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 +3 -0
- package/gsd-core/templates/state.md +7 -0
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +1 -1
- package/gsd-core/workflows/add-phase.md +3 -3
- package/gsd-core/workflows/add-tests.md +3 -8
- package/gsd-core/workflows/add-todo.md +1 -1
- package/gsd-core/workflows/ai-integration-phase.md +4 -9
- package/gsd-core/workflows/audit-fix.md +12 -3
- package/gsd-core/workflows/audit-milestone.md +9 -9
- package/gsd-core/workflows/audit-uat.md +17 -2
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
- package/gsd-core/workflows/autonomous.md +10 -26
- package/gsd-core/workflows/check-todos.md +1 -1
- package/gsd-core/workflows/cleanup.md +2 -2
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +1 -1
- package/gsd-core/workflows/code-review-fix.md +1 -1
- package/gsd-core/workflows/code-review.md +121 -40
- package/gsd-core/workflows/complete-milestone.md +15 -10
- package/gsd-core/workflows/debug.md +5 -3
- package/gsd-core/workflows/diagnose-issues.md +12 -6
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +3 -7
- package/gsd-core/workflows/discuss-phase/modes/text.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
- package/gsd-core/workflows/discuss-phase-assumptions.md +2 -2
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +3 -6
- package/gsd-core/workflows/docs-update.md +5 -4
- package/gsd-core/workflows/edit-phase.md +1 -1
- package/gsd-core/workflows/eval-review.md +4 -9
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +113 -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 +22 -4
- 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 +2 -2
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
- package/gsd-core/workflows/execute-phase.md +38 -54
- package/gsd-core/workflows/execute-plan.md +17 -12
- package/gsd-core/workflows/explore.md +1 -1
- package/gsd-core/workflows/extract-learnings.md +1 -1
- package/gsd-core/workflows/fast.md +2 -2
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +5 -5
- package/gsd-core/workflows/health.md +3 -6
- package/gsd-core/workflows/import.md +14 -11
- package/gsd-core/workflows/inbox.md +4 -5
- package/gsd-core/workflows/ingest-docs.md +44 -11
- package/gsd-core/workflows/insert-phase.md +5 -5
- package/gsd-core/workflows/list-seeds.md +5 -3
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +12 -23
- package/gsd-core/workflows/map-codebase.md +1 -1
- package/gsd-core/workflows/milestone-summary.md +1 -1
- package/gsd-core/workflows/mvp-phase.md +2 -2
- package/gsd-core/workflows/new-milestone.md +9 -21
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
- package/gsd-core/workflows/new-project.md +12 -26
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +2 -2
- package/gsd-core/workflows/pause-work.md +1 -1
- 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 +1 -1
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -4
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +3 -3
- package/gsd-core/workflows/plan-phase.md +121 -42
- package/gsd-core/workflows/plan-review-convergence.md +46 -9
- package/gsd-core/workflows/plant-seed.md +2 -2
- package/gsd-core/workflows/pr-branch.md +187 -51
- package/gsd-core/workflows/profile-user.md +16 -14
- package/gsd-core/workflows/progress.md +27 -12
- package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +1 -3
- package/gsd-core/workflows/quick/steps/quick-verification.md +2 -4
- package/gsd-core/workflows/quick/steps/research-phase.md +2 -4
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
- package/gsd-core/workflows/quick.md +20 -29
- package/gsd-core/workflows/remove-phase.md +4 -4
- package/gsd-core/workflows/remove-workspace.md +2 -2
- package/gsd-core/workflows/resume-project.md +8 -12
- package/gsd-core/workflows/review.md +193 -15
- package/gsd-core/workflows/scan.md +1 -1
- package/gsd-core/workflows/secure-phase.md +2 -2
- package/gsd-core/workflows/settings-advanced.md +7 -9
- package/gsd-core/workflows/settings-integrations.md +64 -31
- package/gsd-core/workflows/settings.md +3 -5
- package/gsd-core/workflows/ship.md +12 -6
- package/gsd-core/workflows/sketch-wrap-up.md +11 -17
- package/gsd-core/workflows/sketch.md +12 -18
- package/gsd-core/workflows/smart-entry.md +3 -5
- package/gsd-core/workflows/spec-phase.md +23 -1
- package/gsd-core/workflows/spike-wrap-up.md +7 -11
- package/gsd-core/workflows/spike.md +20 -31
- package/gsd-core/workflows/stats.md +2 -2
- package/gsd-core/workflows/sync-skills.md +1 -1
- package/gsd-core/workflows/thread.md +11 -7
- package/gsd-core/workflows/transition.md +5 -5
- package/gsd-core/workflows/ui-phase.md +10 -16
- package/gsd-core/workflows/ui-review.md +6 -10
- package/gsd-core/workflows/ultraplan-phase.md +5 -13
- package/gsd-core/workflows/undo.md +8 -16
- package/gsd-core/workflows/update.md +6 -10
- package/gsd-core/workflows/validate-phase.md +2 -2
- 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 +57 -18
- package/hooks/dist/gsd-agent-isolation-guard.js +77 -38
- package/hooks/dist/gsd-config-reload.js +18 -12
- package/hooks/dist/gsd-context-monitor.js +19 -10
- 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 +76 -0
- package/hooks/dist/gsd-phase-boundary.sh +1 -0
- package/hooks/dist/gsd-prompt-guard.js +16 -7
- package/hooks/dist/gsd-read-guard.js +16 -7
- package/hooks/dist/gsd-read-injection-scanner.js +17 -8
- package/hooks/dist/gsd-session-state.sh +1 -0
- package/hooks/dist/gsd-statusline.js +215 -26
- package/hooks/dist/gsd-validate-commit.sh +80 -6
- 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 +34 -16
- 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-probe.js +84 -0
- package/hooks/dist/lib/hook-exit.js +81 -0
- package/hooks/dist/managed-hooks-registry.cjs +3 -0
- package/hooks/gsd-agent-isolation-guard.js +77 -38
- package/hooks/gsd-config-reload.js +18 -12
- package/hooks/gsd-context-monitor.js +19 -10
- 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 +76 -0
- package/hooks/gsd-phase-boundary.sh +1 -0
- package/hooks/gsd-prompt-guard.js +16 -7
- package/hooks/gsd-read-guard.js +16 -7
- package/hooks/gsd-read-injection-scanner.js +17 -8
- package/hooks/gsd-session-state.sh +1 -0
- package/hooks/gsd-statusline.js +215 -26
- package/hooks/gsd-validate-commit.sh +80 -6
- package/hooks/gsd-windsurf-pre-command.js +16 -11
- package/hooks/gsd-windsurf-pre-write.js +22 -13
- package/hooks/gsd-workflow-guard.js +34 -16
- package/hooks/gsd-worktree-path-guard.js +36 -21
- package/hooks/gsd-write-guard.js +35 -25
- package/hooks/lib/cli-exit.js +560 -0
- package/hooks/lib/exit-code-registry.js +98 -0
- package/hooks/lib/git-probe.js +84 -0
- package/hooks/lib/hook-exit.js +81 -0
- package/hooks/managed-hooks-registry.cjs +3 -0
- package/package.json +12 -7
- package/scripts/base64-scan.sh +74 -12
- package/scripts/build-hooks.js +5 -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 +45 -12
- package/scripts/ci-timeout-report.cjs +230 -0
- package/scripts/docs-guard-registry.cjs +396 -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 +134 -1
- 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/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 +193 -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 +21 -8
- package/scripts/lint-planning-prompt-drift.cjs +38 -1
- package/scripts/lint-removed-but-needed.cjs +184 -16
- 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 +122 -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/mutation-matrix.cjs +599 -50
- package/scripts/prompt-injection-scan.sh +75 -14
- 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-import/SKILL.md +1 -1
- package/skills/gsd-quick/SKILL.md +8 -4
- package/vscode/package.json +1 -1
- package/bin/lib/ui-safety-gate.cjs +0 -109
- package/scripts/lint-emitted-drift-ack.cjs +0 -344
- package/scripts/state-write-path-drift-baseline.json +0 -19
|
@@ -29,7 +29,7 @@ const cliExitMod = require("./cli-exit.cjs");
|
|
|
29
29
|
const { ExitError } = cliExitMod;
|
|
30
30
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
31
31
|
const roadmapParserMod = require("./roadmap-parser.cjs");
|
|
32
|
-
const { extractCurrentMilestoneScoped } = roadmapParserMod;
|
|
32
|
+
const { extractCurrentMilestoneScoped, hasPhaseEntries } = roadmapParserMod;
|
|
33
33
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
34
34
|
const planningScopeMod = require("./planning-scope.cjs");
|
|
35
35
|
const { SCOPE } = planningScopeMod;
|
|
@@ -151,11 +151,45 @@ function routeRoadmapCommand({ roadmap, args, cwd, raw, error }) {
|
|
|
151
151
|
warnings.push({ code: 'V003', message: 'ROADMAP.md frontmatter is malformed (unterminated --- fence)' });
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
|
|
154
|
+
// #3641: resolve phase_id_convention ONCE, ahead of every consumer in
|
|
155
|
+
// this validate pass — V004's entry check and V005's scope classifier
|
|
156
|
+
// below, and the W021 milestone-prefix check after them.
|
|
157
|
+
// Authoritative source: .planning/config.json (set by the upgrade
|
|
158
|
+
// command). Fallback: ROADMAP.md frontmatter (for projects that set
|
|
159
|
+
// the field there directly).
|
|
160
|
+
let convention;
|
|
161
|
+
try {
|
|
162
|
+
const cfg = loadConfig(cwd);
|
|
163
|
+
convention = cfg['phase_id_convention'];
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
convention = undefined;
|
|
167
|
+
}
|
|
168
|
+
if (convention === undefined || convention === null) {
|
|
169
|
+
// Fallback: read from ROADMAP.md frontmatter. Bounded to match
|
|
170
|
+
// cmdRoadmapMilestoneScope's copy exactly (#3641 review NEW-1: an
|
|
171
|
+
// unbounded capture here read past 4KB frontmatters the probe's
|
|
172
|
+
// bounded copy could not, diverging validate from the probe).
|
|
173
|
+
const fmMatch = roadmapContent.match(/^---\r?\n([\s\S]{0,4000}?)\r?\n---/);
|
|
174
|
+
if (fmMatch) {
|
|
175
|
+
const kvMatch = fmMatch[1].match(/^phase_id_convention:\s*(.*)$/m);
|
|
176
|
+
if (kvMatch) {
|
|
177
|
+
const val = kvMatch[1].trim();
|
|
178
|
+
if (val !== 'null' && val !== '') {
|
|
179
|
+
convention = val.replace(/^["']|["']$/g, '');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// No recognizable phase structure. #3641: routed through the
|
|
185
|
+
// roadmap-parser owner (`hasPhaseEntries` — headings, #2199 bullets,
|
|
186
|
+
// #3577 table rows) instead of a private inline heading regex, so the
|
|
187
|
+
// document-level check and the V005 scope axis below can never
|
|
188
|
+
// disagree about what a phase entry is — and so a bracket-convention
|
|
189
|
+
// project's `### [GSD.04] 01:` entries are entries here too.
|
|
190
|
+
const hasPhaseEntry = hasPhaseEntries(roadmapContent, convention);
|
|
157
191
|
if (!hasPhaseEntry && !warnings.some((w) => w.code === 'V002')) {
|
|
158
|
-
warnings.push({ code: 'V004', message: 'ROADMAP.md contains no recognizable phase entries (no
|
|
192
|
+
warnings.push({ code: 'V004', message: 'ROADMAP.md contains no recognizable phase entries (no phase headings, bullet entries, or table rows)' });
|
|
159
193
|
}
|
|
160
194
|
// #3263: a whole-document phase check (V004 above) is satisfied by a
|
|
161
195
|
// document whose phase entries live OUTSIDE the active milestone's
|
|
@@ -169,12 +203,14 @@ function routeRoadmapCommand({ roadmap, args, cwd, raw, error }) {
|
|
|
169
203
|
// an unscoped/unreadable window is a different failure mode with a
|
|
170
204
|
// different remediation, deliberately not warned here.
|
|
171
205
|
try {
|
|
172
|
-
|
|
206
|
+
// #3641: thread the resolved convention so the scope axis's
|
|
207
|
+
// hasPhaseEntries comparison recognizes bracket phase entries.
|
|
208
|
+
const scoped = extractCurrentMilestoneScoped(contentAfterBom, cwd, undefined, convention);
|
|
173
209
|
if (scoped.scope === SCOPE.TRUNCATED) {
|
|
174
210
|
warnings.push({
|
|
175
211
|
code: 'V005',
|
|
176
212
|
message: 'Active milestone window is truncated: phase entries exist in ROADMAP.md but are excluded from the ' +
|
|
177
|
-
'active milestone\'s resolved window (check for a heading between the milestone heading and its
|
|
213
|
+
'active milestone\'s resolved window (check for a heading between the milestone heading and its phase-entry sections)',
|
|
178
214
|
});
|
|
179
215
|
}
|
|
180
216
|
}
|
|
@@ -182,30 +218,8 @@ function routeRoadmapCommand({ roadmap, args, cwd, raw, error }) {
|
|
|
182
218
|
// The classifier is best-effort here — a throw must not mask the
|
|
183
219
|
// structural warnings already collected above.
|
|
184
220
|
}
|
|
185
|
-
// W021 only fires when phase_id_convention is explicitly
|
|
186
|
-
//
|
|
187
|
-
// Fallback: ROADMAP.md frontmatter (for projects that set the field there directly).
|
|
188
|
-
let convention;
|
|
189
|
-
try {
|
|
190
|
-
const cfg = loadConfig(cwd);
|
|
191
|
-
convention = cfg['phase_id_convention'];
|
|
192
|
-
}
|
|
193
|
-
catch {
|
|
194
|
-
convention = undefined;
|
|
195
|
-
}
|
|
196
|
-
if (convention === undefined || convention === null) {
|
|
197
|
-
// Fallback: read from ROADMAP.md frontmatter
|
|
198
|
-
const fmMatch = roadmapContent.match(/^---\r?\n([\s\S]+?)\r?\n---/);
|
|
199
|
-
if (fmMatch) {
|
|
200
|
-
const kvMatch = fmMatch[1].match(/^phase_id_convention:\s*(.*)$/m);
|
|
201
|
-
if (kvMatch) {
|
|
202
|
-
const val = kvMatch[1].trim();
|
|
203
|
-
if (val !== 'null' && val !== '') {
|
|
204
|
-
convention = val.replace(/^["']|["']$/g, '');
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
221
|
+
// W021 only fires when phase_id_convention is explicitly
|
|
222
|
+
// 'milestone-prefixed' — the same hoisted resolution above (#3641).
|
|
209
223
|
if (convention === 'milestone-prefixed') {
|
|
210
224
|
warnings.push(...checkW021(roadmapContent));
|
|
211
225
|
}
|
|
@@ -29,7 +29,10 @@ const phaseIdModule = require("./phase-id.cjs");
|
|
|
29
29
|
const { phaseMarkdownRegexSource, stripProjectCodePrefix, OPTIONAL_PHASE_TAG_SOURCE,
|
|
30
30
|
// #2121: roadmapPhaseLookupSources now lives in phase-id.cjs (single owner of
|
|
31
31
|
// the lookup-source ordering); imported here rather than defined locally.
|
|
32
|
-
roadmapPhaseLookupSources, extractPhaseToken, isSentinelPhaseId,
|
|
32
|
+
roadmapPhaseLookupSources, extractPhaseToken, isSentinelPhaseId,
|
|
33
|
+
// #3641: the single-owner heading-intro and digit-token grammar sources —
|
|
34
|
+
// see BRACKET_PHASE_ENTRY_HEADING_RE below.
|
|
35
|
+
PHASE_HEADING_PREFIX_SRC, PHASE_NUMBER_TOKEN_SOURCE, } = phaseIdModule;
|
|
33
36
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
34
37
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
35
38
|
const { planningDir } = planningWorkspace;
|
|
@@ -340,7 +343,7 @@ const MILESTONE_HEADING_SIGNAL_PATTERN = /v\d+\.\d+|✅|📋|🚧|\bMilestone\b/
|
|
|
340
343
|
* template or the #3204/#1761/#3185 reports exercises that shape; it is
|
|
341
344
|
* recorded here rather than hidden.
|
|
342
345
|
*/
|
|
343
|
-
function
|
|
346
|
+
function countMilestoneHeadings(content) {
|
|
344
347
|
const isPhaseHeading = (text) => /^Phase\s+\S/i.test(text);
|
|
345
348
|
let milestoneHeadingCount = 0;
|
|
346
349
|
for (const heading of (0, markdown_sectionizer_cjs_1.tokenizeHeadings)(content)) {
|
|
@@ -351,10 +354,29 @@ function hasMilestoneSectioning(content) {
|
|
|
351
354
|
if (!MILESTONE_HEADING_SIGNAL_PATTERN.test(heading.text))
|
|
352
355
|
continue;
|
|
353
356
|
milestoneHeadingCount++;
|
|
354
|
-
if (milestoneHeadingCount >= 2)
|
|
355
|
-
return true;
|
|
356
357
|
}
|
|
357
|
-
return
|
|
358
|
+
return milestoneHeadingCount;
|
|
359
|
+
}
|
|
360
|
+
function hasMilestoneSectioning(content) {
|
|
361
|
+
// The >=2 short-circuit the inline walk used to have is gone — a ROADMAP's
|
|
362
|
+
// heading count is small and tokenizeHeadings materializes the full token
|
|
363
|
+
// array regardless, so the shared walk pays nothing for it.
|
|
364
|
+
return countMilestoneHeadings(content) >= 2;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* #3642: the >=1 sibling of `hasMilestoneSectioning`. The >=2 predicate
|
|
368
|
+
* answers SIBLING-conflation ("could two sections' phases mix") and is
|
|
369
|
+
* unchanged; but `buildStateFrontmatter`'s unbounded branch asks a question
|
|
370
|
+
* >=2 under-answers: "is there ANY milestone section whose phases a
|
|
371
|
+
* whole-document count would attribute to a milestone that matches no
|
|
372
|
+
* heading?" With exactly ONE section and an asserted milestone absent from
|
|
373
|
+
* the ROADMAP, >=2 said "flat" and the single section's phases leaked into
|
|
374
|
+
* the asserted milestone's total_phases (silent clobber of the stored
|
|
375
|
+
* value). Same walk, same vocabulary, threshold 1 — exported for that
|
|
376
|
+
* consumer only; every other consumer keeps the >=2 semantics.
|
|
377
|
+
*/
|
|
378
|
+
function hasAnyMilestoneSection(content) {
|
|
379
|
+
return countMilestoneHeadings(content) >= 1;
|
|
358
380
|
}
|
|
359
381
|
/**
|
|
360
382
|
* #3184: the sole "which heading is this milestone's" rule — locate the version's
|
|
@@ -405,14 +427,39 @@ function sliceMilestoneWindow(content, version) {
|
|
|
405
427
|
* exist" — a window containing only sentinel phases still reached the
|
|
406
428
|
* region and must read COMPLETE, not TRUNCATED.
|
|
407
429
|
*/
|
|
408
|
-
|
|
430
|
+
// #3641: the bracket-convention phase-ENTRY heading shape — ADR-612 Decision
|
|
431
|
+
// 1's own discriminator: a phase heading is a bracket followed by a
|
|
432
|
+
// DIGIT-then-colon (`### [GSD.04] 01: Name`); a bracket followed by a NAME
|
|
433
|
+
// is a milestone heading and must never count. Every fragment interpolates a
|
|
434
|
+
// single-owner export from phase-id.cts — the heading intro
|
|
435
|
+
// (PHASE_HEADING_PREFIX_SRC: a `[...]` bracket optionally followed by a
|
|
436
|
+
// `Phase ` label, or a bare `Phase ` label), the digit-bearing token
|
|
437
|
+
// (PHASE_NUMBER_TOKEN_SOURCE, which also covers the dotted sub-phase form
|
|
438
|
+
// `[GSD.02] 05.03:`), and the optional pre-colon tag
|
|
439
|
+
// (OPTIONAL_PHASE_TAG_SOURCE) — never a re-typed grammar. Tested IN
|
|
440
|
+
// ADDITION to the legacy pattern below, so bracket mode is a strict
|
|
441
|
+
// superset: mid-migration legacy-labeled headings (`Phase AUTH-101:`-style
|
|
442
|
+
// custom ids included) keep their existing recognition. Review finding: an
|
|
443
|
+
// earlier single-alternative form with a `[\\w]` token admitted
|
|
444
|
+
// `[bracket] Word:` shapes — a colon-bearing MILESTONE heading inside the
|
|
445
|
+
// window read as an entry (defeating V005 outright for that spelling) and a
|
|
446
|
+
// decoy `### [GSD.04] Notes:` outside the window manufactured a false V005
|
|
447
|
+
// while suppressing the correct V004. The digit anchor forecloses both.
|
|
448
|
+
const BRACKET_PHASE_ENTRY_HEADING_RE = new RegExp(`^${PHASE_HEADING_PREFIX_SRC}${PHASE_NUMBER_TOKEN_SOURCE}${OPTIONAL_PHASE_TAG_SOURCE}\\s*:`, 'i');
|
|
449
|
+
function hasPhaseEntries(markdown, phaseIdConvention) {
|
|
409
450
|
// #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag (literal mirror of OPTIONAL_PHASE_TAG_SOURCE).
|
|
451
|
+
// #3641: the widened grammar engages ONLY when the resolved convention is
|
|
452
|
+
// 'bracket' — a project that has not opted in runs the legacy pattern
|
|
453
|
+
// alone, byte-identically.
|
|
410
454
|
const phaseHeadingPattern = /^(?:\[[^\]]{1,200}\]\s*)?Phase\s+([\w][\w.-]*)(?:\s*\([^)\n]{0,200}\))?\s*:/i;
|
|
455
|
+
const bracketMode = phaseIdConvention === 'bracket';
|
|
411
456
|
for (const h of (0, markdown_sectionizer_cjs_1.tokenizeHeadings)(markdown)) {
|
|
412
457
|
if (h.level < 2 || h.level > 4)
|
|
413
458
|
continue;
|
|
414
459
|
if (phaseHeadingPattern.test(h.text))
|
|
415
460
|
return true;
|
|
461
|
+
if (bracketMode && BRACKET_PHASE_ENTRY_HEADING_RE.test(h.text))
|
|
462
|
+
return true;
|
|
416
463
|
}
|
|
417
464
|
// #3184 review finding: the bullet fallback must be fence-aware too, or a
|
|
418
465
|
// FENCED markdown EXAMPLE of the `- [ ] **Phase N — Name**` syntax (e.g. a
|
|
@@ -592,8 +639,18 @@ function classifyMilestoneWindow(input) {
|
|
|
592
639
|
* symbols, 20 direct callers) means its signature and return type do not
|
|
593
640
|
* change. This is the real owner; `extractCurrentMilestone` becomes a
|
|
594
641
|
* one-line wrapper returning `.value` so every existing caller is untouched.
|
|
642
|
+
*
|
|
643
|
+
* @param phaseIdConvention - #3641: the RESOLVED `phase_id_convention`
|
|
644
|
+
* config value, threaded to `hasPhaseEntries` so the scope axis's row-8
|
|
645
|
+
* comparison recognizes bracket-convention phase entries
|
|
646
|
+
* (`### [GSD.04] 01: Name`). Optional: absent, or any value other than
|
|
647
|
+
* `'bracket'`, compiles the legacy entry grammar byte-identically — the
|
|
648
|
+
* widening engages only for a project that resolved the convention
|
|
649
|
+
* explicitly. `extractCurrentMilestone`'s wrapper deliberately does NOT
|
|
650
|
+
* expose it (its 20 callers are not the scope-axis consumers; V005's
|
|
651
|
+
* router site and `getMilestonePhaseFilter` resolve and thread it).
|
|
595
652
|
*/
|
|
596
|
-
function extractCurrentMilestoneScoped(content, cwd, ws) {
|
|
653
|
+
function extractCurrentMilestoneScoped(content, cwd, ws, phaseIdConvention) {
|
|
597
654
|
if (!cwd) {
|
|
598
655
|
// Row 1: a deliberate unscoped read (no cwd supplied) is a real answer —
|
|
599
656
|
// the caller asked for no scoping, so whole-document is COMPLETE.
|
|
@@ -631,12 +688,12 @@ function extractCurrentMilestoneScoped(content, cwd, ws) {
|
|
|
631
688
|
versionResolved,
|
|
632
689
|
hasVersionedMilestones: versionedMilestonesPresent,
|
|
633
690
|
headingFound: false,
|
|
634
|
-
windowHasPhaseEntries: hasPhaseEntries(value),
|
|
635
|
-
documentHasPhaseEntries: hasPhaseEntries(value),
|
|
691
|
+
windowHasPhaseEntries: hasPhaseEntries(value, phaseIdConvention),
|
|
692
|
+
documentHasPhaseEntries: hasPhaseEntries(value, phaseIdConvention),
|
|
636
693
|
}),
|
|
637
694
|
};
|
|
638
695
|
}
|
|
639
|
-
const documentHasPhaseEntries = hasPhaseEntries(stripShippedMilestones(content));
|
|
696
|
+
const documentHasPhaseEntries = hasPhaseEntries(stripShippedMilestones(content), phaseIdConvention);
|
|
640
697
|
const summaryPattern = new RegExp(`<summary[^>]*>([^<]*${(0, pattern_cjs_1.escapeRegex)(version)}[^<]*)<\\/summary>`, 'i');
|
|
641
698
|
const headingMatches = locateMilestoneHeadings(content, version);
|
|
642
699
|
if (headingMatches.length === 0) {
|
|
@@ -666,7 +723,7 @@ function extractCurrentMilestoneScoped(content, cwd, ws) {
|
|
|
666
723
|
versionResolved,
|
|
667
724
|
hasVersionedMilestones: versionedMilestonesPresent,
|
|
668
725
|
headingFound: true,
|
|
669
|
-
windowHasPhaseEntries: hasPhaseEntries(value),
|
|
726
|
+
windowHasPhaseEntries: hasPhaseEntries(value, phaseIdConvention),
|
|
670
727
|
documentHasPhaseEntries,
|
|
671
728
|
}),
|
|
672
729
|
};
|
|
@@ -680,7 +737,7 @@ function extractCurrentMilestoneScoped(content, cwd, ws) {
|
|
|
680
737
|
versionResolved,
|
|
681
738
|
hasVersionedMilestones: versionedMilestonesPresent,
|
|
682
739
|
headingFound: false,
|
|
683
|
-
windowHasPhaseEntries: hasPhaseEntries(value),
|
|
740
|
+
windowHasPhaseEntries: hasPhaseEntries(value, phaseIdConvention),
|
|
684
741
|
documentHasPhaseEntries,
|
|
685
742
|
}),
|
|
686
743
|
};
|
|
@@ -756,7 +813,7 @@ function extractCurrentMilestoneScoped(content, cwd, ws) {
|
|
|
756
813
|
versionResolved,
|
|
757
814
|
hasVersionedMilestones: versionedMilestonesPresent,
|
|
758
815
|
headingFound: true,
|
|
759
|
-
windowHasPhaseEntries: hasPhaseEntries(value),
|
|
816
|
+
windowHasPhaseEntries: hasPhaseEntries(value, phaseIdConvention),
|
|
760
817
|
documentHasPhaseEntries,
|
|
761
818
|
}),
|
|
762
819
|
};
|
|
@@ -1257,7 +1314,7 @@ function getMilestonePhaseFilter(cwd, versionOverride, phaseIdConvention, ws) {
|
|
|
1257
1314
|
const roadmapContent = (0, shell_command_projection_cjs_1.platformReadSync)(roadmapPath);
|
|
1258
1315
|
if (roadmapContent === null)
|
|
1259
1316
|
throw new Error('missing');
|
|
1260
|
-
const scopedResult = extractCurrentMilestoneScoped(roadmapContent, cwd, ws);
|
|
1317
|
+
const scopedResult = extractCurrentMilestoneScoped(roadmapContent, cwd, ws, phaseIdConvention);
|
|
1261
1318
|
let roadmap = scopedResult.value;
|
|
1262
1319
|
// Default: the filter's window IS extractCurrentMilestoneScoped's own
|
|
1263
1320
|
// window (reused verbatim, not re-derived — ADR-3180 Decision 4c).
|
|
@@ -1289,7 +1346,7 @@ function getMilestonePhaseFilter(cwd, versionOverride, phaseIdConvention, ws) {
|
|
|
1289
1346
|
// via `currentVersion`), and `buildStateFrontmatter` (state.cts:1700,
|
|
1290
1347
|
// via `storedMilestone`).
|
|
1291
1348
|
const sliced = sliceMilestoneWindow(roadmapContent, versionOverride);
|
|
1292
|
-
const documentHasPhaseEntries = hasPhaseEntries(stripShippedMilestones(roadmapContent));
|
|
1349
|
+
const documentHasPhaseEntries = hasPhaseEntries(stripShippedMilestones(roadmapContent), phaseIdConvention);
|
|
1293
1350
|
if (sliced !== null) {
|
|
1294
1351
|
versionScoped = true;
|
|
1295
1352
|
versionSectionFound = true;
|
|
@@ -1312,7 +1369,7 @@ function getMilestonePhaseFilter(cwd, versionOverride, phaseIdConvention, ws) {
|
|
|
1312
1369
|
versionResolved: true,
|
|
1313
1370
|
hasVersionedMilestones: hasVersionedMilestonesGlobal,
|
|
1314
1371
|
headingFound: sliced !== null,
|
|
1315
|
-
windowHasPhaseEntries: hasPhaseEntries(roadmap),
|
|
1372
|
+
windowHasPhaseEntries: hasPhaseEntries(roadmap, phaseIdConvention),
|
|
1316
1373
|
documentHasPhaseEntries,
|
|
1317
1374
|
});
|
|
1318
1375
|
}
|
|
@@ -1508,6 +1565,8 @@ module.exports = {
|
|
|
1508
1565
|
sliceMilestoneWindow,
|
|
1509
1566
|
hasVersionedMilestones,
|
|
1510
1567
|
hasMilestoneSectioning,
|
|
1568
|
+
// #3642: the >=1 sibling buildStateFrontmatter's unbounded branch consumes.
|
|
1569
|
+
hasAnyMilestoneSection,
|
|
1511
1570
|
// #1956: sole owner of the #2012 decoy-avoidance scope for the
|
|
1512
1571
|
// `drift-guard phase-status` CLI seam.
|
|
1513
1572
|
findRoadmapProgressTable,
|
|
@@ -1518,4 +1577,8 @@ module.exports = {
|
|
|
1518
1577
|
scanMilestonePhaseIds,
|
|
1519
1578
|
collectTablePhaseRows,
|
|
1520
1579
|
findMilestoneScopeHeadingLines,
|
|
1580
|
+
// #3641: the scope axis's phase-ENTRY predicate, exported so roadmap
|
|
1581
|
+
// validate's V004 document-level check routes through the same single
|
|
1582
|
+
// owner (and its convention gate) instead of a private inline copy.
|
|
1583
|
+
hasPhaseEntries,
|
|
1521
1584
|
};
|
|
@@ -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 } = ioMod;
|
|
20
20
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
21
21
|
const phaseIdMod = require("./phase-id.cjs");
|
|
22
22
|
const { normalizePhaseName, phaseMarkdownRegexSource, matchPhaseDirs, stripProjectCodePrefix, OPTIONAL_PHASE_TAG_SOURCE, roadmapPhaseLookupSources, isSentinelPhaseId, scopeToPhase } = 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;
|
|
@@ -36,6 +36,11 @@ 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
38
|
const { planningPaths, withPlanningLock, findContextMdIn } = 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
|
|
@@ -89,10 +94,21 @@ function countPhasePlansAndSummaries(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
96
|
let phaseFiles = [];
|
|
97
|
+
// #3885 (ADR-3473 §8.5): distinguish "genuinely absent" (ENOENT) from
|
|
98
|
+
// "could not read" (EACCES/EIO/...) — the collapse of both to an empty
|
|
99
|
+
// listing is exactly the defect class this item closes. Mirrors
|
|
100
|
+
// core-utils.cts's getPhaseFileStats / phase-locator.cts's
|
|
101
|
+
// listMilestonePhaseDirs SCOPE.UNREADABLE discriminator.
|
|
102
|
+
let contextReadError = null;
|
|
92
103
|
try {
|
|
93
104
|
phaseFiles = node_fs_1.default.readdirSync(phaseDir);
|
|
94
105
|
}
|
|
95
|
-
catch {
|
|
106
|
+
catch (err) {
|
|
107
|
+
const code = err?.code;
|
|
108
|
+
if (code !== 'ENOENT') {
|
|
109
|
+
contextReadError = `Could not read phase directory ${formatDiagnosticToken(phaseDir)}: ${formatDiagnosticToken(err?.message ?? String(err))}`;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
96
112
|
// #3511: scope the raw listing to this phase dir before the
|
|
97
113
|
// phase-numbered-artifact predicates (hasContext/hasResearch) — planCount/
|
|
98
114
|
// summaryCount above stay on scanPhasePlans's own unscoped listing since a
|
|
@@ -104,6 +120,7 @@ function countPhasePlansAndSummaries(phaseDir) {
|
|
|
104
120
|
summaryCount,
|
|
105
121
|
hasContext: findContextMdIn(scopedFiles) !== null,
|
|
106
122
|
hasResearch: scopedFiles.some(f => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md'),
|
|
123
|
+
contextReadError,
|
|
107
124
|
};
|
|
108
125
|
}
|
|
109
126
|
// `phaseMarkdownRegexSource` lives in phase-id.cjs (#3537) and is imported above.
|
|
@@ -332,6 +349,10 @@ function collectAnalyzePhases(content, phasesDir, phaseDirNames) {
|
|
|
332
349
|
let summaryCount = 0;
|
|
333
350
|
let hasContext = false;
|
|
334
351
|
let hasResearch = false;
|
|
352
|
+
// #3885 (ADR-3473 §8.5): null unless dirMatch resolves and its readdirSync
|
|
353
|
+
// hit a non-ENOENT error — no directory at all is `disk_status:
|
|
354
|
+
// 'no_directory'`, a real (if uninteresting) answer, not a read error.
|
|
355
|
+
let contextReadError = null;
|
|
335
356
|
// DEAD catch removed (#2245 audit): matchPhaseDirs(...) is a pure
|
|
336
357
|
// array lookup on an already-resolved string array, and
|
|
337
358
|
// countPhasePlansAndSummaries is itself fully defensive (its own
|
|
@@ -345,6 +366,7 @@ function collectAnalyzePhases(content, phasesDir, phaseDirNames) {
|
|
|
345
366
|
summaryCount = counts.summaryCount;
|
|
346
367
|
hasContext = counts.hasContext;
|
|
347
368
|
hasResearch = counts.hasResearch;
|
|
369
|
+
contextReadError = counts.contextReadError;
|
|
348
370
|
// ADR-3180 §7.4 (issue #3186, disk-strict, #3168 fix): route "is this
|
|
349
371
|
// phase complete" through the canonical owner (`isPhaseComplete`),
|
|
350
372
|
// which calls readVerificationStatus UNCONDITIONALLY — plan count is
|
|
@@ -394,6 +416,7 @@ function collectAnalyzePhases(content, phasesDir, phaseDirNames) {
|
|
|
394
416
|
has_research: hasResearch,
|
|
395
417
|
disk_status: diskStatus,
|
|
396
418
|
roadmap_complete: roadmapComplete,
|
|
419
|
+
context_read_error: contextReadError,
|
|
397
420
|
});
|
|
398
421
|
}
|
|
399
422
|
// #3577: markdown-table row declarations join the enumeration — same
|
|
@@ -410,12 +433,18 @@ function collectAnalyzePhases(content, phasesDir, phaseDirNames) {
|
|
|
410
433
|
let tSummaryCount = 0;
|
|
411
434
|
let tHasContext = false;
|
|
412
435
|
let tHasResearch = false;
|
|
436
|
+
let tContextReadError = null;
|
|
413
437
|
if (dirMatchA) {
|
|
414
438
|
const counts = countPhasePlansAndSummaries(node_path_1.default.join(phasesDir, dirMatchA));
|
|
415
439
|
tPlanCount = counts.planCount;
|
|
416
440
|
tSummaryCount = counts.summaryCount;
|
|
417
441
|
tHasContext = node_fs_1.default.existsSync(node_path_1.default.join(phasesDir, dirMatchA, 'CONTEXT.md'));
|
|
418
442
|
tHasResearch = node_fs_1.default.existsSync(node_path_1.default.join(phasesDir, dirMatchA, 'RESEARCH.md'));
|
|
443
|
+
// #3885 (ADR-3473 §8.5): reuse the SAME countPhasePlansAndSummaries call's
|
|
444
|
+
// discriminator — this row's hasContext/hasResearch are read via a direct
|
|
445
|
+
// existsSync (which cannot itself distinguish EACCES from absent), but
|
|
446
|
+
// an unreadable phase directory is still surfaced via the sibling call.
|
|
447
|
+
tContextReadError = counts.contextReadError;
|
|
419
448
|
}
|
|
420
449
|
phases.push({
|
|
421
450
|
number: tr.id,
|
|
@@ -429,6 +458,7 @@ function collectAnalyzePhases(content, phasesDir, phaseDirNames) {
|
|
|
429
458
|
has_research: tHasResearch,
|
|
430
459
|
disk_status: dirMatchA ? 'ok' : 'no_directory',
|
|
431
460
|
roadmap_complete: false,
|
|
461
|
+
context_read_error: tContextReadError,
|
|
432
462
|
});
|
|
433
463
|
}
|
|
434
464
|
return phases;
|
|
@@ -446,21 +476,17 @@ function cmdRoadmapAnalyze(cwd, raw) {
|
|
|
446
476
|
const { value: content, scope } = extractCurrentMilestoneScoped(rawContent, cwd);
|
|
447
477
|
const phasesDir = planningPaths(cwd).phases;
|
|
448
478
|
// 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
|
-
})();
|
|
479
|
+
// #3185 exemption reason (ADR-3180 Decision 4a): this is a heading->directory
|
|
480
|
+
// LOOKUP INDEX, not a milestone enumeration. It must see the PHYSICAL set so
|
|
481
|
+
// a heading already scoped by extractCurrentMilestoneScoped above can find
|
|
482
|
+
// its directory; filtering it through listMilestonePhaseDirs would scope
|
|
483
|
+
// the same set twice. #3882 (ADR-3473 §8.2): routed through the named
|
|
484
|
+
// "physical set, sentinels included" axis instead of a hand-rolled
|
|
485
|
+
// readdirSync — every heading matched below already excludes sentinel
|
|
486
|
+
// phase numbers via isSentinelPhaseId before it ever consults this list
|
|
487
|
+
// (collectAnalyzePhases), so a sentinel directory's presence here is
|
|
488
|
+
// output-invariant; this only removes the re-derivation, not the reason.
|
|
489
|
+
const _phaseDirNames = listAllPhaseDirs(phasesDir, { includeSentinels: true }).value;
|
|
464
490
|
// Scan the scoped milestone window for phase-detail headings and enrich each
|
|
465
491
|
// with its on-disk status. Extracted into `collectAnalyzePhases` (#3165) so
|
|
466
492
|
// the SAME enrichment re-runs on the fallback below — not a second copy.
|
|
@@ -615,7 +641,36 @@ function cmdRoadmapMilestoneScope(cwd, raw) {
|
|
|
615
641
|
return;
|
|
616
642
|
}
|
|
617
643
|
const rawContent = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
|
|
618
|
-
|
|
644
|
+
// #3641: resolve phase_id_convention and thread it into the scope axis, so
|
|
645
|
+
// this probe and `roadmap validate`'s V005 answer the SAME question the
|
|
646
|
+
// SAME way for a bracket-convention project — a window the classifier
|
|
647
|
+
// calls TRUNCATED in validate must never read COMPLETE here (the #3262
|
|
648
|
+
// capture/compare guard consumes this scope). Resolution mirrors the
|
|
649
|
+
// validate router's: .planning/config.json first, ROADMAP.md frontmatter
|
|
650
|
+
// as fallback.
|
|
651
|
+
let phaseIdConvention;
|
|
652
|
+
try {
|
|
653
|
+
const cfg = loadConfigForScope(cwd);
|
|
654
|
+
phaseIdConvention = cfg['phase_id_convention'];
|
|
655
|
+
}
|
|
656
|
+
catch {
|
|
657
|
+
phaseIdConvention = undefined;
|
|
658
|
+
}
|
|
659
|
+
if (phaseIdConvention === undefined || phaseIdConvention === null) {
|
|
660
|
+
// Bounded per local/no-unbounded-quantifier (#2128): frontmatter is a
|
|
661
|
+
// short header block — 4KB is orders of magnitude beyond any real one.
|
|
662
|
+
const fmMatch = rawContent.match(/^---\r?\n([\s\S]{0,4000}?)\r?\n---/);
|
|
663
|
+
if (fmMatch) {
|
|
664
|
+
const kvMatch = fmMatch[1].match(/^phase_id_convention:\s*(.*)$/m);
|
|
665
|
+
if (kvMatch) {
|
|
666
|
+
const val = kvMatch[1].trim();
|
|
667
|
+
if (val !== 'null' && val !== '') {
|
|
668
|
+
phaseIdConvention = val.replace(/^["']|["']$/g, '');
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
const { value: window, scope } = extractCurrentMilestoneScoped(rawContent, cwd, undefined, phaseIdConvention);
|
|
619
674
|
// Document order (Set insertion order) — deterministic for a given document.
|
|
620
675
|
const phases = [...scanMilestonePhaseIds(window)];
|
|
621
676
|
output({ scope, phases, phase_count: phases.length }, raw, undefined);
|