@opengsd/gsd-core 1.14.0 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.opencode/plugins/gsd-core.js +85 -5
- 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-code-fixer.compact.md +7 -6
- package/agents/gsd-code-fixer.md +9 -8
- package/agents/gsd-code-reviewer.compact.md +5 -3
- package/agents/gsd-code-reviewer.md +8 -6
- package/agents/gsd-debug-session-manager.compact.md +17 -2
- package/agents/gsd-debug-session-manager.md +17 -2
- package/agents/gsd-debugger.md +3 -3
- package/agents/gsd-eval-auditor.compact.md +1 -1
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +17 -12
- package/agents/gsd-intel-updater.compact.md +1 -1
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.md +2 -2
- package/agents/gsd-phase-researcher.md +19 -11
- package/agents/gsd-plan-checker.md +15 -9
- package/agents/gsd-planner.md +15 -11
- package/agents/gsd-project-researcher.compact.md +1 -1
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.compact.md +1 -1
- package/agents/gsd-research-synthesizer.md +1 -1
- package/agents/gsd-ui-auditor.compact.md +21 -30
- package/agents/gsd-ui-auditor.md +166 -37
- package/agents/gsd-ui-researcher.compact.md +1 -1
- package/agents/gsd-ui-researcher.md +1 -1
- package/agents/gsd-verifier.md +37 -14
- package/bin/install.js +764 -287
- package/commands/gsd/add-tests.md +6 -1
- package/commands/gsd/ai-integration-phase.md +6 -1
- package/commands/gsd/audit-fix.md +5 -0
- package/commands/gsd/audit-milestone.md +6 -1
- package/commands/gsd/autonomous.md +7 -2
- package/commands/gsd/capture.md +9 -5
- package/commands/gsd/code-review.md +7 -2
- package/commands/gsd/complete-milestone.md +4 -0
- package/commands/gsd/config.md +7 -3
- package/commands/gsd/debug.md +11 -7
- package/commands/gsd/discuss-phase.md +7 -3
- package/commands/gsd/docs-update.md +12 -7
- package/commands/gsd/eval-review.md +6 -1
- package/commands/gsd/execute-phase.md +12 -7
- package/commands/gsd/extract-learnings.md +5 -0
- package/commands/gsd/fast.md +4 -0
- package/commands/gsd/forensics.md +5 -1
- package/commands/gsd/graphify.md +10 -6
- package/commands/gsd/health.md +5 -0
- package/commands/gsd/help.md +7 -2
- package/commands/gsd/import.md +7 -3
- package/commands/gsd/inbox.md +5 -0
- package/commands/gsd/ingest-docs.md +5 -1
- package/commands/gsd/manager.md +6 -1
- package/commands/gsd/map-codebase.md +7 -3
- package/commands/gsd/mempalace-capture.md +12 -4
- package/commands/gsd/mempalace-recall.md +5 -1
- package/commands/gsd/milestone-summary.md +5 -1
- package/commands/gsd/mvp-phase.md +8 -3
- package/commands/gsd/new-milestone.md +6 -1
- package/commands/gsd/new-project.md +5 -0
- package/commands/gsd/next.md +6 -1
- package/commands/gsd/ns-context.md +4 -0
- package/commands/gsd/ns-ideate.md +4 -0
- package/commands/gsd/ns-manage.md +4 -0
- package/commands/gsd/ns-project.md +4 -0
- package/commands/gsd/ns-review.md +4 -0
- package/commands/gsd/ns-workflow.md +4 -0
- package/commands/gsd/onboard.md +6 -1
- package/commands/gsd/pause-work.md +5 -1
- package/commands/gsd/phase.md +8 -4
- package/commands/gsd/plan-phase.md +6 -1
- package/commands/gsd/plan-review-convergence.md +11 -7
- package/commands/gsd/pr-branch.md +4 -0
- package/commands/gsd/profile-user.md +5 -1
- package/commands/gsd/progress.md +7 -2
- package/commands/gsd/quick-batch.md +21 -9
- package/commands/gsd/quick.md +12 -7
- package/commands/gsd/review.md +7 -4
- package/commands/gsd/secure-phase.md +6 -1
- package/commands/gsd/ship.md +5 -0
- package/commands/gsd/sketch.md +7 -2
- package/commands/gsd/spec-phase.md +5 -1
- package/commands/gsd/spike.md +8 -3
- package/commands/gsd/surface.md +5 -1
- package/commands/gsd/thread.md +4 -0
- package/commands/gsd/ui-phase.md +6 -1
- package/commands/gsd/ui-review.md +6 -1
- package/commands/gsd/ultraplan-phase.md +5 -1
- package/commands/gsd/undo.md +5 -1
- package/commands/gsd/update.md +6 -2
- package/commands/gsd/validate-phase.md +6 -1
- package/commands/gsd/verify-work.md +6 -1
- package/commands/gsd/workspace.md +7 -3
- package/gsd-core/bin/gsd-tools.cjs +477 -78
- package/gsd-core/bin/lib/active-workstream-store.cjs +15 -0
- package/gsd-core/bin/lib/adr-parser.cjs +3 -1
- package/gsd-core/bin/lib/agent-install-check.cjs +4 -1
- package/gsd-core/bin/lib/audit.cjs +144 -42
- package/gsd-core/bin/lib/broken-windows.cjs +13 -13
- package/gsd-core/bin/lib/capability-activation.cjs +9 -4
- package/gsd-core/bin/lib/capability-registry.cjs +197 -222
- package/gsd-core/bin/lib/capability-validator.cjs +16 -1
- package/gsd-core/bin/lib/check-auto-mode.cjs +35 -0
- package/gsd-core/bin/lib/check-command-router.cjs +164 -1625
- package/gsd-core/bin/lib/cjs-command-router-adapter.cjs +13 -2
- package/gsd-core/bin/lib/cli-exit.cjs +12 -0
- package/gsd-core/bin/lib/codex-agent-toml.cjs +32 -33
- package/gsd-core/bin/lib/command-aliases.cjs +7 -0
- package/gsd-core/bin/lib/command-routing-hub.cjs +48 -1
- package/gsd-core/bin/lib/commands.cjs +343 -207
- package/gsd-core/bin/lib/complexity-trigger.cjs +8 -7
- package/gsd-core/bin/lib/config-loader.cjs +65 -4
- package/gsd-core/bin/lib/config.cjs +76 -19
- package/gsd-core/bin/lib/core-utils.cjs +6 -1
- package/gsd-core/bin/lib/coverage.cjs +4 -8
- package/gsd-core/bin/lib/decision-coverage-support.cjs +259 -0
- package/gsd-core/bin/lib/decisions.cjs +30 -14
- package/gsd-core/bin/lib/drift.cjs +177 -42
- package/gsd-core/bin/lib/frontmatter-fence.cjs +90 -0
- package/gsd-core/bin/lib/frontmatter-splice.cjs +494 -0
- package/gsd-core/bin/lib/frontmatter.cjs +426 -234
- package/gsd-core/bin/lib/gap-checker.cjs +72 -29
- package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +381 -0
- package/gsd-core/bin/lib/gate-args.cjs +53 -0
- package/gsd-core/bin/lib/gate-codebase-drift.cjs +285 -0
- package/gsd-core/bin/lib/gate-config.cjs +46 -0
- package/gsd-core/bin/lib/gate-context-drift.cjs +141 -0
- package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +169 -0
- package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +126 -0
- package/gsd-core/bin/lib/gate-evaluation-scope.cjs +555 -0
- package/gsd-core/bin/lib/gate-evidence.cjs +138 -0
- package/gsd-core/bin/lib/gate-exit.cjs +27 -0
- package/gsd-core/bin/lib/gate-gap-analysis-plan-post.cjs +61 -0
- package/gsd-core/bin/lib/gate-phase-context.cjs +170 -0
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +1 -1
- package/gsd-core/bin/lib/gate-predicate.cjs +165 -0
- package/gsd-core/bin/lib/gate-prohibition-enforcement.cjs +94 -0
- package/gsd-core/bin/lib/gate-schema-drift.cjs +165 -0
- package/gsd-core/bin/lib/gate-tdd-red-evidence.cjs +100 -0
- package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +182 -0
- package/gsd-core/bin/lib/gate-ui-plan.cjs +86 -0
- package/gsd-core/bin/lib/gate-ui-safety.cjs +80 -0
- package/gsd-core/bin/lib/gate-verdict.cjs +64 -0
- package/gsd-core/bin/lib/gate-verify-command-paths.cjs +78 -0
- package/gsd-core/bin/lib/gate-verify-failure-directions.cjs +41 -0
- package/gsd-core/bin/lib/graphify.cjs +10 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +43 -47
- package/gsd-core/bin/lib/health-diagnostic.cjs +45 -8
- package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
- package/gsd-core/bin/lib/init.cjs +364 -132
- package/gsd-core/bin/lib/install-engine.cjs +30 -33
- package/gsd-core/bin/lib/install-profiles.cjs +7 -4
- package/gsd-core/bin/lib/installer-migrations.cjs +8 -1
- package/gsd-core/bin/lib/io.cjs +122 -3
- package/gsd-core/bin/lib/loop-resolver.cjs +95 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +75 -1
- package/gsd-core/bin/lib/milestone.cjs +37 -6
- package/gsd-core/bin/lib/model-resolver.cjs +171 -62
- package/gsd-core/bin/lib/observability/event.cjs +1 -1
- package/gsd-core/bin/lib/observability/logger.cjs +46 -1
- package/gsd-core/bin/lib/pattern.cjs +10 -0
- package/gsd-core/bin/lib/phase-command-router.cjs +20 -5
- package/gsd-core/bin/lib/phase-estimation.cjs +5 -4
- 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 +110 -8
- package/gsd-core/bin/lib/phase-lifecycle.cjs +9 -2
- package/gsd-core/bin/lib/phase-locator.cjs +29 -10
- package/gsd-core/bin/lib/phase-status.cjs +360 -0
- package/gsd-core/bin/lib/phase.cjs +489 -88
- package/gsd-core/bin/lib/plan-document.cjs +142 -20
- package/gsd-core/bin/lib/plan-drift-guard.cjs +5 -0
- package/gsd-core/bin/lib/planning-document.cjs +692 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +60 -9
- package/gsd-core/bin/lib/planning-snapshot.cjs +18 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +83 -55
- package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
- package/gsd-core/bin/lib/pristine-baseline.cjs +10 -0
- package/gsd-core/bin/lib/probe-core.cjs +7 -1
- package/gsd-core/bin/lib/profile-output.cjs +6 -3
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +0 -55
- package/gsd-core/bin/lib/project-root.cjs +41 -2
- package/gsd-core/bin/lib/quick-batch-command-router.cjs +35 -9
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +11 -8
- package/gsd-core/bin/lib/real-home-guard.cjs +9 -1
- package/gsd-core/bin/lib/report-parser.cjs +269 -0
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +10 -30
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
- package/gsd-core/bin/lib/roadmap-command-router.cjs +25 -19
- package/gsd-core/bin/lib/roadmap-parser.cjs +242 -15
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +1653 -65
- package/gsd-core/bin/lib/roadmap.cjs +405 -88
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +373 -187
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +5 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +57 -1
- package/gsd-core/bin/lib/runtime-homes.cjs +14 -7
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +522 -624
- package/gsd-core/bin/lib/runtime-name-policy.cjs +246 -21
- package/gsd-core/bin/lib/runtime-slash.cjs +47 -30
- package/gsd-core/bin/lib/shell-command-projection.cjs +47 -8
- package/gsd-core/bin/lib/smart-entry.cjs +19 -3
- package/gsd-core/bin/lib/stale-bake-guard.cjs +32 -48
- package/gsd-core/bin/lib/state-contract.cjs +15 -18
- package/gsd-core/bin/lib/state-document.cjs +100 -22
- package/gsd-core/bin/lib/state-transition.cjs +39 -2
- package/gsd-core/bin/lib/state.cjs +256 -105
- package/gsd-core/bin/lib/surface.cjs +19 -2
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +48 -79
- package/gsd-core/bin/lib/uat-predicate.cjs +359 -38
- package/gsd-core/bin/lib/uat.cjs +432 -7
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +167 -40
- package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
- package/gsd-core/bin/lib/vendor/README.md +31 -9
- package/gsd-core/bin/lib/vendor/saxes.cjs +1934 -0
- package/gsd-core/bin/lib/vendor/saxes.cjs.LICENSE.txt +92 -0
- package/gsd-core/bin/lib/vendor/tap-parser.cjs +8927 -0
- package/gsd-core/bin/lib/vendor/tap-parser.cjs.LICENSE.txt +152 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +1378 -274
- package/gsd-core/bin/lib/verify-command-grounding.cjs +46 -2
- package/gsd-core/bin/lib/verify-command-router.cjs +18 -7
- package/gsd-core/bin/lib/verify.cjs +357 -531
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +14 -6
- package/gsd-core/bin/lib/workstream-inventory.cjs +31 -21
- package/gsd-core/bin/lib/workstream-name-policy.cjs +31 -1
- package/gsd-core/bin/lib/workstream.cjs +11 -2
- package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
- package/gsd-core/bin/lib/worktree-safety.cjs +784 -51
- package/gsd-core/bin/shared/config-defaults.manifest.json +8 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +5 -0
- package/gsd-core/references/autonomous-smart-discuss.md +2 -1
- package/gsd-core/references/autonomous-ui-design-contract.md +3 -3
- package/gsd-core/references/checkpoints.md +5 -3
- 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-mvp-tdd.md +5 -10
- package/gsd-core/references/execute-phase-between-wave-reset.md +10 -6
- package/gsd-core/references/execute-phase-response-language.md +1 -1
- 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 +7 -1
- package/gsd-core/references/model-profiles.md +1 -1
- package/gsd-core/references/offer-next.md +1 -1
- 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 +12 -3
- package/gsd-core/references/spidr-splitting.md +1 -1
- package/gsd-core/references/tdd.md +37 -8
- package/gsd-core/references/ui-consideration-probe.md +10 -5
- package/gsd-core/references/verifier-phase-gates.md +5 -2
- package/gsd-core/references/verify-command-path-resolvability.md +10 -2
- package/gsd-core/references/verify-mvp-mode.md +2 -2
- package/gsd-core/references/workstream-flag.md +33 -3
- package/gsd-core/references/worktree-path-safety.md +321 -0
- package/gsd-core/templates/README.md +1 -1
- package/gsd-core/templates/UAT.md +17 -1
- package/gsd-core/templates/config.json +2 -11
- 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 +8 -7
- package/gsd-core/workflows/add-tests.md +4 -3
- package/gsd-core/workflows/add-todo.md +6 -5
- package/gsd-core/workflows/ai-integration-phase.md +13 -4
- package/gsd-core/workflows/audit-fix.md +1 -1
- package/gsd-core/workflows/audit-milestone.md +4 -3
- 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 +43 -23
- package/gsd-core/workflows/check-todos.md +7 -6
- package/gsd-core/workflows/cleanup.md +2 -2
- package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +20 -13
- package/gsd-core/workflows/code-review-fix.md +112 -25
- package/gsd-core/workflows/code-review.md +146 -135
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +4 -3
- package/gsd-core/workflows/complete-milestone.md +13 -8
- package/gsd-core/workflows/debug.md +32 -7
- package/gsd-core/workflows/diagnose-issues.md +3 -2
- 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 +4 -3
- package/gsd-core/workflows/discuss-phase.md +4 -3
- package/gsd-core/workflows/do.md +2 -2
- package/gsd-core/workflows/docs-update.md +6 -5
- package/gsd-core/workflows/edit-phase.md +4 -3
- package/gsd-core/workflows/eval-review.md +14 -5
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1019 -0
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +15 -4
- package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +10 -7
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +37 -3
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +3 -1
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +2 -2
- 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/regression-gate.md +2 -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/verify-phase-goal.md +187 -0
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +2 -3
- package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
- package/gsd-core/workflows/execute-phase.md +96 -178
- package/gsd-core/workflows/execute-plan.md +18 -23
- package/gsd-core/workflows/explore.md +4 -4
- package/gsd-core/workflows/extract-learnings.md +4 -2
- 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 +3 -2
- package/gsd-core/workflows/help/modes/full.compact.md +3 -3
- package/gsd-core/workflows/help/modes/full.md +5 -5
- package/gsd-core/workflows/help/modes/topic.md +15 -5
- package/gsd-core/workflows/import.md +4 -3
- package/gsd-core/workflows/inbox.md +2 -2
- package/gsd-core/workflows/ingest-docs.md +3 -3
- package/gsd-core/workflows/insert-phase.md +4 -3
- package/gsd-core/workflows/list-seeds.md +1 -1
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +6 -4
- package/gsd-core/workflows/map-codebase.md +5 -4
- package/gsd-core/workflows/milestone-summary.md +3 -2
- package/gsd-core/workflows/mvp-phase.md +14 -14
- package/gsd-core/workflows/new-milestone.md +11 -11
- 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 +7 -7
- 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 +2 -2
- package/gsd-core/workflows/plan-phase/detail/elaboration.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
- package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +1 -1
- 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 +50 -24
- 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 +113 -13
- package/gsd-core/workflows/profile-user.md +2 -2
- package/gsd-core/workflows/progress.md +19 -49
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +27 -0
- package/gsd-core/workflows/quick/steps/quick-verification.md +4 -4
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +30 -11
- 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 +9 -3
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
- package/gsd-core/workflows/quick-batch.md +15 -10
- package/gsd-core/workflows/quick.md +62 -39
- package/gsd-core/workflows/reapply-patches.md +9 -3
- package/gsd-core/workflows/remove-phase.md +3 -2
- package/gsd-core/workflows/remove-workspace.md +2 -2
- package/gsd-core/workflows/resume-project.md +3 -2
- package/gsd-core/workflows/review.md +33 -17
- package/gsd-core/workflows/scan.md +3 -2
- package/gsd-core/workflows/secure-phase.md +13 -13
- package/gsd-core/workflows/settings-advanced.md +30 -10
- package/gsd-core/workflows/settings-integrations.md +2 -3
- package/gsd-core/workflows/settings.md +4 -4
- package/gsd-core/workflows/ship.md +14 -13
- 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 +2 -2
- package/gsd-core/workflows/transition.md +13 -23
- package/gsd-core/workflows/ui-phase.md +48 -11
- package/gsd-core/workflows/ui-review.md +21 -6
- package/gsd-core/workflows/ultraplan-phase.md +3 -2
- package/gsd-core/workflows/undo.md +339 -20
- package/gsd-core/workflows/update.md +7 -7
- package/gsd-core/workflows/validate-phase.md +12 -13
- package/gsd-core/workflows/verify-work/detail/elaboration.md +43 -3
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -3
- package/gsd-core/workflows/verify-work.md +129 -55
- package/hooks/dist/gsd-agent-isolation-guard.js +32 -0
- package/hooks/dist/gsd-check-update-worker.js +8 -0
- package/hooks/dist/gsd-check-update.js +8 -0
- package/hooks/dist/gsd-context-monitor.js +31 -8
- package/hooks/dist/gsd-cursor-subagent-start.js +8 -0
- package/hooks/dist/gsd-secret-read-guard.js +161 -4
- package/hooks/dist/gsd-statusline.js +85 -20
- package/hooks/dist/gsd-update-banner.js +8 -0
- package/hooks/dist/gsd-validate-commit.sh +63 -4
- package/hooks/dist/gsd-windsurf-pre-write.js +11 -2
- package/hooks/dist/gsd-workflow-guard.js +5 -4
- package/hooks/dist/gsd-worktree-path-guard.js +6 -2
- package/hooks/dist/lib/cli-exit.js +12 -0
- package/hooks/dist/lib/git-probe.js +17 -1
- package/hooks/dist/lib/isolation-sentinel.js +2 -2
- package/hooks/gsd-agent-isolation-guard.js +32 -0
- package/hooks/gsd-check-update-worker.js +8 -0
- package/hooks/gsd-check-update.js +8 -0
- package/hooks/gsd-context-monitor.js +31 -8
- package/hooks/gsd-cursor-subagent-start.js +8 -0
- package/hooks/gsd-secret-read-guard.js +161 -4
- package/hooks/gsd-statusline.js +85 -20
- package/hooks/gsd-update-banner.js +8 -0
- package/hooks/gsd-validate-commit.sh +63 -4
- package/hooks/gsd-windsurf-pre-write.js +11 -2
- package/hooks/gsd-workflow-guard.js +5 -4
- package/hooks/gsd-worktree-path-guard.js +6 -2
- package/hooks/hooks.json +5 -5
- package/hooks/lib/cli-exit.js +12 -0
- package/hooks/lib/git-probe.js +17 -1
- package/hooks/lib/isolation-sentinel.js +2 -2
- package/package.json +22 -4
- package/scripts/build-hooks.js +15 -6
- package/scripts/changeset/parse.cjs +52 -4
- package/scripts/check-contract-drift.cjs +127 -11
- package/scripts/ci-timeout-report.cjs +770 -4
- package/scripts/command-contract-helpers.cjs +15 -8
- package/scripts/docs-guard-registry.cjs +34 -0
- package/scripts/gen-features.cjs +13 -8
- package/scripts/gen-hooks-cli-exit.cjs +12 -28
- package/scripts/gen-loop-host-contract.cjs +79 -1
- package/scripts/gen-platform-conformance-tier.cjs +187 -1
- package/scripts/gen-plugin-skills.cjs +87 -1
- package/scripts/gen-research-agents.cjs +24 -31
- package/scripts/gen-scripts-cli-exit.cjs +30 -3
- package/scripts/gen-test-timings.cjs +32 -7
- package/scripts/lib/cli-exit.cjs +12 -0
- package/scripts/lib/macos-conformance-tier.generated.cjs +34 -2
- package/scripts/lib/ndjson-reporter.cjs +31 -5
- package/scripts/lib/platform-conformance-tier.generated.cjs +45 -5
- package/scripts/lib/registration-ledger-preload.cjs +155 -0
- package/scripts/lib/vendor-bundle.cjs +59 -0
- package/scripts/lib/vendor-licenses/saxes-6.0.0.txt +64 -0
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
- package/scripts/lint-completion-predicate-drift.cjs +18 -19
- package/scripts/lint-descriptions.cjs +7 -3
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +38 -1
- package/scripts/lint-eslint-glob-coverage.allowlist.json +20 -0
- package/scripts/lint-frontmatter-fence-drift.cjs +313 -0
- package/scripts/lint-frontmatter-scalar-broad-grep.cjs +122 -16
- package/scripts/lint-phase-arg-assignment.cjs +257 -0
- package/scripts/lint-phase-enumeration-drift.cjs +12 -8
- package/scripts/lint-phase-id-drift.cjs +319 -5
- package/scripts/lint-planning-document-positive-control.cjs +329 -0
- package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
- package/scripts/lint-response-language-coverage.cjs +3 -0
- package/scripts/lint-retired-runtime-name.cjs +619 -0
- package/scripts/lint-skill-deps.cjs +7 -3
- package/scripts/lint-state-write-path-drift.cjs +93 -0
- package/scripts/lint-test-file-count.allowlist.json +38 -9
- package/scripts/lint-test-file-count.cjs +34 -1
- package/scripts/lint-vendored-deps.cjs +41 -5
- package/scripts/lint-workflow-shellcheck-baseline.json +25 -10
- package/scripts/mutation-matrix.cjs +50 -5
- package/scripts/prompt-injection-scan.sh +4 -0
- package/scripts/release-tarball-smoke.cjs +194 -1
- package/scripts/require-issue-link-policy.cjs +6 -2
- package/scripts/sync-runtime-launcher.cjs +184 -2
- package/scripts/verify-npm-publish.cjs +76 -20
- package/skills/gsd-add-tests/SKILL.md +6 -1
- package/skills/gsd-ai-integration-phase/SKILL.md +6 -1
- package/skills/gsd-audit-fix/SKILL.md +5 -0
- package/skills/gsd-audit-milestone/SKILL.md +6 -1
- package/skills/gsd-autonomous/SKILL.md +7 -2
- package/skills/gsd-capture/SKILL.md +9 -5
- package/skills/gsd-code-review/SKILL.md +7 -2
- package/skills/gsd-complete-milestone/SKILL.md +4 -0
- package/skills/gsd-config/SKILL.md +8 -4
- package/skills/gsd-debug/SKILL.md +11 -7
- package/skills/gsd-discuss-phase/SKILL.md +7 -3
- package/skills/gsd-docs-update/SKILL.md +12 -7
- package/skills/gsd-eval-review/SKILL.md +6 -1
- package/skills/gsd-execute-phase/SKILL.md +12 -7
- package/skills/gsd-extract-learnings/SKILL.md +5 -0
- package/skills/gsd-fast/SKILL.md +4 -0
- package/skills/gsd-forensics/SKILL.md +5 -1
- package/skills/gsd-graphify/SKILL.md +10 -6
- package/skills/gsd-health/SKILL.md +5 -0
- package/skills/gsd-help/SKILL.md +7 -2
- package/skills/gsd-import/SKILL.md +7 -3
- package/skills/gsd-inbox/SKILL.md +5 -0
- package/skills/gsd-ingest-docs/SKILL.md +5 -1
- package/skills/gsd-manager/SKILL.md +6 -1
- package/skills/gsd-map-codebase/SKILL.md +7 -3
- package/skills/gsd-mempalace-capture/SKILL.md +12 -4
- package/skills/gsd-mempalace-recall/SKILL.md +5 -1
- package/skills/gsd-milestone-summary/SKILL.md +5 -1
- package/skills/gsd-mvp-phase/SKILL.md +8 -3
- package/skills/gsd-new-milestone/SKILL.md +6 -1
- package/skills/gsd-new-project/SKILL.md +5 -0
- package/skills/gsd-next/SKILL.md +6 -1
- package/skills/gsd-ns-context/SKILL.md +4 -0
- package/skills/gsd-ns-ideate/SKILL.md +4 -0
- package/skills/gsd-ns-manage/SKILL.md +4 -0
- package/skills/gsd-ns-project/SKILL.md +4 -0
- package/skills/gsd-ns-review/SKILL.md +4 -0
- package/skills/gsd-ns-workflow/SKILL.md +4 -0
- package/skills/gsd-onboard/SKILL.md +6 -1
- package/skills/gsd-pause-work/SKILL.md +5 -1
- package/skills/gsd-phase/SKILL.md +8 -4
- package/skills/gsd-plan-phase/SKILL.md +6 -1
- package/skills/gsd-plan-review-convergence/SKILL.md +10 -6
- package/skills/gsd-pr-branch/SKILL.md +4 -0
- package/skills/gsd-profile-user/SKILL.md +5 -1
- package/skills/gsd-progress/SKILL.md +7 -2
- package/skills/gsd-quick/SKILL.md +16 -10
- package/skills/gsd-quick-batch/SKILL.md +21 -9
- package/skills/gsd-review/SKILL.md +7 -4
- package/skills/gsd-review-backlog/SKILL.md +3 -2
- package/skills/gsd-secure-phase/SKILL.md +6 -1
- package/skills/gsd-ship/SKILL.md +5 -0
- package/skills/gsd-sketch/SKILL.md +7 -2
- package/skills/gsd-spec-phase/SKILL.md +5 -1
- package/skills/gsd-spike/SKILL.md +8 -3
- package/skills/gsd-surface/SKILL.md +5 -1
- package/skills/gsd-thread/SKILL.md +4 -0
- package/skills/gsd-ui-phase/SKILL.md +6 -1
- package/skills/gsd-ui-review/SKILL.md +6 -1
- package/skills/gsd-ultraplan-phase/SKILL.md +5 -1
- package/skills/gsd-undo/SKILL.md +5 -1
- package/skills/gsd-update/SKILL.md +6 -2
- package/skills/gsd-validate-phase/SKILL.md +6 -1
- package/skills/gsd-verify-work/SKILL.md +6 -1
- package/skills/gsd-workspace/SKILL.md +7 -3
- package/skills/gsd-workstreams/SKILL.md +6 -6
- package/vscode/package.json +1 -1
|
@@ -14,6 +14,7 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
14
14
|
const text_lines_cjs_1 = require("./text-lines.cjs");
|
|
15
15
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
16
16
|
const pattern_cjs_1 = require("./pattern.cjs");
|
|
17
|
+
const frontmatter_fence_cjs_1 = require("./frontmatter-fence.cjs");
|
|
17
18
|
const security_cjs_1 = require("./security.cjs");
|
|
18
19
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
19
20
|
const ioMod = require("./io.cjs");
|
|
@@ -30,7 +31,10 @@ const coreUtilsMod = require("./core-utils.cjs");
|
|
|
30
31
|
const { toPosixPath, generateSlugInternal, extractOneLinerFromBody } = coreUtilsMod;
|
|
31
32
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
32
33
|
const phaseIdMod = require("./phase-id.cjs");
|
|
33
|
-
const { normalizePhaseName, comparePhaseNum, extractPhaseToken, PHASE_NUMBER_TOKEN_SOURCE, isSentinelPhaseId, renderPhaseBranchName } = phaseIdMod;
|
|
34
|
+
const { normalizePhaseName, comparePhaseNum, extractPhaseToken, PHASE_NUMBER_TOKEN_SOURCE, isSentinelPhaseId, renderPhaseBranchName, parsePhaseId, renderPhaseId, phaseHeadingPrefixSrcFor, PHASE_HEADING_BASELINE, } = phaseIdMod;
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
36
|
+
const phaseIdDisplayMod = require("./phase-id-display.cjs");
|
|
37
|
+
const { renderBracketMilestoneDisplay } = phaseIdDisplayMod;
|
|
34
38
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
35
39
|
const phaseLocatorMod = require("./phase-locator.cjs");
|
|
36
40
|
const { getArchivedPhaseDirs, findPhaseInternal, listMilestonePhaseDirs } = phaseLocatorMod;
|
|
@@ -56,10 +60,10 @@ const codex_agent_toml_cjs_1 = require("./codex-agent-toml.cjs");
|
|
|
56
60
|
const hostIntegrationMod = require("./host-integration.cjs");
|
|
57
61
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
58
62
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
59
|
-
const { planningDir, planningPaths, todosDir } = planningWorkspace;
|
|
63
|
+
const { planningDir, planningPaths, todosDir, resolvePhaseIdConvention } = planningWorkspace;
|
|
60
64
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
61
65
|
const frontmatter = require("./frontmatter.cjs");
|
|
62
|
-
const { extractFrontmatter, agentScalarNeedsDoubleQuoting, escapeDoubleQuotedScalar } = frontmatter;
|
|
66
|
+
const { extractFrontmatter, agentScalarNeedsDoubleQuoting, escapeDoubleQuotedScalar, FRONTMATTER_UNPARSEABLE } = frontmatter;
|
|
63
67
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
64
68
|
const modelProfiles = require("./model-profiles.cjs");
|
|
65
69
|
const { MODEL_PROFILES, VALID_PHASE_TYPES } = modelProfiles;
|
|
@@ -69,96 +73,65 @@ const phase_lifecycle_cjs_1 = require("./phase-lifecycle.cjs");
|
|
|
69
73
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
70
74
|
const planScanMod = require("./plan-scan.cjs");
|
|
71
75
|
const { scanPhasePlans } = planScanMod;
|
|
72
|
-
//
|
|
76
|
+
// #5060: Phase Status Module — the single owner of "what state is phase P
|
|
77
|
+
// in?" (ADR-5057 §1/§2). Replaces this file's own precedence ladder and
|
|
78
|
+
// determinePhaseStatus derivation.
|
|
79
|
+
const phase_status_cjs_1 = require("./phase-status.cjs");
|
|
80
|
+
// #5118: an aggregate over a refused verification report fails through the owner.
|
|
81
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
73
82
|
const verificationMod = require("./verification.cjs");
|
|
74
|
-
const {
|
|
75
|
-
|
|
83
|
+
const { failOnVerificationStatusError, firstStatusError } = verificationMod;
|
|
84
|
+
/** The effort-sync line editors' fence reading: a block behind a preamble is still edited (#3706). */
|
|
85
|
+
const EFFORT_SYNC_FENCE = Object.freeze({ allowPreamble: true });
|
|
86
|
+
/** Prefer the Claude agents beside the running local install over the global home. */
|
|
87
|
+
function claudeEffortAgentsDir(explicitDir) {
|
|
88
|
+
if (explicitDir)
|
|
89
|
+
return node_path_1.default.join(explicitDir, 'agents');
|
|
90
|
+
// Compiled commands.cjs lives at <configDir>/gsd-core/bin/lib. In a source
|
|
91
|
+
// checkout the same path leads to the repository root, where no install
|
|
92
|
+
// manifest exists; keep the existing global fallback in that case.
|
|
93
|
+
const configDir = node_path_1.default.resolve(__dirname, '..', '..', '..');
|
|
94
|
+
try {
|
|
95
|
+
const manifestPath = node_path_1.default.join(configDir, 'gsd-file-manifest.json');
|
|
96
|
+
if (node_fs_1.default.lstatSync(manifestPath).isFile()) {
|
|
97
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
98
|
+
const migrations = require('./installer-migrations.cjs');
|
|
99
|
+
const manifest = migrations.readInstallManifest(configDir);
|
|
100
|
+
if (manifest.runtime === 'claude' && manifest.scope === 'local') {
|
|
101
|
+
return node_path_1.default.join(configDir, 'agents');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch { /* an absent or unreadable manifest retains the global fallback */ }
|
|
106
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/unbound-method
|
|
107
|
+
const { getGlobalConfigDir } = require('./runtime-homes.cjs');
|
|
108
|
+
return node_path_1.default.join(getGlobalConfigDir('claude'), 'agents');
|
|
109
|
+
}
|
|
76
110
|
/**
|
|
77
|
-
*
|
|
111
|
+
* Project one canonical bracket directory onto its display identity and slug.
|
|
78
112
|
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* than overwritten last-write-wins — otherwise `/gsd-stats` reports whatever
|
|
83
|
-
* directory `fs.readdirSync` happened to yield last, which is non-deterministic
|
|
84
|
-
* across platforms and can silently call a `Complete` phase `Not Started`.
|
|
85
|
-
*/
|
|
86
|
-
const PHASE_STATUS_PRECEDENCE = [
|
|
87
|
-
'Complete',
|
|
88
|
-
'Needs Review',
|
|
89
|
-
'Executed',
|
|
90
|
-
'In Progress',
|
|
91
|
-
'Planned',
|
|
92
|
-
'Not Started',
|
|
93
|
-
'Pending',
|
|
94
|
-
];
|
|
95
|
-
const PHASE_STATUS_RANK = new Map(PHASE_STATUS_PRECEDENCE.map((s, i) => [s, i]));
|
|
96
|
-
/**
|
|
97
|
-
* Fold two phase statuses by precedence — returns whichever is further along
|
|
98
|
-
* the {@link PHASE_STATUS_PRECEDENCE} ladder. Unrecognized statuses fall behind
|
|
99
|
-
* every recognized one (so a recognized status always wins over an unknown one;
|
|
100
|
-
* two unrecognized statuses favor `a` for determinism).
|
|
113
|
+
* The identity is deliberately obtained only through parsePhaseId/renderPhaseId;
|
|
114
|
+
* this helper owns no second bracket grammar. Callers invoke it only after the
|
|
115
|
+
* project's resolved convention is exactly `bracket`.
|
|
101
116
|
*/
|
|
102
|
-
function
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return ra <= rb ? a : b;
|
|
117
|
+
function bracketPhaseDirProjection(dir) {
|
|
118
|
+
const id = parsePhaseId(dir);
|
|
119
|
+
const number = id.subphase ? `${id.phase}.${id.subphase}` : id.phase;
|
|
120
|
+
const identityPrefix = `${id.project}.${id.milestone}-${number}`;
|
|
121
|
+
const slug = dir.slice(identityPrefix.length).replace(/^-/, '');
|
|
122
|
+
return {
|
|
123
|
+
number,
|
|
124
|
+
display_id: renderPhaseId(id),
|
|
125
|
+
name: slug ? slug.replace(/-/g, ' ') : '',
|
|
126
|
+
};
|
|
113
127
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (summaries < plans && summaries > 0)
|
|
122
|
-
return 'In Progress';
|
|
123
|
-
if (summaries < plans)
|
|
124
|
-
return 'Planned';
|
|
125
|
-
// summaries >= plans — check verification
|
|
126
|
-
try {
|
|
127
|
-
const files = node_fs_1.default.readdirSync(phaseDir);
|
|
128
|
-
// #3473 F2: routed through the shared resolver (readdir order is
|
|
129
|
-
// filesystem-dependent, so the prior hand-rolled `.find()` could pick
|
|
130
|
-
// either file when a phase held both a canonical report and an ad-hoc
|
|
131
|
-
// `-CORRECTION-VERIFICATION.md` worksheet — see #3357).
|
|
132
|
-
// #3492: pin selection to THIS phase's own token so a stray cross-phase
|
|
133
|
-
// or sentinel-numbered canonically-shaped file cannot outrank this
|
|
134
|
-
// phase's own (possibly non-canonical) report.
|
|
135
|
-
const phaseDirName = node_path_1.default.basename(phaseDir);
|
|
136
|
-
const phaseToken = extractPhaseToken(phaseDirName);
|
|
137
|
-
const verificationFile = resolveVerificationFile(files, { allowBare: true, phaseToken, phaseDirName });
|
|
138
|
-
if (verificationFile) {
|
|
139
|
-
const verificationFilePath = node_path_1.default.join(phaseDir, verificationFile);
|
|
140
|
-
const content = (0, shell_command_projection_cjs_1.platformReadSync)(verificationFilePath) || '';
|
|
141
|
-
// #1159 (Defect A): read ONLY the frontmatter `status` key to avoid false
|
|
142
|
-
// matches from historical body metadata such as `previous_status: gaps_found`.
|
|
143
|
-
// Full-text regexes like /status:\s*gaps_found/ match the substring inside
|
|
144
|
-
// `previous_status: gaps_found`, producing incorrect phase status labels.
|
|
145
|
-
const fm = extractFrontmatter(content, verificationFilePath);
|
|
146
|
-
// Normalise to lower-case to preserve the prior case-insensitive behaviour
|
|
147
|
-
// while reading only the frontmatter `status` key (not the full body text).
|
|
148
|
-
const fmStatus = typeof fm['status'] === 'string' ? fm['status'].trim().toLowerCase() : '';
|
|
149
|
-
if (fmStatus === 'passed')
|
|
150
|
-
return 'Complete';
|
|
151
|
-
if (fmStatus === 'human_needed')
|
|
152
|
-
return 'Needs Review';
|
|
153
|
-
if (fmStatus === 'gaps_found')
|
|
154
|
-
return 'Executed';
|
|
155
|
-
// Verification exists but unrecognized status — treat as executed
|
|
156
|
-
return 'Executed';
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
catch { /* directory read failed — fall through */ }
|
|
160
|
-
// No verification file — executed but not verified
|
|
161
|
-
return 'Executed';
|
|
128
|
+
function recoverBracketPhaseName(dir, phaseToken) {
|
|
129
|
+
const tokenBoundary = `-${phaseToken}`;
|
|
130
|
+
const tokenOffset = dir.indexOf(tokenBoundary);
|
|
131
|
+
if (tokenOffset === -1)
|
|
132
|
+
return '';
|
|
133
|
+
const afterToken = dir.slice(tokenOffset + tokenBoundary.length).replace(/^-/, '');
|
|
134
|
+
return afterToken ? afterToken.replace(/-/g, ' ') : '';
|
|
162
135
|
}
|
|
163
136
|
function cmdGenerateSlug(text, raw) {
|
|
164
137
|
if (!text) {
|
|
@@ -237,26 +210,43 @@ function cmdListTodos(cwd, area, raw) {
|
|
|
237
210
|
* displayed field is passed through sanitizeForDisplay and each file path is
|
|
238
211
|
* validated with requireSafePath before reading. Read-only — never mutates.
|
|
239
212
|
*/
|
|
213
|
+
/**
|
|
214
|
+
* Seed id grammars. `SEED-YYMMDD-xxx` (date + 3 base36 chars, the shape
|
|
215
|
+
* `.planning/quick/` uses) is what plant-seed has minted since #4378 removed
|
|
216
|
+
* the shared `wc -l` counter; `SEED-NNN` is the legacy counter form, which
|
|
217
|
+
* keeps parsing forever — existing seeds must never lose their identity.
|
|
218
|
+
*
|
|
219
|
+
* Known (theoretical, documented-not-fixed per #4378 review): a frontmatter-less
|
|
220
|
+
* legacy file whose counter is exactly 6 digits and whose slug opens with
|
|
221
|
+
* exactly 3 base36 chars parses as new-format. Requires a counter >= 100000 AND
|
|
222
|
+
* a missing frontmatter id; with frontmatter the legacy id always wins.
|
|
223
|
+
*/
|
|
224
|
+
const CANONICAL_SEED_ID_RE = /^SEED-(?:\d{6}-[a-z0-9]{3}|\d+)$/i;
|
|
225
|
+
const SEED_ID_PREFIX_RE = /^(SEED-(?:\d{6}-[a-z0-9]{3}|\d+))/i;
|
|
226
|
+
const SEED_SLUG_RE = /^SEED-(?:\d{6}-[a-z0-9]{3}|\d+)-(.+)$/i;
|
|
240
227
|
/**
|
|
241
228
|
* Derive the canonical `{ seed_id, slug }` from a seed filename stem and the
|
|
242
229
|
* frontmatter `id:` value. Pure (no I/O) so it can be property-tested directly.
|
|
243
230
|
*
|
|
244
|
-
* seed_id: frontmatter `id:` when it matches
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
231
|
+
* seed_id: frontmatter `id:` when it matches a seed id grammar (`SEED-YYMMDD-xxx`
|
|
232
|
+
* or legacy `SEED-NNN`), else the id prefix of the filename (`SEED-…-<slug>`),
|
|
233
|
+
* else the whole stem. The prefix fallback must keep the FULL new-format id —
|
|
234
|
+
* truncating at the date gives every same-day seed the same id (#4378).
|
|
235
|
+
* slug: the descriptive remainder after the id, else the stem with a leading
|
|
236
|
+
* `SEED-` stripped. `rawFmId` is `unknown` because frontmatter values are not
|
|
237
|
+
* guaranteed strings.
|
|
248
238
|
*/
|
|
249
239
|
function deriveSeedIdentity(stem, rawFmId) {
|
|
250
240
|
const fmId = typeof rawFmId === 'string' ? rawFmId.trim() : '';
|
|
251
241
|
let seedId;
|
|
252
|
-
if (
|
|
242
|
+
if (CANONICAL_SEED_ID_RE.test(fmId)) {
|
|
253
243
|
seedId = fmId;
|
|
254
244
|
}
|
|
255
245
|
else {
|
|
256
|
-
const
|
|
257
|
-
seedId =
|
|
246
|
+
const prefixMatch = stem.match(SEED_ID_PREFIX_RE);
|
|
247
|
+
seedId = prefixMatch ? prefixMatch[1] : stem;
|
|
258
248
|
}
|
|
259
|
-
const slugMatch = stem.match(
|
|
249
|
+
const slugMatch = stem.match(SEED_SLUG_RE);
|
|
260
250
|
const slug = slugMatch ? slugMatch[1] : stem.replace(/^SEED-/i, '');
|
|
261
251
|
return { seed_id: seedId, slug };
|
|
262
252
|
}
|
|
@@ -303,9 +293,11 @@ function cmdListSeeds(cwd, statusFilter, raw) {
|
|
|
303
293
|
// sanitizeForDisplay is for output, not comparison.
|
|
304
294
|
if (wantStatus && status !== wantStatus)
|
|
305
295
|
continue;
|
|
306
|
-
// Canonical seed
|
|
307
|
-
//
|
|
308
|
-
//
|
|
296
|
+
// Canonical seed ids are `SEED-YYMMDD-xxx` (frontmatter `id:`, what
|
|
297
|
+
// plant-seed has minted since #4378) or legacy `SEED-NNN`; deriveSeedIdentity
|
|
298
|
+
// owns that grammar. Fall back to the id prefix of the filename, then to the
|
|
299
|
+
// whole stem. The descriptive remainder of the filename (`SEED-…-<slug>.md`)
|
|
300
|
+
// is the slug.
|
|
309
301
|
const stem = node_path_1.default.basename(entry.name, '.md');
|
|
310
302
|
const { seed_id: seedId, slug } = deriveSeedIdentity(stem, fm.id);
|
|
311
303
|
let title = (0, security_cjs_1.sanitizeForDisplay)(fmStr(fm.title).slice(0, 100));
|
|
@@ -503,13 +495,10 @@ function cmdResolveExecution(cwd, agentType, raw, opts) {
|
|
|
503
495
|
opts = opts || {};
|
|
504
496
|
const config = loadConfig(cwd);
|
|
505
497
|
const profile = config['model_profile'] || 'balanced';
|
|
506
|
-
// #2068: resolve the model per-attempt so dynamic_routing
|
|
507
|
-
// (heavy tier) alongside effort.
|
|
508
|
-
//
|
|
509
|
-
//
|
|
510
|
-
// including dynamic_routing-enabled users who don't pass --attempt), and only an
|
|
511
|
-
// explicit attempt routes through the tier ladder. resolveModelForTier itself
|
|
512
|
-
// still falls back to resolveModelInternal when dynamic_routing is off.
|
|
498
|
+
// #2068: resolve the model per-attempt so dynamic_routing ESCALATES the MODEL
|
|
499
|
+
// (heavy tier) alongside effort. The FIRST-spawn tier now comes from
|
|
500
|
+
// resolveModelInternal's own dynamic_routing step (#4505), so the absent-attempt
|
|
501
|
+
// branch below reaches it too — this gate is only about escalation.
|
|
513
502
|
let model = (opts.attempt !== undefined && opts.attempt !== null)
|
|
514
503
|
? resolveModelForTier(cwd, agentType, opts.attempt)
|
|
515
504
|
: resolveModelInternal(cwd, agentType);
|
|
@@ -557,19 +546,19 @@ function cmdResolveExecution(cwd, agentType, raw, opts) {
|
|
|
557
546
|
let effortEffective = effort;
|
|
558
547
|
if (runtime === 'claude') {
|
|
559
548
|
try {
|
|
560
|
-
|
|
561
|
-
const { getGlobalConfigDir } = require('./runtime-homes.cjs');
|
|
562
|
-
const agentsDirEff = node_path_1.default.join(getGlobalConfigDir(runtime), 'agents');
|
|
549
|
+
const agentsDirEff = claudeEffortAgentsDir();
|
|
563
550
|
// agentType is an unvalidated CLI positional: keep the read inside the
|
|
564
551
|
// agents dir so `../../x` cannot point it elsewhere (defense in depth —
|
|
565
552
|
// the reflected surface is only a frontmatter effort line). Untrusted
|
|
566
553
|
// input feeding a real read → realpath family (ADR-4650 decision 6).
|
|
567
554
|
const agentPath = (0, security_cjs_1.assertWithinRoot)(`${agentType}.md`, agentsDirEff, 'agent file');
|
|
568
555
|
const agentContent = node_fs_1.default.readFileSync(agentPath, 'utf8');
|
|
569
|
-
//
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
556
|
+
// Byte-0 read: the runtime only honors a frontmatter block that opens at
|
|
557
|
+
// byte 0, so a block behind a preamble is not frontmatter to the loader
|
|
558
|
+
// and this reports what the installed agent will actually run at (#5105).
|
|
559
|
+
const fmSpanEff = agentFrontmatterSpan(agentContent);
|
|
560
|
+
if (fmSpanEff) {
|
|
561
|
+
const effortLine = /^effort:[ \t]*(.+?)[ \t]*$/m.exec(fmSpanEff.body);
|
|
573
562
|
if (effortLine) {
|
|
574
563
|
effortEffective = effortLine[1];
|
|
575
564
|
effortEffectiveSource = 'frontmatter';
|
|
@@ -656,33 +645,42 @@ function effortSurfaceForHost(cwd, host) {
|
|
|
656
645
|
return 'none';
|
|
657
646
|
}
|
|
658
647
|
}
|
|
648
|
+
/**
|
|
649
|
+
* The leading frontmatter block of an installed agent file, as the one fence owner
|
|
650
|
+
* (`locateFrontmatterFence`) finds it: `body` is the text between the two fences (the last
|
|
651
|
+
* content line's line ending included), `bodyStart`/`closingStart` bound it, and `eol` is the
|
|
652
|
+
* opening fence's line ending. Null when the file has no closed block. By default a block that
|
|
653
|
+
* does not open at byte 0 is not one, exactly as the runtime that loads the agent reads it;
|
|
654
|
+
* the effort-sync line editors pass `EFFORT_SYNC_FENCE` to edit a block behind a preamble too
|
|
655
|
+
* (#3706 pinned that).
|
|
656
|
+
*/
|
|
657
|
+
function agentFrontmatterSpan(content, options) {
|
|
658
|
+
const fence = (0, frontmatter_fence_cjs_1.locateFrontmatterFence)(content, options);
|
|
659
|
+
if (!fence?.closed)
|
|
660
|
+
return null;
|
|
661
|
+
return { body: content.slice(fence.openEnd, fence.closingStart), bodyStart: fence.openEnd, closingStart: fence.closingStart, eol: fence.eol };
|
|
662
|
+
}
|
|
659
663
|
/**
|
|
660
664
|
* #488 — Replace or inject the `<key>:` value in YAML frontmatter.
|
|
661
665
|
* Unlike injectEffortFrontmatter (install.js), this overwrites an existing value.
|
|
662
666
|
* #3706: key-parameterised so the same line-editor serves both claude's
|
|
663
|
-
* `effort:` and OpenCode's `variant:`. #3706: all offsets (eol,
|
|
664
|
-
* closingStart)
|
|
665
|
-
*
|
|
667
|
+
* `effort:` and OpenCode's `variant:`. #3706: all offsets (eol, bodyStart,
|
|
668
|
+
* closingStart) come from the block `agentFrontmatterSpan` found, and the
|
|
669
|
+
* existing-key replace is scoped to the frontmatter span only.
|
|
666
670
|
*/
|
|
667
671
|
function setFrontmatterKeyLine(content, key, value) {
|
|
668
|
-
const
|
|
669
|
-
|
|
670
|
-
if (!match)
|
|
672
|
+
const span = agentFrontmatterSpan(content, EFFORT_SYNC_FENCE);
|
|
673
|
+
if (!span)
|
|
671
674
|
return content;
|
|
672
|
-
const fmBody =
|
|
675
|
+
const fmBody = span.body;
|
|
673
676
|
// Both writers of these frontmatter keys — this sync path and the
|
|
674
677
|
// install-side `frontmatterScalar` in runtime-artifact-conversion.cts —
|
|
675
678
|
// now share one escaping rule: quote via `agentScalarNeedsDoubleQuoting` +
|
|
676
679
|
// `escapeDoubleQuotedScalar` (both from frontmatter.cts) rather than each
|
|
677
680
|
// interpolating `value` raw/differently.
|
|
678
681
|
const renderedValue = agentScalarNeedsDoubleQuoting(value) ? `"${escapeDoubleQuotedScalar(value)}"` : value;
|
|
679
|
-
//
|
|
680
|
-
|
|
681
|
-
// offset below by one byte and mangles the opening fence.
|
|
682
|
-
const eol = /^---\r\n/.test(match[0]) ? '\r\n' : '\n';
|
|
683
|
-
const openLen = 3 + eol.length;
|
|
684
|
-
const bodyStart = match.index + openLen;
|
|
685
|
-
const closingStart = bodyStart + fmBody.length;
|
|
682
|
+
// Every offset and the EOL come from the block the fence owner found (#3706).
|
|
683
|
+
const { eol, bodyStart, closingStart } = span;
|
|
686
684
|
// #3706: key is now generic (not just the literal 'effort'/'variant'
|
|
687
685
|
// callers happen to pass today) — escape it before interpolating into the
|
|
688
686
|
// RegExp so a future caller can't have its key metacharacters reinterpreted.
|
|
@@ -718,19 +716,17 @@ function setFrontmatterKeyLine(content, key, value) {
|
|
|
718
716
|
* codex-agent-toml strip discipline: targeted line removal, EOL-aware, every
|
|
719
717
|
* other byte (comments, sibling keys, the body) untouched.
|
|
720
718
|
* #3706: key-parameterised so the same line-editor serves both claude's
|
|
721
|
-
* `effort:` and OpenCode's `variant:`. #3706:
|
|
722
|
-
*
|
|
723
|
-
* would otherwise misalign every offset below.
|
|
719
|
+
* `effort:` and OpenCode's `variant:`. #3706: every offset comes from the
|
|
720
|
+
* block `agentFrontmatterSpan` found.
|
|
724
721
|
*/
|
|
725
722
|
function removeFrontmatterKeyLine(content, key) {
|
|
726
723
|
// Scoped to the FIRST frontmatter block (not a whole-file /m match): a
|
|
727
724
|
// preamble or body line starting with `<key>:` (a fenced config example,
|
|
728
725
|
// a thematic-break flanked fragment) must never be the line removed.
|
|
729
|
-
const
|
|
730
|
-
|
|
731
|
-
if (!match)
|
|
726
|
+
const span = agentFrontmatterSpan(content, EFFORT_SYNC_FENCE);
|
|
727
|
+
if (!span)
|
|
732
728
|
return content;
|
|
733
|
-
const fmBody =
|
|
729
|
+
const fmBody = span.body;
|
|
734
730
|
// #3706: same generic-key escape as setFrontmatterKeyLine above.
|
|
735
731
|
const lineRe = new RegExp(`^${(0, pattern_cjs_1.escapeRegex)(key)}:[ \\t]*.*\\r?\\n?`, 'm');
|
|
736
732
|
if (!lineRe.test(fmBody))
|
|
@@ -745,12 +741,7 @@ function removeFrontmatterKeyLine(content, key) {
|
|
|
745
741
|
// is removed in one pass.
|
|
746
742
|
const stripAllRe = new RegExp(`^${(0, pattern_cjs_1.escapeRegex)(key)}:[ \\t]*.*\\r?\\n?`, 'gm');
|
|
747
743
|
const strippedFm = fmBody.replace(stripAllRe, '');
|
|
748
|
-
|
|
749
|
-
// block, not the start of the file, or a preambled CRLF document misaligns.
|
|
750
|
-
const eol = /^---\r\n/.test(match[0]) ? '\r\n' : '\n';
|
|
751
|
-
const openLen = 3 + eol.length;
|
|
752
|
-
const closingStart = match.index + openLen + fmBody.length;
|
|
753
|
-
return content.slice(0, match.index + openLen) + strippedFm + content.slice(closingStart);
|
|
744
|
+
return content.slice(0, span.bodyStart) + strippedFm + content.slice(span.closingStart);
|
|
754
745
|
}
|
|
755
746
|
/** #488 — Replace or inject the `effort:` value in YAML frontmatter. */
|
|
756
747
|
function setEffortFrontmatter(content, effortValue) {
|
|
@@ -795,8 +786,6 @@ function cmdEffortSync(cwd, raw, opts) {
|
|
|
795
786
|
output({ synced: 0, skipped: 0, changes: [], dry_run: dryRun, reason: `runtime '${runtime}' does not use effort: frontmatter` }, raw, '');
|
|
796
787
|
return;
|
|
797
788
|
}
|
|
798
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/unbound-method
|
|
799
|
-
const { getGlobalConfigDir } = require('./runtime-homes.cjs');
|
|
800
789
|
// Use install-time resolvers: they merge ~/.gsd/defaults.json with project config,
|
|
801
790
|
// matching the exact logic used when agents were originally installed. #2071: these
|
|
802
791
|
// live in the shipped sibling install-effort-resolver.cjs (extracted from the
|
|
@@ -804,7 +793,7 @@ function cmdEffortSync(cwd, raw, opts) {
|
|
|
804
793
|
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/unbound-method
|
|
805
794
|
const { readGsdEffectiveEffortConfig, resolveInstallTimeEffort } = require('./install-effort-resolver.cjs');
|
|
806
795
|
const effortCfg = readGsdEffectiveEffortConfig(cwd);
|
|
807
|
-
const agentsDir =
|
|
796
|
+
const agentsDir = claudeEffortAgentsDir(opts.configDir);
|
|
808
797
|
if (!node_fs_1.default.existsSync(agentsDir)) {
|
|
809
798
|
output({ synced: 0, skipped: 0, changes: [], dry_run: dryRun, agents_dir: agentsDir, reason: 'agents directory not found' }, raw, '');
|
|
810
799
|
return;
|
|
@@ -822,6 +811,10 @@ function cmdEffortSync(cwd, raw, opts) {
|
|
|
822
811
|
}).sort(); // #3706: sorted like the codex and
|
|
823
812
|
// opencode branches — readdir order is platform-dependent, so leaving it unsorted makes the
|
|
824
813
|
// reported `changes` ordering differ across machines for identical inputs.
|
|
814
|
+
if (files.length === 0) {
|
|
815
|
+
output({ synced: 0, skipped: 0, changes: [], dry_run: dryRun, agents_dir: agentsDir, reason: 'no GSD agent files found' }, raw, '');
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
825
818
|
const changes = [];
|
|
826
819
|
let synced = 0;
|
|
827
820
|
let skipped = 0;
|
|
@@ -866,7 +859,7 @@ function cmdEffortSync(cwd, raw, opts) {
|
|
|
866
859
|
// drift and the sync re-added a hand-stripped key on every apply. A
|
|
867
860
|
// present key under inherit is stripped, reported as {from, to: null}.
|
|
868
861
|
if (universalEffort === 'inherit') {
|
|
869
|
-
const fmMatchInherit =
|
|
862
|
+
const fmMatchInherit = agentFrontmatterSpan(content, EFFORT_SYNC_FENCE);
|
|
870
863
|
if (!fmMatchInherit) {
|
|
871
864
|
skipped++;
|
|
872
865
|
continue;
|
|
@@ -877,12 +870,12 @@ function cmdEffortSync(cwd, raw, opts) {
|
|
|
877
870
|
// from a null value alone is wrong here — it would leave an
|
|
878
871
|
// unresolvable `effort: null` key on disk forever. Test presence with
|
|
879
872
|
// its own regex, and only compare values once presence is known.
|
|
880
|
-
const effortPresentInherit = /^effort:/m.test(fmMatchInherit
|
|
873
|
+
const effortPresentInherit = /^effort:/m.test(fmMatchInherit.body);
|
|
881
874
|
if (!effortPresentInherit) {
|
|
882
875
|
skipped++;
|
|
883
876
|
continue;
|
|
884
877
|
}
|
|
885
|
-
const effortMatchInherit = /^effort:[ \t]*(.+?)[ \t]*$/m.exec(fmMatchInherit
|
|
878
|
+
const effortMatchInherit = /^effort:[ \t]*(.+?)[ \t]*$/m.exec(fmMatchInherit.body);
|
|
886
879
|
// `effortPresentInherit` is guaranteed true here (checked above), so a
|
|
887
880
|
// failed value match means the key is present with an EMPTY value —
|
|
888
881
|
// report `''`, not `null`, so "present-but-empty" is never conflated
|
|
@@ -941,7 +934,7 @@ function cmdEffortSync(cwd, raw, opts) {
|
|
|
941
934
|
// codex's 'ultra' rejection can produce a null value).
|
|
942
935
|
const rendered = (0, model_catalog_cjs_1.renderEffortForRuntime)(runtime, universalEffort);
|
|
943
936
|
const newEffortValue = rendered.value;
|
|
944
|
-
const fmMatch =
|
|
937
|
+
const fmMatch = agentFrontmatterSpan(content, EFFORT_SYNC_FENCE);
|
|
945
938
|
if (!fmMatch) {
|
|
946
939
|
skipped++;
|
|
947
940
|
continue;
|
|
@@ -954,8 +947,8 @@ function cmdEffortSync(cwd, raw, opts) {
|
|
|
954
947
|
// never null on this path (guarded above), so an absent key already
|
|
955
948
|
// yields `currentEffort === null !== newEffortValue` without consulting
|
|
956
949
|
// presence separately.
|
|
957
|
-
const effortPresent = /^effort:/m.test(fmMatch
|
|
958
|
-
const effortMatch = /^effort:[ \t]*(.+?)[ \t]*$/m.exec(fmMatch
|
|
950
|
+
const effortPresent = /^effort:/m.test(fmMatch.body);
|
|
951
|
+
const effortMatch = /^effort:[ \t]*(.+?)[ \t]*$/m.exec(fmMatch.body);
|
|
959
952
|
// `null` (key absent) and `''` (key present, value empty) are distinct
|
|
960
953
|
// states `effortPresent` deliberately disambiguates — collapsing both to
|
|
961
954
|
// `null` here would make the reported `from` lie about which case fired.
|
|
@@ -1228,7 +1221,7 @@ function cmdEffortSyncOpencode(cwd, raw, dryRun, configDir) {
|
|
|
1228
1221
|
// exactly what install writes.
|
|
1229
1222
|
const universal = effortCfg ? resolveInstallTimeEffort(effortCfg, agentName) : null;
|
|
1230
1223
|
const target = universal ? clampEffortForHost('opencode', universal) : null;
|
|
1231
|
-
const fmMatch =
|
|
1224
|
+
const fmMatch = agentFrontmatterSpan(content, EFFORT_SYNC_FENCE);
|
|
1232
1225
|
if (!fmMatch) {
|
|
1233
1226
|
skipped++;
|
|
1234
1227
|
continue;
|
|
@@ -1240,8 +1233,8 @@ function cmdEffortSyncOpencode(cwd, raw, dryRun, configDir) {
|
|
|
1240
1233
|
// null — it would leave an unresolvable `variant: null` key on disk
|
|
1241
1234
|
// forever. Test presence with its own regex, and only compare values
|
|
1242
1235
|
// once presence is known.
|
|
1243
|
-
const variantPresent = /^variant:/m.test(fmMatch
|
|
1244
|
-
const variantMatch = /^variant:[ \t]*(.+?)[ \t]*$/m.exec(fmMatch
|
|
1236
|
+
const variantPresent = /^variant:/m.test(fmMatch.body);
|
|
1237
|
+
const variantMatch = /^variant:[ \t]*(.+?)[ \t]*$/m.exec(fmMatch.body);
|
|
1245
1238
|
// `null` (key absent) and `''` (key present, value empty) are distinct
|
|
1246
1239
|
// states this code deliberately tracks via `variantPresent` above — a
|
|
1247
1240
|
// reported `from` that collapses both to `null` would make "no key" and
|
|
@@ -2838,13 +2831,38 @@ function cmdSummaryExtract(cwd, summaryPath, fields, raw) {
|
|
|
2838
2831
|
if (!summaryPath) {
|
|
2839
2832
|
error('summary-path required for summary-extract');
|
|
2840
2833
|
}
|
|
2841
|
-
|
|
2834
|
+
// #5013: requireSafePath (not path.join) so an absolute summaryPath resolves to
|
|
2835
|
+
// that literal path instead of being concatenated onto cwd — path.join has no
|
|
2836
|
+
// special case for an absolute second argument the way path.resolve does. Uses
|
|
2837
|
+
// requireSafePath (the same PathAcceptance.AbsoluteInsideRoot pattern
|
|
2838
|
+
// cmdAuditAcknowledge's write-side counterpart already uses in audit.cts)
|
|
2839
|
+
// rather than a bare path.resolve: a plain path.resolve would also have made
|
|
2840
|
+
// an absolute summaryPath anywhere on the filesystem resolve and have its
|
|
2841
|
+
// content echoed back (this command reads and returns file content, unlike
|
|
2842
|
+
// cmdVerifyPathExists's exists-only check) — an orthogonal-review finding on
|
|
2843
|
+
// this same fix. requireSafePath keeps the acceptance criterion (an absolute
|
|
2844
|
+
// path within the project resolves correctly) while refusing one that
|
|
2845
|
+
// escapes cwd, and rejects relative `../` escapes the same way (pre-existing
|
|
2846
|
+
// in this function, closed as a direct consequence, not separately in scope).
|
|
2847
|
+
const fullPath = (0, security_cjs_1.requireSafePath)(summaryPath, cwd, 'summary-extract target', security_cjs_1.PathAcceptance.AbsoluteInsideRoot);
|
|
2842
2848
|
if (!node_fs_1.default.existsSync(fullPath)) {
|
|
2843
2849
|
output({ error: 'File not found', path: summaryPath }, raw, undefined);
|
|
2844
2850
|
return;
|
|
2845
2851
|
}
|
|
2846
2852
|
const content = node_fs_1.default.readFileSync(fullPath, 'utf-8');
|
|
2847
2853
|
const fm = extractFrontmatter(content, fullPath);
|
|
2854
|
+
// #5013: an unparseable frontmatter block (malformed YAML in the fence) is
|
|
2855
|
+
// NOT the same as an absent/empty one — extractFrontmatter marks it with
|
|
2856
|
+
// FRONTMATTER_UNPARSEABLE (mirrors state.cts's isUnparseableFrontmatter /
|
|
2857
|
+
// state-transition.cts's identical helper). Left unchecked, every field
|
|
2858
|
+
// below silently falls back to its empty default and this command exits 0,
|
|
2859
|
+
// so a milestone audit reading requirements_completed through it would
|
|
2860
|
+
// report "zero requirements completed" for a plan that completed several,
|
|
2861
|
+
// with nothing on stderr. Refuse instead, naming the file, the same way
|
|
2862
|
+
// cmdAuditAcknowledge (audit.cts) refuses on the write-side counterpart.
|
|
2863
|
+
if (fm[FRONTMATTER_UNPARSEABLE] === true) {
|
|
2864
|
+
error(`summary-extract: the frontmatter of "${summaryPath}" is not parseable YAML — fix the syntax error in the file (e.g. an unquoted ": " inside a block-sequence item) before re-running; every field would otherwise silently read as empty`, ERROR_REASON.SUMMARY_EXTRACT_UNPARSEABLE, { path: summaryPath });
|
|
2865
|
+
}
|
|
2848
2866
|
// Parse key-decisions into structured format
|
|
2849
2867
|
const parseDecisions = (decisionsList) => {
|
|
2850
2868
|
if (!decisionsList || !Array.isArray(decisionsList))
|
|
@@ -3006,34 +3024,77 @@ async function cmdWebsearch(query, options, raw) {
|
|
|
3006
3024
|
function cmdProgressRender(cwd, format, raw) {
|
|
3007
3025
|
const phasesDir = planningPaths(cwd).phases;
|
|
3008
3026
|
const milestone = getMilestoneInfo(cwd).value;
|
|
3027
|
+
const phaseIdConvention = resolvePhaseIdConvention(cwd);
|
|
3028
|
+
const milestoneDisplay = phaseIdConvention === 'bracket'
|
|
3029
|
+
? renderBracketMilestoneDisplay(milestone?.version, loadConfig(cwd).project_code)
|
|
3030
|
+
: null;
|
|
3031
|
+
const milestoneVersion = milestoneDisplay ?? milestone?.version ?? null;
|
|
3009
3032
|
const phases = [];
|
|
3010
3033
|
let totalPlans = 0;
|
|
3011
3034
|
let totalSummaries = 0;
|
|
3012
3035
|
let phaseScope = null;
|
|
3036
|
+
// #5118: carried out of the scan below and failed on after it.
|
|
3037
|
+
let statusError;
|
|
3013
3038
|
try {
|
|
3014
3039
|
// #3185 (ADR-3180 Decision 1): the single owner applies the milestone
|
|
3015
3040
|
// window AND the sentinel filter and returns dirs already sorted by
|
|
3016
3041
|
// comparePhaseNum. This command previously read the phases directory
|
|
3017
3042
|
// directly with neither, which is why `query progress` listed 999.*
|
|
3018
3043
|
// backlog directories as current-milestone phases (#3167).
|
|
3019
|
-
const { value: dirs, scope } = listMilestonePhaseDirs(phasesDir, {
|
|
3044
|
+
const { value: dirs, scope } = listMilestonePhaseDirs(phasesDir, {
|
|
3045
|
+
cwd,
|
|
3046
|
+
phaseIdConvention,
|
|
3047
|
+
});
|
|
3020
3048
|
phaseScope = scope;
|
|
3021
3049
|
for (const dir of dirs) {
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3050
|
+
let phaseNum;
|
|
3051
|
+
let phaseName;
|
|
3052
|
+
let displayId;
|
|
3053
|
+
if (phaseIdConvention === 'bracket') {
|
|
3054
|
+
try {
|
|
3055
|
+
const projection = bracketPhaseDirProjection(dir);
|
|
3056
|
+
phaseNum = projection.number;
|
|
3057
|
+
phaseName = projection.name;
|
|
3058
|
+
displayId = projection.display_id;
|
|
3059
|
+
}
|
|
3060
|
+
catch {
|
|
3061
|
+
// A malformed/tolerated directory must not suppress every later row.
|
|
3062
|
+
// It cannot receive a canonical display_id because parsePhaseId
|
|
3063
|
+
// rejected it, but the convention-aware token keeps it observable.
|
|
3064
|
+
const phaseToken = extractPhaseToken(dir, phaseIdConvention);
|
|
3065
|
+
phaseNum = phaseToken || dir;
|
|
3066
|
+
phaseName = recoverBracketPhaseName(dir, phaseToken);
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
else {
|
|
3070
|
+
const dm = dir.match(/^(\d+(?:\.\d+)*)-?(.*)/);
|
|
3071
|
+
phaseNum = dm ? dm[1] : dir;
|
|
3072
|
+
phaseName = dm && dm[2] ? dm[2].replace(/-/g, ' ') : '';
|
|
3073
|
+
}
|
|
3074
|
+
// #3183/#5060: canonical plan/summary counts and lifecycle status
|
|
3075
|
+
// (root+nested, superseded-excluded, canonical pairing) from the
|
|
3076
|
+
// single owner (Phase Status Module, ADR-5057).
|
|
3077
|
+
const ps = (0, phase_status_cjs_1.phaseStatus)(node_path_1.default.join(phasesDir, dir), { convention: phaseIdConvention });
|
|
3078
|
+
statusError = firstStatusError(statusError, ps.value.statusError);
|
|
3079
|
+
const plans = ps.value.planCount;
|
|
3080
|
+
const summaries = ps.value.summaryCount;
|
|
3030
3081
|
totalPlans += plans;
|
|
3031
3082
|
totalSummaries += summaries;
|
|
3032
|
-
const status =
|
|
3033
|
-
phases.push({
|
|
3083
|
+
const status = (0, phase_status_cjs_1.toDisplayLabel)(ps.value.status, { pendingWord: 'Pending' });
|
|
3084
|
+
phases.push({
|
|
3085
|
+
number: phaseNum,
|
|
3086
|
+
...(displayId ? { display_id: displayId } : {}),
|
|
3087
|
+
name: phaseName,
|
|
3088
|
+
plans,
|
|
3089
|
+
summaries,
|
|
3090
|
+
status,
|
|
3091
|
+
});
|
|
3034
3092
|
}
|
|
3035
3093
|
}
|
|
3036
3094
|
catch { /* intentionally empty */ }
|
|
3095
|
+
// #5118: a read-only aggregate over a refused report prints nothing.
|
|
3096
|
+
if (statusError)
|
|
3097
|
+
failOnVerificationStatusError(statusError);
|
|
3037
3098
|
// #3217 (ADR-3180 §7.6 rule 4): `phaseScope` was already computed above
|
|
3038
3099
|
// (Phase 3, #3222) but never consulted before rendering — a percentage was
|
|
3039
3100
|
// rendered from counts the scope said were not answers (TRUNCATED /
|
|
@@ -3048,12 +3109,12 @@ function cmdProgressRender(cwd, format, raw) {
|
|
|
3048
3109
|
// Render markdown table
|
|
3049
3110
|
const bar = (0, phase_lifecycle_cjs_1.renderProgressBar)(percent, 10);
|
|
3050
3111
|
const percentSuffix = percent === null ? '' : ` (${percent}%)`;
|
|
3051
|
-
let out = `# ${
|
|
3112
|
+
let out = `# ${milestoneVersion ?? ''} ${milestone?.name ?? ''}\n\n`;
|
|
3052
3113
|
out += `**Progress:** [${bar}] ${totalSummaries}/${totalPlans} plans${percentSuffix}\n\n`;
|
|
3053
3114
|
out += `| Phase | Name | Plans | Status |\n`;
|
|
3054
3115
|
out += `|-------|------|-------|--------|\n`;
|
|
3055
3116
|
for (const p of phases) {
|
|
3056
|
-
out += `| ${p.number} | ${p.name} | ${p.summaries}/${p.plans} | ${p.status} |\n`;
|
|
3117
|
+
out += `| ${p.display_id ?? p.number} | ${p.name} | ${p.summaries}/${p.plans} | ${p.status} |\n`;
|
|
3057
3118
|
}
|
|
3058
3119
|
output({ rendered: out }, raw, out);
|
|
3059
3120
|
}
|
|
@@ -3066,7 +3127,7 @@ function cmdProgressRender(cwd, format, raw) {
|
|
|
3066
3127
|
else {
|
|
3067
3128
|
// JSON format
|
|
3068
3129
|
output({
|
|
3069
|
-
milestone_version:
|
|
3130
|
+
milestone_version: milestoneVersion,
|
|
3070
3131
|
milestone_name: milestone?.name ?? null,
|
|
3071
3132
|
phases,
|
|
3072
3133
|
total_plans: totalPlans,
|
|
@@ -3192,29 +3253,35 @@ function cmdTodoMatchPhase(cwd, phase, raw) {
|
|
|
3192
3253
|
// #4096: upsert completion keys INSIDE the leading frontmatter block. Never a
|
|
3193
3254
|
// bare prefix line above the opening `---` (that displaces the fence to line 2
|
|
3194
3255
|
// and breaks every fence-locating reader). A file with no well-formed block
|
|
3195
|
-
// (absent, or an unterminated opening fence) gains a complete block.
|
|
3256
|
+
// (absent, or an unterminated opening fence) gains a complete block. The block is
|
|
3257
|
+
// the one the one fence owner (`locateFrontmatterFence`) finds, so this writer and
|
|
3258
|
+
// every todo reader agree on where it is.
|
|
3196
3259
|
function upsertTodoCompletionFields(content, today) {
|
|
3197
|
-
const lines = content.split('\n');
|
|
3198
3260
|
const fields = [`completed: ${today}`, 'status: completed'];
|
|
3199
|
-
const
|
|
3200
|
-
|
|
3201
|
-
if (!hasOpeningFence || closeIdx === -1) {
|
|
3261
|
+
const fence = (0, frontmatter_fence_cjs_1.locateFrontmatterFence)(content);
|
|
3262
|
+
if (!fence?.closed) {
|
|
3202
3263
|
// No parseable frontmatter: wrap the whole content in a complete block
|
|
3203
|
-
// rather than prefixing bare keys (#4096 fix 2).
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3264
|
+
// rather than prefixing bare keys (#4096 fix 2). A leading BOM (#2977) stays
|
|
3265
|
+
// the document's first character, ahead of the new fence.
|
|
3266
|
+
const bom = content.charCodeAt(0) === 0xFEFF ? content.slice(0, 1) : '';
|
|
3267
|
+
return `${bom}---\n${fields.join('\n')}\n---\n\n${content.slice(bom.length)}`;
|
|
3268
|
+
}
|
|
3269
|
+
// The block's lines, split on a bare '\n' so a CRLF line keeps its '\r' — and a
|
|
3270
|
+
// written field line carries one too, so a CRLF block never gains LF-only lines.
|
|
3271
|
+
const inner = content.slice(fence.openEnd, fence.closingStart);
|
|
3272
|
+
const block = inner === '' ? [] : inner.slice(0, -1).split('\n');
|
|
3273
|
+
const cr = fence.eol === '\r\n' ? '\r' : '';
|
|
3207
3274
|
for (const field of fields) {
|
|
3208
3275
|
const key = `${field.slice(0, field.indexOf(':'))}:`;
|
|
3209
3276
|
const idx = block.findIndex(l => l.startsWith(key));
|
|
3210
3277
|
if (idx === -1) {
|
|
3211
|
-
block.push(field);
|
|
3278
|
+
block.push(field + cr);
|
|
3212
3279
|
}
|
|
3213
3280
|
else {
|
|
3214
|
-
block[idx] = field;
|
|
3281
|
+
block[idx] = field + cr;
|
|
3215
3282
|
}
|
|
3216
3283
|
}
|
|
3217
|
-
return
|
|
3284
|
+
return `${content.slice(0, fence.openEnd)}${block.join('\n')}\n${content.slice(fence.closingStart)}`;
|
|
3218
3285
|
}
|
|
3219
3286
|
function cmdTodoComplete(cwd, filename, options, raw) {
|
|
3220
3287
|
if (!filename) {
|
|
@@ -3232,7 +3299,7 @@ function cmdTodoComplete(cwd, filename, options, raw) {
|
|
|
3232
3299
|
// to a location inside todosRoot (or inside pending/) and would pass
|
|
3233
3300
|
// containment, yet none of them is a bare filename. Reject on basename
|
|
3234
3301
|
// shape FIRST, before any path is even joined — same predicate shape as
|
|
3235
|
-
// findPhaseArtifact in
|
|
3302
|
+
// findPhaseArtifact in gate-predicate.cts. Checking both `/` and
|
|
3236
3303
|
// `\` explicitly (not just path.basename) matters on POSIX, where a
|
|
3237
3304
|
// literal backslash is just an ordinary filename character to
|
|
3238
3305
|
// path.basename but not to path.win32.basename or to the user's intent.
|
|
@@ -3361,7 +3428,15 @@ function cmdStats(cwd, format, raw) {
|
|
|
3361
3428
|
const reqPath = planningPaths(cwd).requirements;
|
|
3362
3429
|
const statePath = planningPaths(cwd).state;
|
|
3363
3430
|
const milestone = getMilestoneInfo(cwd).value;
|
|
3431
|
+
const phaseIdConvention = resolvePhaseIdConvention(cwd);
|
|
3432
|
+
const milestoneDisplay = phaseIdConvention === 'bracket'
|
|
3433
|
+
? renderBracketMilestoneDisplay(milestone?.version, loadConfig(cwd).project_code)
|
|
3434
|
+
: null;
|
|
3435
|
+
const milestoneVersion = milestoneDisplay ?? milestone?.version ?? null;
|
|
3364
3436
|
// Phase & plan stats (reuse progress pattern)
|
|
3437
|
+
// #5060: `phaseStatus` here is the internal PHASE_STATUS ladder value, never
|
|
3438
|
+
// a display label — the output projection below is the only place a label
|
|
3439
|
+
// is produced (toDisplayLabel).
|
|
3365
3440
|
const phasesByNumber = new Map();
|
|
3366
3441
|
let totalPlans = 0;
|
|
3367
3442
|
let totalSummaries = 0;
|
|
@@ -3380,54 +3455,105 @@ function cmdStats(cwd, format, raw) {
|
|
|
3380
3455
|
// prose mentioning `### Phase N:` inside an inline code span produced a phantom
|
|
3381
3456
|
// Not-Started row and made phases_total disagree with roadmap analyze.
|
|
3382
3457
|
// phase-id-owner: uses the [.-] (dot-or-dash) separator variant, not the canonical dot-only token; a swap to PHASE_NUMBER_TOKEN_SOURCE would drop hyphenated phase-id matches.
|
|
3383
|
-
const
|
|
3458
|
+
const capturesBracketId = phaseIdConvention === 'bracket';
|
|
3459
|
+
const headingPrefix = phaseHeadingPrefixSrcFor(PHASE_HEADING_BASELINE.ANY_BRACKET, phaseIdConvention, capturesBracketId);
|
|
3460
|
+
// phase-id-owner: this preserves cmdStats's shipped dot-or-dash heading
|
|
3461
|
+
// token variant; PHASE_NUMBER_TOKEN_SOURCE is dot-only and would drop M-NN.
|
|
3462
|
+
const headingPattern = new RegExp(
|
|
3463
|
+
// phase-id-owner: cmdStats's shipped [.-] token variant; the canonical token is dot-only.
|
|
3464
|
+
`#{2,4}\\s*${headingPrefix}([A-Za-z]?\\d+[A-Z]?(?:[.-]\\d+)*)(?:\\s*\\([^)\\n]{0,200}\\))?\\s*:\\s*([^\\n]+)`, 'gi');
|
|
3384
3465
|
let match;
|
|
3385
3466
|
while ((match = headingPattern.exec(roadmapContent)) !== null) {
|
|
3467
|
+
const bracketId = capturesBracketId ? match[1] : undefined;
|
|
3468
|
+
const phaseToken = capturesBracketId ? match[2] : match[1];
|
|
3469
|
+
const phaseName = capturesBracketId ? match[3] : match[2];
|
|
3386
3470
|
// #3185: the heading seed carried no sentinel filter, so a
|
|
3387
3471
|
// `### Phase 999.1:` backlog heading produced a stats row even with no
|
|
3388
3472
|
// directory on disk. Uses the canonical predicate (phase-id.cts), not a
|
|
3389
3473
|
// local literal — the rule had five copies and three regex variants
|
|
3390
3474
|
// before this phase, disagreeing about Phase 0.
|
|
3391
|
-
|
|
3475
|
+
const sentinelId = bracketId ? `${bracketId}-${phaseToken}` : phaseToken;
|
|
3476
|
+
if (capturesBracketId
|
|
3477
|
+
? isSentinelPhaseId(sentinelId, phaseIdConvention)
|
|
3478
|
+
: isSentinelPhaseId(sentinelId))
|
|
3392
3479
|
continue;
|
|
3393
|
-
const key = normalizePhaseName(
|
|
3480
|
+
const key = normalizePhaseName(phaseToken);
|
|
3481
|
+
let displayId;
|
|
3482
|
+
if (bracketId) {
|
|
3483
|
+
try {
|
|
3484
|
+
displayId = renderPhaseId(parsePhaseId(`${bracketId}-${phaseToken}`));
|
|
3485
|
+
}
|
|
3486
|
+
catch {
|
|
3487
|
+
// Read tolerance can admit a non-canonical heading spelling; keep
|
|
3488
|
+
// the stats row but do not invent a canonical identity for it.
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3394
3491
|
phasesByNumber.set(key, {
|
|
3395
3492
|
number: key,
|
|
3396
|
-
|
|
3493
|
+
...(displayId ? { display_id: displayId } : {}),
|
|
3494
|
+
name: phaseName.replace(/\(INSERTED\)/i, '').trim(),
|
|
3397
3495
|
plans: 0,
|
|
3398
3496
|
summaries: 0,
|
|
3399
|
-
|
|
3497
|
+
phaseStatus: phase_status_cjs_1.PHASE_STATUS.NOT_STARTED,
|
|
3400
3498
|
});
|
|
3401
3499
|
}
|
|
3402
3500
|
}
|
|
3403
3501
|
catch { /* intentionally empty */ }
|
|
3502
|
+
// #5118: the first phase whose verification report `status` is outside the
|
|
3503
|
+
// closed set — carried out of the scan below and failed on after it.
|
|
3504
|
+
let statusError;
|
|
3404
3505
|
try {
|
|
3405
3506
|
// #3185 (ADR-3180 Decision 1): route through the single owner. This
|
|
3406
3507
|
// previously applied the milestone window but NOT a directory-level
|
|
3407
3508
|
// sentinel filter — and getMilestonePhaseFilter degrades to a pass-all
|
|
3408
3509
|
// predicate when its heading set is empty, at which point every directory
|
|
3409
3510
|
// on disk passed, backlog included (#3167).
|
|
3410
|
-
const { value: dirs, scope } = listMilestonePhaseDirs(phasesDir, {
|
|
3511
|
+
const { value: dirs, scope } = listMilestonePhaseDirs(phasesDir, {
|
|
3512
|
+
cwd,
|
|
3513
|
+
phaseIdConvention,
|
|
3514
|
+
});
|
|
3411
3515
|
phaseScope = scope;
|
|
3412
3516
|
for (const dir of dirs) {
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3517
|
+
let phaseNum;
|
|
3518
|
+
let phaseName;
|
|
3519
|
+
let displayId;
|
|
3520
|
+
if (phaseIdConvention === 'bracket') {
|
|
3521
|
+
try {
|
|
3522
|
+
const projection = bracketPhaseDirProjection(dir);
|
|
3523
|
+
phaseNum = projection.number;
|
|
3524
|
+
phaseName = projection.name;
|
|
3525
|
+
displayId = projection.display_id;
|
|
3526
|
+
}
|
|
3527
|
+
catch {
|
|
3528
|
+
const phaseToken = extractPhaseToken(dir, phaseIdConvention);
|
|
3529
|
+
phaseNum = phaseToken || dir;
|
|
3530
|
+
phaseName = recoverBracketPhaseName(dir, phaseToken);
|
|
3531
|
+
}
|
|
3532
|
+
}
|
|
3533
|
+
else {
|
|
3534
|
+
// Use extractPhaseToken to correctly parse M-NN-style and code-prefixed dir names.
|
|
3535
|
+
const phaseToken = extractPhaseToken(dir);
|
|
3536
|
+
phaseNum = phaseToken || dir;
|
|
3537
|
+
// phaseName is everything after the token (strip leading '-')
|
|
3538
|
+
const afterToken = dir.slice(phaseToken ? phaseToken.length : 0).replace(/^-/, '');
|
|
3539
|
+
phaseName = afterToken ? afterToken.replace(/-/g, ' ') : '';
|
|
3540
|
+
}
|
|
3541
|
+
// #3183/#5060: canonical plan/summary counts and lifecycle status
|
|
3542
|
+
// (root+nested, superseded-excluded, canonical pairing) from the
|
|
3543
|
+
// single owner (Phase Status Module, ADR-5057).
|
|
3544
|
+
const ps = (0, phase_status_cjs_1.phaseStatus)(node_path_1.default.join(phasesDir, dir), { convention: phaseIdConvention });
|
|
3545
|
+
statusError = firstStatusError(statusError, ps.value.statusError);
|
|
3546
|
+
const plans = ps.value.planCount;
|
|
3547
|
+
const summaries = ps.value.summaryCount;
|
|
3424
3548
|
totalPlans += plans;
|
|
3425
3549
|
totalSummaries += summaries;
|
|
3426
|
-
const status = determinePhaseStatus(plans, summaries, node_path_1.default.join(phasesDir, dir), 'Not Started');
|
|
3427
3550
|
const normalizedNum = normalizePhaseName(phaseNum);
|
|
3428
3551
|
const existing = phasesByNumber.get(normalizedNum);
|
|
3429
3552
|
phasesByNumber.set(normalizedNum, {
|
|
3430
3553
|
number: normalizedNum,
|
|
3554
|
+
...(existing?.display_id || displayId
|
|
3555
|
+
? { display_id: existing?.display_id || displayId }
|
|
3556
|
+
: {}),
|
|
3431
3557
|
name: existing?.name || phaseName,
|
|
3432
3558
|
plans: (existing?.plans || 0) + plans,
|
|
3433
3559
|
summaries: (existing?.summaries || 0) + summaries,
|
|
@@ -3436,13 +3562,26 @@ function cmdStats(cwd, format, raw) {
|
|
|
3436
3562
|
// platforms, so a naive overwrite can report a Complete phase as Not
|
|
3437
3563
|
// Started (or vice versa) depending on read order. The fold picks the
|
|
3438
3564
|
// furthest-along status, matching what an operator expects.
|
|
3439
|
-
|
|
3565
|
+
phaseStatus: existing ? (0, phase_status_cjs_1.foldPhaseStatuses)(existing.phaseStatus, ps.value.status) : ps.value.status,
|
|
3440
3566
|
});
|
|
3441
3567
|
}
|
|
3442
3568
|
}
|
|
3443
3569
|
catch { /* intentionally empty */ }
|
|
3444
|
-
|
|
3445
|
-
|
|
3570
|
+
// #5118: a read-only aggregate over a refused report prints nothing.
|
|
3571
|
+
if (statusError)
|
|
3572
|
+
failOnVerificationStatusError(statusError);
|
|
3573
|
+
const internalPhases = [...phasesByNumber.values()].sort((a, b) => comparePhaseNum(a.number, b.number));
|
|
3574
|
+
const completedPhases = internalPhases.filter(p => p.phaseStatus === phase_status_cjs_1.PHASE_STATUS.COMPLETE).length;
|
|
3575
|
+
// #5060: project the internal PHASE_STATUS ladder to the display label at
|
|
3576
|
+
// the output boundary only — the internal `phaseStatus` field is never emitted.
|
|
3577
|
+
const phases = internalPhases.map(p => ({
|
|
3578
|
+
number: p.number,
|
|
3579
|
+
...(p.display_id ? { display_id: p.display_id } : {}),
|
|
3580
|
+
name: p.name,
|
|
3581
|
+
plans: p.plans,
|
|
3582
|
+
summaries: p.summaries,
|
|
3583
|
+
status: (0, phase_status_cjs_1.toDisplayLabel)(p.phaseStatus, { pendingWord: 'Not Started' }),
|
|
3584
|
+
}));
|
|
3446
3585
|
// #3217 (ADR-3180 §7.6 rule 4): both percentages here are derived from the
|
|
3447
3586
|
// same `phaseScope`-carrying directory enumeration above (Phase 3, #3222) —
|
|
3448
3587
|
// withhold both when that scope is not COMPLETE, same rationale as
|
|
@@ -3486,7 +3625,7 @@ function cmdStats(cwd, format, raw) {
|
|
|
3486
3625
|
}
|
|
3487
3626
|
}
|
|
3488
3627
|
const result = {
|
|
3489
|
-
milestone_version:
|
|
3628
|
+
milestone_version: milestoneVersion,
|
|
3490
3629
|
milestone_name: milestone?.name ?? null,
|
|
3491
3630
|
phases,
|
|
3492
3631
|
phases_completed: completedPhases,
|
|
@@ -3506,7 +3645,7 @@ function cmdStats(cwd, format, raw) {
|
|
|
3506
3645
|
};
|
|
3507
3646
|
if (format === 'table') {
|
|
3508
3647
|
const bar = (0, phase_lifecycle_cjs_1.renderProgressBar)(percent, 10);
|
|
3509
|
-
let out = `# ${
|
|
3648
|
+
let out = `# ${milestoneVersion ?? ''} ${milestone?.name ?? ''} — Statistics\n\n`;
|
|
3510
3649
|
const percentSuffix = percent === null ? '' : ` (${percent}%)`;
|
|
3511
3650
|
out += `**Progress:** [${bar}] ${completedPhases}/${phases.length} phases${percentSuffix}\n`;
|
|
3512
3651
|
if (totalPlans > 0 && planPercent !== null) {
|
|
@@ -3520,7 +3659,7 @@ function cmdStats(cwd, format, raw) {
|
|
|
3520
3659
|
out += `| Phase | Name | Plans | Completed | Status |\n`;
|
|
3521
3660
|
out += `|-------|------|-------|-----------|--------|\n`;
|
|
3522
3661
|
for (const p of phases) {
|
|
3523
|
-
out += `| ${p.number} | ${p.name} | ${p.plans} | ${p.summaries} | ${p.status} |\n`;
|
|
3662
|
+
out += `| ${p.display_id ?? p.number} | ${p.name} | ${p.plans} | ${p.summaries} | ${p.status} |\n`;
|
|
3524
3663
|
}
|
|
3525
3664
|
if (gitCommits > 0) {
|
|
3526
3665
|
out += `\n**Git:** ${gitCommits} commits`;
|
|
@@ -3768,9 +3907,6 @@ function cmdCommitDocsGuardDisable(cwd, raw) {
|
|
|
3768
3907
|
module.exports = {
|
|
3769
3908
|
effortSurfaceForHost,
|
|
3770
3909
|
groupFilesBySubrepo,
|
|
3771
|
-
determinePhaseStatus,
|
|
3772
|
-
foldPhaseStatus,
|
|
3773
|
-
PHASE_STATUS_PRECEDENCE,
|
|
3774
3910
|
cmdGenerateSlug,
|
|
3775
3911
|
cmdCurrentTimestamp,
|
|
3776
3912
|
cmdListTodos,
|