@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
|
@@ -23,7 +23,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
23
23
|
const node_path_1 = __importDefault(require("node:path"));
|
|
24
24
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
25
25
|
const io = require("./io.cjs");
|
|
26
|
-
const { output, error } = io;
|
|
26
|
+
const { output, error, formatDiagnosticToken } = io;
|
|
27
27
|
const pattern_cjs_1 = require("./pattern.cjs");
|
|
28
28
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
29
29
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
@@ -36,6 +36,9 @@ const { scanPhasePlans } = planScanMod;
|
|
|
36
36
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
37
37
|
const phaseIdMod = require("./phase-id.cjs");
|
|
38
38
|
const { scopeToPhase } = phaseIdMod;
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
40
|
+
const planningScopeMod = require("./planning-scope.cjs");
|
|
41
|
+
const { SCOPE } = planningScopeMod;
|
|
39
42
|
/**
|
|
40
43
|
* Parse REQ-IDs from REQUIREMENTS.md content.
|
|
41
44
|
*
|
|
@@ -54,6 +57,17 @@ function parseRequirements(reqMd) {
|
|
|
54
57
|
// The **ID** is extracted from the bullet text caller-side — the seam provides
|
|
55
58
|
// the raw text; we parse the bold-ID prefix from it here.
|
|
56
59
|
const boldIdRe = new RegExp(`^\\*\\*(${ID_PATTERN})\\*\\*\\s*(.*)$`);
|
|
60
|
+
// The shipped template (gsd-core/templates/requirements.md) writes
|
|
61
|
+
// `- [ ] **AUTH-01**: User can sign up` — a single separator delimiter
|
|
62
|
+
// between the bold ID and the description. Strip AT MOST ONE leading
|
|
63
|
+
// delimiter (plus its surrounding whitespace) before the final `.trim()`,
|
|
64
|
+
// mirroring roadmap-parser.cts's `stripLeadingDelimiter` delimiter set
|
|
65
|
+
// (em dash, en dash, colon, hyphen) — that helper is not exported, and its
|
|
66
|
+
// own `+`-quantified strip removes an entire delimiter RUN, which would
|
|
67
|
+
// also eat a second, meaningful marker (`**X-01**: -- weird` must keep the
|
|
68
|
+
// `--`), so the set is mirrored here with a single-occurrence match instead
|
|
69
|
+
// of reused verbatim.
|
|
70
|
+
const ONE_LEADING_DELIMITER_RE = /^\s*[—–:-]\s*/;
|
|
57
71
|
for (const bullet of (0, markdown_sectionizer_cjs_1.iterateBullets)(reqMd)) {
|
|
58
72
|
if (bullet.marker !== 'checkbox-unchecked' && bullet.marker !== 'checkbox-checked')
|
|
59
73
|
continue;
|
|
@@ -65,7 +79,8 @@ function parseRequirements(reqMd) {
|
|
|
65
79
|
continue;
|
|
66
80
|
if (!seen.has(id)) {
|
|
67
81
|
seen.add(id);
|
|
68
|
-
|
|
82
|
+
const rawText = m[2] || '';
|
|
83
|
+
out.push({ id, text: rawText.replace(ONE_LEADING_DELIMITER_RE, '').trim() });
|
|
69
84
|
}
|
|
70
85
|
}
|
|
71
86
|
// Pipe-table-row path and separator-row skip stay caller-side
|
|
@@ -290,6 +305,8 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
|
|
|
290
305
|
table: '',
|
|
291
306
|
summary: 'workflow.post_planning_gaps disabled — skipping post-planning gap analysis',
|
|
292
307
|
counts: { total: 0, covered: 0, uncovered: 0 },
|
|
308
|
+
phase_dir_read_error: null,
|
|
309
|
+
phase_dir_scope: SCOPE.COMPLETE,
|
|
293
310
|
};
|
|
294
311
|
}
|
|
295
312
|
const absPhaseDir = node_path_1.default.isAbsolute(phaseDir) ? phaseDir : node_path_1.default.join(cwd, phaseDir);
|
|
@@ -312,12 +329,20 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
|
|
|
312
329
|
}
|
|
313
330
|
// Read the phase directory once; reuse the listing for both context detection
|
|
314
331
|
// and plan-file enumeration (avoids redundant readdirSync calls).
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
332
|
+
//
|
|
333
|
+
// #4014 (epic #3473 B4): findContextMdIn's directory-string form now owns
|
|
334
|
+
// the listing + ENOENT-vs-other discrimination, retiring the local
|
|
335
|
+
// existsSync-guarded readdirSync try/catch — ENOENT (genuinely absent)
|
|
336
|
+
// and a successful-but-empty read both resolve to SCOPE.COMPLETE with an
|
|
337
|
+
// empty listing, exactly like the existsSync guard's short-circuit did.
|
|
338
|
+
const { files: phaseDirFiles, scope: phaseDirScope } = findContextMdIn(absPhaseDir);
|
|
339
|
+
// #3885 (ADR-3473 §8.5): `phaseDirReadError` stays additive for the
|
|
340
|
+
// shipped `phase_dir_read_error` JSON field — derived from `phaseDirScope`
|
|
341
|
+
// rather than from its own caught error, since findContextMdIn's
|
|
342
|
+
// directory-string form reports SCOPE, not the raw errno message.
|
|
343
|
+
const phaseDirReadError = phaseDirScope === SCOPE.UNREADABLE
|
|
344
|
+
? `Could not read phase directory ${formatDiagnosticToken(absPhaseDir)}`
|
|
345
|
+
: null;
|
|
321
346
|
// #3511-class: scope the raw listing to this phase dir before the
|
|
322
347
|
// phase-numbered -CONTEXT.md predicate. `phaseDirFiles` itself stays raw —
|
|
323
348
|
// it is also reused below only as a `.length > 0` guard ahead of
|
|
@@ -384,6 +409,8 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
|
|
|
384
409
|
table: formatGapTable(rows) + '\n' + coverageSummary + '\n\n' + mismatchMsg,
|
|
385
410
|
summary: coverageSummary + '; extracted 0 of N — possible format mismatch',
|
|
386
411
|
counts: { total: rows.length, covered, uncovered },
|
|
412
|
+
phase_dir_read_error: phaseDirReadError,
|
|
413
|
+
phase_dir_scope: phaseDirScope,
|
|
387
414
|
};
|
|
388
415
|
}
|
|
389
416
|
return {
|
|
@@ -392,6 +419,8 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
|
|
|
392
419
|
table: mismatchMsg,
|
|
393
420
|
summary: 'extracted 0 of N — possible format mismatch',
|
|
394
421
|
counts: { total: 0, covered: 0, uncovered: 0 },
|
|
422
|
+
phase_dir_read_error: phaseDirReadError,
|
|
423
|
+
phase_dir_scope: phaseDirScope,
|
|
395
424
|
};
|
|
396
425
|
}
|
|
397
426
|
// #1365: if no items at all, surface a clean no-check message.
|
|
@@ -408,6 +437,8 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
|
|
|
408
437
|
table: '## Post-Planning Gap Analysis\n\nNo requirements or decisions to check.\n',
|
|
409
438
|
summary: 'no requirements or decisions to check',
|
|
410
439
|
counts: { total: 0, covered: 0, uncovered: 0 },
|
|
440
|
+
phase_dir_read_error: phaseDirReadError,
|
|
441
|
+
phase_dir_scope: phaseDirScope,
|
|
411
442
|
};
|
|
412
443
|
}
|
|
413
444
|
const rows = sortRows([
|
|
@@ -425,6 +456,8 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
|
|
|
425
456
|
table: formatGapTable(rows) + '\n' + summary + '\n',
|
|
426
457
|
summary,
|
|
427
458
|
counts: { total: rows.length, covered, uncovered },
|
|
459
|
+
phase_dir_read_error: phaseDirReadError,
|
|
460
|
+
phase_dir_scope: phaseDirScope,
|
|
428
461
|
};
|
|
429
462
|
}
|
|
430
463
|
function cmdGapAnalysis(cwd, args, raw) {
|
|
@@ -34,57 +34,138 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
34
34
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
35
35
|
};
|
|
36
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
-
exports.
|
|
37
|
+
exports._readGitNested = _readGitNested;
|
|
38
|
+
exports._readGitKey = _readGitKey;
|
|
38
39
|
exports.trySymbolicRef = trySymbolicRef;
|
|
39
40
|
exports.tryRemoteShow = tryRemoteShow;
|
|
40
41
|
exports.tryLocalBranch = tryLocalBranch;
|
|
41
42
|
exports.resolveBaseBranchDiagnostics = resolveBaseBranchDiagnostics;
|
|
42
43
|
exports.resolveBaseBranch = resolveBaseBranch;
|
|
44
|
+
exports.resolveProtectedBranchStatus = resolveProtectedBranchStatus;
|
|
43
45
|
exports.gitWorktreeInfoInternal = gitWorktreeInfoInternal;
|
|
44
46
|
exports.phaseStartCommit = phaseStartCommit;
|
|
45
47
|
exports.changedFilesSince = changedFilesSince;
|
|
46
48
|
exports.cmdGitBaseBranch = cmdGitBaseBranch;
|
|
47
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
|
48
49
|
const node_path_1 = __importDefault(require("node:path"));
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
51
|
+
const configLoader = require("./config-loader.cjs");
|
|
52
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
53
|
+
const io = require("./io.cjs");
|
|
54
|
+
const configuration_cjs_1 = require("./configuration.cjs");
|
|
55
|
+
const security_cjs_1 = require("./security.cjs");
|
|
49
56
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
50
|
-
|
|
57
|
+
const { error, ERROR_REASON } = io;
|
|
58
|
+
const { loadConfig: loadConfigSeam } = configLoader;
|
|
59
|
+
/** Render a rejected config value for a diagnostic without throwing on exotic input. */
|
|
60
|
+
function renderRejected(value) {
|
|
61
|
+
try {
|
|
62
|
+
return (0, security_cjs_1.sanitizeLabel)(JSON.stringify(value) ?? String(value));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return (0, security_cjs_1.sanitizeLabel)(String(value));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** Nested-only read of `git.<field>`, mirroring `loadConfigResolved`'s `getNested`. */
|
|
69
|
+
function _readGitNested(config, field) {
|
|
70
|
+
const git = config['git'];
|
|
71
|
+
if (git !== null && typeof git === 'object' && !Array.isArray(git)) {
|
|
72
|
+
return git[field];
|
|
73
|
+
}
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Flat-then-nested read, mirroring `loadConfigResolved`'s own `get()`.
|
|
78
|
+
*
|
|
79
|
+
* Used for `base_branch` ONLY, and only because it HAS a legacy flat spelling:
|
|
80
|
+
* `normalizeLegacyKeys` normally hoists it away, so a flat key that SURVIVED
|
|
81
|
+
* normalization is one the migration refused (a non-object `git` section,
|
|
82
|
+
* #3760) and is the user's last remaining expression of intent.
|
|
83
|
+
*
|
|
84
|
+
* `protected_branches` deliberately does NOT use this. It is new in #3552 with
|
|
85
|
+
* no legacy form, so honouring a top-level spelling would invent an
|
|
86
|
+
* undocumented alias that silently outranks the canonical nested key
|
|
87
|
+
* (round-4 external review).
|
|
88
|
+
*/
|
|
89
|
+
function _readGitKey(config, field) {
|
|
90
|
+
if (config[field] !== undefined)
|
|
91
|
+
return config[field];
|
|
92
|
+
return _readGitNested(config, field);
|
|
93
|
+
}
|
|
51
94
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
95
|
+
* Read the effective root/workstream configuration once for branch policy.
|
|
96
|
+
*
|
|
97
|
+
* Production takes the `loadConfig` branch, with `persist: false` — this is a
|
|
98
|
+
* PREDICATE, invoked on every `execute-phase` and every `ship` run, and a
|
|
99
|
+
* question must not rewrite the file it is asking about. Without it, any project
|
|
100
|
+
* carrying a legacy flat key (`base_branch`, `branching_strategy`, `depth`, …)
|
|
101
|
+
* has `.planning/config.json` silently normalized and rewritten by a call whose
|
|
102
|
+
* entire contract is to answer a boolean (#3648 review Blocker 1).
|
|
103
|
+
*
|
|
104
|
+
* The `readFile` branch is a unit-test seam, NOT a second production path, and
|
|
105
|
+
* it is deliberately narrower than `loadConfig`. It covers exactly two of
|
|
106
|
+
* production's steps — `normalizeLegacyKeys`, then the flat-then-nested lookup
|
|
107
|
+
* — over a single `<cwd>/.planning/config.json`. It does NOT apply
|
|
108
|
+
* root/workstream `_deepMergeConfig`, builtin or `~/.gsd/defaults.json`
|
|
109
|
+
* defaults, or `mergeFederatedConfig`. Tests that assert on any of those must
|
|
110
|
+
* drive `loadConfig` instead; the seam's own tests are scoped to normalization
|
|
111
|
+
* and shape validation, which is all it reproduces (#3648 review Major 3).
|
|
54
112
|
*/
|
|
55
|
-
function
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
113
|
+
function readEffectiveGitConfig(cwd, deps) {
|
|
114
|
+
let config = {};
|
|
115
|
+
if (deps?.readFile && !deps.loadConfig) {
|
|
116
|
+
const raw = deps.readFile(node_path_1.default.join(cwd, '.planning', 'config.json'));
|
|
117
|
+
if (raw) {
|
|
118
|
+
try {
|
|
119
|
+
const parsed = JSON.parse(raw);
|
|
120
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
121
|
+
const { parsed: normalized } = (0, configuration_cjs_1.normalizeLegacyKeys)(parsed);
|
|
122
|
+
config = {
|
|
123
|
+
base_branch: _readGitKey(normalized, 'base_branch'),
|
|
124
|
+
protected_branches: _readGitNested(normalized, 'protected_branches'),
|
|
125
|
+
allow_default_branch_commits: _readGitNested(normalized, 'allow_default_branch_commits'),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch { /* malformed direct edit contributes no policy values */ }
|
|
59
130
|
}
|
|
60
|
-
catch {
|
|
61
|
-
return null;
|
|
62
|
-
} });
|
|
63
|
-
const configPath = node_path_1.default.join(planningDir, 'config.json');
|
|
64
|
-
const raw = readFile(configPath);
|
|
65
|
-
if (!raw)
|
|
66
|
-
return null;
|
|
67
|
-
let cfg;
|
|
68
|
-
try {
|
|
69
|
-
cfg = JSON.parse(raw);
|
|
70
131
|
}
|
|
71
|
-
|
|
72
|
-
|
|
132
|
+
else {
|
|
133
|
+
try {
|
|
134
|
+
config = (deps?.loadConfig ?? loadConfigSeam)(cwd, { persist: false });
|
|
135
|
+
}
|
|
136
|
+
catch { /* configuration loading is fail-soft for branch resolution */ }
|
|
73
137
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
138
|
+
const rawBaseBranch = config.base_branch;
|
|
139
|
+
const baseBranch = typeof rawBaseBranch === 'string' && rawBaseBranch.trim()
|
|
140
|
+
? rawBaseBranch.trim()
|
|
141
|
+
: null;
|
|
142
|
+
// A protection predicate must not fail OPEN. `config-set` validation is
|
|
143
|
+
// bypassable by a direct edit of .planning/config.json, so one bad element
|
|
144
|
+
// discarding the whole list would silently answer "not protected" for names
|
|
145
|
+
// the user believes are protected — the exact failure #3552 exists to close,
|
|
146
|
+
// reintroduced through a different door (#3648 review Blocker 3). Drop only
|
|
147
|
+
// the bad elements, and report every rejection so it cannot pass unnoticed.
|
|
148
|
+
const rawProtectedBranches = config.protected_branches;
|
|
149
|
+
const protectedBranches = [];
|
|
150
|
+
const rejectedProtectedBranches = [];
|
|
151
|
+
if (Array.isArray(rawProtectedBranches)) {
|
|
152
|
+
for (const branch of rawProtectedBranches) {
|
|
153
|
+
if (typeof branch === 'string' && branch.trim() !== '') {
|
|
154
|
+
protectedBranches.push(branch.trim());
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
rejectedProtectedBranches.push(renderRejected(branch));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
else if (rawProtectedBranches !== undefined && rawProtectedBranches !== null) {
|
|
162
|
+
// Not a list at all — contributes no names, but is still a misconfiguration.
|
|
163
|
+
rejectedProtectedBranches.push(renderRejected(rawProtectedBranches));
|
|
83
164
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
return
|
|
165
|
+
// #3819: nested-only read (mirrors protected_branches — new key, no legacy
|
|
166
|
+
// flat form, so a top-level spelling must not become an undocumented alias).
|
|
167
|
+
const allowDefaultBranchCommits = config.allow_default_branch_commits === true;
|
|
168
|
+
return { baseBranch, protectedBranches, rejectedProtectedBranches, allowDefaultBranchCommits };
|
|
88
169
|
}
|
|
89
170
|
/**
|
|
90
171
|
* Try `git symbolic-ref --short refs/remotes/origin/HEAD` (no network).
|
|
@@ -170,7 +251,7 @@ function tryLocalBranch(cwd, execGit) {
|
|
|
170
251
|
* Consults the full precedence ladder and always returns a non-empty string.
|
|
171
252
|
* Never throws.
|
|
172
253
|
*/
|
|
173
|
-
function
|
|
254
|
+
function resolveBaseBranchDiagnosticsWithConfig(cwd, configured, deps) {
|
|
174
255
|
const rawExecGit = deps?.execGit ?? shell_command_projection_cjs_1.execGit;
|
|
175
256
|
// A genuine execGit failure (timeout, or the call could not even spawn —
|
|
176
257
|
// e.g. git missing, surfaced as exitCode 127 with `error` set) is distinct
|
|
@@ -186,10 +267,7 @@ function resolveBaseBranchDiagnostics(cwd, deps) {
|
|
|
186
267
|
anyGitFailure = true;
|
|
187
268
|
return r;
|
|
188
269
|
};
|
|
189
|
-
// Derive .planning dir relative to cwd (mirrors planningDir() in planning-workspace.cjs)
|
|
190
|
-
const planningDir = node_path_1.default.join(cwd, '.planning');
|
|
191
270
|
// 1. Config override
|
|
192
|
-
const configured = readConfigBaseBranch(planningDir, deps);
|
|
193
271
|
if (configured)
|
|
194
272
|
return { branch: configured, verified: true };
|
|
195
273
|
// 2. symbolic-ref (fast, no network)
|
|
@@ -210,6 +288,10 @@ function resolveBaseBranchDiagnostics(cwd, deps) {
|
|
|
210
288
|
// not answer (#3057 B4).
|
|
211
289
|
return { branch: 'main', verified: !anyGitFailure };
|
|
212
290
|
}
|
|
291
|
+
function resolveBaseBranchDiagnostics(cwd, deps) {
|
|
292
|
+
const { baseBranch } = readEffectiveGitConfig(cwd, deps);
|
|
293
|
+
return resolveBaseBranchDiagnosticsWithConfig(cwd, baseBranch, deps);
|
|
294
|
+
}
|
|
213
295
|
/**
|
|
214
296
|
* Resolve the default/base branch for the repository at `cwd`.
|
|
215
297
|
*
|
|
@@ -220,6 +302,23 @@ function resolveBaseBranchDiagnostics(cwd, deps) {
|
|
|
220
302
|
function resolveBaseBranch(cwd, deps) {
|
|
221
303
|
return resolveBaseBranchDiagnostics(cwd, deps).branch;
|
|
222
304
|
}
|
|
305
|
+
/** Resolve the base branch plus configured protected-branch extensions. */
|
|
306
|
+
function resolveProtectedBranchStatus(cwd, currentBranch, deps) {
|
|
307
|
+
const effectiveConfig = readEffectiveGitConfig(cwd, deps);
|
|
308
|
+
const { branch: baseBranch, verified } = resolveBaseBranchDiagnosticsWithConfig(cwd, effectiveConfig.baseBranch, deps);
|
|
309
|
+
const protectedBranches = [...new Set([
|
|
310
|
+
...(effectiveConfig.allowDefaultBranchCommits ? [] : [baseBranch]),
|
|
311
|
+
...effectiveConfig.protectedBranches,
|
|
312
|
+
])];
|
|
313
|
+
return {
|
|
314
|
+
baseBranch,
|
|
315
|
+
protectedBranches,
|
|
316
|
+
rejectedProtectedBranches: effectiveConfig.rejectedProtectedBranches,
|
|
317
|
+
isProtected: protectedBranches.includes(currentBranch),
|
|
318
|
+
verified,
|
|
319
|
+
allowDefaultBranchCommits: effectiveConfig.allowDefaultBranchCommits,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
223
322
|
/**
|
|
224
323
|
* Detect whether `cwd` sits inside a git worktree, and if so, return the
|
|
225
324
|
* absolute path of the worktree root.
|
|
@@ -352,7 +451,51 @@ function changedFilesSince(cwd, sinceRef, execGit) {
|
|
|
352
451
|
* Resolves the default branch and writes it to stdout (raw string, newline-terminated).
|
|
353
452
|
* Called by workflows via `gsd_run query git.base-branch`.
|
|
354
453
|
*/
|
|
355
|
-
function cmdGitBaseBranch(cwd,
|
|
454
|
+
function cmdGitBaseBranch(cwd, args, deps) {
|
|
455
|
+
if (args[0] === '--is-protected') {
|
|
456
|
+
if (args.length > 2) {
|
|
457
|
+
error('Usage: git base-branch --is-protected [<branch>]', ERROR_REASON.USAGE);
|
|
458
|
+
}
|
|
459
|
+
const writeDiagnostic = deps?.writeDiagnostic ?? ((s) => process.stderr.write(s));
|
|
460
|
+
// `git branch --show-current` prints nothing on a detached HEAD, so the
|
|
461
|
+
// call sites legitimately pass an explicit empty string: no protected
|
|
462
|
+
// branch is named '', the answer is false, and that is not a fault worth
|
|
463
|
+
// reporting. The flag with NO argument is a different thing — a caller bug
|
|
464
|
+
// that `args[1] ?? ''` used to collapse into the detached-HEAD case. Same
|
|
465
|
+
// handling, but said out loud so the two can be told apart.
|
|
466
|
+
//
|
|
467
|
+
// This diagnostic deliberately does NOT state the answer. The empty branch
|
|
468
|
+
// matches no protected name, but the fail-closed guard below still renders
|
|
469
|
+
// `true` when the base branch could not be verified — so promising "false"
|
|
470
|
+
// here would contradict what this same call prints on stdout (#3648 review).
|
|
471
|
+
if (args.length < 2) {
|
|
472
|
+
writeDiagnostic(`⚠ git-base-branch: --is-protected was called without a branch argument; ` +
|
|
473
|
+
`treating it as an empty branch name. Pass the branch to test, ` +
|
|
474
|
+
`e.g. --is-protected "$CURRENT_BRANCH".\n`);
|
|
475
|
+
}
|
|
476
|
+
const status = resolveProtectedBranchStatus(cwd, args[1] ?? '', deps);
|
|
477
|
+
if (status.rejectedProtectedBranches.length > 0) {
|
|
478
|
+
writeDiagnostic(`⚠ git-base-branch: ignoring ${status.rejectedProtectedBranches.length} unusable ` +
|
|
479
|
+
`git.protected_branches entr${status.rejectedProtectedBranches.length === 1 ? 'y' : 'ies'} ` +
|
|
480
|
+
`(${status.rejectedProtectedBranches.join(', ')}) — each must be a non-empty branch name. ` +
|
|
481
|
+
`The remaining names are still enforced. See #3552.\n`);
|
|
482
|
+
}
|
|
483
|
+
// A protection guard must fail closed: if the base branch could not be
|
|
484
|
+
// verified against this repository (a git query timed out or failed to
|
|
485
|
+
// run — #3057 B4), report "protected" rather than silently trusting an
|
|
486
|
+
// unverified guess that might happen to not match the current branch.
|
|
487
|
+
const rendered = String(status.verified ? status.isProtected : true);
|
|
488
|
+
if (!status.verified) {
|
|
489
|
+
writeDiagnostic(`⚠ git-base-branch: --is-protected could not verify repository branch metadata; ` +
|
|
490
|
+
`defaulting to protected (fail-closed). See #3057.\n`);
|
|
491
|
+
}
|
|
492
|
+
const write = deps?.write ?? ((s) => process.stdout.write(s));
|
|
493
|
+
write(rendered + '\n');
|
|
494
|
+
return rendered;
|
|
495
|
+
}
|
|
496
|
+
if (args.length > 0) {
|
|
497
|
+
error(`Unknown flag for git.base-branch: ${args[0]}`, ERROR_REASON.USAGE);
|
|
498
|
+
}
|
|
356
499
|
const { branch, verified } = resolveBaseBranchDiagnostics(cwd, deps);
|
|
357
500
|
if (!verified) {
|
|
358
501
|
const writeDiagnostic = deps?.writeDiagnostic ?? ((s) => process.stderr.write(s));
|
|
@@ -35,7 +35,7 @@ const healthDiagnosticMod = require("../health-diagnostic-types.cjs");
|
|
|
35
35
|
const { SEVERITY, adviseRemedy } = healthDiagnosticMod;
|
|
36
36
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
37
37
|
const phaseIdMod = require("../phase-id.cjs");
|
|
38
|
-
const {
|
|
38
|
+
const { isSentinelPhaseDir } = phaseIdMod;
|
|
39
39
|
// ─── C001 — gap in disk phase numbering (integer sequence) ────────────────
|
|
40
40
|
// (verify.cts:1504-1519)
|
|
41
41
|
function checkC001(snapshot) {
|
|
@@ -47,8 +47,12 @@ function checkC001(snapshot) {
|
|
|
47
47
|
return [];
|
|
48
48
|
const integerPhases = snapshot.allPhaseDirNames.value
|
|
49
49
|
// #3225: exclude sentinel phase ids (999.x/0.x) — never part of the
|
|
50
|
-
// sequential numbering, mirrors verify.cts:1510 verbatim.
|
|
51
|
-
|
|
50
|
+
// sequential numbering, mirrors verify.cts:1510 verbatim. The dot filter
|
|
51
|
+
// already drops every code-prefixed (bracket) name before the sentinel
|
|
52
|
+
// test, so the dir-aware recognizer below is defense-in-depth for the
|
|
53
|
+
// dotless legacy forms — kept so this guard cannot regress if the dot
|
|
54
|
+
// filter is ever loosened (#3639).
|
|
55
|
+
.filter((p) => !p.includes('.') && !isSentinelPhaseDir(p))
|
|
52
56
|
.map((p) => parseInt(p, 10))
|
|
53
57
|
.filter((n) => !Number.isNaN(n))
|
|
54
58
|
.sort((a, b) => a - b);
|
|
@@ -56,7 +56,13 @@ const healthDiagnosticMod = require("../health-diagnostic-types.cjs");
|
|
|
56
56
|
const { SEVERITY, adviseRemedy } = healthDiagnosticMod;
|
|
57
57
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
58
58
|
const validateMod = require("../validate.cjs");
|
|
59
|
-
|
|
59
|
+
// #612: `isPhaseDirName` is the convention-SELECTED shape test wrapping
|
|
60
|
+
// `phaseDirNameRe`. Handed no convention it delegates to that very regex, so a
|
|
61
|
+
// legacy repo's W005 reading is byte-identical; handed 'bracket' it also
|
|
62
|
+
// recognizes `{CODE}.{MM}-{PP}-slug`, which `phaseDirNameRe` rejects outright —
|
|
63
|
+
// left un-threaded, W005 fires on EVERY phase directory of a repo that opted
|
|
64
|
+
// into the convention, i.e. the check inverts on exactly the repos PR-2 widens.
|
|
65
|
+
const { isPhaseDirName } = validateMod;
|
|
60
66
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
61
67
|
const phaseIdMod = require("../phase-id.cjs");
|
|
62
68
|
const { extractPhaseToken, normalizePhaseName, comparePhaseNum } = phaseIdMod;
|
|
@@ -64,7 +70,7 @@ const { extractPhaseToken, normalizePhaseName, comparePhaseNum } = phaseIdMod;
|
|
|
64
70
|
function checkW005(snapshot) {
|
|
65
71
|
const diagnostics = [];
|
|
66
72
|
for (const name of snapshot.phaseDirs.value) {
|
|
67
|
-
if (!name.
|
|
73
|
+
if (!isPhaseDirName(name, snapshot.phaseIdConvention)) {
|
|
68
74
|
diagnostics.push({
|
|
69
75
|
code: 'W005',
|
|
70
76
|
severity: SEVERITY.WARNING,
|
|
@@ -104,7 +104,7 @@ const planningScopeMod = require("../planning-scope.cjs");
|
|
|
104
104
|
const { SCOPE } = planningScopeMod;
|
|
105
105
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
106
106
|
const phaseIdMod = require("../phase-id.cjs");
|
|
107
|
-
const { matchPhaseDirs, normalizePhaseName, extractPhaseToken, isSentinelPhaseId } = phaseIdMod;
|
|
107
|
+
const { matchPhaseDirs, normalizePhaseName, extractPhaseToken, isSentinelPhaseId, isSentinelPhaseDir } = phaseIdMod;
|
|
108
108
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
109
109
|
const validateMod = require("../validate.cjs");
|
|
110
110
|
const { phaseVariants } = validateMod;
|
|
@@ -116,8 +116,16 @@ const { phaseVariants } = validateMod;
|
|
|
116
116
|
* does the roadmap claim, for W007) call this — one matcher, reused, per the
|
|
117
117
|
* file-level comment.
|
|
118
118
|
*/
|
|
119
|
-
function dirsForPhase(dirs, phaseId) {
|
|
120
|
-
|
|
119
|
+
function dirsForPhase(dirs, phaseId, convention) {
|
|
120
|
+
// #612: `convention` reaches BOTH operands of this pairing or it pairs
|
|
121
|
+
// nothing. The phases come from a bracket-aware heading scan and the dirs
|
|
122
|
+
// from a bracket-aware disk scan, so a convention-less selector here leaves
|
|
123
|
+
// W006's existence check and W007's `claimedDirs` empty on exactly the repos
|
|
124
|
+
// both scans just widened — every bracket phase reads as "no directory on
|
|
125
|
+
// disk" AND every bracket directory reads as unclaimed. `matchPhaseDirs`
|
|
126
|
+
// forwards it to its primary `phaseTokenMatches`; handed nothing it is
|
|
127
|
+
// byte-identical, so no legacy repo's pairing changes.
|
|
128
|
+
const canonical = matchPhaseDirs(dirs, normalizePhaseName(phaseId), convention).matches;
|
|
121
129
|
if (canonical.length > 0)
|
|
122
130
|
return canonical;
|
|
123
131
|
// Bug 2 (#3309 W006/W007 migration cluster, found while fixing the
|
|
@@ -139,7 +147,12 @@ function dirsForPhase(dirs, phaseId) {
|
|
|
139
147
|
// alone misses without re-deriving a second matcher.
|
|
140
148
|
const variants = phaseVariants(phaseId);
|
|
141
149
|
return dirs.filter((d) => {
|
|
142
|
-
|
|
150
|
+
// #612: the same convention the primary matcher above got. This fallback
|
|
151
|
+
// compares a DIRECTORY-derived token against phase-id variants, so left
|
|
152
|
+
// convention-less it reads `GSD.02-05-slug` with the legacy grammar and can
|
|
153
|
+
// never agree with a bracket phase id — the fallback would then be dead on
|
|
154
|
+
// precisely the convention it is reached for.
|
|
155
|
+
const token = extractPhaseToken(d, convention).toUpperCase();
|
|
143
156
|
for (const variant of variants) {
|
|
144
157
|
if (token === variant.toUpperCase())
|
|
145
158
|
return true;
|
|
@@ -173,15 +186,40 @@ function checkW006(snapshot) {
|
|
|
173
186
|
if (snapshot.roadmapDeclaredPhases.scope !== SCOPE.COMPLETE)
|
|
174
187
|
return [];
|
|
175
188
|
const dirs = snapshot.allPhaseDirNames.value;
|
|
189
|
+
const convention = snapshot.phaseIdConvention;
|
|
176
190
|
const archivedTokens = new Set(snapshot.archivedPhaseTokens.value);
|
|
191
|
+
// No separate scope guard: `roadmapSentinelPhaseTokens` and
|
|
192
|
+
// `roadmapDeclaredPhases` are produced by ONE builder call
|
|
193
|
+
// (`buildRoadmapDeclaredPhasesField`) from ONE `buildRoadmapPhaseVariants`
|
|
194
|
+
// result, so their scopes are yoked by construction — the COMPLETE check
|
|
195
|
+
// above covers both. Stated rather than left implicit, because reading the
|
|
196
|
+
// sentinel set unconditionally would be a latent bug the day the two fields
|
|
197
|
+
// acquire independent builders: an UNREADABLE sentinel set silently degrades
|
|
198
|
+
// to "nothing is a sentinel," which ADDS W006 warnings rather than dropping
|
|
199
|
+
// them.
|
|
200
|
+
const sentinelTokens = new Set(snapshot.roadmapSentinelPhaseTokens.value);
|
|
177
201
|
const checkboxes = snapshot.roadmapPhaseCheckboxes.value;
|
|
178
202
|
const diagnostics = [];
|
|
179
203
|
for (const { phaseId } of snapshot.roadmapDeclaredPhases.value) {
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
|
|
204
|
+
// A backlog/icebox phase legitimately has no directory. The two guards are
|
|
205
|
+
// DISJOINT, which is why both are needed and neither subsumes the other:
|
|
206
|
+
//
|
|
207
|
+
// #612 `roadmapSentinelPhaseTokens` — bracket sentinels. Under the
|
|
208
|
+
// bracket convention the sentinel-ness lives in the MILESTONE, not
|
|
209
|
+
// the token: the heading `### [GSD.999] 07:` yields phase id `07`,
|
|
210
|
+
// so a bare-token test cannot see it. The snapshot's
|
|
211
|
+
// `buildRoadmapPhaseVariants` call carries the bracket id alongside
|
|
212
|
+
// and reports the token here. Empty unless the bracket convention
|
|
213
|
+
// is active, so no legacy repo's reading changes.
|
|
214
|
+
// #3225 `isSentinelPhaseId` — the legacy/bare leading-int rule (999.x/0.x),
|
|
215
|
+
// which sees `### Phase 999:` and is blind to `[GSD.999] 07`.
|
|
216
|
+
//
|
|
217
|
+
// Upstream's call is kept one-arg verbatim: every `phaseId` here is a bare
|
|
218
|
+
// token, never a `{CODE}.{MM}`-qualified id, so a convention argument could
|
|
219
|
+
// not change its result.
|
|
220
|
+
if (sentinelTokens.has(phaseId) || isSentinelPhaseId(phaseId))
|
|
183
221
|
continue;
|
|
184
|
-
if (dirsForPhase(dirs, phaseId).length > 0)
|
|
222
|
+
if (dirsForPhase(dirs, phaseId, convention).length > 0)
|
|
185
223
|
continue;
|
|
186
224
|
// Bug 1 (#3309 W006/W007 migration cluster): a phase whose ONLY
|
|
187
225
|
// directory lives under a milestone archive
|
|
@@ -209,10 +247,10 @@ function checkW006(snapshot) {
|
|
|
209
247
|
// ─── W007 — an on-disk phase directory has no matching ROADMAP entry ──────
|
|
210
248
|
// (verify.cts:2086-2101)
|
|
211
249
|
/** Every directory in `dirs` that ANY declared roadmap phase resolves to. */
|
|
212
|
-
function computeClaimedDirs(dirs, declaredPhases) {
|
|
250
|
+
function computeClaimedDirs(dirs, declaredPhases, convention) {
|
|
213
251
|
const claimed = new Set();
|
|
214
252
|
for (const { phaseId } of declaredPhases) {
|
|
215
|
-
for (const dir of dirsForPhase(dirs, phaseId))
|
|
253
|
+
for (const dir of dirsForPhase(dirs, phaseId, convention))
|
|
216
254
|
claimed.add(dir);
|
|
217
255
|
}
|
|
218
256
|
return claimed;
|
|
@@ -222,17 +260,25 @@ function checkW007(snapshot) {
|
|
|
222
260
|
if (snapshot.roadmapDeclaredPhases.scope !== SCOPE.COMPLETE)
|
|
223
261
|
return [];
|
|
224
262
|
const dirs = snapshot.allPhaseDirNames.value;
|
|
225
|
-
const
|
|
263
|
+
const convention = snapshot.phaseIdConvention;
|
|
264
|
+
const claimedDirs = computeClaimedDirs(dirs, snapshot.roadmapDeclaredPhases.value, convention);
|
|
226
265
|
const diagnostics = [];
|
|
227
266
|
for (const dirName of dirs) {
|
|
228
267
|
// `extractPhaseToken` is the phase-id.cts owner `PHASE_TOKEN_FROM_DIR_RE`
|
|
229
268
|
// (`src/validate.cts:73-76`) is documented to match exactly
|
|
230
269
|
// (verify.cts's original `p` key from `collectDiskPhaseEntries`,
|
|
231
270
|
// `verify.cts:1373-1397`) — same token, relocated read, not reinvented.
|
|
232
|
-
|
|
271
|
+
// #612: `collectDiskPhaseEntries` keyed that map through the
|
|
272
|
+
// convention-aware extractor, so the token this message names must be
|
|
273
|
+
// derived the same way — otherwise a bracket repo's unclaimed
|
|
274
|
+
// `GSD.02-05-slug` is reported as phase `GSD.02` rather than `05`.
|
|
275
|
+
const token = extractPhaseToken(dirName, convention);
|
|
233
276
|
// #3225: a sentinel dir on disk (999-interim, 0-drafts) is defined as
|
|
234
|
-
// never-on-roadmap; it must not trigger W007.
|
|
235
|
-
|
|
277
|
+
// never-on-roadmap; it must not trigger W007. #3639: judged on the DIR
|
|
278
|
+
// NAME via the dir-aware recognizer — the extracted token is
|
|
279
|
+
// milestone-stripped, so a bracket sentinel (GSD.999-07-icebox) was
|
|
280
|
+
// invisible to the id predicate and false-fired as an orphan.
|
|
281
|
+
if (isSentinelPhaseDir(dirName))
|
|
236
282
|
continue;
|
|
237
283
|
if (claimedDirs.has(dirName))
|
|
238
284
|
continue;
|