@opengsd/gsd-core 1.10.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 +12 -1
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-doc-synthesizer.md +2 -4
- package/agents/gsd-dom-verifier.md +169 -0
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +22 -14
- package/agents/gsd-framework-selector.md +1 -3
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.md +5 -3
- package/agents/gsd-pattern-mapper.md +11 -0
- package/agents/gsd-phase-researcher.md +23 -2
- package/agents/gsd-plan-checker.md +50 -53
- package/agents/gsd-planner.md +50 -50
- 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-user-profiler.md +3 -0
- package/agents/gsd-verifier.md +13 -4
- package/bin/install.js +1448 -1103
- package/commands/gsd/code-review.md +1 -1
- 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/map-codebase.md +1 -1
- package/commands/gsd/mempalace-capture.md +1 -1
- package/commands/gsd/mempalace-recall.md +1 -1
- package/commands/gsd/new-milestone.md +1 -1
- package/commands/gsd/quick.md +9 -5
- package/commands/gsd/review-backlog.md +2 -1
- package/commands/gsd/verify-work.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +1035 -138
- package/gsd-core/bin/lib/active-workstream-store.cjs +146 -22
- package/gsd-core/bin/lib/adr-parser.cjs +13 -7
- package/gsd-core/bin/lib/agent-install-check.cjs +392 -32
- package/gsd-core/bin/lib/api-coverage.cjs +33 -14
- package/gsd-core/bin/lib/artifacts.cjs +5 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +32 -15
- package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
- package/gsd-core/bin/lib/audit.cjs +1026 -268
- package/gsd-core/bin/lib/broken-windows.cjs +306 -28
- package/gsd-core/bin/lib/capability-consent.cjs +149 -15
- package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
- package/gsd-core/bin/lib/capability-lock.cjs +10 -4
- package/gsd-core/bin/lib/capability-registry.cjs +845 -130
- package/gsd-core/bin/lib/capability-source.cjs +92 -0
- package/gsd-core/bin/lib/capability-state.cjs +18 -3
- package/gsd-core/bin/lib/capability-trust.cjs +444 -25
- package/gsd-core/bin/lib/capability-validator.cjs +700 -40
- package/gsd-core/bin/lib/capability-writer.cjs +3 -2
- package/gsd-core/bin/lib/check-command-router.cjs +216 -42
- package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
- 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 +735 -0
- package/gsd-core/bin/lib/command-aliases.cjs +22 -0
- package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
- package/gsd-core/bin/lib/command-roster.cjs +44 -1
- package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
- package/gsd-core/bin/lib/commands.cjs +1172 -108
- package/gsd-core/bin/lib/commonjs-marker.cjs +12 -6
- package/gsd-core/bin/lib/complexity-trigger.cjs +1192 -0
- package/gsd-core/bin/lib/config-loader.cjs +187 -23
- package/gsd-core/bin/lib/config.cjs +102 -3
- package/gsd-core/bin/lib/configuration.cjs +129 -37
- package/gsd-core/bin/lib/core-utils.cjs +208 -33
- package/gsd-core/bin/lib/decisions.cjs +23 -0
- 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/fallow-runner.cjs +20 -44
- package/gsd-core/bin/lib/frontmatter.cjs +899 -229
- package/gsd-core/bin/lib/gap-checker.cjs +95 -10
- package/gsd-core/bin/lib/git-base-branch.cjs +276 -39
- package/gsd-core/bin/lib/gsd2-import.cjs +10 -1
- package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +149 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +268 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +187 -0
- package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +451 -0
- package/gsd-core/bin/lib/host-integration.cjs +39 -6
- package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
- package/gsd-core/bin/lib/init-command-router.cjs +118 -21
- package/gsd-core/bin/lib/init.cjs +439 -168
- package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
- package/gsd-core/bin/lib/install-engine.cjs +811 -259
- package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +235 -0
- package/gsd-core/bin/lib/install-profiles.cjs +212 -61
- package/gsd-core/bin/lib/install-scope.cjs +270 -0
- package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
- package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
- 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 +148 -38
- package/gsd-core/bin/lib/intel.cjs +101 -26
- package/gsd-core/bin/lib/io.cjs +170 -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-sectionizer.cjs +2 -1
- package/gsd-core/bin/lib/markdown-table.cjs +183 -22
- package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
- package/gsd-core/bin/lib/milestone.cjs +842 -73
- package/gsd-core/bin/lib/model-catalog.cjs +232 -16
- package/gsd-core/bin/lib/model-resolver.cjs +193 -68
- package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
- package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
- package/gsd-core/bin/lib/pattern.cjs +122 -0
- package/gsd-core/bin/lib/phase-estimation.cjs +18 -9
- package/gsd-core/bin/lib/phase-id.cjs +514 -40
- package/gsd-core/bin/lib/phase-lifecycle.cjs +52 -19
- package/gsd-core/bin/lib/phase-locator.cjs +262 -34
- package/gsd-core/bin/lib/phase.cjs +1038 -214
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +72 -1
- package/gsd-core/bin/lib/plan-document.cjs +263 -0
- package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
- package/gsd-core/bin/lib/plan-scan.cjs +98 -3
- 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-scope.cjs +31 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +894 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +112 -6
- package/gsd-core/bin/lib/probe-core.cjs +5 -2
- package/gsd-core/bin/lib/profile-output.cjs +1 -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 +766 -0
- package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +11 -6
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +22 -13
- package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +421 -66
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
- package/gsd-core/bin/lib/roadmap-command-router.cjs +59 -11
- package/gsd-core/bin/lib/roadmap-parser.cjs +1006 -184
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
- package/gsd-core/bin/lib/roadmap.cjs +442 -96
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +702 -52
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +459 -55
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
- package/gsd-core/bin/lib/runtime-homes.cjs +69 -3
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +402 -58
- package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
- package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
- package/gsd-core/bin/lib/runtime-slash.cjs +96 -8
- package/gsd-core/bin/lib/security.cjs +104 -5
- package/gsd-core/bin/lib/shell-command-projection.cjs +342 -7
- package/gsd-core/bin/lib/smart-entry.cjs +133 -23
- package/gsd-core/bin/lib/spec-section.cjs +12 -7
- package/gsd-core/bin/lib/state-command-router.cjs +52 -19
- package/gsd-core/bin/lib/state-contract.cjs +359 -0
- package/gsd-core/bin/lib/state-document.cjs +338 -8
- package/gsd-core/bin/lib/state-md-schema.cjs +221 -0
- package/gsd-core/bin/lib/state-transition.cjs +846 -176
- package/gsd-core/bin/lib/state.cjs +2589 -369
- package/gsd-core/bin/lib/surface.cjs +33 -11
- 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/text-lines.cjs +80 -0
- package/gsd-core/bin/lib/token-scanner.cjs +76 -0
- package/gsd-core/bin/lib/uat-predicate.cjs +67 -23
- package/gsd-core/bin/lib/uat.cjs +1761 -167
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
- package/gsd-core/bin/lib/ui-safety-gate.cjs +51 -12
- package/gsd-core/bin/lib/unusable-input.cjs +37 -0
- package/gsd-core/bin/lib/update-context.cjs +8 -2
- package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
- package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
- package/gsd-core/bin/lib/validate.cjs +20 -6
- package/gsd-core/bin/lib/vendor/README.md +75 -0
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
- package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
- package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +272 -9
- package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
- package/gsd-core/bin/lib/verify.cjs +453 -918
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +53 -32
- package/gsd-core/bin/lib/workstream-inventory.cjs +63 -10
- package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
- package/gsd-core/bin/lib/workstream.cjs +2 -2
- package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
- package/gsd-core/bin/lib/worktree-safety.cjs +341 -18
- package/gsd-core/bin/shared/config-defaults.manifest.json +8 -1
- package/gsd-core/bin/shared/config-schema.manifest.json +12 -1
- 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 +44 -26
- 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 +39 -21
- package/gsd-core/references/context-budget.md +1 -1
- package/gsd-core/references/decimal-phase-calculation.md +5 -5
- package/gsd-core/references/dispatch-isolation-gate.md +138 -0
- package/gsd-core/references/doc-conflict-engine.md +1 -1
- package/gsd-core/references/edge-probe.md +8 -0
- package/gsd-core/references/execute-mvp-tdd.md +4 -6
- package/gsd-core/references/execute-phase-between-wave-reset.md +15 -14
- package/gsd-core/references/execute-phase-context-guard.md +1 -1
- package/gsd-core/references/execute-phase-response-language.md +1 -1
- package/gsd-core/references/execute-phase-wave-guard.md +17 -11
- 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 +5 -4
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +61 -2
- package/gsd-core/references/model-profiles.md +12 -4
- package/gsd-core/references/mvp-concepts.md +9 -9
- 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-guidance.md +3 -9
- package/gsd-core/references/planner-human-verify-mode.md +15 -1
- package/gsd-core/references/planner-preconditions.md +1 -1
- package/gsd-core/references/planner-reviews.md +1 -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 +44 -13
- package/gsd-core/references/reviewer-instances.md +31 -0
- package/gsd-core/references/revision-loop.md +1 -1
- package/gsd-core/references/runtime-aware-dispatch.md +1 -1
- package/gsd-core/references/specless-probe-fallback.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 +5 -5
- package/gsd-core/references/verifier-phase-gates.md +192 -0
- package/gsd-core/references/verify-command-path-resolvability.md +42 -0
- package/gsd-core/references/verify-mvp-mode.md +2 -2
- package/gsd-core/references/workstream-flag.md +33 -17
- 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/discussion-log.md +1 -1
- package/gsd-core/templates/phase-prompt.md +5 -4
- package/gsd-core/templates/state.md +11 -4
- package/gsd-core/templates/verification-report.md +9 -1
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +1 -1
- package/gsd-core/workflows/add-phase.md +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 +13 -20
- 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 +11 -27
- package/gsd-core/workflows/check-todos.md +1 -1
- package/gsd-core/workflows/cleanup.md +64 -5
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +14 -4
- package/gsd-core/workflows/code-review-fix.md +38 -11
- package/gsd-core/workflows/code-review.md +159 -52
- package/gsd-core/workflows/complete-milestone.md +151 -23
- package/gsd-core/workflows/debug.md +12 -8
- package/gsd-core/workflows/diagnose-issues.md +47 -15
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -8
- package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
- 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 +4 -3
- 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 +27 -2
- package/gsd-core/workflows/eval-review.md +7 -14
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +142 -15
- 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 +77 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +24 -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 +72 -100
- package/gsd-core/workflows/execute-plan.md +52 -15
- package/gsd-core/workflows/explore.md +131 -4
- package/gsd-core/workflows/extract-learnings.md +1 -1
- package/gsd-core/workflows/fast.md +10 -2
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +5 -5
- package/gsd-core/workflows/health.md +76 -10
- package/gsd-core/workflows/import.md +18 -15
- package/gsd-core/workflows/inbox.md +4 -5
- package/gsd-core/workflows/ingest-docs.md +49 -16
- 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 +8 -5
- package/gsd-core/workflows/new-milestone.md +22 -29
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
- package/gsd-core/workflows/new-project.md +26 -40
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +14 -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 +162 -59
- package/gsd-core/workflows/plan-review-convergence.md +96 -11
- 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 +61 -18
- package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +5 -7
- package/gsd-core/workflows/quick/steps/quick-verification.md +28 -9
- package/gsd-core/workflows/quick/steps/research-phase.md +4 -6
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
- package/gsd-core/workflows/quick.md +55 -44
- 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 +219 -20
- package/gsd-core/workflows/scan.md +1 -1
- package/gsd-core/workflows/secure-phase.md +3 -3
- package/gsd-core/workflows/session-report.md +2 -1
- package/gsd-core/workflows/settings-advanced.md +7 -9
- package/gsd-core/workflows/settings-integrations.md +64 -31
- package/gsd-core/workflows/settings.md +69 -7
- package/gsd-core/workflows/ship.md +116 -50
- 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 +53 -13
- 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 +64 -9
- package/gsd-core/workflows/thread.md +11 -7
- package/gsd-core/workflows/transition.md +49 -14
- package/gsd-core/workflows/ui-phase.md +15 -21
- package/gsd-core/workflows/ui-review.md +8 -12
- package/gsd-core/workflows/ultraplan-phase.md +5 -13
- package/gsd-core/workflows/undo.md +8 -16
- package/gsd-core/workflows/update.md +7 -11
- 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 +66 -25
- package/hooks/dist/gsd-agent-isolation-guard.js +158 -30
- package/hooks/dist/gsd-check-update-worker.js +56 -13
- package/hooks/dist/gsd-check-update.js +19 -1
- 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 +2 -3
- 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 +83 -3
- package/hooks/dist/gsd-cursor-subagent-stop.js +6 -3
- 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 +37 -27
- package/hooks/dist/gsd-read-guard.js +16 -7
- package/hooks/dist/gsd-read-injection-scanner.js +55 -32
- package/hooks/dist/gsd-session-state.sh +1 -0
- package/hooks/dist/gsd-statusline.js +231 -24
- package/hooks/dist/gsd-update-banner.js +22 -1
- 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 +162 -46
- 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 +92 -59
- 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 +45 -0
- package/hooks/dist/lib/isolation-deny-reason.js +39 -0
- package/hooks/dist/lib/isolation-sentinel.js +9 -0
- package/hooks/dist/managed-hooks-registry.cjs +3 -0
- package/hooks/gsd-agent-isolation-guard.js +158 -30
- package/hooks/gsd-check-update-worker.js +56 -13
- package/hooks/gsd-check-update.js +19 -1
- 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 +2 -3
- package/hooks/gsd-cursor-session-start.js +2 -1
- package/hooks/gsd-cursor-stop.js +2 -1
- package/hooks/gsd-cursor-subagent-start.js +83 -3
- package/hooks/gsd-cursor-subagent-stop.js +6 -3
- 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 +37 -27
- package/hooks/gsd-read-guard.js +16 -7
- package/hooks/gsd-read-injection-scanner.js +55 -32
- package/hooks/gsd-session-state.sh +1 -0
- package/hooks/gsd-statusline.js +231 -24
- package/hooks/gsd-update-banner.js +22 -1
- 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 +162 -46
- 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-cmd.js +92 -59
- package/hooks/lib/git-probe.js +84 -0
- package/hooks/lib/hook-exit.js +81 -0
- package/hooks/lib/injection-patterns.js +45 -0
- package/hooks/lib/isolation-deny-reason.js +39 -0
- package/hooks/lib/isolation-sentinel.js +9 -0
- package/hooks/managed-hooks-registry.cjs +3 -0
- package/package.json +28 -11
- package/pi/gsd.cjs +19 -5
- package/scripts/base64-scan.sh +74 -12
- package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
- package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
- package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
- package/scripts/build-hooks.js +5 -0
- package/scripts/changeset/lint.cjs +60 -5
- package/scripts/check-alias-drift.cjs +7 -43
- package/scripts/check-contract-drift.cjs +297 -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 +64 -14
- package/scripts/ci-timeout-report.cjs +230 -0
- package/scripts/command-contract-helpers.cjs +903 -1
- package/scripts/docs-guard-registry.cjs +396 -0
- package/scripts/gen-adr-index.cjs +728 -38
- package/scripts/gen-capability-registry.cjs +11 -21
- package/scripts/gen-context-index.cjs +2 -11
- 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-health-docs.cjs +390 -0
- package/scripts/gen-hooks-cli-exit.cjs +239 -0
- package/scripts/gen-install-tree-fixtures.cjs +2 -2
- package/scripts/gen-inventory-manifest.cjs +50 -4
- package/scripts/gen-loop-host-contract.cjs +138 -25
- package/scripts/gen-registry.cjs +3 -14
- 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/alias-drift-families.cjs +46 -0
- package/scripts/lib/ci-job-timing.cjs +72 -0
- package/scripts/lib/cli-exit.cjs +546 -44
- package/scripts/lib/drift-scan.cjs +308 -0
- package/scripts/lib/exit-code-registry.cjs +98 -0
- package/scripts/lib/ndjson-reporter.cjs +119 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +1 -26
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
- package/scripts/lint-canary-version-leak.cjs +73 -0
- package/scripts/lint-command-contract.cjs +96 -13
- package/scripts/lint-completion-predicate-drift.cjs +933 -0
- package/scripts/lint-completion-ratio-drift.cjs +214 -0
- package/scripts/lint-default-flip-documentation.cjs +193 -0
- 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 +38 -0
- package/scripts/lint-eslint-glob-coverage.cjs +340 -0
- package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
- package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
- package/scripts/lint-health-diagnostic-rule-table.cjs +461 -0
- package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
- package/scripts/lint-milestone-window-drift.cjs +468 -0
- package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
- package/scripts/lint-phase-enumeration-drift.cjs +492 -0
- package/scripts/lint-plan-count-drift.cjs +318 -0
- package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
- package/scripts/lint-planning-prompt-drift.cjs +471 -0
- package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
- package/scripts/lint-regression-test-names.cjs +15 -13
- package/scripts/lint-removed-but-needed.cjs +488 -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-field-drift.cjs +805 -0
- package/scripts/lint-state-write-path-drift.cjs +950 -0
- package/scripts/lint-test-file-count.allowlist.json +137 -8
- package/scripts/lint-test-file-count.cjs +25 -3
- package/scripts/lint-unreachable-guard-drift.cjs +830 -0
- package/scripts/lint-vendored-deps.cjs +297 -0
- package/scripts/mutation-matrix.cjs +599 -50
- package/scripts/pr-changed-files.cjs +63 -0
- package/scripts/pr-template-policy.cjs +14 -4
- package/scripts/prompt-injection-scan.sh +100 -14
- package/scripts/require-issue-link-policy.cjs +192 -0
- package/scripts/secret-scan.sh +75 -13
- package/scripts/select-docs-guards.cjs +56 -0
- package/scripts/sync-runtime-launcher.cjs +24 -7
- package/skills/gsd-autonomous/SKILL.md +0 -1
- package/skills/gsd-code-review/SKILL.md +1 -1
- package/skills/gsd-discuss-phase/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -2
- package/skills/gsd-import/SKILL.md +1 -1
- package/skills/gsd-map-codebase/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +1 -1
- package/skills/gsd-mempalace-recall/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-next/SKILL.md +0 -1
- package/skills/gsd-plan-phase/SKILL.md +0 -1
- package/skills/gsd-progress/SKILL.md +0 -1
- package/skills/gsd-quick/SKILL.md +9 -5
- package/skills/gsd-review-backlog/SKILL.md +2 -1
- package/skills/gsd-stats/SKILL.md +0 -1
- package/skills/gsd-verify-work/SKILL.md +1 -1
- package/vscode/package.json +1 -1
- package/bin/lib/ui-safety-gate.cjs +0 -107
- package/gsd-core/workflows/discovery-phase.md +0 -298
- package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
- package/gsd-core/workflows/verify-phase.md +0 -574
- package/scripts/affected-tests-lib.cjs +0 -554
- package/scripts/lint-allow-test-rule-refs.cjs +0 -162
- package/scripts/lint-emitted-drift-ack.cjs +0 -344
- package/scripts/run-affected-tests.cjs +0 -7
- package/scripts/run-tests.cjs +0 -1051
|
@@ -8,12 +8,23 @@
|
|
|
8
8
|
* boundary moved. The core.cjs re-export spine was retired in epic #1267;
|
|
9
9
|
* callers import phase-id helpers from phase-id.cjs directly.
|
|
10
10
|
*
|
|
11
|
-
* Dependencies:
|
|
11
|
+
* Dependencies:
|
|
12
|
+
* - ./pattern.cjs (escapeRegex — #3212 Phase 1 seam; this module is no
|
|
13
|
+
* longer the owner of pattern-escaping, only a consumer)
|
|
14
|
+
* - ./core-utils.cjs (generateSlugInternal — #3883/ADR-3473 §8.3: the
|
|
15
|
+
* canonical slug formula). core-utils.cjs also requires THIS module
|
|
16
|
+
* (comparePhaseNum, scopeToPhase), so a top-level require here would be
|
|
17
|
+
* circular and — per this codebase's compiled-.cjs convention of a
|
|
18
|
+
* single `module.exports = {...}` reassignment at the bottom of each
|
|
19
|
+
* file — a top-level circular require captures a stale, still-empty
|
|
20
|
+
* exports object forever (verified live: it throws
|
|
21
|
+
* "generateSlugInternal is not a function" when core-utils.cjs happens
|
|
22
|
+
* to load first). The require is deferred (lazy, inside each function
|
|
23
|
+
* body) instead, mirroring the same cycle-break already used by
|
|
24
|
+
* core-utils.cts's own getPhaseFileStats/plan-scan.cjs seam.
|
|
12
25
|
*/
|
|
26
|
+
const pattern_cjs_1 = require("./pattern.cjs");
|
|
13
27
|
// ─── Phase-id helpers ─────────────────────────────────────────────────────────
|
|
14
|
-
function escapeRegex(value) {
|
|
15
|
-
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
16
|
-
}
|
|
17
28
|
// project_code values start with an uppercase letter (e.g. PROJ, APP_CODE);
|
|
18
29
|
// leading underscores are not valid project codes per .planning/config.json.
|
|
19
30
|
const PROJECT_CODE_PREFIX_STRIP_RE = /^[A-Z][A-Z0-9_]*-(?=\d)/;
|
|
@@ -48,6 +59,22 @@ const OPTIONAL_PHASE_TAG_SOURCE = '(?:\\s*\\([^)\\n]{0,200}\\))?';
|
|
|
48
59
|
// (scripts/lint-phase-id-drift.cjs) fails CI if a literal re-derivation is
|
|
49
60
|
// introduced outside this module without a `// phase-id-owner:` justification.
|
|
50
61
|
const PHASE_NUMBER_TOKEN_SOURCE = '\\d+[A-Z]?(?:\\.\\d+)*';
|
|
62
|
+
// #2528 review: the CASE-FLEXIBLE renderings of the two sources above, for call
|
|
63
|
+
// sites that scan directory names (where a project code or a variant suffix may
|
|
64
|
+
// legitimately be lowercase) and therefore cannot use a case-sensitive class.
|
|
65
|
+
//
|
|
66
|
+
// They live HERE, beside the sources they widen, because the alternative in use
|
|
67
|
+
// was `SOURCE.replaceAll('A-Z', 'A-Za-z')` at the consuming site — a derivation
|
|
68
|
+
// that depends on the owner rendering that exact literal. It passes
|
|
69
|
+
// lint-phase-id-drift.cjs (no literal copy of the grammar), but the day this
|
|
70
|
+
// module expresses the same class any other way (`[[:upper:]]`, a named
|
|
71
|
+
// fragment, an escaped range) the replaceAll silently no-ops and the consumer
|
|
72
|
+
// quietly narrows to uppercase-only — the failure is a NON-match, so nothing
|
|
73
|
+
// throws and no test that only feeds uppercase input notices. Deriving it once,
|
|
74
|
+
// where the source is defined, makes that impossible: a rename here is a
|
|
75
|
+
// compile-visible change, not a silent behavior change three modules away.
|
|
76
|
+
const CASE_FLEXIBLE_PROJECT_CODE_PREFIX_SOURCE = OPTIONAL_PROJECT_CODE_PREFIX_SOURCE.replaceAll('A-Z', 'A-Za-z');
|
|
77
|
+
const CASE_FLEXIBLE_PHASE_NUMBER_TOKEN_SOURCE = PHASE_NUMBER_TOKEN_SOURCE.replaceAll('A-Z', 'A-Za-z');
|
|
51
78
|
// #2232: the canonical CONTINUATION-segment grammar — a dash-separated segment
|
|
52
79
|
// that extends a phase token (a zero-padded sub-phase or plan number, e.g. the
|
|
53
80
|
// "01" in "02-01-setup"). getPhaseDirFromPhaseId writes these zero-padded to
|
|
@@ -59,7 +86,8 @@ const PHASE_NUMBER_TOKEN_SOURCE = '\\d+[A-Z]?(?:\\.\\d+)*';
|
|
|
59
86
|
// trailing grammar (letter suffixes, dotted sub-phases, segment boundaries).
|
|
60
87
|
// POLICY (locked by boundary tests): sub-phase/plan numbers ≥100 are out of the
|
|
61
88
|
// dir-token grammar — the LEADING phase number stays unbounded (`\d+`), only
|
|
62
|
-
// continuation segments
|
|
89
|
+
// continuation segments begin with a two-digit run; consuming sites retain
|
|
90
|
+
// their established suffix and boundary grammar. Shared from here so the five #2043
|
|
63
91
|
// call sites cannot drift independently (see scripts/lint-phase-id-drift.cjs).
|
|
64
92
|
const PHASE_CONTINUATION_SEGMENT_SOURCE = '\\d{2}(?!\\d)';
|
|
65
93
|
const PHASE_CONTINUATION_SEGMENT_PREFIX_RE = new RegExp(`^${PHASE_CONTINUATION_SEGMENT_SOURCE}`);
|
|
@@ -124,7 +152,8 @@ const BRACKET_CANONICAL_NUMERIC_SOURCE = '(?:[1-9]\\d{2,}|\\d{2})';
|
|
|
124
152
|
const BRACKET_PHASE_TOKEN_SOURCE = `\\d+[A-Z]?` +
|
|
125
153
|
`(?:-${BRACKET_CANONICAL_NUMERIC_SOURCE}(?!\\d))?` +
|
|
126
154
|
`(?:\\.${BRACKET_CANONICAL_NUMERIC_SOURCE}(?!\\d))?` +
|
|
127
|
-
`(?:-${PHASE_CONTINUATION_SEGMENT_SOURCE})
|
|
155
|
+
`(?:-${PHASE_CONTINUATION_SEGMENT_SOURCE})?` +
|
|
156
|
+
`(?=-|$)`;
|
|
128
157
|
// A phase HEADING intro under bracket is either a `[...]` bracket (optionally
|
|
129
158
|
// followed by a `Phase ` label) or a bare `Phase ` label; a bare number is NOT
|
|
130
159
|
// a phase-heading intro. The `[^\]]{1,200}` bound mirrors the existing
|
|
@@ -196,9 +225,15 @@ function getPhaseDirFromPhaseId(phaseId, phaseName, projectCode) {
|
|
|
196
225
|
const milestone = String(parseInt(m[1], 10)).padStart(2, '0');
|
|
197
226
|
const subParts = m[2].split('-').map(p => String(parseInt(p, 10)).padStart(2, '0'));
|
|
198
227
|
const sub = subParts.join('-');
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
228
|
+
// #3883 (ADR-3473 §8.3): delegate to the canonical slug formula
|
|
229
|
+
// (generateSlugInternal, core-utils.cts) rather than re-implementing it.
|
|
230
|
+
// `maxLen: null` preserves this site's pre-migration untruncated contract —
|
|
231
|
+
// the 60-char default would silently shadow one on-disk phase dir's
|
|
232
|
+
// reported phase_slug behind another distinct >60-char phase name's.
|
|
233
|
+
// Lazy require to break the core-utils.cjs <-> phase-id.cjs cycle (see the
|
|
234
|
+
// module dependency doc comment above).
|
|
235
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
|
236
|
+
const slug = phaseName ? (require('./core-utils.cjs').generateSlugInternal(phaseName, null) ?? '') : '';
|
|
202
237
|
const parts = [milestone, sub, slug].filter(Boolean);
|
|
203
238
|
const base = parts.join('-');
|
|
204
239
|
return projectCode ? `${projectCode}-${base}` : base;
|
|
@@ -306,7 +341,22 @@ function toDir(id, slug) {
|
|
|
306
341
|
const sub = id.subphase ? `.${id.subphase}` : '';
|
|
307
342
|
// Slug guard: the slug becomes an on-disk path segment, so collapse it to a
|
|
308
343
|
// safe lowercase token — never a path separator or `..` traversal.
|
|
309
|
-
|
|
344
|
+
// #3883 (ADR-3473 §8.3): delegate the sanitize formula itself to the
|
|
345
|
+
// canonical (generateSlugInternal, core-utils.cts) — this fixes the
|
|
346
|
+
// Cyrillic-collapses-to-empty defect (#2848-class) that toDir carried
|
|
347
|
+
// before (it never transliterated). The empty-sanitize and all-digit
|
|
348
|
+
// throw guards below stay: they are a DECLARED DIFFERENCE from every
|
|
349
|
+
// other slug call site, not a bug — a slug here becomes a real directory
|
|
350
|
+
// name, and toDir protects the parsePhaseId dir↔identity bijection
|
|
351
|
+
// (see the toDir docstring above) by refusing to emit an unusable name,
|
|
352
|
+
// where every other site silently accepts "" or a re-truncated value.
|
|
353
|
+
// `maxLen: null` preserves toDir's pre-migration untruncated contract — the
|
|
354
|
+
// 60-char default let two distinct >60-char phase names collapse onto the
|
|
355
|
+
// identical directory name, one silently shadowing the other on disk.
|
|
356
|
+
// Lazy require to break the core-utils.cjs <-> phase-id.cjs cycle (see the
|
|
357
|
+
// module dependency doc comment above).
|
|
358
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
|
359
|
+
const safeSlug = require('./core-utils.cjs').generateSlugInternal(slug, null) ?? '';
|
|
310
360
|
// A slug that sanitizes to nothing (e.g. '!!!') would otherwise emit a
|
|
311
361
|
// dangling trailing hyphen.
|
|
312
362
|
if (!safeSlug) {
|
|
@@ -342,6 +392,39 @@ function isSentinelPhaseId(phaseId, convention) {
|
|
|
342
392
|
return false;
|
|
343
393
|
return SENTINEL_RANGES.includes(parseInt(legacy[1], 10));
|
|
344
394
|
}
|
|
395
|
+
/**
|
|
396
|
+
* Disk-side sentinel recognizer (#3639): is this on-disk PHASE DIRECTORY a
|
|
397
|
+
* sentinel (never-on-roadmap by convention)?
|
|
398
|
+
*
|
|
399
|
+
* The disk-side guards (C001 gap numbering, W007 orphan dirs) see raw
|
|
400
|
+
* directory names and do not know the repo's naming convention — and neither
|
|
401
|
+
* convention-blind route could recognize a bracket sentinel: `isSentinelPhaseId`
|
|
402
|
+
* without the convention argument reads only the legacy leading int, while
|
|
403
|
+
* `extractPhaseToken(dirName)` (convention-aware or not) strips the MILESTONE
|
|
404
|
+
* and returns the bare phase token — bracket sentinel-ness lives in the
|
|
405
|
+
* milestone portion (`GSD.999-07-icebox` is icebox because of the 999, not
|
|
406
|
+
* the 07). This helper reads the milestone directly off the dir name.
|
|
407
|
+
*
|
|
408
|
+
* The bracket branch requires the FULL bracket dir shape — code prefix, dot,
|
|
409
|
+
* milestone digits, hyphen, PHASE DIGITS — so a #1324 letter-prefixed real
|
|
410
|
+
* dir with a LETTER slug (`P0.0-foundation`) never matches it (ADR-2121
|
|
411
|
+
* indistinguishability, same gate as extractPhaseToken below). DISCLOSED
|
|
412
|
+
* RESIDUAL (#3639 review): the #1324 family also has digit continuations
|
|
413
|
+
* (`P0.0-1-foundation`, `P0.3-2` are real shapes per derivePhaseTokenSegments),
|
|
414
|
+
* and `{code}.{0|999}-{digit}...` is string-indistinguishable from a bracket
|
|
415
|
+
* sentinel dir — no convention-free discriminator exists (ADR-2121). Such a
|
|
416
|
+
* dir reads as sentinel here, which at the disk-guard call sites suppresses
|
|
417
|
+
* a warning (conservative for a linter) rather than deleting data. The
|
|
418
|
+
* digit-continuation family with NON-sentinel first decimals (`P0.3-2`)
|
|
419
|
+
* reads milestone 3 — ordinary — exactly as the convention-gated id
|
|
420
|
+
* predicate does. Everything else falls to the legacy leading-int rule.
|
|
421
|
+
*/
|
|
422
|
+
function isSentinelPhaseDir(dirName) {
|
|
423
|
+
const bracketDir = dirName.match(/^[A-Z][A-Z0-9_]*\.(\d+)-\d/); // milestone digits + hyphen + phase DIGITS
|
|
424
|
+
if (bracketDir)
|
|
425
|
+
return SENTINEL_RANGES.includes(parseInt(bracketDir[1], 10));
|
|
426
|
+
return isSentinelPhaseId(dirName);
|
|
427
|
+
}
|
|
345
428
|
/**
|
|
346
429
|
* Render a regex source fragment matching a phase number against ROADMAP/STATE
|
|
347
430
|
* prose regardless of zero-padding on either side.
|
|
@@ -355,20 +438,24 @@ function phaseMarkdownRegexSource(phaseNum) {
|
|
|
355
438
|
const subParts = milestoneSegments[2].slice(1).split('-');
|
|
356
439
|
const subFragments = subParts.map(s => {
|
|
357
440
|
const unpadded = s.replace(/^0+/, '') || '0';
|
|
358
|
-
return `0*${escapeRegex(unpadded)}`;
|
|
441
|
+
return `0*${(0, pattern_cjs_1.escapeRegex)(unpadded)}`;
|
|
359
442
|
});
|
|
360
443
|
const suffix = milestoneSegments[3] || '';
|
|
361
|
-
const suffixFragment = suffix ? escapeRegex(suffix) : '';
|
|
362
|
-
return `0*${escapeRegex(majorUnpadded)}-${subFragments.join('-')}${suffixFragment}`;
|
|
444
|
+
const suffixFragment = suffix ? (0, pattern_cjs_1.escapeRegex)(suffix) : '';
|
|
445
|
+
return `0*${(0, pattern_cjs_1.escapeRegex)(majorUnpadded)}-${subFragments.join('-')}${suffixFragment}`;
|
|
363
446
|
}
|
|
364
447
|
// Plain numeric phase: 1, 01, 12A, 12.1
|
|
365
448
|
const match = stripped.match(/^0*(\d+)([A-Z])?((?:\.\d+)*)$/i);
|
|
449
|
+
// #3212: escapeRegex now requires a string (the seam owns coercion policy,
|
|
450
|
+
// not this module) — String(...) here preserves this function's own
|
|
451
|
+
// pre-existing `unknown` acceptance for callers that pass a non-string
|
|
452
|
+
// phaseNum through to this fallback branch.
|
|
366
453
|
if (!match)
|
|
367
|
-
return escapeRegex(phaseNum);
|
|
454
|
+
return (0, pattern_cjs_1.escapeRegex)(String(phaseNum));
|
|
368
455
|
const integer = match[1].replace(/^0+/, '') || '0';
|
|
369
|
-
const letter = match[2] ? escapeRegex(match[2]) : '';
|
|
370
|
-
const decimal = match[3] ? escapeRegex(match[3]) : '';
|
|
371
|
-
return `0*${escapeRegex(integer)}${letter}${decimal}`;
|
|
456
|
+
const letter = match[2] ? (0, pattern_cjs_1.escapeRegex)(match[2]) : '';
|
|
457
|
+
const decimal = match[3] ? (0, pattern_cjs_1.escapeRegex)(match[3]) : '';
|
|
458
|
+
return `0*${(0, pattern_cjs_1.escapeRegex)(integer)}${letter}${decimal}`;
|
|
372
459
|
}
|
|
373
460
|
/**
|
|
374
461
|
* #3599: when the caller passed a project-code-prefixed ID like `PROJ-42`,
|
|
@@ -378,7 +465,7 @@ function phaseMarkdownRegexSourceExact(phaseNum) {
|
|
|
378
465
|
const raw = String(phaseNum);
|
|
379
466
|
if (!hasProjectCodePrefix(raw))
|
|
380
467
|
return null;
|
|
381
|
-
return escapeRegex(raw);
|
|
468
|
+
return (0, pattern_cjs_1.escapeRegex)(raw);
|
|
382
469
|
}
|
|
383
470
|
function comparePhaseNum(a, b) {
|
|
384
471
|
// Strip optional project_code prefix before comparing
|
|
@@ -436,27 +523,18 @@ function comparePhaseNum(a, b) {
|
|
|
436
523
|
return 0;
|
|
437
524
|
}
|
|
438
525
|
/**
|
|
439
|
-
*
|
|
526
|
+
* Segmentation core shared by `extractPhaseToken` (the token VALUE) and
|
|
527
|
+
* `isPhaseArtifact` (the DERIVABILITY check — #3511). Factored out so the two
|
|
528
|
+
* questions ("what is this dir's token" and "did a real token exist at all")
|
|
529
|
+
* can never diverge — see CLAUDE.md's "Generative Fix Divergence" note: this
|
|
530
|
+
* is exactly a shared parser between two parallel surfaces.
|
|
531
|
+
*
|
|
532
|
+
* Returns `tokenSegments.length === 0` iff dirName's own leading segment
|
|
533
|
+
* carries no phase-number token (the `extractPhaseToken` dirName-unchanged
|
|
534
|
+
* fallback) — i.e. the directory name itself does not start with a digit or a
|
|
535
|
+
* short letter+digit prefix, so no reliable phase token can be read from it.
|
|
440
536
|
*/
|
|
441
|
-
function
|
|
442
|
-
// #612 bracket dir form `{CODE}.{MM}-{PP}[.{SS}]-slug` → phase token `PP[.SS]`.
|
|
443
|
-
// GATED on convention === 'bracket' (mirrors getMilestoneFromPhaseId's READING-B
|
|
444
|
-
// decision above). A bracket dir `{CODE}.{MM}-{PP}` is string-INDISTINGUISHABLE
|
|
445
|
-
// from the legacy #2043/#1324 letter-prefixed-decimal family (`P0.3-2`,
|
|
446
|
-
// `P0.12-34`) whenever the project code ends in a digit, so NO string-only
|
|
447
|
-
// discriminator can separate the two conventions — auto-detecting here silently
|
|
448
|
-
// reinterpreted `P0.3-2` → `2` (was `P0.3-2`), a byte-identical-read regression
|
|
449
|
-
// on this CRITICAL 6-caller helper (ADR-2121). Requiring an explicit convention
|
|
450
|
-
// signal keeps every existing (convention-less) call site byte-identical to
|
|
451
|
-
// prior behaviour — see the #2043 numeric-tail characterization in
|
|
452
|
-
// tests/phase-id.test.cjs — while keeping the helper pure (optional param, no
|
|
453
|
-
// config read). The captured token is dot-only (`PP[.SS]`); the milestone↔phase
|
|
454
|
-
// hyphen and any trailing plan/slug are excluded.
|
|
455
|
-
if (convention === 'bracket') {
|
|
456
|
-
const bracketDir = dirName.match(/^[A-Z][A-Z0-9_]*\.\d+-(\d+(?:\.\d+)?)/);
|
|
457
|
-
if (bracketDir)
|
|
458
|
-
return bracketDir[1];
|
|
459
|
-
}
|
|
537
|
+
function derivePhaseTokenSegments(dirName) {
|
|
460
538
|
const codePrefixMatch = dirName.match(PROJECT_CODE_PREFIX_CAPTURE_RE_I);
|
|
461
539
|
let prefix = '';
|
|
462
540
|
let rest = dirName;
|
|
@@ -493,18 +571,295 @@ function extractPhaseToken(dirName, convention) {
|
|
|
493
571
|
break;
|
|
494
572
|
}
|
|
495
573
|
}
|
|
496
|
-
else if (
|
|
574
|
+
else if ((firstLetterPrefixed && /^\d/.test(seg)) ||
|
|
575
|
+
(!firstLetterPrefixed && isPhaseContinuationSegment(seg))) {
|
|
497
576
|
tokenSegments.push(seg);
|
|
498
577
|
}
|
|
499
578
|
else {
|
|
500
579
|
break;
|
|
501
580
|
}
|
|
502
581
|
}
|
|
582
|
+
return { prefix, tokenSegments, firstLetterPrefixed };
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Extract the phase token from a directory name.
|
|
586
|
+
*/
|
|
587
|
+
function extractPhaseToken(dirName, convention) {
|
|
588
|
+
// #612 bracket dir form `{CODE}.{MM}-{PP}[.{SS}]-slug` → phase token `PP[.SS]`.
|
|
589
|
+
// GATED on convention === 'bracket' (mirrors getMilestoneFromPhaseId's READING-B
|
|
590
|
+
// decision above). A bracket dir `{CODE}.{MM}-{PP}` is string-INDISTINGUISHABLE
|
|
591
|
+
// from the legacy #2043/#1324 letter-prefixed-decimal family (`P0.3-2`,
|
|
592
|
+
// `P0.12-34`) whenever the project code ends in a digit, so NO string-only
|
|
593
|
+
// discriminator can separate the two conventions — auto-detecting here silently
|
|
594
|
+
// reinterpreted `P0.3-2` → `2` (was `P0.3-2`), a byte-identical-read regression
|
|
595
|
+
// on this CRITICAL 6-caller helper (ADR-2121). Requiring an explicit convention
|
|
596
|
+
// signal keeps every existing (convention-less) call site byte-identical to
|
|
597
|
+
// prior behaviour — see the #2043 numeric-tail characterization in
|
|
598
|
+
// tests/phase-id.test.cjs — while keeping the helper pure (optional param, no
|
|
599
|
+
// config read). The captured token is dot-only (`PP[.SS]`); the milestone↔phase
|
|
600
|
+
// hyphen and any trailing plan/slug are excluded.
|
|
601
|
+
if (convention === 'bracket') {
|
|
602
|
+
const bracketDir = dirName.match(/^[A-Z][A-Z0-9_]*\.\d+-(\d+(?:\.\d+)?)/);
|
|
603
|
+
if (bracketDir)
|
|
604
|
+
return bracketDir[1];
|
|
605
|
+
}
|
|
606
|
+
const { prefix, tokenSegments, firstLetterPrefixed } = derivePhaseTokenSegments(dirName);
|
|
503
607
|
if (tokenSegments.length === 0) {
|
|
504
608
|
return dirName;
|
|
505
609
|
}
|
|
610
|
+
// #2528 (re-review): the tokenizer deliberately does NOT try to tell a 2-digit
|
|
611
|
+
// slug word ("24" of "24/7 Autonomy") from a genuine zero-padded continuation
|
|
612
|
+
// ("24" of sub-phase 10.24) — by width alone they are the same string, the gap
|
|
613
|
+
// between #2043's 1-digit and #2232's ≥3-digit guards, and no LOCAL signal
|
|
614
|
+
// separates them. An earlier revision of this fix rewound the token when the
|
|
615
|
+
// segment that stopped the scan was a 1-digit word, which reads
|
|
616
|
+
// "10-24-7-autonomy" correctly but silently re-tokenizes the equally real
|
|
617
|
+
// "10-24-7-zip" (sub-phase 10.24 named "7-Zip …") from "10-24" to "10" — it
|
|
618
|
+
// trades the reported ambiguity for the symmetric one a level down, on a
|
|
619
|
+
// CRITICAL 15-caller chokepoint whose output feeds query-less derivations
|
|
620
|
+
// (STATE.md phase counts, W007, the #2562 key surface).
|
|
621
|
+
//
|
|
622
|
+
// So the token stays the LITERAL reading of the name, and disambiguation lives
|
|
623
|
+
// ONE layer up, in matchPhaseDirs, where a QUERY exists to disambiguate
|
|
624
|
+
// against: a bare-integer lookup falls back to the directory's leading digit
|
|
625
|
+
// run and resolves "10-24-7-autonomy" for "10" without touching what the
|
|
626
|
+
// directory's own token is. That is the same bounded mechanism the
|
|
627
|
+
// "05-80-20-cleanup" shape already uses — one rule for the whole
|
|
628
|
+
// digit-leading-slug family instead of two overlapping ones.
|
|
629
|
+
//
|
|
630
|
+
// A generated slug is lowercase. If the owner admitted a two-digit prefix
|
|
631
|
+
// from a digit+letter slug segment ("10x", "25abc"), remove only that final
|
|
632
|
+
// segment. Uppercase suffixes remain available to the established plan-ID
|
|
633
|
+
// grammar, and dotted continuations remain intact.
|
|
634
|
+
if (!firstLetterPrefixed &&
|
|
635
|
+
tokenSegments.length > 1 &&
|
|
636
|
+
/^\d{2}[a-z][a-z0-9]*$/.test(tokenSegments[tokenSegments.length - 1])) {
|
|
637
|
+
tokenSegments.pop();
|
|
638
|
+
}
|
|
506
639
|
return prefix + tokenSegments.join('-');
|
|
507
640
|
}
|
|
641
|
+
/**
|
|
642
|
+
* #3511 (reworked — adversarial review found the membership rule wrong in
|
|
643
|
+
* approach, not just detail): predicate for AGGREGATE phase-directory scans
|
|
644
|
+
* (every matching file contributes, e.g.
|
|
645
|
+
* uat-predicate/phase.cts/state.cts/uat.cts/audit.cts's `*-UAT.md` /
|
|
646
|
+
* `*-VERIFICATION.md` scans) — answers "does fileName belong to THIS phase"
|
|
647
|
+
* so a stray, cross-phase, or ad-hoc file (`04-VERIFICATION.md` sitting in
|
|
648
|
+
* phase 03's directory) cannot contribute its status to phase 03. This is
|
|
649
|
+
* deliberately NOT `resolveVerificationFile` (`src/verification.cts`,
|
|
650
|
+
* #3357/#3492) — that resolver answers a SINGLE-PICK question ("which one
|
|
651
|
+
* candidate is THE report") for a phase dir already known to hold one; this
|
|
652
|
+
* answers a per-file membership question for a scan that must fold in EVERY
|
|
653
|
+
* match. See "Reconciliation" below — the two do NOT fully agree.
|
|
654
|
+
*
|
|
655
|
+
* THE ORIGINAL BUG: files are named by `normalizePhaseName`
|
|
656
|
+
* (`cmdScaffold`, `src/commands.cts` — PADDED, project-code-STRIPPED), while
|
|
657
|
+
* this predicate read the directory's OWN token via the literal, unpadded,
|
|
658
|
+
* project-code-CARRYING `extractPhaseToken(phaseDirName)`. Two different
|
|
659
|
+
* normalizations of the same phase number, so a literal
|
|
660
|
+
* `startsWith(token + '-')` excluded a phase's own artifacts whenever they
|
|
661
|
+
* disagreed: `CK-01-foundation` (token `CK-01`, file `01-VERIFICATION.md`),
|
|
662
|
+
* `1-unpadded` (token `1`, file `01-VERIFICATION.md`), and the #2528
|
|
663
|
+
* digit-leading-slug family `05-80-20-cleanup` / `10-24-7-autonomy` (token
|
|
664
|
+
* over-absorbs past the digit run `cmdScaffold` actually writes: `05-80-20`
|
|
665
|
+
* vs the real `05-UAT.md`).
|
|
666
|
+
*
|
|
667
|
+
* THE FIX: build the set of every phase-number READING this directory could
|
|
668
|
+
* plausibly resolve to elsewhere in the module, then check fileName against
|
|
669
|
+
* ALL of them — reusing the exact readings `matchPhaseDirs` /
|
|
670
|
+
* `phaseNumberForMatch` (#2528, below) already carry for directory
|
|
671
|
+
* RESOLUTION, so this membership check can never diverge from what "the
|
|
672
|
+
* directory for phase N" means elsewhere in the module (no third
|
|
673
|
+
* normalization — CLAUDE.md's Generative Fix Divergence class):
|
|
674
|
+
* 1. the literal token (`extractPhaseToken(phaseDirName)` — still correct
|
|
675
|
+
* for the common case and for genuine decimal / letter-suffixed
|
|
676
|
+
* sub-phase dirs);
|
|
677
|
+
* 2. the same token read off the project-code-STRIPPED name
|
|
678
|
+
* (`stripProjectCodePrefix` — the exact fallback `phaseTokenMatches`
|
|
679
|
+
* already applies, #612/#1324);
|
|
680
|
+
* 3. the directory's own LEADING DIGIT RUN on the stripped name
|
|
681
|
+
* (`LEADING_DIGIT_RUN_RE` — the #2528 bare-integer-fallback reading,
|
|
682
|
+
* the one that actually matches what `cmdScaffold` writes for the
|
|
683
|
+
* digit-leading-slug family); and
|
|
684
|
+
* 4. each of (1)-(3) additionally passed through `normalizePhaseName`,
|
|
685
|
+
* since files always carry the PADDED form and directories often do
|
|
686
|
+
* not (`1-unpadded` vs `01-...`).
|
|
687
|
+
* A file belongs when it starts with any candidate + `-` OR any candidate +
|
|
688
|
+
* `.`, compared case-insensitively (matching `phaseTokenMatches`' own rule —
|
|
689
|
+
* review item 8: `03A-VERIFICATION.md` vs `03a-foo`). This is a PREFIX check,
|
|
690
|
+
* not a full-token equality — `03-01-SUMMARY.md` (phase 03, plan 01) must
|
|
691
|
+
* still match dir `03-foo` on candidate `03`, even though
|
|
692
|
+
* `extractPhaseToken('03-01-SUMMARY.md')` would (wrongly, for this purpose)
|
|
693
|
+
* read `03-01` as a mis-absorbed 2-digit continuation.
|
|
694
|
+
*
|
|
695
|
+
* DOTTED SUB-PHASE CONTINUATION: the dot arm of the check exists because this
|
|
696
|
+
* module's own token grammar (`PHASE_NUMBER_TOKEN_SOURCE`) admits a dotted
|
|
697
|
+
* sub-phase continuation (`(?:\.\d+)*`) alongside dash-continuations — a
|
|
698
|
+
* sub-phase artifact `01.1-CONTEXT.md` is `01`'s own file, written into `01`'s
|
|
699
|
+
* directory, not a stray from a different phase. A dash-only prefix check
|
|
700
|
+
* excluded it (`01.1-` does not start with `01-`), which is over-exclusion:
|
|
701
|
+
* the dangerous direction for an aggregate scan whose fail-safes above all
|
|
702
|
+
* default to inclusion when membership is unclear. Widening dash-only to
|
|
703
|
+
* dash-OR-dot only ever ADDS a match a candidate already earned; it cannot
|
|
704
|
+
* newly admit a file whose leading digits differ from `candidate`, so it
|
|
705
|
+
* cannot resolve a genuinely different phase's artifact (`02.1-...` still
|
|
706
|
+
* fails every `01`-rooted candidate).
|
|
707
|
+
*
|
|
708
|
+
* BRACKET CONVENTION (review item 7): a letter-prefixed-decimal dir
|
|
709
|
+
* (`P0.3-2-slug`) is string-INDISTINGUISHABLE from a bracket-dir token
|
|
710
|
+
* (`extractPhaseToken` above, gated on `convention === 'bracket'`) without an
|
|
711
|
+
* explicit convention signal — and this predicate is never given one: none
|
|
712
|
+
* of its 9 call sites thread `convention`/config through today. Rather than
|
|
713
|
+
* guess a reading it cannot know is active and risk excluding the phase's OWN
|
|
714
|
+
* artifact (the exact defect class this rework exists to fix), this family
|
|
715
|
+
* (`firstLetterPrefixed` dirs) falls into the same include-everything
|
|
716
|
+
* fail-safe as the zero-segment case below — a documented, deliberate
|
|
717
|
+
* widening (it also stops excluding a genuine stray from a DIFFERENT
|
|
718
|
+
* letter-prefixed-decimal phase, narrowly) accepted in trade for never
|
|
719
|
+
* dropping the phase's own report. Convention-aware scoping for this family
|
|
720
|
+
* is deferred to whenever a call site actually threads `convention` through.
|
|
721
|
+
*
|
|
722
|
+
* FAIL-SAFE (#3511, unchanged): when dirName's own leading segment carries no
|
|
723
|
+
* phase-number token at all (`derivePhaseTokenSegments` finds zero segments —
|
|
724
|
+
* the same condition `extractPhaseToken` treats as "return dirName
|
|
725
|
+
* unchanged"), no reliable token exists to scope against. Excluding on an
|
|
726
|
+
* unreliable token would make an aggregate gate silently PERMISSIVE in the
|
|
727
|
+
* wrong direction — dropping the phase's own real blockers — which is worse
|
|
728
|
+
* than the cross-phase-contamination bug this predicate exists to fix.
|
|
729
|
+
* Instead every file is treated as belonging to the phase (returns `true`
|
|
730
|
+
* unconditionally), matching pre-fix (unscoped) behaviour for that directory.
|
|
731
|
+
*
|
|
732
|
+
* FIX 2 — bare `VERIFICATION.md` / `UAT.md` (no dash, no token of its own):
|
|
733
|
+
* `derivePhaseTokenSegments(fileName)` also finds zero segments for these —
|
|
734
|
+
* the file carries no phase number to compare against anything. Directory
|
|
735
|
+
* containment is the only signal available for a token-less file, and it is
|
|
736
|
+
* sufficient: every call site passes `fs.readdirSync` results for ONE
|
|
737
|
+
* specific phase dir, so a token-less candidate already reaching this
|
|
738
|
+
* predicate (past each call site's own verification/UAT suffix pre-filter)
|
|
739
|
+
* is, by construction, that phase's own
|
|
740
|
+
* listing. Returns `true` unconditionally, same as the dir-side fail-safe.
|
|
741
|
+
*
|
|
742
|
+
* RECONCILIATION WITH resolveVerificationFile (#3357/#3492/#3511) — the two
|
|
743
|
+
* surfaces now AGREE. `resolveVerificationFile`'s fallback (`verification.cts`,
|
|
744
|
+
* "Fallback" step in its own docblock) filters its dashed candidates through
|
|
745
|
+
* THIS predicate — `isPhaseArtifact(f, phaseDirName)` — before picking
|
|
746
|
+
* alphabetically-first, via a new `phaseDirName` option every call site
|
|
747
|
+
* threads in (the same basename each already derives for `phaseToken`). So a
|
|
748
|
+
* stray cross-phase file can no longer win the single-pick fallback either:
|
|
749
|
+
* it is excluded there for the identical reason it is excluded from the
|
|
750
|
+
* aggregate scans here — membership, not canonical shape. The fail-safes stay
|
|
751
|
+
* aligned too: when this predicate cannot determine membership for a
|
|
752
|
+
* directory (returns `true` unconditionally — see FAIL-SAFE above),
|
|
753
|
+
* `resolveVerificationFile`'s filter is a no-op and its fallback degrades to
|
|
754
|
+
* the original pre-#3357 "alphabetically first of ALL dashed candidates"
|
|
755
|
+
* behavior, exactly as it always did for that directory shape.
|
|
756
|
+
*/
|
|
757
|
+
function isPhaseArtifact(fileName, phaseDirName) {
|
|
758
|
+
const { tokenSegments, firstLetterPrefixed } = derivePhaseTokenSegments(phaseDirName);
|
|
759
|
+
if (tokenSegments.length === 0)
|
|
760
|
+
return true;
|
|
761
|
+
const literalToken = extractPhaseToken(phaseDirName);
|
|
762
|
+
const strippedDir = stripProjectCodePrefix(phaseDirName);
|
|
763
|
+
const strippedToken = strippedDir !== phaseDirName ? extractPhaseToken(strippedDir) : literalToken;
|
|
764
|
+
const leadingRunMatch = strippedDir.match(LEADING_DIGIT_RUN_RE);
|
|
765
|
+
const rawCandidates = [literalToken, strippedToken, leadingRunMatch?.[1]].filter((t) => Boolean(t));
|
|
766
|
+
// Each reading is compared in BOTH its padded and de-padded form: files are
|
|
767
|
+
// written padded by `normalizePhaseName` (`cmdScaffold`) while directories
|
|
768
|
+
// are often not (`1-unpadded`), and legacy trees carry the reverse pairing.
|
|
769
|
+
// De-padding is numeric-only — a token with a letter suffix or a dotted
|
|
770
|
+
// sub-phase (`03A`, `03.1`) has no meaningful de-padded form and is left
|
|
771
|
+
// alone, so this only ever ADDS a reading and can never drop one.
|
|
772
|
+
const depad = (t) => (/^\d+$/.test(t) ? String(Number(t)) : t);
|
|
773
|
+
const candidates = new Set(rawCandidates
|
|
774
|
+
.flatMap(t => [t, normalizePhaseName(t), depad(t)])
|
|
775
|
+
.map(t => t.toUpperCase()));
|
|
776
|
+
const fileUpper = fileName.toUpperCase();
|
|
777
|
+
for (const candidate of candidates) {
|
|
778
|
+
// A dotted sub-phase segment (e.g. `01.1-CONTEXT.md`) is a legitimate
|
|
779
|
+
// continuation of `candidate` per this module's own token grammar
|
|
780
|
+
// (PHASE_NUMBER_TOKEN_SOURCE admits `(?:\.\d+)*`), so it belongs to
|
|
781
|
+
// `candidate`'s own directory just as a dash-continuation does. Inclusion
|
|
782
|
+
// is the safe direction for these aggregate scans (see FAIL-SAFE above) —
|
|
783
|
+
// widening a dash-only check to dash-OR-dot never drops a genuine match,
|
|
784
|
+
// it only stops wrongly excluding one.
|
|
785
|
+
//
|
|
786
|
+
// Accepted separator class after a matched candidate: `-`, `.`, or `_`.
|
|
787
|
+
// The underscore was added for state.cts's `cmdStateValidate` S006/S007
|
|
788
|
+
// scan, whose own pre-filter is deliberately broader than the dashed
|
|
789
|
+
// grammar every other call site uses (`.includes('VERIFICATION')`, no
|
|
790
|
+
// dash required — see the WARNING-4 comment there), so it admits names
|
|
791
|
+
// like `03_VERIFICATION.md`. Before this predicate accepted `_` as a
|
|
792
|
+
// boundary, such a file failed the `-`/`.`-only check here even though
|
|
793
|
+
// its digits matched `candidate` exactly, and `scopeToPhase` dropped it —
|
|
794
|
+
// a real same-phase verification report reported as absent. Widening the
|
|
795
|
+
// separator class only ever EXTENDS a candidate whose digits already
|
|
796
|
+
// match exactly; it cannot admit a genuinely different phase's file,
|
|
797
|
+
// since the candidate comparison itself is unchanged.
|
|
798
|
+
if (fileUpper.startsWith(`${candidate}-`) ||
|
|
799
|
+
fileUpper.startsWith(`${candidate}.`) ||
|
|
800
|
+
fileUpper.startsWith(`${candidate}_`))
|
|
801
|
+
return true;
|
|
802
|
+
}
|
|
803
|
+
// FIX 2: token-less filename (bare "VERIFICATION.md"/"UAT.md") — containment
|
|
804
|
+
// in this phase's own directory listing is sufficient.
|
|
805
|
+
if (derivePhaseTokenSegments(fileName).tokenSegments.length === 0)
|
|
806
|
+
return true;
|
|
807
|
+
// Bracket-convention ambiguity fail-safe — see docblock above.
|
|
808
|
+
if (firstLetterPrefixed)
|
|
809
|
+
return true;
|
|
810
|
+
return false;
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* #3511: scope `fileNames` to the subset that passes
|
|
814
|
+
* `isPhaseArtifact(fileName, phaseDirName)`. The single seam every
|
|
815
|
+
* phase-directory scan routes through, so the membership rule has ONE owner.
|
|
816
|
+
*
|
|
817
|
+
* AN EMPTY RESULT IS A REAL ANSWER — deliberately, and this is the hard-won
|
|
818
|
+
* part. An earlier revision of this helper carried an extra rule ("scoping
|
|
819
|
+
* must never turn a non-empty set into an empty one": if the filter removed
|
|
820
|
+
* every file, return the unfiltered input). It was added to rescue a
|
|
821
|
+
* directory whose basename merely PARSES phase-shaped —
|
|
822
|
+
* `gsd-651-broad-grep-a1b2`, an `mkdtemp`-style fixture name that
|
|
823
|
+
* `extractPhaseToken` reads as project code `gsd` + phase `651` (the capture
|
|
824
|
+
* regex is case-INSENSITIVE) — holding only `01-bg-VERIFICATION.md`, which
|
|
825
|
+
* the filter then dropped, yielding an empty set indistinguishable from "no
|
|
826
|
+
* report exists".
|
|
827
|
+
*
|
|
828
|
+
* That rescue was wrong, and no local rule can make it right: a directory
|
|
829
|
+
* whose own name says phase 651 holding only a file that says phase 01 is
|
|
830
|
+
* STRING-INDISTINGUISHABLE from `03-foo/` holding only `04-VERIFICATION.md`
|
|
831
|
+
* — the exact cross-phase stray #3511 exists to exclude. Keeping the rule
|
|
832
|
+
* meant a real phase directory holding only a MISFILED report would resolve
|
|
833
|
+
* to it and publish another phase's `passed` as its own: the reported bug, in
|
|
834
|
+
* its single most damaging form. `missing` is the correct answer when a
|
|
835
|
+
* phase's own report is genuinely absent, and every caller already has a
|
|
836
|
+
* `missing`/`null` branch for it.
|
|
837
|
+
*
|
|
838
|
+
* The over-exclusion that rule was reaching for is instead handled where it
|
|
839
|
+
* is actually determinable, inside `isPhaseArtifact`: the zero-token dir
|
|
840
|
+
* fail-safe, the `firstLetterPrefixed` bracket-ambiguity fail-safe, the
|
|
841
|
+
* token-less-filename rule, and the multi-reading candidate set (literal /
|
|
842
|
+
* project-code-stripped / leading-digit-run, each also padded AND de-padded)
|
|
843
|
+
* that covers every normalization a phase directory and its files can
|
|
844
|
+
* legitimately disagree on. A file excluded after all of those genuinely
|
|
845
|
+
* names a different phase.
|
|
846
|
+
*
|
|
847
|
+
* SITE DISCIPLINE: every aggregate-scan call site (`uat.cts`,
|
|
848
|
+
* `uat-predicate.cts`, `phase.cts`, `audit.cts`, `state.cts`,
|
|
849
|
+
* `core-utils.cts`'s `getPhaseFileStats` — #3511 BLOCKER-2 — and
|
|
850
|
+
* `init.cts`'s two phase-info-projection sites — #3511 BLOCKER-3, both of
|
|
851
|
+
* which scope the raw listing once up front and reuse it for every bare
|
|
852
|
+
* `.find()`/`.some()` artifact predicate: context/research/UAT/reviews/
|
|
853
|
+
* patterns) and `resolveVerificationFile`'s single-pick fallback
|
|
854
|
+
* (`verification.cts`) MUST route through this helper rather than calling
|
|
855
|
+
* `isPhaseArtifact` in a filter position directly, so the rule cannot be
|
|
856
|
+
* re-derived per site (CLAUDE.md's Generative Fix Divergence class).
|
|
857
|
+
* `isPhaseArtifact` stays exported for single-item membership questions and
|
|
858
|
+
* its own unit tests.
|
|
859
|
+
*/
|
|
860
|
+
function scopeToPhase(fileNames, phaseDirName) {
|
|
861
|
+
return fileNames.filter((f) => isPhaseArtifact(f, phaseDirName));
|
|
862
|
+
}
|
|
508
863
|
/**
|
|
509
864
|
* Check if a directory name's phase token matches the normalized phase exactly.
|
|
510
865
|
*/
|
|
@@ -520,6 +875,119 @@ function phaseTokenMatches(dirName, normalized) {
|
|
|
520
875
|
}
|
|
521
876
|
return false;
|
|
522
877
|
}
|
|
878
|
+
/**
|
|
879
|
+
* #2528: the LEADING DIGIT RUN of a directory name — the fragment the
|
|
880
|
+
* bare-integer fallback selects on, and the one `phaseNumberForMatch` then
|
|
881
|
+
* displays. Named (per this module's convention of naming grammar fragments
|
|
882
|
+
* rather than inlining them) because the two sites must not drift: selecting on
|
|
883
|
+
* one run and displaying another would resolve a directory and then label it
|
|
884
|
+
* with a number that never matched.
|
|
885
|
+
*
|
|
886
|
+
* `LEADING_DIGIT_RUN_RE` anchors a trailing `-`-or-end so the run is a whole
|
|
887
|
+
* segment; `_PREFIX` is the same run without that boundary, for reading the run
|
|
888
|
+
* back off a name already known to match.
|
|
889
|
+
*/
|
|
890
|
+
const LEADING_DIGIT_RUN_SOURCE = '\\d+';
|
|
891
|
+
const LEADING_DIGIT_RUN_RE = new RegExp(`^(${LEADING_DIGIT_RUN_SOURCE})(?:-|$)`);
|
|
892
|
+
const LEADING_DIGIT_RUN_PREFIX_RE = new RegExp(`^${LEADING_DIGIT_RUN_SOURCE}`);
|
|
893
|
+
const BARE_INTEGER_RE = new RegExp(`^${LEADING_DIGIT_RUN_SOURCE}$`);
|
|
894
|
+
/** Strip leading zeros for numeric-equality compare, keeping a lone "0". */
|
|
895
|
+
const unpad = (digits) => digits.replace(/^0+(?=\d)/, '');
|
|
896
|
+
/**
|
|
897
|
+
* #2528: the CANONICAL phase-directory match selection — the one rule every
|
|
898
|
+
* directory-resolution path (the shared locator plus the `find-phase` and
|
|
899
|
+
* `phase-plan-index` command scans) applies to a candidate dir list. Extracted
|
|
900
|
+
* here because the surrounding scan/ambiguity/shaping code exists per site and
|
|
901
|
+
* had already diverged; the selection itself must not.
|
|
902
|
+
*
|
|
903
|
+
* Two passes:
|
|
904
|
+
* 1. PRIMARY — exact token match (`phaseTokenMatches`), unchanged behavior.
|
|
905
|
+
* 2. BARE-INTEGER FALLBACK — only when the primary pass matched NOTHING and
|
|
906
|
+
* the query is a bare integer, re-filter by each directory's own LEADING
|
|
907
|
+
* digit run (zero-padded compare). This catches digit-leading slug shapes
|
|
908
|
+
* the tokenizer cannot disambiguate from genuine sub-phase segments
|
|
909
|
+
* (e.g. "05-80-20-cleanup", phase 5 named "80/20 Cleanup", whose token
|
|
910
|
+
* "05-80-20" is byte-identical in shape to a real deep-decomposition dir).
|
|
911
|
+
* The fallback can only turn a silent not-found into a resolution or into
|
|
912
|
+
* a surfaced ambiguity (callers keep their #2237 multi-match guards) —
|
|
913
|
+
* never override a primary match.
|
|
914
|
+
*
|
|
915
|
+
* SCOPE, precisely (#2528 re-review). Non-bare QUERIES ("46-6", "12A",
|
|
916
|
+
* "PROJ-42") never enter the fallback, so nothing changes about how a
|
|
917
|
+
* deep-decomposition or letter-suffix lookup is asked. What DOES change is the
|
|
918
|
+
* DIRECTORY side: a bare query now reaches directories the tokenizer classified
|
|
919
|
+
* as multi-segment, and a genuine sub-phase directory has exactly that shape.
|
|
920
|
+
* So `5` against a lone `05-01-auth` resolves (phase_number "05", phase_name
|
|
921
|
+
* "01-auth") where it previously found nothing.
|
|
922
|
+
*
|
|
923
|
+
* That widening is DELIBERATE and it is irreducible from directory names alone.
|
|
924
|
+
* `05-01-auth` (sub-phase 5.1) and `30-12-factor-refactor` (phase 30 named
|
|
925
|
+
* "12-Factor Refactor") are the same string shape — `NN-NN-<slug>` — and the
|
|
926
|
+
* discriminator that would separate them, "is the second segment a valid decimal
|
|
927
|
+
* sub-phase", accepts both (`5.1` and `30.12` are equally well-formed). Any rule
|
|
928
|
+
* strong enough to exclude `05-01-auth` also excludes `30-12-factor-refactor`,
|
|
929
|
+
* which is the defect #2528 exists to fix. The tie is therefore broken in favour
|
|
930
|
+
* of resolving, and the consequence is bounded on the side that matters: when
|
|
931
|
+
* BOTH readings have a directory (`05-01-auth` + `05-02-api`) the result is two
|
|
932
|
+
* matches. `tests/phase-resolution-parity.test.cjs` pins both directions: the
|
|
933
|
+
* lone-directory resolution and the two-directory refusal.
|
|
934
|
+
*
|
|
935
|
+
* WHAT IS SHARED IS SELECTION, NOT AMBIGUITY POLICY. This function is the one
|
|
936
|
+
* owner of "which directories does this query name". What a caller does with
|
|
937
|
+
* two of them stays the caller's own decision, and the callers split in two
|
|
938
|
+
* tiers on purpose:
|
|
939
|
+
*
|
|
940
|
+
* REFUSE on `matches.length > 1` — `searchPhaseInDir`, `cmdFindPhase`,
|
|
941
|
+
* `cmdPhasePlanIndex`, `cmdPhaseRemove`. These either act destructively or
|
|
942
|
+
* answer "which phase is this", so guessing is worse than reporting the
|
|
943
|
+
* candidates (#2237).
|
|
944
|
+
*
|
|
945
|
+
* TAKE `matches[0]` — `cmdPhasesList`, `cmdInitManager`, `cmdRoadmapAnalyze`,
|
|
946
|
+
* `cmdVerifySchemaDrift`, `detectVerifyFailed`. Each read a directory to
|
|
947
|
+
* DECORATE a row they are already emitting; each used `.find()` before this
|
|
948
|
+
* PR, so first-match is their prior behavior preserved verbatim, and each is
|
|
949
|
+
* order-stable because the directory list is sorted and this function filters
|
|
950
|
+
* without reordering.
|
|
951
|
+
*
|
|
952
|
+
* The honest caveat on that second tier: the bare-number fallback makes
|
|
953
|
+
* multi-match newly REACHABLE for inputs that previously found nothing, so those
|
|
954
|
+
* five can now silently pick one of several candidates where they used to report
|
|
955
|
+
* not-found. That is a widening of an existing first-match rule, not a new rule
|
|
956
|
+
* — but it is a widening, and promoting any of them to refusal is a UX decision
|
|
957
|
+
* about their own output, not a change to selection, so it does not belong here.
|
|
958
|
+
*
|
|
959
|
+
* `usedBareFallback` tells callers to derive the displayed phase number from
|
|
960
|
+
* the directory's leading digit run instead of `extractPhaseToken` (whose
|
|
961
|
+
* token for these dirs is the mis-absorbed multi-segment form).
|
|
962
|
+
*/
|
|
963
|
+
function matchPhaseDirs(dirs, normalized) {
|
|
964
|
+
const primary = dirs.filter(d => phaseTokenMatches(d, normalized));
|
|
965
|
+
if (primary.length > 0)
|
|
966
|
+
return { matches: primary, usedBareFallback: false };
|
|
967
|
+
const bare = String(normalized);
|
|
968
|
+
if (!BARE_INTEGER_RE.test(bare))
|
|
969
|
+
return { matches: primary, usedBareFallback: false };
|
|
970
|
+
const want = unpad(bare);
|
|
971
|
+
const fallback = dirs.filter(d => {
|
|
972
|
+
const m = stripProjectCodePrefix(d).match(LEADING_DIGIT_RUN_RE);
|
|
973
|
+
return m !== null && unpad(m[1]) === want;
|
|
974
|
+
});
|
|
975
|
+
return { matches: fallback, usedBareFallback: fallback.length > 0 };
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* #2528: the display phase number for a directory selected by matchPhaseDirs.
|
|
979
|
+
* Primary matches keep the extracted token; bare-fallback matches use the
|
|
980
|
+
* directory's leading digit run (the whole point of the fallback is that the
|
|
981
|
+
* extracted token is wrong for these dirs).
|
|
982
|
+
*/
|
|
983
|
+
function phaseNumberForMatch(dirName, usedBareFallback) {
|
|
984
|
+
if (!usedBareFallback)
|
|
985
|
+
return extractPhaseToken(dirName);
|
|
986
|
+
const stripped = stripProjectCodePrefix(dirName);
|
|
987
|
+
const prefix = dirName.slice(0, dirName.length - stripped.length);
|
|
988
|
+
const m = stripped.match(LEADING_DIGIT_RUN_PREFIX_RE);
|
|
989
|
+
return m ? prefix + m[0] : extractPhaseToken(dirName);
|
|
990
|
+
}
|
|
523
991
|
// ─── Canonical phase KEY surface (#2562) ─────────────────────────────────────
|
|
524
992
|
//
|
|
525
993
|
// A phase "key" is the padding-, case- and project-code-insensitive identity of
|
|
@@ -699,10 +1167,11 @@ function roadmapPhaseLookupSources(phaseNum) {
|
|
|
699
1167
|
return [...new Set(sources)];
|
|
700
1168
|
}
|
|
701
1169
|
module.exports = {
|
|
702
|
-
escapeRegex,
|
|
703
1170
|
OPTIONAL_PROJECT_CODE_PREFIX_SOURCE,
|
|
704
1171
|
OPTIONAL_PHASE_TAG_SOURCE,
|
|
705
1172
|
PHASE_NUMBER_TOKEN_SOURCE,
|
|
1173
|
+
CASE_FLEXIBLE_PROJECT_CODE_PREFIX_SOURCE,
|
|
1174
|
+
CASE_FLEXIBLE_PHASE_NUMBER_TOKEN_SOURCE,
|
|
706
1175
|
PHASE_CONTINUATION_SEGMENT_SOURCE,
|
|
707
1176
|
isPhaseContinuationSegment,
|
|
708
1177
|
BRACKET_PHASE_TOKEN_SOURCE,
|
|
@@ -716,11 +1185,16 @@ module.exports = {
|
|
|
716
1185
|
toDir,
|
|
717
1186
|
SENTINEL_RANGES,
|
|
718
1187
|
isSentinelPhaseId,
|
|
1188
|
+
isSentinelPhaseDir,
|
|
719
1189
|
phaseMarkdownRegexSource,
|
|
720
1190
|
phaseMarkdownRegexSourceExact,
|
|
721
1191
|
comparePhaseNum,
|
|
722
1192
|
extractPhaseToken,
|
|
1193
|
+
isPhaseArtifact,
|
|
1194
|
+
scopeToPhase,
|
|
723
1195
|
phaseTokenMatches,
|
|
1196
|
+
matchPhaseDirs,
|
|
1197
|
+
phaseNumberForMatch,
|
|
724
1198
|
phaseKeyFromToken,
|
|
725
1199
|
phaseKeyFromDir,
|
|
726
1200
|
phaseKeyFromProse,
|