@opengsd/gsd-core 1.13.0 → 1.15.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/README.ja-JP.md +3 -3
- package/README.ko-KR.md +3 -3
- package/README.pt-BR.md +3 -3
- package/README.zh-CN.md +3 -3
- package/agents/gsd-advisor-researcher.compact.md +85 -0
- package/agents/gsd-ai-researcher.compact.md +96 -0
- package/agents/gsd-assumptions-analyzer.compact.md +81 -0
- package/agents/gsd-code-fixer.compact.md +459 -0
- package/agents/gsd-code-fixer.md +9 -8
- package/agents/gsd-code-reviewer.compact.md +269 -0
- package/agents/gsd-code-reviewer.md +15 -3
- package/agents/gsd-codebase-mapper.compact.md +760 -0
- package/agents/gsd-debug-session-manager.compact.md +360 -0
- package/agents/gsd-debug-session-manager.md +17 -2
- package/agents/gsd-debugger.md +2 -2
- package/agents/gsd-doc-classifier.compact.md +192 -0
- package/agents/gsd-doc-synthesizer.compact.md +200 -0
- package/agents/gsd-doc-verifier.compact.md +143 -0
- package/agents/gsd-doc-writer.compact.md +440 -0
- package/agents/gsd-dom-verifier.compact.md +138 -0
- package/agents/gsd-domain-researcher.compact.md +141 -0
- package/agents/gsd-eval-auditor.compact.md +160 -0
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-eval-planner.compact.md +137 -0
- package/agents/gsd-executor.md +13 -8
- package/agents/gsd-framework-selector.compact.md +82 -0
- package/agents/gsd-integration-checker.compact.md +245 -0
- package/agents/gsd-intel-updater.compact.md +226 -0
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.compact.md +45 -0
- package/agents/gsd-nyquist-auditor.compact.md +179 -0
- package/agents/gsd-pattern-mapper.compact.md +275 -0
- package/agents/gsd-phase-researcher.md +19 -11
- package/agents/gsd-plan-checker.md +8 -7
- package/agents/gsd-planner.md +12 -8
- package/agents/gsd-project-researcher.compact.md +587 -0
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.compact.md +212 -0
- package/agents/gsd-research-synthesizer.md +1 -1
- package/agents/gsd-roadmapper.compact.md +454 -0
- package/agents/gsd-roadmapper.md +13 -0
- package/agents/gsd-security-auditor.compact.md +162 -0
- package/agents/gsd-ui-auditor.compact.md +404 -0
- package/agents/gsd-ui-auditor.md +155 -17
- package/agents/gsd-ui-checker.compact.md +277 -0
- package/agents/gsd-ui-researcher.compact.md +282 -0
- package/agents/gsd-ui-researcher.md +1 -1
- package/agents/gsd-user-profiler.compact.md +108 -0
- package/agents/gsd-verifier.md +10 -9
- package/bin/install.js +848 -163
- package/commands/gsd/autonomous.md +2 -2
- package/commands/gsd/capture.md +1 -1
- package/commands/gsd/cleanup.md +1 -0
- package/commands/gsd/code-review.md +2 -1
- package/commands/gsd/complete-milestone.md +1 -0
- package/commands/gsd/config.md +1 -0
- package/commands/gsd/debug.md +1 -0
- package/commands/gsd/graphify.md +1 -0
- package/commands/gsd/health.md +1 -0
- package/commands/gsd/mempalace-capture.md +8 -3
- package/commands/gsd/mempalace-recall.md +1 -0
- package/commands/gsd/new-milestone.md +1 -0
- package/commands/gsd/new-project.md +1 -0
- package/commands/gsd/next.md +1 -0
- package/commands/gsd/pause-work.md +1 -0
- package/commands/gsd/phase.md +1 -0
- package/commands/gsd/plan-review-convergence.md +6 -6
- package/commands/gsd/pr-branch.md +1 -0
- package/commands/gsd/progress.md +1 -1
- package/commands/gsd/quick-batch.md +1 -1
- package/commands/gsd/resume-work.md +1 -0
- package/commands/gsd/review-backlog.md +1 -0
- package/commands/gsd/review.md +2 -3
- package/commands/gsd/settings.md +2 -1
- package/commands/gsd/stats.md +1 -0
- package/commands/gsd/thread.md +1 -0
- package/commands/gsd/workspace.md +1 -0
- package/commands/gsd/workstreams.md +1 -0
- package/gsd-core/bin/check-latest-version.cjs +8 -3
- package/gsd-core/bin/gsd-tools.cjs +672 -146
- package/gsd-core/bin/lib/adr-parser.cjs +4 -2
- package/gsd-core/bin/lib/artifacts.cjs +2 -1
- package/gsd-core/bin/lib/audit.cjs +119 -34
- package/gsd-core/bin/lib/broken-windows.cjs +168 -49
- package/gsd-core/bin/lib/capability-lifecycle.cjs +10 -6
- package/gsd-core/bin/lib/capability-loader.cjs +135 -1
- package/gsd-core/bin/lib/capability-registry.cjs +96 -189
- package/gsd-core/bin/lib/capability-source.cjs +19 -2
- package/gsd-core/bin/lib/capability-validator.cjs +14 -2
- package/gsd-core/bin/lib/check-command-router.cjs +213 -49
- package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
- package/gsd-core/bin/lib/codex-agent-toml.cjs +21 -25
- package/gsd-core/bin/lib/commands.cjs +823 -112
- package/gsd-core/bin/lib/config-loader.cjs +66 -4
- package/gsd-core/bin/lib/config.cjs +186 -45
- package/gsd-core/bin/lib/coverage.cjs +1 -1
- package/gsd-core/bin/lib/decisions.cjs +164 -45
- package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
- package/gsd-core/bin/lib/frontmatter.cjs +13 -0
- package/gsd-core/bin/lib/graphify.cjs +10 -2
- package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +12 -1
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
- package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
- package/gsd-core/bin/lib/init.cjs +614 -86
- package/gsd-core/bin/lib/install-engine.cjs +29 -3
- package/gsd-core/bin/lib/install-profiles.cjs +14 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +41 -5
- package/gsd-core/bin/lib/loop-resolver.cjs +50 -31
- package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
- package/gsd-core/bin/lib/milestone.cjs +37 -13
- package/gsd-core/bin/lib/model-resolver.cjs +253 -53
- package/gsd-core/bin/lib/phase-command-router.cjs +16 -2
- package/gsd-core/bin/lib/phase-id-card.cjs +32 -0
- package/gsd-core/bin/lib/phase-id-display.cjs +78 -0
- package/gsd-core/bin/lib/phase-id.cjs +268 -27
- package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
- package/gsd-core/bin/lib/phase-locator.cjs +29 -10
- package/gsd-core/bin/lib/phase.cjs +393 -88
- package/gsd-core/bin/lib/plan-document.cjs +49 -1
- package/gsd-core/bin/lib/planning-document.cjs +459 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +52 -19
- package/gsd-core/bin/lib/planning-snapshot.cjs +61 -12
- package/gsd-core/bin/lib/planning-workspace.cjs +57 -3
- package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
- package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
- package/gsd-core/bin/lib/probe-core.cjs +7 -1
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
- package/gsd-core/bin/lib/project-root.cjs +41 -2
- package/gsd-core/bin/lib/quick-batch.cjs +1 -1
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +61 -2
- package/gsd-core/bin/lib/research-store.cjs +11 -12
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +10 -30
- package/gsd-core/bin/lib/review-lane-invocation.cjs +23 -0
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
- package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
- package/gsd-core/bin/lib/roadmap-command-router.cjs +12 -4
- package/gsd-core/bin/lib/roadmap-parser.cjs +219 -18
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +1539 -13
- package/gsd-core/bin/lib/roadmap.cjs +356 -42
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +310 -41
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +15 -4
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +13 -5
- package/gsd-core/bin/lib/runtime-homes.cjs +4 -0
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +408 -37
- package/gsd-core/bin/lib/runtime-name-policy.cjs +111 -1
- package/gsd-core/bin/lib/security.cjs +126 -7
- package/gsd-core/bin/lib/shell-command-projection.cjs +10 -6
- package/gsd-core/bin/lib/state-document.cjs +130 -28
- package/gsd-core/bin/lib/state-md-schema.cjs +21 -14
- package/gsd-core/bin/lib/state-transition.cjs +181 -30
- package/gsd-core/bin/lib/state.cjs +265 -27
- package/gsd-core/bin/lib/surface.cjs +77 -3
- package/gsd-core/bin/lib/task-command-router.cjs +12 -6
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +78 -5
- package/gsd-core/bin/lib/uat-predicate.cjs +47 -4
- package/gsd-core/bin/lib/uat.cjs +9 -1
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +100 -9
- package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
- package/gsd-core/bin/lib/update-context.cjs +30 -24
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
- package/gsd-core/bin/lib/verification.cjs +315 -30
- package/gsd-core/bin/lib/verify-command-grounding.cjs +47 -3
- package/gsd-core/bin/lib/verify.cjs +320 -48
- package/gsd-core/bin/lib/workstream-inventory.cjs +1 -0
- package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
- package/gsd-core/bin/lib/worktree-safety.cjs +797 -58
- package/gsd-core/bin/shared/config-defaults.manifest.json +4 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +6 -0
- package/gsd-core/bin/verify-reapply-patches.cjs +439 -80
- package/gsd-core/references/checkpoints.md +5 -3
- package/gsd-core/references/compact-content-gate.md +66 -0
- package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe.md +195 -21
- package/gsd-core/references/execute-phase-between-wave-reset.md +7 -6
- package/gsd-core/references/execute-phase-wave-guard.md +22 -11
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +18 -0
- package/gsd-core/references/model-profiles.md +13 -4
- package/gsd-core/references/phase-argument-parsing.md +9 -7
- package/gsd-core/references/phase-id-convention.md +28 -0
- package/gsd-core/references/planner-gap-closure.md +2 -0
- package/gsd-core/references/planner-load-graph-context.md +24 -13
- package/gsd-core/references/planner-verify-command-grounding.md +14 -0
- package/gsd-core/references/planning-config.md +14 -2
- package/gsd-core/references/tdd.md +30 -4
- package/gsd-core/references/thinking-models-planning.md +18 -2
- package/gsd-core/references/ui-consideration-probe.md +10 -5
- package/gsd-core/references/verification-patterns.md +17 -4
- package/gsd-core/references/verify-command-path-resolvability.md +10 -2
- package/gsd-core/references/worktree-path-safety.md +433 -2
- package/gsd-core/templates/README.md +7 -1
- package/gsd-core/templates/state.md +6 -3
- package/gsd-core/templates/summary.compact.md +212 -0
- package/gsd-core/templates/user-setup.compact.md +199 -0
- package/gsd-core/templates/user-setup.md +0 -9
- package/gsd-core/templates/verification-report.md +1 -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 +1 -1
- package/gsd-core/workflows/add-tests.md +2 -2
- package/gsd-core/workflows/add-todo.md +6 -5
- package/gsd-core/workflows/ai-integration-phase.md +11 -3
- package/gsd-core/workflows/audit-fix.md +1 -1
- package/gsd-core/workflows/audit-milestone.md +1 -1
- package/gsd-core/workflows/audit-uat.md +1 -1
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +9 -18
- package/gsd-core/workflows/autonomous.md +29 -16
- package/gsd-core/workflows/check-todos.md +6 -4
- package/gsd-core/workflows/cleanup.md +5 -3
- package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +8 -1
- package/gsd-core/workflows/code-review-fix.md +108 -22
- package/gsd-core/workflows/code-review.md +216 -73
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
- package/gsd-core/workflows/complete-milestone.md +41 -264
- package/gsd-core/workflows/debug.md +3 -3
- package/gsd-core/workflows/diagnose-issues.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +2 -2
- package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
- package/gsd-core/workflows/docs-update.md +17 -158
- package/gsd-core/workflows/edit-phase.md +1 -1
- package/gsd-core/workflows/eval-review.md +10 -3
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
- package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1017 -0
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +19 -4
- package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +43 -4
- package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
- 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 +1 -1
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +46 -9
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/ready-wave-gate.md +37 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
- package/gsd-core/workflows/execute-phase/steps/stale-reverification.md +24 -0
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/threat-id-gate.md +28 -0
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
- package/gsd-core/workflows/execute-phase.md +83 -172
- package/gsd-core/workflows/execute-plan.md +24 -10
- package/gsd-core/workflows/explore.md +3 -3
- package/gsd-core/workflows/extract-learnings.md +2 -1
- package/gsd-core/workflows/fast.md +1 -1
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +2 -2
- package/gsd-core/workflows/help/modes/full.compact.md +398 -0
- package/gsd-core/workflows/help/modes/full.md +5 -5
- package/gsd-core/workflows/help/modes/topic.md +15 -5
- package/gsd-core/workflows/help.md +1 -1
- package/gsd-core/workflows/import.md +2 -2
- package/gsd-core/workflows/inbox.md +2 -2
- package/gsd-core/workflows/ingest-docs.md +3 -3
- package/gsd-core/workflows/insert-phase.md +1 -1
- package/gsd-core/workflows/list-seeds.md +1 -1
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +2 -2
- package/gsd-core/workflows/map-codebase.md +52 -5
- package/gsd-core/workflows/milestone-summary.md +1 -1
- package/gsd-core/workflows/mvp-phase.md +1 -1
- package/gsd-core/workflows/new-milestone.md +56 -14
- package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +3 -3
- package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +1 -1
- package/gsd-core/workflows/new-project.md +39 -209
- package/gsd-core/workflows/new-workspace.md +2 -2
- package/gsd-core/workflows/next.md +1 -1
- package/gsd-core/workflows/note.md +1 -1
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +1 -1
- package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +23 -5
- package/gsd-core/workflows/plan-phase.md +45 -187
- package/gsd-core/workflows/plan-review-convergence.md +21 -5
- package/gsd-core/workflows/plant-seed.md +62 -20
- package/gsd-core/workflows/pr-branch.md +132 -20
- package/gsd-core/workflows/profile-user.md +2 -2
- package/gsd-core/workflows/progress.md +1 -1
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +25 -0
- package/gsd-core/workflows/quick/steps/quick-verification.md +1 -1
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
- package/gsd-core/workflows/quick-batch.md +1 -1
- package/gsd-core/workflows/quick.md +29 -10
- package/gsd-core/workflows/reapply-patches.md +86 -6
- package/gsd-core/workflows/remove-phase.md +1 -1
- package/gsd-core/workflows/remove-workspace.md +2 -2
- package/gsd-core/workflows/resume-project.md +1 -1
- package/gsd-core/workflows/review.md +31 -16
- package/gsd-core/workflows/scan.md +1 -1
- package/gsd-core/workflows/secure-phase.md +3 -2
- package/gsd-core/workflows/settings-advanced.md +30 -10
- package/gsd-core/workflows/settings-integrations.md +2 -3
- package/gsd-core/workflows/settings.md +22 -9
- package/gsd-core/workflows/ship.md +3 -2
- package/gsd-core/workflows/sketch-wrap-up.md +1 -1
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/smart-entry.md +2 -2
- package/gsd-core/workflows/spec-phase.md +15 -5
- package/gsd-core/workflows/spike-wrap-up.md +1 -1
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +1 -1
- package/gsd-core/workflows/sync-skills.md +5 -5
- package/gsd-core/workflows/thread.md +1 -1
- package/gsd-core/workflows/transition.md +1 -1
- package/gsd-core/workflows/ui-phase.md +44 -8
- package/gsd-core/workflows/ui-review.md +18 -4
- package/gsd-core/workflows/ultraplan-phase.md +1 -1
- package/gsd-core/workflows/undo.md +339 -20
- package/gsd-core/workflows/update.md +14 -12
- package/gsd-core/workflows/validate-phase.md +3 -2
- package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
- package/gsd-core/workflows/verify-work.md +101 -196
- package/hooks/dist/gsd-agent-isolation-guard.js +66 -16
- package/hooks/dist/gsd-context-monitor.js +88 -15
- package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
- package/hooks/dist/gsd-secret-read-guard.js +71 -19
- package/hooks/dist/gsd-statusline.js +81 -20
- package/hooks/dist/gsd-validate-commit.sh +97 -8
- package/hooks/dist/gsd-worktree-path-guard.js +25 -14
- package/hooks/dist/gsd-write-guard.js +46 -1
- package/hooks/dist/lib/dispatch-identity.js +187 -0
- package/hooks/dist/lib/filename-classification.js +64 -0
- package/hooks/dist/lib/isolation-deny-reason.js +53 -1
- package/hooks/dist/lib/isolation-sentinel.js +58 -19
- package/hooks/gsd-agent-isolation-guard.js +66 -16
- package/hooks/gsd-context-monitor.js +88 -15
- package/hooks/gsd-cursor-subagent-start.js +34 -14
- package/hooks/gsd-secret-read-guard.js +71 -19
- package/hooks/gsd-statusline.js +81 -20
- package/hooks/gsd-validate-commit.sh +97 -8
- package/hooks/gsd-worktree-path-guard.js +25 -14
- package/hooks/gsd-write-guard.js +46 -1
- package/hooks/lib/dispatch-identity.js +187 -0
- package/hooks/lib/filename-classification.js +64 -0
- package/hooks/lib/isolation-deny-reason.js +53 -1
- package/hooks/lib/isolation-sentinel.js +58 -19
- package/package.json +11 -6
- package/scripts/benchmark-compact-content-variants.cjs +298 -0
- package/scripts/benchmark-compact-content.cjs +368 -0
- package/scripts/build-hooks.js +15 -6
- package/scripts/check-contract-drift.cjs +131 -12
- package/scripts/check-env.cjs +36 -8
- package/scripts/check-glossary-refs.cjs +25 -21
- package/scripts/ci-next-health.cjs +271 -0
- package/scripts/ci-prepare-test-scope.cjs +7 -7
- package/scripts/ci-test-scope.cjs +126 -20
- package/scripts/ci-timeout-report.cjs +1 -1
- package/scripts/command-contract-helpers.cjs +3 -0
- package/scripts/diff-touches-shipped-paths.cjs +1 -1
- package/scripts/docs-guard-registry.cjs +35 -2
- package/scripts/gen-adr-index.cjs +8 -2
- package/scripts/gen-inventory-manifest.cjs +12 -0
- package/scripts/gen-loop-host-contract.cjs +69 -0
- package/scripts/gen-platform-conformance-tier.cjs +557 -0
- package/scripts/lib/drift-scan.cjs +1 -1
- package/scripts/lib/macos-conformance-tier.generated.cjs +224 -0
- package/scripts/lib/ndjson-reporter.cjs +3 -2
- package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
- package/scripts/lib/platform-conformance-tier.generated.cjs +287 -0
- package/scripts/lib/suite-detection.cjs +32 -0
- package/scripts/lint-allowed-tools-parity.cjs +221 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +47 -3
- package/scripts/lint-phase-arg-assignment.cjs +257 -0
- package/scripts/lint-phase-id-drift.cjs +623 -13
- package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
- package/scripts/lint-response-language-coverage.cjs +9 -3
- package/scripts/lint-retired-runtime-name.cjs +619 -0
- package/scripts/lint-source-test-name-collision.cjs +1 -1
- package/scripts/lint-state-write-path-drift.cjs +93 -0
- package/scripts/lint-test-file-count.allowlist.json +29 -9
- package/scripts/lint-vendored-deps.cjs +128 -17
- package/scripts/lint-workflow-shellcheck-baseline.json +100 -0
- package/scripts/prompt-injection-scan.sh +18 -0
- package/scripts/release-tarball-smoke.cjs +194 -1
- package/scripts/workflow-size.cjs +139 -0
- package/skills/gsd-autonomous/SKILL.md +2 -2
- package/skills/gsd-capture/SKILL.md +1 -1
- package/skills/gsd-cleanup/SKILL.md +1 -0
- package/skills/gsd-code-review/SKILL.md +2 -1
- package/skills/gsd-complete-milestone/SKILL.md +1 -0
- package/skills/gsd-config/SKILL.md +1 -0
- package/skills/gsd-debug/SKILL.md +1 -0
- package/skills/gsd-graphify/SKILL.md +1 -0
- package/skills/gsd-health/SKILL.md +1 -0
- package/skills/gsd-mempalace-capture/SKILL.md +8 -3
- package/skills/gsd-mempalace-recall/SKILL.md +1 -0
- package/skills/gsd-new-milestone/SKILL.md +1 -0
- package/skills/gsd-new-project/SKILL.md +1 -0
- package/skills/gsd-next/SKILL.md +1 -0
- package/skills/gsd-pause-work/SKILL.md +1 -0
- package/skills/gsd-phase/SKILL.md +1 -0
- package/skills/gsd-plan-review-convergence/SKILL.md +5 -5
- package/skills/gsd-pr-branch/SKILL.md +1 -0
- package/skills/gsd-progress/SKILL.md +1 -1
- package/skills/gsd-quick-batch/SKILL.md +1 -1
- package/skills/gsd-resume-work/SKILL.md +1 -0
- package/skills/gsd-review/SKILL.md +2 -3
- package/skills/gsd-review-backlog/SKILL.md +1 -0
- package/skills/gsd-settings/SKILL.md +2 -1
- package/skills/gsd-stats/SKILL.md +1 -0
- package/skills/gsd-thread/SKILL.md +1 -0
- package/skills/gsd-workspace/SKILL.md +1 -0
- package/skills/gsd-workstreams/SKILL.md +1 -0
- package/vscode/package.json +1 -1
- package/gsd-core/templates/claude-md.md +0 -145
- package/gsd-core/templates/codebase/concerns.md +0 -310
- package/gsd-core/templates/codebase/conventions.md +0 -307
- package/gsd-core/templates/codebase/integrations.md +0 -280
- package/gsd-core/templates/codebase/structure.md +0 -285
- package/gsd-core/templates/codebase/testing.md +0 -480
- package/gsd-core/templates/debug-subagent-prompt.md +0 -91
- package/gsd-core/templates/discovery.md +0 -146
|
@@ -59,6 +59,20 @@ const OPTIONAL_PHASE_TAG_SOURCE = '(?:\\s*\\([^)\\n]{0,200}\\))?';
|
|
|
59
59
|
// (scripts/lint-phase-id-drift.cjs) fails CI if a literal re-derivation is
|
|
60
60
|
// introduced outside this module without a `// phase-id-owner:` justification.
|
|
61
61
|
const PHASE_NUMBER_TOKEN_SOURCE = '\\d+[A-Z]?(?:\\.\\d+)*';
|
|
62
|
+
// #4764: a phase REFERENCE in depends-on PROSE — the token in context, directly
|
|
63
|
+
// following "Phase"/"Phases", with bare-token list continuation ("Phases 1 and 2",
|
|
64
|
+
// "Phase 1, 2, and 3", "Phase 1-3"). Built HERE, beside the token grammar it is
|
|
65
|
+
// anchored on, because two readers consume Depends-on prose (init.manager's
|
|
66
|
+
// dep_phases and planning-inspect's dependencies) and re-deriving the anchored
|
|
67
|
+
// form at each would drift exactly the way PHASE_NUMBER_TOKEN_SOURCE's own
|
|
68
|
+
// anti-divergence rule exists to prevent. A bare digit run is the right shape
|
|
69
|
+
// test for a phase id and the WRONG test for a reference: the whole-field
|
|
70
|
+
// scrape this replaces pulled calendar dates ("2026-09-14" → 2026/09/14), git
|
|
71
|
+
// shas ("8bf403100d" → 8b/403100d/…) and ledger ids (WINDOWS #1843) in as
|
|
72
|
+
// dependencies. The `-` separator deliberately extracts range ENDPOINTS only
|
|
73
|
+
// ("Phase 1-3" → 1, 3) — the pre-#4764 behavior; interior enumeration stays
|
|
74
|
+
// out (a range's middle is not written as a reference).
|
|
75
|
+
const PHASE_DEP_REF_SOURCE = `\\bphases?\\s+(${PHASE_NUMBER_TOKEN_SOURCE}(?:(?:\\s*,\\s*(?:and\\s+)?|\\s+and\\s+|\\s*&\\s*|\\s+(?:to|through)\\s+|\\s*-\\s*)${PHASE_NUMBER_TOKEN_SOURCE})*)`;
|
|
62
76
|
// #2528 review: the CASE-FLEXIBLE renderings of the two sources above, for call
|
|
63
77
|
// sites that scan directory names (where a project code or a variant suffix may
|
|
64
78
|
// legitimately be lowercase) and therefore cannot use a case-sensitive class.
|
|
@@ -308,6 +322,30 @@ function phaseHeadingPrefixSrcFor(baseline, convention, capturing = false) {
|
|
|
308
322
|
const bracketAlt = `\\[${id}\\][ \\t]*(?:Phase\\s+|(?=\\d))`;
|
|
309
323
|
return `(?:${bracketAlt}|${base})`;
|
|
310
324
|
}
|
|
325
|
+
/**
|
|
326
|
+
* The single owner of the "scan a whole document for every phase heading"
|
|
327
|
+
* pattern (#4865 / ADR-4910 §8) — anchored (`^ {0,3}#{2,4}`, so indentation up
|
|
328
|
+
* to 3 spaces is tolerated but a heading is never matched mid-line), global,
|
|
329
|
+
* multiline, convention-aware via `phaseHeadingPrefixSrcFor`. Returns the
|
|
330
|
+
* capture-group layout alongside the regex so a caller never re-derives the
|
|
331
|
+
* bracket-convention offset (`const G = convention === 'bracket' ? 1 : 0`)
|
|
332
|
+
* itself — a second, independently-computed offset is exactly the kind of
|
|
333
|
+
* drift `scripts/lint-phase-id-drift.cjs` exists to catch structurally, not
|
|
334
|
+
* just by pattern text.
|
|
335
|
+
*
|
|
336
|
+
* Distinct from `buildPhaseHeadingRegex` (`src/roadmap.cts`), which searches
|
|
337
|
+
* for ONE already-known phase number (anchored `^`, no `g` flag, phase number
|
|
338
|
+
* interpolated literally) — a different contract for a different question.
|
|
339
|
+
* This function answers "which phases exist in this content", not "does this
|
|
340
|
+
* specific phase exist".
|
|
341
|
+
*/
|
|
342
|
+
function buildPhaseHeadingScanRegex(baseline, convention) {
|
|
343
|
+
const bracketGroup = convention === 'bracket' ? 1 : null;
|
|
344
|
+
const phaseNumGroup = bracketGroup ? 2 : 1;
|
|
345
|
+
const phaseNameGroup = phaseNumGroup + 1;
|
|
346
|
+
const regex = new RegExp(`^ {0,3}#{2,4}\\s*${phaseHeadingPrefixSrcFor(baseline, convention, true)}(${PHASE_NUMBER_TOKEN_SOURCE})${OPTIONAL_PHASE_TAG_SOURCE}:\\s*([^\\n]+)`, 'gim');
|
|
347
|
+
return { regex, bracketGroup, phaseNumGroup, phaseNameGroup };
|
|
348
|
+
}
|
|
311
349
|
function stripProjectCodePrefix(value, caseInsensitive = true) {
|
|
312
350
|
const input = String(value);
|
|
313
351
|
const re = caseInsensitive ? PROJECT_CODE_PREFIX_STRIP_RE_I : PROJECT_CODE_PREFIX_STRIP_RE;
|
|
@@ -451,10 +489,27 @@ function parsePhaseId(input) {
|
|
|
451
489
|
// tokens unchanged.
|
|
452
490
|
throw new Error(`parsePhaseId: not a bracket phase id: ${JSON.stringify(input)}`);
|
|
453
491
|
}
|
|
492
|
+
function renderMilestoneId(id) {
|
|
493
|
+
return `[${id.project}.${id.milestone}]`;
|
|
494
|
+
}
|
|
454
495
|
function renderPhaseId(id) {
|
|
455
496
|
const sub = id.subphase ? `.${id.subphase}` : '';
|
|
456
497
|
const plan = id.plan ? `-${id.plan}` : '';
|
|
457
|
-
return
|
|
498
|
+
return `${renderMilestoneId(id)} ${id.phase}${sub}${plan}`;
|
|
499
|
+
}
|
|
500
|
+
/** Whether a legacy phase token has a lossless spelling in bracket display grammar. */
|
|
501
|
+
function isBracketPhaseTokenRepresentable(token) {
|
|
502
|
+
try {
|
|
503
|
+
const bracketToken = normalizePhaseName(token)
|
|
504
|
+
.split('.')
|
|
505
|
+
.map((segment) => segment.padStart(2, '0'))
|
|
506
|
+
.join('.');
|
|
507
|
+
parsePhaseId(`[GSD.00] ${bracketToken}`);
|
|
508
|
+
return true;
|
|
509
|
+
}
|
|
510
|
+
catch {
|
|
511
|
+
return false;
|
|
512
|
+
}
|
|
458
513
|
}
|
|
459
514
|
// PhaseId is a structural type: nothing forces a caller through parsePhaseId,
|
|
460
515
|
// so toDir cannot trust project/milestone/phase/subphase are already
|
|
@@ -884,19 +939,42 @@ function extractPhaseToken(dirName, convention) {
|
|
|
884
939
|
* cannot resolve a genuinely different phase's artifact (`02.1-...` still
|
|
885
940
|
* fails every `01`-rooted candidate).
|
|
886
941
|
*
|
|
887
|
-
* BRACKET CONVENTION (review item 7): a
|
|
888
|
-
* (`P0.3-2-slug`) is string-INDISTINGUISHABLE
|
|
889
|
-
* (`extractPhaseToken` above, gated on
|
|
890
|
-
*
|
|
891
|
-
*
|
|
892
|
-
*
|
|
893
|
-
*
|
|
894
|
-
* (`
|
|
895
|
-
*
|
|
896
|
-
*
|
|
897
|
-
*
|
|
898
|
-
*
|
|
899
|
-
*
|
|
942
|
+
* BRACKET CONVENTION (review item 7; reworked by #612/#2761): a
|
|
943
|
+
* letter-prefixed-decimal dir (`P0.3-2-slug`) is string-INDISTINGUISHABLE
|
|
944
|
+
* from a bracket-dir token (`extractPhaseToken` above, gated on
|
|
945
|
+
* `convention === 'bracket'`) without an explicit convention signal. This
|
|
946
|
+
* predicate now takes the same optional trailing `convention` its sibling
|
|
947
|
+
* helpers do (ADR-2121 additive shape): when a call site threads
|
|
948
|
+
* `'bracket'` and the dir is a WELL-FORMED bracket dir
|
|
949
|
+
* (`BRACKET_DIR_TOKEN_RE` — the one grammar owner, shared with
|
|
950
|
+
* `extractPhaseToken`'s bracket branch), the dir's real phase token is
|
|
951
|
+
* readable and membership is scoped through the identical candidate
|
|
952
|
+
* comparison its legacy twin gets — so `GSD.02-02-two` reads exactly what
|
|
953
|
+
* `02-two` reads, which is PR-2's core invariant. Convention-less calls,
|
|
954
|
+
* bracket-MALFORMED names (1-digit milestone, letter-suffixed token — the
|
|
955
|
+
* shapes W005 reports), and legacy-shaped dirs inside a bracket repo all
|
|
956
|
+
* resolve to the unchanged body below.
|
|
957
|
+
*
|
|
958
|
+
* A filename carrying its own bracket-qualified stem is compared on the
|
|
959
|
+
* qualified key — see the branch body. An M-NN-stem artifact name
|
|
960
|
+
* (`02-01-VERIFICATION.md` in `GSD.02-01-one`) is excluded under the bracket
|
|
961
|
+
* convention, deliberately: that is exactly what the legacy twin does with
|
|
962
|
+
* the same file, twin-parity is this PR's contract, and migration-window
|
|
963
|
+
* artifact renaming belongs to the migrator slice per ADR-612 — the trade is
|
|
964
|
+
* pinned in tests/adr-612-bracket-phase-counting.test.cjs.
|
|
965
|
+
*
|
|
966
|
+
* For those unthreaded/unparseable cases the include-everything trade
|
|
967
|
+
* stands: rather than guess a reading it cannot know is active and risk
|
|
968
|
+
* excluding the phase's OWN artifact (the exact defect class this rework
|
|
969
|
+
* exists to fix), the ambiguous families widen to include-everything,
|
|
970
|
+
* accepted in trade for never dropping the phase's own report. Note the
|
|
971
|
+
* bracket family splits across BOTH fail-safes, not just one: a short
|
|
972
|
+
* digit-bearing project code (`A1.02-…`) lands in `firstLetterPrefixed`,
|
|
973
|
+
* but a 3+-letter code (`GSD.02-…`) derives ZERO token segments —
|
|
974
|
+
* `PROJECT_CODE_PREFIX_CAPTURE_RE_I` strips `{CODE}-`, not `{CODE}.` — so
|
|
975
|
+
* the zero-token fail-safe fires first. A fixer patching only the
|
|
976
|
+
* `firstLetterPrefixed` branch covers neither; the convention gate above
|
|
977
|
+
* covers both.
|
|
900
978
|
*
|
|
901
979
|
* FAIL-SAFE (#3511, unchanged): when dirName's own leading segment carries no
|
|
902
980
|
* phase-number token at all (`derivePhaseTokenSegments` finds zero segments —
|
|
@@ -933,7 +1011,44 @@ function extractPhaseToken(dirName, convention) {
|
|
|
933
1011
|
* the original pre-#3357 "alphabetically first of ALL dashed candidates"
|
|
934
1012
|
* behavior, exactly as it always did for that directory shape.
|
|
935
1013
|
*/
|
|
936
|
-
function isPhaseArtifact(fileName, phaseDirName) {
|
|
1014
|
+
function isPhaseArtifact(fileName, phaseDirName, convention) {
|
|
1015
|
+
// #612/#2761: the bracket branch — see "BRACKET CONVENTION" in the docblock.
|
|
1016
|
+
// Gated on the same explicit signal AND the same grammar
|
|
1017
|
+
// (`BRACKET_DIR_TOKEN_RE`) as `extractPhaseToken`'s bracket branch, so the
|
|
1018
|
+
// two surfaces can never disagree about which names are bracket dirs. A
|
|
1019
|
+
// name that fails the grammar falls through to the unchanged body below.
|
|
1020
|
+
if (convention === 'bracket') {
|
|
1021
|
+
const bracketDir = phaseDirName.match(BRACKET_DIR_TOKEN_RE);
|
|
1022
|
+
if (bracketDir) {
|
|
1023
|
+
// The convention must gate the FILENAME reading, not just the directory
|
|
1024
|
+
// reading — a milestone-qualified artifact name (the layout the
|
|
1025
|
+
// read-tolerance suite models) derives zero legacy token segments, so
|
|
1026
|
+
// without this it enters FIX 2's token-less containment and any bracket
|
|
1027
|
+
// dir admits it. Compared on the qualified key: the phase's own
|
|
1028
|
+
// qualified artifact stays included — the exact key, and the dotted
|
|
1029
|
+
// sub-phase continuation (`GSD.02-05.1-…` is `GSD.02-05`'s own file,
|
|
1030
|
+
// the same dash-OR-dot rule matchesPhaseTokenCandidates applies; a
|
|
1031
|
+
// dash-continuation never reaches this comparison, the phase group's
|
|
1032
|
+
// `(?=-|$)` boundary already stopped the key before it) — while a
|
|
1033
|
+
// WELL-FORMED qualified stem naming a different phase or milestone is
|
|
1034
|
+
// excluded. A qualified-SHAPED but grammar-malformed stem (letter
|
|
1035
|
+
// suffix, 1-digit milestone, 2-level dot — the W005 shapes) yields a
|
|
1036
|
+
// null fileKey and keeps the module's documented include-everything
|
|
1037
|
+
// fail-safe via FIX 2 below, same as every other undecidable name.
|
|
1038
|
+
const fileKey = bracketQualifiedKey(fileName, convention);
|
|
1039
|
+
if (fileKey !== null) {
|
|
1040
|
+
const dirKey = bracketQualifiedKey(phaseDirName, convention);
|
|
1041
|
+
if (dirKey !== null)
|
|
1042
|
+
return fileKey === dirKey || fileKey.startsWith(`${dirKey}.`);
|
|
1043
|
+
}
|
|
1044
|
+
// Reached when the filename carries no well-formed qualified stem (the
|
|
1045
|
+
// common case — every unqualified name), or when the DIR's own key is
|
|
1046
|
+
// null despite matching the dir grammar (an unsafe-integer milestone or
|
|
1047
|
+
// phase — bracketQualifiedKey refuses both rather than collide).
|
|
1048
|
+
// Either way the candidate path decides, deliberately.
|
|
1049
|
+
return matchesPhaseTokenCandidates(fileName, [bracketDir[1]]);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
937
1052
|
const { tokenSegments, firstLetterPrefixed } = derivePhaseTokenSegments(phaseDirName);
|
|
938
1053
|
if (tokenSegments.length === 0)
|
|
939
1054
|
return true;
|
|
@@ -942,6 +1057,22 @@ function isPhaseArtifact(fileName, phaseDirName) {
|
|
|
942
1057
|
const strippedToken = strippedDir !== phaseDirName ? extractPhaseToken(strippedDir) : literalToken;
|
|
943
1058
|
const leadingRunMatch = strippedDir.match(LEADING_DIGIT_RUN_RE);
|
|
944
1059
|
const rawCandidates = [literalToken, strippedToken, leadingRunMatch?.[1]].filter((t) => Boolean(t));
|
|
1060
|
+
if (matchesPhaseTokenCandidates(fileName, rawCandidates))
|
|
1061
|
+
return true;
|
|
1062
|
+
// Bracket-convention ambiguity fail-safe — see docblock above.
|
|
1063
|
+
if (firstLetterPrefixed)
|
|
1064
|
+
return true;
|
|
1065
|
+
return false;
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Candidate-comparison core shared by `isPhaseArtifact`'s two entry paths —
|
|
1069
|
+
* the legacy segment-derived readings and the #612 bracket-dir token. One
|
|
1070
|
+
* comparison rule, not two that agree today and drift tomorrow: the padded/
|
|
1071
|
+
* de-padded expansion, the separator class, and FIX 2 apply identically
|
|
1072
|
+
* whichever path produced the candidates, which is what makes a bracket dir
|
|
1073
|
+
* read exactly what its legacy twin reads.
|
|
1074
|
+
*/
|
|
1075
|
+
function expandPhaseTokenCandidates(rawCandidates) {
|
|
945
1076
|
// Each reading is compared in BOTH its padded and de-padded form: files are
|
|
946
1077
|
// written padded by `normalizePhaseName` (`cmdScaffold`) while directories
|
|
947
1078
|
// are often not (`1-unpadded`), and legacy trees carry the reverse pairing.
|
|
@@ -949,9 +1080,19 @@ function isPhaseArtifact(fileName, phaseDirName) {
|
|
|
949
1080
|
// sub-phase (`03A`, `03.1`) has no meaningful de-padded form and is left
|
|
950
1081
|
// alone, so this only ever ADDS a reading and can never drop one.
|
|
951
1082
|
const depad = (t) => (/^\d+$/.test(t) ? String(Number(t)) : t);
|
|
952
|
-
|
|
1083
|
+
return new Set(rawCandidates
|
|
953
1084
|
.flatMap(t => [t, normalizePhaseName(t), depad(t)])
|
|
954
1085
|
.map(t => t.toUpperCase()));
|
|
1086
|
+
}
|
|
1087
|
+
function matchesPhaseTokenCandidates(fileName, rawCandidates) {
|
|
1088
|
+
const candidates = expandPhaseTokenCandidates(rawCandidates);
|
|
1089
|
+
if (matchPhaseTokenCandidateSpan(fileName, candidates) !== null)
|
|
1090
|
+
return true;
|
|
1091
|
+
// FIX 2: token-less filename (bare "VERIFICATION.md"/"UAT.md") — containment
|
|
1092
|
+
// in this phase's own directory listing is sufficient.
|
|
1093
|
+
return derivePhaseTokenSegments(fileName).tokenSegments.length === 0;
|
|
1094
|
+
}
|
|
1095
|
+
function matchPhaseTokenCandidateSpan(fileName, candidates) {
|
|
955
1096
|
const fileUpper = fileName.toUpperCase();
|
|
956
1097
|
for (const candidate of candidates) {
|
|
957
1098
|
// A dotted sub-phase segment (e.g. `01.1-CONTEXT.md`) is a legitimate
|
|
@@ -977,16 +1118,43 @@ function isPhaseArtifact(fileName, phaseDirName) {
|
|
|
977
1118
|
if (fileUpper.startsWith(`${candidate}-`) ||
|
|
978
1119
|
fileUpper.startsWith(`${candidate}.`) ||
|
|
979
1120
|
fileUpper.startsWith(`${candidate}_`))
|
|
980
|
-
return
|
|
1121
|
+
return { start: 0, end: candidate.length };
|
|
981
1122
|
}
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
1123
|
+
return null;
|
|
1124
|
+
}
|
|
1125
|
+
/**
|
|
1126
|
+
* Return the exact leading token span by which the phase-artifact reader
|
|
1127
|
+
* attributes a phase-qualified filename to `phaseDirName`.
|
|
1128
|
+
*
|
|
1129
|
+
* Membership is first decided by `isPhaseArtifact`, then the span is selected
|
|
1130
|
+
* from the same padded, de-padded, case-folded candidate set used by
|
|
1131
|
+
* `matchesPhaseTokenCandidates`. Token-less containment fallbacks return null
|
|
1132
|
+
* because there is no phase token in the filename to replace.
|
|
1133
|
+
*/
|
|
1134
|
+
function phaseArtifactTokenSpan(fileName, phaseDirName, convention) {
|
|
1135
|
+
if (!isPhaseArtifact(fileName, phaseDirName, convention))
|
|
1136
|
+
return null;
|
|
1137
|
+
if (convention === 'bracket') {
|
|
1138
|
+
const bracketDir = phaseDirName.match(BRACKET_DIR_TOKEN_RE);
|
|
1139
|
+
if (bracketDir) {
|
|
1140
|
+
const qualified = fileName.match(BRACKET_QUALIFIED_KEY_RE);
|
|
1141
|
+
if (qualified && bracketQualifiedKey(fileName, convention) !== null) {
|
|
1142
|
+
return { start: 0, end: qualified[0].length };
|
|
1143
|
+
}
|
|
1144
|
+
const bracketCandidates = expandPhaseTokenCandidates([bracketDir[1]]);
|
|
1145
|
+
return matchPhaseTokenCandidateSpan(fileName, bracketCandidates);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
const { tokenSegments } = derivePhaseTokenSegments(phaseDirName);
|
|
1149
|
+
if (tokenSegments.length === 0)
|
|
1150
|
+
return null;
|
|
1151
|
+
const literalToken = extractPhaseToken(phaseDirName);
|
|
1152
|
+
const strippedDir = stripProjectCodePrefix(phaseDirName);
|
|
1153
|
+
const strippedToken = strippedDir !== phaseDirName ? extractPhaseToken(strippedDir) : literalToken;
|
|
1154
|
+
const leadingRunMatch = strippedDir.match(LEADING_DIGIT_RUN_RE);
|
|
1155
|
+
const rawCandidates = [literalToken, strippedToken, leadingRunMatch?.[1]].filter((token) => Boolean(token));
|
|
1156
|
+
const candidates = expandPhaseTokenCandidates(rawCandidates);
|
|
1157
|
+
return matchPhaseTokenCandidateSpan(fileName, candidates);
|
|
990
1158
|
}
|
|
991
1159
|
/**
|
|
992
1160
|
* #3511: scope `fileNames` to the subset that passes
|
|
@@ -1035,9 +1203,19 @@ function isPhaseArtifact(fileName, phaseDirName) {
|
|
|
1035
1203
|
* re-derived per site (CLAUDE.md's Generative Fix Divergence class).
|
|
1036
1204
|
* `isPhaseArtifact` stays exported for single-item membership questions and
|
|
1037
1205
|
* its own unit tests.
|
|
1206
|
+
*
|
|
1207
|
+
* #612/#2761: both helpers take the same optional trailing `convention` their
|
|
1208
|
+
* sibling primitives (`extractPhaseToken`, `phaseTokenMatches`) do — the
|
|
1209
|
+
* ADR-2121 additive shape, so every existing two-argument call resolves to
|
|
1210
|
+
* the unchanged legacy body. A call site that already holds the resolved
|
|
1211
|
+
* convention threads it and bracket dirs scope exactly like their legacy
|
|
1212
|
+
* twins; convention-less call sites keep the documented include-everything
|
|
1213
|
+
* fail-safes and are the follow-up-slice work (they cannot scope a bracket
|
|
1214
|
+
* dir until they can resolve the convention, and guessing is the one thing
|
|
1215
|
+
* this predicate must never do).
|
|
1038
1216
|
*/
|
|
1039
|
-
function scopeToPhase(fileNames, phaseDirName) {
|
|
1040
|
-
return fileNames.filter((f) => isPhaseArtifact(f, phaseDirName));
|
|
1217
|
+
function scopeToPhase(fileNames, phaseDirName, convention) {
|
|
1218
|
+
return fileNames.filter((f) => isPhaseArtifact(f, phaseDirName, convention));
|
|
1041
1219
|
}
|
|
1042
1220
|
/**
|
|
1043
1221
|
* Canonical comparable key for a milestone-qualified bracket id or dir name.
|
|
@@ -1386,6 +1564,63 @@ function isForeignPrefixedPhaseQuery(phase, projectCode) {
|
|
|
1386
1564
|
* form so a canonical heading (`### Phase 117:`) is preferred over a drifted
|
|
1387
1565
|
* prefixed one (`### Phase MANIFOLD-117:`) when both exist in one ROADMAP.
|
|
1388
1566
|
*/
|
|
1567
|
+
// Separator characters a branch-name template may plausibly use to join
|
|
1568
|
+
// `{slug}` to its neighbours — the four this repo's shipped templates and
|
|
1569
|
+
// docs/CONFIGURATION.md's custom-template examples use (`/`, `-`, `_`, `.`).
|
|
1570
|
+
const BRANCH_TEMPLATE_SEP_RE = /[-_./]/;
|
|
1571
|
+
const BRANCH_TEMPLATE_SEP_RUN_RE = /([-_./])\1+/g;
|
|
1572
|
+
const BRANCH_TEMPLATE_EDGE_SEP_RE = /^[-_./]+|[-_./]+$/g;
|
|
1573
|
+
/**
|
|
1574
|
+
* #4126: the ONE branch-name-template renderer for the `phase` branching
|
|
1575
|
+
* strategy, shared by `cmdCommit` (src/commands.cts) and
|
|
1576
|
+
* `cmdInitExecutePhase` (src/init.cts) so the two call sites cannot diverge
|
|
1577
|
+
* on how an undeliverable `phaseSlug` degrades (they previously each
|
|
1578
|
+
* independently substituted the literal word `phase`, producing a
|
|
1579
|
+
* non-identifying branch name like `gsd/phase-08-phase` that contradicts an
|
|
1580
|
+
* honestly-reported `phase_slug: null`).
|
|
1581
|
+
*
|
|
1582
|
+
* `{project}` is deliberately NOT handled here — init.cts substitutes it
|
|
1583
|
+
* separately, before calling this, because it is a config-level field with
|
|
1584
|
+
* its own fallback (`''`), not a phase-derived one.
|
|
1585
|
+
*
|
|
1586
|
+
* When `phaseSlug` is a non-empty string, `{slug}` substitutes normally
|
|
1587
|
+
* (unchanged from prior behavior). When it is empty or not a string (e.g.
|
|
1588
|
+
* `null`, `undefined`, a number), the `{slug}` token is DROPPED — along with
|
|
1589
|
+
* one adjacent separator character — rather than replaced with a placeholder
|
|
1590
|
+
* word: a dropped token keeps the branch name honest about what it does not
|
|
1591
|
+
* know, where a placeholder reads as a real (but wrong) name. The result is
|
|
1592
|
+
* always a syntactically plausible git-ref fragment: no leading, trailing, or
|
|
1593
|
+
* doubled separator, for both the shipped default template
|
|
1594
|
+
* (`gsd/phase-{phase}-{slug}`) and a differently-shaped custom one (this
|
|
1595
|
+
* field is user-configurable per docs/CONFIGURATION.md).
|
|
1596
|
+
*/
|
|
1597
|
+
function renderPhaseBranchName(template, phaseNumber, phaseSlug) {
|
|
1598
|
+
const withPhase = template.replace('{phase}', normalizePhaseName(phaseNumber));
|
|
1599
|
+
const slug = typeof phaseSlug === 'string' ? phaseSlug : '';
|
|
1600
|
+
if (slug)
|
|
1601
|
+
return withPhase.replace('{slug}', slug);
|
|
1602
|
+
const at = withPhase.indexOf('{slug}');
|
|
1603
|
+
if (at === -1)
|
|
1604
|
+
return withPhase;
|
|
1605
|
+
let before = withPhase.slice(0, at);
|
|
1606
|
+
let after = withPhase.slice(at + '{slug}'.length);
|
|
1607
|
+
// Drop exactly ONE adjacent separator — preferring the one immediately
|
|
1608
|
+
// before the token — so `a-{slug}` and `{slug}-a` both degrade to `a`
|
|
1609
|
+
// rather than leaving a dangling `a-` / `-a`.
|
|
1610
|
+
if (before && BRANCH_TEMPLATE_SEP_RE.test(before[before.length - 1])) {
|
|
1611
|
+
before = before.slice(0, -1);
|
|
1612
|
+
}
|
|
1613
|
+
else if (after && BRANCH_TEMPLATE_SEP_RE.test(after[0])) {
|
|
1614
|
+
after = after.slice(1);
|
|
1615
|
+
}
|
|
1616
|
+
// Collapse any doubled separator the drop can leave behind (e.g. a
|
|
1617
|
+
// `feature//{slug}` template dropping to `feature/`→`feature`), then trim
|
|
1618
|
+
// a resulting leading/trailing separator — valid for the shipped default
|
|
1619
|
+
// template AND any user-configured shape, not just the one this repo ships.
|
|
1620
|
+
const joined = before + after;
|
|
1621
|
+
const collapsed = joined.replace(BRANCH_TEMPLATE_SEP_RUN_RE, '$1').replace(BRANCH_TEMPLATE_EDGE_SEP_RE, '');
|
|
1622
|
+
return collapsed || null;
|
|
1623
|
+
}
|
|
1389
1624
|
function roadmapPhaseLookupSources(phaseNum) {
|
|
1390
1625
|
const sources = [];
|
|
1391
1626
|
const exactSource = phaseMarkdownRegexSourceExact(phaseNum);
|
|
@@ -1400,6 +1635,7 @@ module.exports = {
|
|
|
1400
1635
|
OPTIONAL_PROJECT_CODE_PREFIX_SOURCE,
|
|
1401
1636
|
OPTIONAL_PHASE_TAG_SOURCE,
|
|
1402
1637
|
PHASE_NUMBER_TOKEN_SOURCE,
|
|
1638
|
+
PHASE_DEP_REF_SOURCE,
|
|
1403
1639
|
CASE_FLEXIBLE_PROJECT_CODE_PREFIX_SOURCE,
|
|
1404
1640
|
CASE_FLEXIBLE_PHASE_NUMBER_TOKEN_SOURCE,
|
|
1405
1641
|
PHASE_CONTINUATION_SEGMENT_SOURCE,
|
|
@@ -1416,6 +1652,7 @@ module.exports = {
|
|
|
1416
1652
|
BASE_PHASE_LABEL_PREFIX_SRC,
|
|
1417
1653
|
PHASE_HEADING_BASELINE,
|
|
1418
1654
|
phaseHeadingPrefixSrcFor,
|
|
1655
|
+
buildPhaseHeadingScanRegex,
|
|
1419
1656
|
foldBracketId,
|
|
1420
1657
|
bracketQualifiedKey,
|
|
1421
1658
|
stripProjectCodePrefix,
|
|
@@ -1423,7 +1660,9 @@ module.exports = {
|
|
|
1423
1660
|
getMilestoneFromPhaseId,
|
|
1424
1661
|
getPhaseDirFromPhaseId,
|
|
1425
1662
|
parsePhaseId,
|
|
1663
|
+
renderMilestoneId,
|
|
1426
1664
|
renderPhaseId,
|
|
1665
|
+
isBracketPhaseTokenRepresentable,
|
|
1427
1666
|
toDir,
|
|
1428
1667
|
SENTINEL_RANGES,
|
|
1429
1668
|
isSentinelPhaseId,
|
|
@@ -1433,6 +1672,7 @@ module.exports = {
|
|
|
1433
1672
|
comparePhaseNum,
|
|
1434
1673
|
extractPhaseToken,
|
|
1435
1674
|
isPhaseArtifact,
|
|
1675
|
+
phaseArtifactTokenSpan,
|
|
1436
1676
|
scopeToPhase,
|
|
1437
1677
|
phaseTokenMatches,
|
|
1438
1678
|
matchPhaseDirs,
|
|
@@ -1445,4 +1685,5 @@ module.exports = {
|
|
|
1445
1685
|
stripConfiguredProjectCodePrefix,
|
|
1446
1686
|
isForeignPrefixedPhaseQuery,
|
|
1447
1687
|
roadmapPhaseLookupSources,
|
|
1688
|
+
renderPhaseBranchName,
|
|
1448
1689
|
};
|
|
@@ -25,6 +25,8 @@ exports.locateProgressTable = locateProgressTable;
|
|
|
25
25
|
exports.deriveProgressFromRoadmap = deriveProgressFromRoadmap;
|
|
26
26
|
exports.clampPercentFromFraction = clampPercentFromFraction;
|
|
27
27
|
exports.clampPercent = clampPercent;
|
|
28
|
+
exports.progressBarFilledCells = progressBarFilledCells;
|
|
29
|
+
exports.renderProgressBar = renderProgressBar;
|
|
28
30
|
const markdown_table_cjs_1 = require("./markdown-table.cjs");
|
|
29
31
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- phase-id.cjs is an export= CommonJS module
|
|
30
32
|
const phaseIdMod = require("./phase-id.cjs");
|
|
@@ -140,3 +142,62 @@ function clampPercent(completed, total) {
|
|
|
140
142
|
return 0;
|
|
141
143
|
return clampPercentFromFraction(completed / total);
|
|
142
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* How many cells of a `width`-cell progress bar are filled for `percent`.
|
|
147
|
+
*
|
|
148
|
+
* #4294: the RENDER half's kernel — the counterpart of `clampPercentFromFraction`
|
|
149
|
+
* one layer down. That function owns `fraction -> integer percent`; this one
|
|
150
|
+
* owns `integer percent -> filled cells`, and is the single place that rounding
|
|
151
|
+
* is expressed. Five inline copies of the rounding carried it before this
|
|
152
|
+
* change — three in `commands.cts`, and one each in `gsd2-import.cts` and
|
|
153
|
+
* `formatProgressMachineSegment` here; the latter two used `/ 10` with the
|
|
154
|
+
* width already substituted (`pct` in `gsd2-import.cts`, `clamped` in the
|
|
155
|
+
* formatter). (#4294 counts SIX call sites because it counts
|
|
156
|
+
* `cmdStateUpdateProgress` and `syncCore` separately; #4231 had already routed
|
|
157
|
+
* both through `formatProgressMachineSegment` — as it does
|
|
158
|
+
* `applyPostSyncPreservation` — so by this branch's base they share one copy.)
|
|
159
|
+
* Every copy saturated: at width 10 that rounds to a full bar from 95 up, at
|
|
160
|
+
* width 20 from 98 up, so a project at 19/20 plans drew the same bar as a
|
|
161
|
+
* shipped one beside a number that said otherwise.
|
|
162
|
+
*
|
|
163
|
+
* Contract:
|
|
164
|
+
* - A FULL bar is reserved for an actual 100. Below 100 the fill is held one
|
|
165
|
+
* cell short of the width. This is the only departure from the old formula:
|
|
166
|
+
* at width 10 exactly 95-99 move (10 -> 9), at width 20 exactly 98-99
|
|
167
|
+
* (20 -> 19); every other percent in 0-100 rounds as before.
|
|
168
|
+
* - `null` / `undefined` / non-finite renders an empty bar, matching the
|
|
169
|
+
* `percent === null ? 0 : ...` guard the `progress` renderers already carried.
|
|
170
|
+
* - Out-of-range input is clamped to 0-100 before rounding, so the count is
|
|
171
|
+
* always within `[0, width]` and a `'░'.repeat(width - filled)` can never
|
|
172
|
+
* be handed a negative count (the old inline form threw `RangeError` at
|
|
173
|
+
* 120%). A non-positive width yields 0.
|
|
174
|
+
*
|
|
175
|
+
* Callers wanting the glyph run call `renderProgressBar`; this is exported so
|
|
176
|
+
* the rounding rule can be pinned directly against the legacy curve.
|
|
177
|
+
*/
|
|
178
|
+
function progressBarFilledCells(percent, width) {
|
|
179
|
+
const cells = Number.isFinite(width) && width > 0 ? Math.floor(width) : 0;
|
|
180
|
+
if (cells === 0)
|
|
181
|
+
return 0;
|
|
182
|
+
if (typeof percent !== 'number' || !Number.isFinite(percent))
|
|
183
|
+
return 0;
|
|
184
|
+
const clamped = Math.max(0, Math.min(100, percent));
|
|
185
|
+
if (clamped >= 100)
|
|
186
|
+
return cells;
|
|
187
|
+
// Scale by the WIDTH, not by 100 — this is cells-from-percent, not the
|
|
188
|
+
// completion-ratio derivation lint-completion-ratio-drift.cjs guards.
|
|
189
|
+
const rounded = Math.round((clamped / 100) * cells);
|
|
190
|
+
return Math.min(rounded, cells - 1);
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Render the glyph run of a `width`-cell progress bar for `percent` —
|
|
194
|
+
* `'█'` for each filled cell, `'░'` for the rest, always exactly `width`
|
|
195
|
+
* glyphs (an empty string for a non-positive width). Brackets, the printed
|
|
196
|
+
* percent and any suffix stay with the caller; the fill rule is
|
|
197
|
+
* `progressBarFilledCells` (#4294).
|
|
198
|
+
*/
|
|
199
|
+
function renderProgressBar(percent, width) {
|
|
200
|
+
const filled = progressBarFilledCells(percent, width);
|
|
201
|
+
const cells = Number.isFinite(width) && width > 0 ? Math.floor(width) : 0;
|
|
202
|
+
return '█'.repeat(filled) + '░'.repeat(cells - filled);
|
|
203
|
+
}
|
|
@@ -35,7 +35,7 @@ const frontmatterModule = require("./frontmatter.cjs");
|
|
|
35
35
|
const { extractFrontmatter } = frontmatterModule;
|
|
36
36
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
37
37
|
const planDependencyGraphModule = require("./plan-dependency-graph.cjs");
|
|
38
|
-
const { computeHaltPropagation, buildSummaryFileIndex, isSummaryFileHalted } = planDependencyGraphModule;
|
|
38
|
+
const { computeHaltPropagation, buildSummaryFileIndex, isSummaryFileHalted, isSummaryFileBlocked } = planDependencyGraphModule;
|
|
39
39
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
40
40
|
const roadmapParserModule = require("./roadmap-parser.cjs");
|
|
41
41
|
const { getMilestonePhaseFilter } = roadmapParserModule;
|
|
@@ -146,13 +146,15 @@ function listArchiveVersionDirs(cwd, wsOverride) {
|
|
|
146
146
|
}
|
|
147
147
|
return out;
|
|
148
148
|
}
|
|
149
|
-
function searchPhaseInDir(baseDir, relBase, normalized) {
|
|
149
|
+
function searchPhaseInDir(baseDir, relBase, normalized, convention) {
|
|
150
150
|
try {
|
|
151
151
|
const dirs = readSubdirectories(baseDir, true);
|
|
152
152
|
// #2528: canonical two-pass selection (exact token match, then the
|
|
153
153
|
// bare-integer leading-digit-run fallback) shared with the find-phase and
|
|
154
154
|
// phase-plan-index scans — see phase-id.cts::matchPhaseDirs.
|
|
155
|
-
|
|
155
|
+
// #4801: the convention is threaded (optional) so bracket-convention
|
|
156
|
+
// consumers get exact token matching here too.
|
|
157
|
+
const { matches, usedBareFallback } = matchPhaseDirs(dirs, normalized, convention);
|
|
156
158
|
if (matches.length === 0)
|
|
157
159
|
return null;
|
|
158
160
|
// #2237: fail loud when multiple directories match the same bare phase
|
|
@@ -176,6 +178,7 @@ function searchPhaseInDir(baseDir, relBase, normalized) {
|
|
|
176
178
|
halted_plans: [],
|
|
177
179
|
blocked_by: {},
|
|
178
180
|
runnable_plans: [],
|
|
181
|
+
ready_plans: [],
|
|
179
182
|
};
|
|
180
183
|
}
|
|
181
184
|
const match = matches[0];
|
|
@@ -208,12 +211,19 @@ function searchPhaseInDir(baseDir, relBase, normalized) {
|
|
|
208
211
|
const planIds = plans.map(p => p.replace('-PLAN.md', '').replace('PLAN.md', ''));
|
|
209
212
|
const planIdByLower = new Map(planIds.map(id => [id.toLowerCase(), id]));
|
|
210
213
|
const canonicalToPlanId = new Map(plans.map((p, i) => [extractCanonicalPlanId(p).toLowerCase(), planIds[i]]));
|
|
214
|
+
// #4628: raw depends_on per plan, so readiness can fail closed on a
|
|
215
|
+
// DROPPED edge (a dep token that resolves to nothing carries no evidence)
|
|
216
|
+
// instead of the resolution silently shrinking the dependency list.
|
|
217
|
+
const rawDeps = plans.map((p) => parsePlanDependsOn(phaseDir, p));
|
|
218
|
+
// #4628: completion evidence excludes status:blocked summaries (#3345) —
|
|
219
|
+
// a failure record is not completion, matching cmdPhasePlanIndex's count.
|
|
220
|
+
const completionEvidence = buildSummaryFileIndex(summaries.filter((f) => !isSummaryFileBlocked(node_path_1.default.join(phaseDir, f))), extractCanonicalPlanId);
|
|
211
221
|
const haltNodes = plans.map((p, i) => {
|
|
212
222
|
const planId = planIds[i];
|
|
213
223
|
const canonical = extractCanonicalPlanId(p);
|
|
214
224
|
const summaryFile = summaryFileByPlanId.get(planId) ?? summaryFileByPlanId.get(canonical);
|
|
215
225
|
const halted = summaryFile !== undefined && isSummaryFileHalted(node_path_1.default.join(phaseDir, summaryFile));
|
|
216
|
-
const resolvedDependsOn =
|
|
226
|
+
const resolvedDependsOn = rawDeps[i]
|
|
217
227
|
.map((dep) => {
|
|
218
228
|
const lower = dep.toLowerCase();
|
|
219
229
|
return planIdByLower.get(lower) ?? canonicalToPlanId.get(lower) ?? null;
|
|
@@ -226,6 +236,7 @@ function searchPhaseInDir(baseDir, relBase, normalized) {
|
|
|
226
236
|
const incompletePlanSet = new Set(incompletePlans);
|
|
227
237
|
const blockedByFiles = {};
|
|
228
238
|
const runnablePlans = [];
|
|
239
|
+
const readyPlans = [];
|
|
229
240
|
for (let i = 0; i < plans.length; i++) {
|
|
230
241
|
const p = plans[i];
|
|
231
242
|
if (!incompletePlanSet.has(p))
|
|
@@ -233,10 +244,16 @@ function searchPhaseInDir(baseDir, relBase, normalized) {
|
|
|
233
244
|
const causes = blockedBy.get(planIds[i]) ?? [];
|
|
234
245
|
if (causes.length > 0) {
|
|
235
246
|
blockedByFiles[p] = causes;
|
|
247
|
+
continue;
|
|
236
248
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
249
|
+
runnablePlans.push(p);
|
|
250
|
+
// #4628: DAG-ready on top of runnable — every dependency must have
|
|
251
|
+
// completion evidence (a matching, non-blocked SUMMARY) and no dropped
|
|
252
|
+
// edge: both readers of this contract fail closed identically.
|
|
253
|
+
const depsComplete = rawDeps[i].length === haltNodes[i].resolvedDependsOn.length &&
|
|
254
|
+
haltNodes[i].resolvedDependsOn.every((dep) => completionEvidence.has(dep));
|
|
255
|
+
if (depsComplete)
|
|
256
|
+
readyPlans.push(p);
|
|
240
257
|
}
|
|
241
258
|
return {
|
|
242
259
|
found: true,
|
|
@@ -259,19 +276,21 @@ function searchPhaseInDir(baseDir, relBase, normalized) {
|
|
|
259
276
|
halted_plans: haltedPlans,
|
|
260
277
|
blocked_by: blockedByFiles,
|
|
261
278
|
runnable_plans: runnablePlans,
|
|
279
|
+
ready_plans: readyPlans,
|
|
262
280
|
};
|
|
263
281
|
}
|
|
264
282
|
catch {
|
|
265
283
|
return null;
|
|
266
284
|
}
|
|
267
285
|
}
|
|
268
|
-
function findPhaseInternal(cwd, phase) {
|
|
286
|
+
function findPhaseInternal(cwd, phase, convention) {
|
|
269
287
|
if (!phase)
|
|
270
288
|
return null;
|
|
271
289
|
const phasesDir = node_path_1.default.join(planningDir(cwd), 'phases');
|
|
272
290
|
const normalized = normalizePhaseName(phase);
|
|
273
291
|
const relPhasesDir = toPosixPath(node_path_1.default.relative(cwd, phasesDir));
|
|
274
|
-
|
|
292
|
+
// #4801: convention threaded through to the matcher (see searchPhaseInDir).
|
|
293
|
+
const current = searchPhaseInDir(phasesDir, relPhasesDir, normalized, convention);
|
|
275
294
|
if (current)
|
|
276
295
|
return current;
|
|
277
296
|
// #2855: scope the archived-milestone fallback to the SAME workstream as the
|
|
@@ -285,7 +304,7 @@ function findPhaseInternal(cwd, phase) {
|
|
|
285
304
|
// getArchivedPhaseDirs via listArchiveVersionDirs (see its doc comment).
|
|
286
305
|
for (const { version, archivePath } of listArchiveVersionDirs(cwd)) {
|
|
287
306
|
const relBase = toPosixPath(node_path_1.default.relative(cwd, archivePath));
|
|
288
|
-
const result = searchPhaseInDir(archivePath, relBase, normalized);
|
|
307
|
+
const result = searchPhaseInDir(archivePath, relBase, normalized, convention);
|
|
289
308
|
if (result) {
|
|
290
309
|
result.archived = version;
|
|
291
310
|
return result;
|