@opengsd/gsd-core 1.15.0 → 1.17.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 -2
- package/.opencode/plugins/gsd-core.js +336 -6
- package/.opencode/plugins/gsd-statusline/tui.tsx +55 -0
- package/GEMINI.md +1 -1
- package/agents/gsd-code-fixer.compact.md +1 -1
- package/agents/gsd-code-fixer.md +1 -1
- 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 +7 -1
- package/agents/gsd-debug-session-manager.md +7 -1
- package/agents/gsd-debugger.md +2 -2
- package/agents/gsd-eval-auditor.compact.md +1 -1
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +4 -4
- 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 +1 -1
- package/agents/gsd-plan-checker.md +7 -2
- package/agents/gsd-planner.md +3 -3
- 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 +42 -51
- package/agents/gsd-ui-researcher.compact.md +1 -1
- package/agents/gsd-ui-researcher.md +1 -1
- package/agents/gsd-verifier.md +31 -9
- package/bin/gsd-mcp-server.js +3 -1
- package/bin/install.js +487 -290
- 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 +5 -0
- package/commands/gsd/capture.md +8 -4
- 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 +5 -1
- 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 +5 -1
- package/commands/gsd/pr-branch.md +4 -0
- package/commands/gsd/profile-user.md +5 -1
- package/commands/gsd/progress.md +6 -1
- package/commands/gsd/quick-batch.md +20 -8
- package/commands/gsd/quick.md +12 -7
- package/commands/gsd/review.md +5 -1
- 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 +159 -58
- package/gsd-core/bin/lib/active-workstream-store.cjs +15 -0
- package/gsd-core/bin/lib/adr-parser.cjs +37 -5
- package/gsd-core/bin/lib/agent-install-check.cjs +4 -1
- package/gsd-core/bin/lib/audit.cjs +78 -44
- 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 +249 -117
- package/gsd-core/bin/lib/capability-validator.cjs +18 -1
- package/gsd-core/bin/lib/capability-writer.cjs +2 -1
- package/gsd-core/bin/lib/check-auto-mode.cjs +35 -0
- package/gsd-core/bin/lib/check-command-router.cjs +164 -1712
- 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 +11 -8
- 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 +201 -178
- package/gsd-core/bin/lib/complexity-trigger.cjs +8 -7
- package/gsd-core/bin/lib/config.cjs +43 -12
- 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/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 +413 -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 +142 -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 +81 -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 +3 -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/init.cjs +194 -108
- package/gsd-core/bin/lib/install-engine.cjs +30 -35
- package/gsd-core/bin/lib/install-profiles.cjs +7 -4
- package/gsd-core/bin/lib/install-shadow-report.cjs +12 -0
- package/gsd-core/bin/lib/installer-migrations/011-opencode-mcp-no-prompts.cjs +99 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +54 -1
- package/gsd-core/bin/lib/io.cjs +122 -3
- package/gsd-core/bin/lib/jsonc-edit.cjs +418 -0
- package/gsd-core/bin/lib/loop-host-contract.cjs +2 -1
- 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/mcp-server.cjs +5 -1
- package/gsd-core/bin/lib/milestone.cjs +19 -1
- package/gsd-core/bin/lib/model-resolver.cjs +18 -18
- 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/opencode-mcp-entry.cjs +39 -0
- package/gsd-core/bin/lib/opencode-version.cjs +486 -0
- package/gsd-core/bin/lib/pattern.cjs +10 -0
- package/gsd-core/bin/lib/phase-command-router.cjs +11 -4
- package/gsd-core/bin/lib/phase-estimation.cjs +5 -4
- package/gsd-core/bin/lib/phase-id.cjs +1 -1
- package/gsd-core/bin/lib/phase-lifecycle.cjs +9 -2
- package/gsd-core/bin/lib/phase-status.cjs +360 -0
- package/gsd-core/bin/lib/phase.cjs +301 -80
- package/gsd-core/bin/lib/plan-document.cjs +93 -19
- package/gsd-core/bin/lib/plan-drift-guard.cjs +5 -0
- package/gsd-core/bin/lib/planning-document.cjs +273 -40
- package/gsd-core/bin/lib/planning-inspect.cjs +42 -8
- package/gsd-core/bin/lib/planning-snapshot.cjs +18 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +76 -53
- package/gsd-core/bin/lib/pristine-baseline.cjs +10 -0
- 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/quick-batch-command-router.cjs +35 -9
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +13 -9
- 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/roadmap-command-router.cjs +13 -15
- package/gsd-core/bin/lib/roadmap-parser.cjs +82 -15
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +127 -65
- package/gsd-core/bin/lib/roadmap.cjs +169 -72
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +186 -161
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +2 -1
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +57 -1
- package/gsd-core/bin/lib/runtime-homes.cjs +13 -10
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +365 -594
- package/gsd-core/bin/lib/runtime-name-policy.cjs +142 -27
- package/gsd-core/bin/lib/runtime-slash.cjs +60 -30
- package/gsd-core/bin/lib/shell-command-projection.cjs +37 -2
- package/gsd-core/bin/lib/smart-entry.cjs +111 -56
- 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 +67 -9
- package/gsd-core/bin/lib/state.cjs +668 -171
- package/gsd-core/bin/lib/surface.cjs +2 -1
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +48 -152
- package/gsd-core/bin/lib/uat-predicate.cjs +313 -35
- package/gsd-core/bin/lib/uat.cjs +424 -7
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +94 -58
- package/gsd-core/bin/lib/update-context.cjs +18 -1
- 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 +1265 -324
- package/gsd-core/bin/lib/verify-command-router.cjs +18 -7
- package/gsd-core/bin/lib/verify.cjs +286 -567
- 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/shared/config-defaults.manifest.json +5 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +4 -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/execute-mvp-tdd.md +5 -10
- package/gsd-core/references/execute-phase-between-wave-reset.md +3 -0
- package/gsd-core/references/execute-phase-response-language.md +1 -1
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +7 -1
- package/gsd-core/references/offer-next.md +1 -1
- package/gsd-core/references/planning-config.md +1 -1
- package/gsd-core/references/spidr-splitting.md +1 -1
- package/gsd-core/references/tdd.md +10 -4
- package/gsd-core/references/verifier-phase-gates.md +5 -2
- package/gsd-core/references/verify-mvp-mode.md +2 -2
- package/gsd-core/references/workstream-flag.md +33 -3
- 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 +3 -2
- package/gsd-core/workflows/add-todo.md +4 -3
- package/gsd-core/workflows/ai-integration-phase.md +3 -2
- 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.md +28 -18
- package/gsd-core/workflows/check-todos.md +6 -5
- package/gsd-core/workflows/cleanup.md +1 -1
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +20 -13
- package/gsd-core/workflows/code-review-fix.md +5 -4
- package/gsd-core/workflows/code-review.md +100 -91
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +4 -3
- package/gsd-core/workflows/complete-milestone.md +12 -7
- package/gsd-core/workflows/debug.md +30 -5
- 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 +1 -1
- package/gsd-core/workflows/docs-update.md +4 -3
- package/gsd-core/workflows/edit-phase.md +4 -3
- package/gsd-core/workflows/eval-review.md +5 -3
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +4 -2
- 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 +2 -2
- 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 +1 -1
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
- 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/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.md +81 -171
- package/gsd-core/workflows/execute-plan.md +13 -19
- package/gsd-core/workflows/explore.md +2 -2
- package/gsd-core/workflows/extract-learnings.md +3 -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 +2 -1
- package/gsd-core/workflows/import.md +3 -2
- package/gsd-core/workflows/inbox.md +1 -1
- package/gsd-core/workflows/ingest-docs.md +1 -1
- 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 +5 -3
- package/gsd-core/workflows/map-codebase.md +4 -3
- 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 +10 -10
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
- package/gsd-core/workflows/new-project.md +1 -1
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.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 +1 -1
- 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 +1 -1
- package/gsd-core/workflows/plan-phase.md +27 -18
- package/gsd-core/workflows/plan-review-convergence.md +1 -1
- package/gsd-core/workflows/plant-seed.md +1 -1
- package/gsd-core/workflows/pr-branch.md +1 -1
- package/gsd-core/workflows/profile-user.md +1 -1
- package/gsd-core/workflows/progress.md +19 -49
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +4 -2
- 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 +42 -31
- package/gsd-core/workflows/remove-phase.md +3 -2
- package/gsd-core/workflows/remove-workspace.md +1 -1
- package/gsd-core/workflows/resume-project.md +17 -12
- package/gsd-core/workflows/review.md +3 -2
- package/gsd-core/workflows/scan.md +3 -2
- package/gsd-core/workflows/secure-phase.md +11 -12
- package/gsd-core/workflows/settings-advanced.md +1 -1
- package/gsd-core/workflows/settings-integrations.md +1 -1
- package/gsd-core/workflows/settings.md +1 -1
- package/gsd-core/workflows/ship.md +12 -12
- 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 +1 -1
- package/gsd-core/workflows/spec-phase.md +1 -1
- 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 +1 -1
- package/gsd-core/workflows/thread.md +2 -2
- package/gsd-core/workflows/transition.md +13 -23
- package/gsd-core/workflows/ui-phase.md +5 -4
- package/gsd-core/workflows/ui-review.md +4 -3
- package/gsd-core/workflows/ultraplan-phase.md +3 -2
- package/gsd-core/workflows/undo.md +1 -1
- package/gsd-core/workflows/validate-phase.md +10 -12
- 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 +68 -59
- package/hooks/dist/gsd-agent-isolation-guard.js +8 -0
- package/hooks/dist/gsd-check-update-worker.js +8 -0
- package/hooks/dist/gsd-check-update.js +34 -20
- package/hooks/dist/gsd-context-monitor.js +45 -13
- package/hooks/dist/gsd-cursor-subagent-start.js +8 -0
- package/hooks/dist/gsd-secret-read-guard.js +263 -16
- package/hooks/dist/gsd-statusline.js +336 -29
- package/hooks/dist/gsd-update-banner.js +8 -0
- 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 +8 -0
- package/hooks/gsd-check-update-worker.js +8 -0
- package/hooks/gsd-check-update.js +34 -20
- package/hooks/gsd-context-monitor.js +45 -13
- package/hooks/gsd-cursor-subagent-start.js +8 -0
- package/hooks/gsd-secret-read-guard.js +263 -16
- package/hooks/gsd-statusline.js +336 -29
- package/hooks/gsd-update-banner.js +8 -0
- 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 +21 -4
- package/scripts/changeset/parse.cjs +52 -4
- package/scripts/ci-timeout-report.cjs +770 -4
- package/scripts/command-contract-helpers.cjs +12 -8
- package/scripts/docs-guard-registry.cjs +6 -0
- package/scripts/gen-capability-registry.cjs +7 -1
- package/scripts/gen-features.cjs +13 -8
- package/scripts/gen-hooks-cli-exit.cjs +12 -28
- package/scripts/gen-loop-host-contract.cjs +14 -2
- 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 +24 -2
- package/scripts/lib/ndjson-reporter.cjs +28 -3
- package/scripts/lib/platform-conformance-tier.generated.cjs +44 -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 +10 -0
- 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-enumeration-drift.cjs +12 -8
- package/scripts/lint-phase-id-drift.cjs +29 -0
- package/scripts/lint-planning-document-positive-control.cjs +329 -0
- package/scripts/lint-response-language-coverage.cjs +3 -0
- package/scripts/lint-skill-deps.cjs +7 -3
- package/scripts/lint-test-file-count.allowlist.json +11 -1
- package/scripts/lint-test-file-count.cjs +34 -1
- package/scripts/lint-vendored-deps.cjs +41 -5
- package/scripts/lint-workflow-shellcheck-baseline.json +10 -10
- package/scripts/mutation-matrix.cjs +53 -6
- package/scripts/release-tarball-smoke.cjs +14 -7
- 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 +5 -0
- package/skills/gsd-capture/SKILL.md +8 -4
- 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 +5 -1
- 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 +5 -1
- package/skills/gsd-pr-branch/SKILL.md +4 -0
- package/skills/gsd-profile-user/SKILL.md +5 -1
- package/skills/gsd-progress/SKILL.md +6 -1
- package/skills/gsd-quick/SKILL.md +16 -10
- package/skills/gsd-quick-batch/SKILL.md +20 -8
- package/skills/gsd-review/SKILL.md +5 -1
- 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
- package/gsd-core/workflows/execute-phase/steps/stale-reverification.md +0 -24
|
@@ -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");
|
|
@@ -62,7 +63,7 @@ const planningWorkspace = require("./planning-workspace.cjs");
|
|
|
62
63
|
const { planningDir, planningPaths, todosDir, resolvePhaseIdConvention } = planningWorkspace;
|
|
63
64
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
64
65
|
const frontmatter = require("./frontmatter.cjs");
|
|
65
|
-
const { extractFrontmatter, agentScalarNeedsDoubleQuoting, escapeDoubleQuotedScalar } = frontmatter;
|
|
66
|
+
const { extractFrontmatter, agentScalarNeedsDoubleQuoting, escapeDoubleQuotedScalar, FRONTMATTER_UNPARSEABLE } = frontmatter;
|
|
66
67
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
67
68
|
const modelProfiles = require("./model-profiles.cjs");
|
|
68
69
|
const { MODEL_PROFILES, VALID_PHASE_TYPES } = modelProfiles;
|
|
@@ -72,9 +73,40 @@ const phase_lifecycle_cjs_1 = require("./phase-lifecycle.cjs");
|
|
|
72
73
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
73
74
|
const planScanMod = require("./plan-scan.cjs");
|
|
74
75
|
const { scanPhasePlans } = planScanMod;
|
|
75
|
-
//
|
|
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
|
|
76
82
|
const verificationMod = require("./verification.cjs");
|
|
77
|
-
const {
|
|
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
|
+
}
|
|
78
110
|
/**
|
|
79
111
|
* Project one canonical bracket directory onto its display identity and slug.
|
|
80
112
|
*
|
|
@@ -101,94 +133,6 @@ function recoverBracketPhaseName(dir, phaseToken) {
|
|
|
101
133
|
const afterToken = dir.slice(tokenOffset + tokenBoundary.length).replace(/^-/, '');
|
|
102
134
|
return afterToken ? afterToken.replace(/-/g, ' ') : '';
|
|
103
135
|
}
|
|
104
|
-
// ─── Phase Status ─────────────────────────────────────────────────────────────
|
|
105
|
-
/**
|
|
106
|
-
* Phase-status precedence ladder — furthest-along wins (#2408).
|
|
107
|
-
*
|
|
108
|
-
* `cmdStats` builds `phasesByNumber` by scanning on-disk phase directories.
|
|
109
|
-
* When two directories normalize to the same phase key (e.g. `05-real/` and
|
|
110
|
-
* `05-real-stray/`), the status field must be folded by precedence rather
|
|
111
|
-
* than overwritten last-write-wins — otherwise `/gsd-stats` reports whatever
|
|
112
|
-
* directory `fs.readdirSync` happened to yield last, which is non-deterministic
|
|
113
|
-
* across platforms and can silently call a `Complete` phase `Not Started`.
|
|
114
|
-
*/
|
|
115
|
-
const PHASE_STATUS_PRECEDENCE = [
|
|
116
|
-
'Complete',
|
|
117
|
-
'Needs Review',
|
|
118
|
-
'Executed',
|
|
119
|
-
'In Progress',
|
|
120
|
-
'Planned',
|
|
121
|
-
'Not Started',
|
|
122
|
-
'Pending',
|
|
123
|
-
];
|
|
124
|
-
const PHASE_STATUS_RANK = new Map(PHASE_STATUS_PRECEDENCE.map((s, i) => [s, i]));
|
|
125
|
-
/**
|
|
126
|
-
* Fold two phase statuses by precedence — returns whichever is further along
|
|
127
|
-
* the {@link PHASE_STATUS_PRECEDENCE} ladder. Unrecognized statuses fall behind
|
|
128
|
-
* every recognized one (so a recognized status always wins over an unknown one;
|
|
129
|
-
* two unrecognized statuses favor `a` for determinism).
|
|
130
|
-
*/
|
|
131
|
-
function foldPhaseStatus(a, b) {
|
|
132
|
-
const ra = PHASE_STATUS_RANK.get(a);
|
|
133
|
-
const rb = PHASE_STATUS_RANK.get(b);
|
|
134
|
-
if (ra === undefined && rb === undefined)
|
|
135
|
-
return a;
|
|
136
|
-
if (ra === undefined)
|
|
137
|
-
return b;
|
|
138
|
-
if (rb === undefined)
|
|
139
|
-
return a;
|
|
140
|
-
// Lower rank = higher precedence (Complete=0 wins over Not Started=5).
|
|
141
|
-
return ra <= rb ? a : b;
|
|
142
|
-
}
|
|
143
|
-
/**
|
|
144
|
-
* Determine phase status by checking plan/summary counts AND verification state.
|
|
145
|
-
* Introduces "Executed" for phases with all summaries but no passing verification.
|
|
146
|
-
*/
|
|
147
|
-
function determinePhaseStatus(plans, summaries, phaseDir, defaultPending) {
|
|
148
|
-
if (plans === 0)
|
|
149
|
-
return defaultPending;
|
|
150
|
-
if (summaries < plans && summaries > 0)
|
|
151
|
-
return 'In Progress';
|
|
152
|
-
if (summaries < plans)
|
|
153
|
-
return 'Planned';
|
|
154
|
-
// summaries >= plans — check verification
|
|
155
|
-
try {
|
|
156
|
-
const files = node_fs_1.default.readdirSync(phaseDir);
|
|
157
|
-
// #3473 F2: routed through the shared resolver (readdir order is
|
|
158
|
-
// filesystem-dependent, so the prior hand-rolled `.find()` could pick
|
|
159
|
-
// either file when a phase held both a canonical report and an ad-hoc
|
|
160
|
-
// `-CORRECTION-VERIFICATION.md` worksheet — see #3357).
|
|
161
|
-
// #3492: pin selection to THIS phase's own token so a stray cross-phase
|
|
162
|
-
// or sentinel-numbered canonically-shaped file cannot outrank this
|
|
163
|
-
// phase's own (possibly non-canonical) report.
|
|
164
|
-
const phaseDirName = node_path_1.default.basename(phaseDir);
|
|
165
|
-
const phaseToken = extractPhaseToken(phaseDirName);
|
|
166
|
-
const verificationFile = resolveVerificationFile(files, { allowBare: true, phaseToken, phaseDirName });
|
|
167
|
-
if (verificationFile) {
|
|
168
|
-
const verificationFilePath = node_path_1.default.join(phaseDir, verificationFile);
|
|
169
|
-
const content = (0, shell_command_projection_cjs_1.platformReadSync)(verificationFilePath) || '';
|
|
170
|
-
// #1159 (Defect A): read ONLY the frontmatter `status` key to avoid false
|
|
171
|
-
// matches from historical body metadata such as `previous_status: gaps_found`.
|
|
172
|
-
// Full-text regexes like /status:\s*gaps_found/ match the substring inside
|
|
173
|
-
// `previous_status: gaps_found`, producing incorrect phase status labels.
|
|
174
|
-
const fm = extractFrontmatter(content, verificationFilePath);
|
|
175
|
-
// Normalise to lower-case to preserve the prior case-insensitive behaviour
|
|
176
|
-
// while reading only the frontmatter `status` key (not the full body text).
|
|
177
|
-
const fmStatus = typeof fm['status'] === 'string' ? fm['status'].trim().toLowerCase() : '';
|
|
178
|
-
if (fmStatus === 'passed')
|
|
179
|
-
return 'Complete';
|
|
180
|
-
if (fmStatus === 'human_needed')
|
|
181
|
-
return 'Needs Review';
|
|
182
|
-
if (fmStatus === 'gaps_found')
|
|
183
|
-
return 'Executed';
|
|
184
|
-
// Verification exists but unrecognized status — treat as executed
|
|
185
|
-
return 'Executed';
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
catch { /* directory read failed — fall through */ }
|
|
189
|
-
// No verification file — executed but not verified
|
|
190
|
-
return 'Executed';
|
|
191
|
-
}
|
|
192
136
|
function cmdGenerateSlug(text, raw) {
|
|
193
137
|
if (!text) {
|
|
194
138
|
error('text required for slug generation');
|
|
@@ -602,19 +546,19 @@ function cmdResolveExecution(cwd, agentType, raw, opts) {
|
|
|
602
546
|
let effortEffective = effort;
|
|
603
547
|
if (runtime === 'claude') {
|
|
604
548
|
try {
|
|
605
|
-
|
|
606
|
-
const { getGlobalConfigDir } = require('./runtime-homes.cjs');
|
|
607
|
-
const agentsDirEff = node_path_1.default.join(getGlobalConfigDir(runtime), 'agents');
|
|
549
|
+
const agentsDirEff = claudeEffortAgentsDir();
|
|
608
550
|
// agentType is an unvalidated CLI positional: keep the read inside the
|
|
609
551
|
// agents dir so `../../x` cannot point it elsewhere (defense in depth —
|
|
610
552
|
// the reflected surface is only a frontmatter effort line). Untrusted
|
|
611
553
|
// input feeding a real read → realpath family (ADR-4650 decision 6).
|
|
612
554
|
const agentPath = (0, security_cjs_1.assertWithinRoot)(`${agentType}.md`, agentsDirEff, 'agent file');
|
|
613
555
|
const agentContent = node_fs_1.default.readFileSync(agentPath, 'utf8');
|
|
614
|
-
//
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
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);
|
|
618
562
|
if (effortLine) {
|
|
619
563
|
effortEffective = effortLine[1];
|
|
620
564
|
effortEffectiveSource = 'frontmatter';
|
|
@@ -701,33 +645,42 @@ function effortSurfaceForHost(cwd, host) {
|
|
|
701
645
|
return 'none';
|
|
702
646
|
}
|
|
703
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
|
+
}
|
|
704
663
|
/**
|
|
705
664
|
* #488 — Replace or inject the `<key>:` value in YAML frontmatter.
|
|
706
665
|
* Unlike injectEffortFrontmatter (install.js), this overwrites an existing value.
|
|
707
666
|
* #3706: key-parameterised so the same line-editor serves both claude's
|
|
708
|
-
* `effort:` and OpenCode's `variant:`. #3706: all offsets (eol,
|
|
709
|
-
* closingStart)
|
|
710
|
-
*
|
|
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.
|
|
711
670
|
*/
|
|
712
671
|
function setFrontmatterKeyLine(content, key, value) {
|
|
713
|
-
const
|
|
714
|
-
|
|
715
|
-
if (!match)
|
|
672
|
+
const span = agentFrontmatterSpan(content, EFFORT_SYNC_FENCE);
|
|
673
|
+
if (!span)
|
|
716
674
|
return content;
|
|
717
|
-
const fmBody =
|
|
675
|
+
const fmBody = span.body;
|
|
718
676
|
// Both writers of these frontmatter keys — this sync path and the
|
|
719
677
|
// install-side `frontmatterScalar` in runtime-artifact-conversion.cts —
|
|
720
678
|
// now share one escaping rule: quote via `agentScalarNeedsDoubleQuoting` +
|
|
721
679
|
// `escapeDoubleQuotedScalar` (both from frontmatter.cts) rather than each
|
|
722
680
|
// interpolating `value` raw/differently.
|
|
723
681
|
const renderedValue = agentScalarNeedsDoubleQuoting(value) ? `"${escapeDoubleQuotedScalar(value)}"` : value;
|
|
724
|
-
//
|
|
725
|
-
|
|
726
|
-
// offset below by one byte and mangles the opening fence.
|
|
727
|
-
const eol = /^---\r\n/.test(match[0]) ? '\r\n' : '\n';
|
|
728
|
-
const openLen = 3 + eol.length;
|
|
729
|
-
const bodyStart = match.index + openLen;
|
|
730
|
-
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;
|
|
731
684
|
// #3706: key is now generic (not just the literal 'effort'/'variant'
|
|
732
685
|
// callers happen to pass today) — escape it before interpolating into the
|
|
733
686
|
// RegExp so a future caller can't have its key metacharacters reinterpreted.
|
|
@@ -763,19 +716,17 @@ function setFrontmatterKeyLine(content, key, value) {
|
|
|
763
716
|
* codex-agent-toml strip discipline: targeted line removal, EOL-aware, every
|
|
764
717
|
* other byte (comments, sibling keys, the body) untouched.
|
|
765
718
|
* #3706: key-parameterised so the same line-editor serves both claude's
|
|
766
|
-
* `effort:` and OpenCode's `variant:`. #3706:
|
|
767
|
-
*
|
|
768
|
-
* would otherwise misalign every offset below.
|
|
719
|
+
* `effort:` and OpenCode's `variant:`. #3706: every offset comes from the
|
|
720
|
+
* block `agentFrontmatterSpan` found.
|
|
769
721
|
*/
|
|
770
722
|
function removeFrontmatterKeyLine(content, key) {
|
|
771
723
|
// Scoped to the FIRST frontmatter block (not a whole-file /m match): a
|
|
772
724
|
// preamble or body line starting with `<key>:` (a fenced config example,
|
|
773
725
|
// a thematic-break flanked fragment) must never be the line removed.
|
|
774
|
-
const
|
|
775
|
-
|
|
776
|
-
if (!match)
|
|
726
|
+
const span = agentFrontmatterSpan(content, EFFORT_SYNC_FENCE);
|
|
727
|
+
if (!span)
|
|
777
728
|
return content;
|
|
778
|
-
const fmBody =
|
|
729
|
+
const fmBody = span.body;
|
|
779
730
|
// #3706: same generic-key escape as setFrontmatterKeyLine above.
|
|
780
731
|
const lineRe = new RegExp(`^${(0, pattern_cjs_1.escapeRegex)(key)}:[ \\t]*.*\\r?\\n?`, 'm');
|
|
781
732
|
if (!lineRe.test(fmBody))
|
|
@@ -790,12 +741,7 @@ function removeFrontmatterKeyLine(content, key) {
|
|
|
790
741
|
// is removed in one pass.
|
|
791
742
|
const stripAllRe = new RegExp(`^${(0, pattern_cjs_1.escapeRegex)(key)}:[ \\t]*.*\\r?\\n?`, 'gm');
|
|
792
743
|
const strippedFm = fmBody.replace(stripAllRe, '');
|
|
793
|
-
|
|
794
|
-
// block, not the start of the file, or a preambled CRLF document misaligns.
|
|
795
|
-
const eol = /^---\r\n/.test(match[0]) ? '\r\n' : '\n';
|
|
796
|
-
const openLen = 3 + eol.length;
|
|
797
|
-
const closingStart = match.index + openLen + fmBody.length;
|
|
798
|
-
return content.slice(0, match.index + openLen) + strippedFm + content.slice(closingStart);
|
|
744
|
+
return content.slice(0, span.bodyStart) + strippedFm + content.slice(span.closingStart);
|
|
799
745
|
}
|
|
800
746
|
/** #488 — Replace or inject the `effort:` value in YAML frontmatter. */
|
|
801
747
|
function setEffortFrontmatter(content, effortValue) {
|
|
@@ -840,8 +786,6 @@ function cmdEffortSync(cwd, raw, opts) {
|
|
|
840
786
|
output({ synced: 0, skipped: 0, changes: [], dry_run: dryRun, reason: `runtime '${runtime}' does not use effort: frontmatter` }, raw, '');
|
|
841
787
|
return;
|
|
842
788
|
}
|
|
843
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/unbound-method
|
|
844
|
-
const { getGlobalConfigDir } = require('./runtime-homes.cjs');
|
|
845
789
|
// Use install-time resolvers: they merge ~/.gsd/defaults.json with project config,
|
|
846
790
|
// matching the exact logic used when agents were originally installed. #2071: these
|
|
847
791
|
// live in the shipped sibling install-effort-resolver.cjs (extracted from the
|
|
@@ -849,7 +793,7 @@ function cmdEffortSync(cwd, raw, opts) {
|
|
|
849
793
|
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/unbound-method
|
|
850
794
|
const { readGsdEffectiveEffortConfig, resolveInstallTimeEffort } = require('./install-effort-resolver.cjs');
|
|
851
795
|
const effortCfg = readGsdEffectiveEffortConfig(cwd);
|
|
852
|
-
const agentsDir =
|
|
796
|
+
const agentsDir = claudeEffortAgentsDir(opts.configDir);
|
|
853
797
|
if (!node_fs_1.default.existsSync(agentsDir)) {
|
|
854
798
|
output({ synced: 0, skipped: 0, changes: [], dry_run: dryRun, agents_dir: agentsDir, reason: 'agents directory not found' }, raw, '');
|
|
855
799
|
return;
|
|
@@ -867,6 +811,10 @@ function cmdEffortSync(cwd, raw, opts) {
|
|
|
867
811
|
}).sort(); // #3706: sorted like the codex and
|
|
868
812
|
// opencode branches — readdir order is platform-dependent, so leaving it unsorted makes the
|
|
869
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
|
+
}
|
|
870
818
|
const changes = [];
|
|
871
819
|
let synced = 0;
|
|
872
820
|
let skipped = 0;
|
|
@@ -911,7 +859,7 @@ function cmdEffortSync(cwd, raw, opts) {
|
|
|
911
859
|
// drift and the sync re-added a hand-stripped key on every apply. A
|
|
912
860
|
// present key under inherit is stripped, reported as {from, to: null}.
|
|
913
861
|
if (universalEffort === 'inherit') {
|
|
914
|
-
const fmMatchInherit =
|
|
862
|
+
const fmMatchInherit = agentFrontmatterSpan(content, EFFORT_SYNC_FENCE);
|
|
915
863
|
if (!fmMatchInherit) {
|
|
916
864
|
skipped++;
|
|
917
865
|
continue;
|
|
@@ -922,12 +870,12 @@ function cmdEffortSync(cwd, raw, opts) {
|
|
|
922
870
|
// from a null value alone is wrong here — it would leave an
|
|
923
871
|
// unresolvable `effort: null` key on disk forever. Test presence with
|
|
924
872
|
// its own regex, and only compare values once presence is known.
|
|
925
|
-
const effortPresentInherit = /^effort:/m.test(fmMatchInherit
|
|
873
|
+
const effortPresentInherit = /^effort:/m.test(fmMatchInherit.body);
|
|
926
874
|
if (!effortPresentInherit) {
|
|
927
875
|
skipped++;
|
|
928
876
|
continue;
|
|
929
877
|
}
|
|
930
|
-
const effortMatchInherit = /^effort:[ \t]*(.+?)[ \t]*$/m.exec(fmMatchInherit
|
|
878
|
+
const effortMatchInherit = /^effort:[ \t]*(.+?)[ \t]*$/m.exec(fmMatchInherit.body);
|
|
931
879
|
// `effortPresentInherit` is guaranteed true here (checked above), so a
|
|
932
880
|
// failed value match means the key is present with an EMPTY value —
|
|
933
881
|
// report `''`, not `null`, so "present-but-empty" is never conflated
|
|
@@ -986,7 +934,7 @@ function cmdEffortSync(cwd, raw, opts) {
|
|
|
986
934
|
// codex's 'ultra' rejection can produce a null value).
|
|
987
935
|
const rendered = (0, model_catalog_cjs_1.renderEffortForRuntime)(runtime, universalEffort);
|
|
988
936
|
const newEffortValue = rendered.value;
|
|
989
|
-
const fmMatch =
|
|
937
|
+
const fmMatch = agentFrontmatterSpan(content, EFFORT_SYNC_FENCE);
|
|
990
938
|
if (!fmMatch) {
|
|
991
939
|
skipped++;
|
|
992
940
|
continue;
|
|
@@ -999,8 +947,8 @@ function cmdEffortSync(cwd, raw, opts) {
|
|
|
999
947
|
// never null on this path (guarded above), so an absent key already
|
|
1000
948
|
// yields `currentEffort === null !== newEffortValue` without consulting
|
|
1001
949
|
// presence separately.
|
|
1002
|
-
const effortPresent = /^effort:/m.test(fmMatch
|
|
1003
|
-
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);
|
|
1004
952
|
// `null` (key absent) and `''` (key present, value empty) are distinct
|
|
1005
953
|
// states `effortPresent` deliberately disambiguates — collapsing both to
|
|
1006
954
|
// `null` here would make the reported `from` lie about which case fired.
|
|
@@ -1273,7 +1221,7 @@ function cmdEffortSyncOpencode(cwd, raw, dryRun, configDir) {
|
|
|
1273
1221
|
// exactly what install writes.
|
|
1274
1222
|
const universal = effortCfg ? resolveInstallTimeEffort(effortCfg, agentName) : null;
|
|
1275
1223
|
const target = universal ? clampEffortForHost('opencode', universal) : null;
|
|
1276
|
-
const fmMatch =
|
|
1224
|
+
const fmMatch = agentFrontmatterSpan(content, EFFORT_SYNC_FENCE);
|
|
1277
1225
|
if (!fmMatch) {
|
|
1278
1226
|
skipped++;
|
|
1279
1227
|
continue;
|
|
@@ -1285,25 +1233,45 @@ function cmdEffortSyncOpencode(cwd, raw, dryRun, configDir) {
|
|
|
1285
1233
|
// null — it would leave an unresolvable `variant: null` key on disk
|
|
1286
1234
|
// forever. Test presence with its own regex, and only compare values
|
|
1287
1235
|
// once presence is known.
|
|
1288
|
-
|
|
1289
|
-
|
|
1236
|
+
// Native OpenCode 2.x form (ADR-5331 D5): the effort rides on the model reference as
|
|
1237
|
+
// `provider/model#variant`, with no separate `variant:` key. When the agent's `model:` carries a
|
|
1238
|
+
// `#` suffix that suffix IS the effort, so the sync edits the model instead of adding a second,
|
|
1239
|
+
// conflicting `variant:` line.
|
|
1240
|
+
const modelLineMatch = /^model:[ \t]*(.+?)[ \t]*$/m.exec(fmMatch.body);
|
|
1241
|
+
const modelValue = modelLineMatch ? modelLineMatch[1].replace(/\r$/, '').replace(/^(["'])(.*)\1$/, '$2') : null;
|
|
1242
|
+
const hashAt = modelValue === null ? -1 : modelValue.lastIndexOf('#');
|
|
1243
|
+
let modelFormContent = null;
|
|
1244
|
+
let modelFormFrom = null;
|
|
1245
|
+
if (modelValue !== null && hashAt > 0) {
|
|
1246
|
+
const base = modelValue.slice(0, hashAt);
|
|
1247
|
+
modelFormFrom = modelValue.slice(hashAt + 1);
|
|
1248
|
+
if (target !== null && modelFormFrom === target) {
|
|
1249
|
+
skipped++;
|
|
1250
|
+
continue;
|
|
1251
|
+
}
|
|
1252
|
+
modelFormContent = setFrontmatterKeyLine(content, 'model', target === null ? base : `${base}#${target}`);
|
|
1253
|
+
}
|
|
1254
|
+
const variantPresent = /^variant:/m.test(fmMatch.body);
|
|
1255
|
+
const variantMatch = /^variant:[ \t]*(.+?)[ \t]*$/m.exec(fmMatch.body);
|
|
1290
1256
|
// `null` (key absent) and `''` (key present, value empty) are distinct
|
|
1291
1257
|
// states this code deliberately tracks via `variantPresent` above — a
|
|
1292
1258
|
// reported `from` that collapses both to `null` would make "no key" and
|
|
1293
1259
|
// "empty key" indistinguishable in the sync output, even though only one
|
|
1294
1260
|
// of them actually has a `variant:` line to remove.
|
|
1295
1261
|
const currentVariant = variantPresent ? (variantMatch ? variantMatch[1] : '') : null;
|
|
1296
|
-
if (
|
|
1297
|
-
if (
|
|
1262
|
+
if (modelFormContent === null) {
|
|
1263
|
+
if (target === null) {
|
|
1264
|
+
if (!variantPresent) {
|
|
1265
|
+
skipped++;
|
|
1266
|
+
continue;
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
else if (variantPresent && currentVariant === target) {
|
|
1298
1270
|
skipped++;
|
|
1299
1271
|
continue;
|
|
1300
1272
|
}
|
|
1301
1273
|
}
|
|
1302
|
-
|
|
1303
|
-
skipped++;
|
|
1304
|
-
continue;
|
|
1305
|
-
}
|
|
1306
|
-
changes.push({ agent: agentName, from: currentVariant, to: target });
|
|
1274
|
+
changes.push({ agent: agentName, from: modelFormContent === null ? currentVariant : modelFormFrom, to: target });
|
|
1307
1275
|
synced++;
|
|
1308
1276
|
if (!dryRun) {
|
|
1309
1277
|
// Atomic publish AND mode preservation, same discipline as
|
|
@@ -1331,7 +1299,7 @@ function cmdEffortSyncOpencode(cwd, raw, dryRun, configDir) {
|
|
|
1331
1299
|
}
|
|
1332
1300
|
catch { /* non-fatal: fall back to writing without an explicit mode */ }
|
|
1333
1301
|
try {
|
|
1334
|
-
node_fs_1.default.writeFileSync(tmpPath, target === null ? removeFrontmatterKeyLine(content, 'variant') : setFrontmatterKeyLine(content, 'variant', target), originalMode !== undefined ? { mode: originalMode } : undefined);
|
|
1302
|
+
node_fs_1.default.writeFileSync(tmpPath, modelFormContent ?? (target === null ? removeFrontmatterKeyLine(content, 'variant') : setFrontmatterKeyLine(content, 'variant', target)), originalMode !== undefined ? { mode: originalMode } : undefined);
|
|
1335
1303
|
// Not redundant with the `mode` option above: `mode` only applies
|
|
1336
1304
|
// when the file is actually created (O_CREAT). A leftover tmp file
|
|
1337
1305
|
// from an earlier crashed run would be reused (truncated) at its OLD
|
|
@@ -2883,13 +2851,38 @@ function cmdSummaryExtract(cwd, summaryPath, fields, raw) {
|
|
|
2883
2851
|
if (!summaryPath) {
|
|
2884
2852
|
error('summary-path required for summary-extract');
|
|
2885
2853
|
}
|
|
2886
|
-
|
|
2854
|
+
// #5013: requireSafePath (not path.join) so an absolute summaryPath resolves to
|
|
2855
|
+
// that literal path instead of being concatenated onto cwd — path.join has no
|
|
2856
|
+
// special case for an absolute second argument the way path.resolve does. Uses
|
|
2857
|
+
// requireSafePath (the same PathAcceptance.AbsoluteInsideRoot pattern
|
|
2858
|
+
// cmdAuditAcknowledge's write-side counterpart already uses in audit.cts)
|
|
2859
|
+
// rather than a bare path.resolve: a plain path.resolve would also have made
|
|
2860
|
+
// an absolute summaryPath anywhere on the filesystem resolve and have its
|
|
2861
|
+
// content echoed back (this command reads and returns file content, unlike
|
|
2862
|
+
// cmdVerifyPathExists's exists-only check) — an orthogonal-review finding on
|
|
2863
|
+
// this same fix. requireSafePath keeps the acceptance criterion (an absolute
|
|
2864
|
+
// path within the project resolves correctly) while refusing one that
|
|
2865
|
+
// escapes cwd, and rejects relative `../` escapes the same way (pre-existing
|
|
2866
|
+
// in this function, closed as a direct consequence, not separately in scope).
|
|
2867
|
+
const fullPath = (0, security_cjs_1.requireSafePath)(summaryPath, cwd, 'summary-extract target', security_cjs_1.PathAcceptance.AbsoluteInsideRoot);
|
|
2887
2868
|
if (!node_fs_1.default.existsSync(fullPath)) {
|
|
2888
2869
|
output({ error: 'File not found', path: summaryPath }, raw, undefined);
|
|
2889
2870
|
return;
|
|
2890
2871
|
}
|
|
2891
2872
|
const content = node_fs_1.default.readFileSync(fullPath, 'utf-8');
|
|
2892
2873
|
const fm = extractFrontmatter(content, fullPath);
|
|
2874
|
+
// #5013: an unparseable frontmatter block (malformed YAML in the fence) is
|
|
2875
|
+
// NOT the same as an absent/empty one — extractFrontmatter marks it with
|
|
2876
|
+
// FRONTMATTER_UNPARSEABLE (mirrors state.cts's isUnparseableFrontmatter /
|
|
2877
|
+
// state-transition.cts's identical helper). Left unchecked, every field
|
|
2878
|
+
// below silently falls back to its empty default and this command exits 0,
|
|
2879
|
+
// so a milestone audit reading requirements_completed through it would
|
|
2880
|
+
// report "zero requirements completed" for a plan that completed several,
|
|
2881
|
+
// with nothing on stderr. Refuse instead, naming the file, the same way
|
|
2882
|
+
// cmdAuditAcknowledge (audit.cts) refuses on the write-side counterpart.
|
|
2883
|
+
if (fm[FRONTMATTER_UNPARSEABLE] === true) {
|
|
2884
|
+
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 });
|
|
2885
|
+
}
|
|
2893
2886
|
// Parse key-decisions into structured format
|
|
2894
2887
|
const parseDecisions = (decisionsList) => {
|
|
2895
2888
|
if (!decisionsList || !Array.isArray(decisionsList))
|
|
@@ -3060,6 +3053,8 @@ function cmdProgressRender(cwd, format, raw) {
|
|
|
3060
3053
|
let totalPlans = 0;
|
|
3061
3054
|
let totalSummaries = 0;
|
|
3062
3055
|
let phaseScope = null;
|
|
3056
|
+
// #5118: carried out of the scan below and failed on after it.
|
|
3057
|
+
let statusError;
|
|
3063
3058
|
try {
|
|
3064
3059
|
// #3185 (ADR-3180 Decision 1): the single owner applies the milestone
|
|
3065
3060
|
// window AND the sentinel filter and returns dirs already sorted by
|
|
@@ -3096,14 +3091,16 @@ function cmdProgressRender(cwd, format, raw) {
|
|
|
3096
3091
|
phaseNum = dm ? dm[1] : dir;
|
|
3097
3092
|
phaseName = dm && dm[2] ? dm[2].replace(/-/g, ' ') : '';
|
|
3098
3093
|
}
|
|
3099
|
-
// #3183: canonical plan/summary counts
|
|
3100
|
-
// canonical pairing) from the
|
|
3101
|
-
|
|
3102
|
-
const
|
|
3103
|
-
|
|
3094
|
+
// #3183/#5060: canonical plan/summary counts and lifecycle status
|
|
3095
|
+
// (root+nested, superseded-excluded, canonical pairing) from the
|
|
3096
|
+
// single owner (Phase Status Module, ADR-5057).
|
|
3097
|
+
const ps = (0, phase_status_cjs_1.phaseStatus)(node_path_1.default.join(phasesDir, dir), { convention: phaseIdConvention });
|
|
3098
|
+
statusError = firstStatusError(statusError, ps.value.statusError);
|
|
3099
|
+
const plans = ps.value.planCount;
|
|
3100
|
+
const summaries = ps.value.summaryCount;
|
|
3104
3101
|
totalPlans += plans;
|
|
3105
3102
|
totalSummaries += summaries;
|
|
3106
|
-
const status =
|
|
3103
|
+
const status = (0, phase_status_cjs_1.toDisplayLabel)(ps.value.status, { pendingWord: 'Pending' });
|
|
3107
3104
|
phases.push({
|
|
3108
3105
|
number: phaseNum,
|
|
3109
3106
|
...(displayId ? { display_id: displayId } : {}),
|
|
@@ -3115,6 +3112,9 @@ function cmdProgressRender(cwd, format, raw) {
|
|
|
3115
3112
|
}
|
|
3116
3113
|
}
|
|
3117
3114
|
catch { /* intentionally empty */ }
|
|
3115
|
+
// #5118: a read-only aggregate over a refused report prints nothing.
|
|
3116
|
+
if (statusError)
|
|
3117
|
+
failOnVerificationStatusError(statusError);
|
|
3118
3118
|
// #3217 (ADR-3180 §7.6 rule 4): `phaseScope` was already computed above
|
|
3119
3119
|
// (Phase 3, #3222) but never consulted before rendering — a percentage was
|
|
3120
3120
|
// rendered from counts the scope said were not answers (TRUNCATED /
|
|
@@ -3273,29 +3273,35 @@ function cmdTodoMatchPhase(cwd, phase, raw) {
|
|
|
3273
3273
|
// #4096: upsert completion keys INSIDE the leading frontmatter block. Never a
|
|
3274
3274
|
// bare prefix line above the opening `---` (that displaces the fence to line 2
|
|
3275
3275
|
// and breaks every fence-locating reader). A file with no well-formed block
|
|
3276
|
-
// (absent, or an unterminated opening fence) gains a complete block.
|
|
3276
|
+
// (absent, or an unterminated opening fence) gains a complete block. The block is
|
|
3277
|
+
// the one the one fence owner (`locateFrontmatterFence`) finds, so this writer and
|
|
3278
|
+
// every todo reader agree on where it is.
|
|
3277
3279
|
function upsertTodoCompletionFields(content, today) {
|
|
3278
|
-
const lines = content.split('\n');
|
|
3279
3280
|
const fields = [`completed: ${today}`, 'status: completed'];
|
|
3280
|
-
const
|
|
3281
|
-
|
|
3282
|
-
if (!hasOpeningFence || closeIdx === -1) {
|
|
3281
|
+
const fence = (0, frontmatter_fence_cjs_1.locateFrontmatterFence)(content);
|
|
3282
|
+
if (!fence?.closed) {
|
|
3283
3283
|
// No parseable frontmatter: wrap the whole content in a complete block
|
|
3284
|
-
// rather than prefixing bare keys (#4096 fix 2).
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3284
|
+
// rather than prefixing bare keys (#4096 fix 2). A leading BOM (#2977) stays
|
|
3285
|
+
// the document's first character, ahead of the new fence.
|
|
3286
|
+
const bom = content.charCodeAt(0) === 0xFEFF ? content.slice(0, 1) : '';
|
|
3287
|
+
return `${bom}---\n${fields.join('\n')}\n---\n\n${content.slice(bom.length)}`;
|
|
3288
|
+
}
|
|
3289
|
+
// The block's lines, split on a bare '\n' so a CRLF line keeps its '\r' — and a
|
|
3290
|
+
// written field line carries one too, so a CRLF block never gains LF-only lines.
|
|
3291
|
+
const inner = content.slice(fence.openEnd, fence.closingStart);
|
|
3292
|
+
const block = inner === '' ? [] : inner.slice(0, -1).split('\n');
|
|
3293
|
+
const cr = fence.eol === '\r\n' ? '\r' : '';
|
|
3288
3294
|
for (const field of fields) {
|
|
3289
3295
|
const key = `${field.slice(0, field.indexOf(':'))}:`;
|
|
3290
3296
|
const idx = block.findIndex(l => l.startsWith(key));
|
|
3291
3297
|
if (idx === -1) {
|
|
3292
|
-
block.push(field);
|
|
3298
|
+
block.push(field + cr);
|
|
3293
3299
|
}
|
|
3294
3300
|
else {
|
|
3295
|
-
block[idx] = field;
|
|
3301
|
+
block[idx] = field + cr;
|
|
3296
3302
|
}
|
|
3297
3303
|
}
|
|
3298
|
-
return
|
|
3304
|
+
return `${content.slice(0, fence.openEnd)}${block.join('\n')}\n${content.slice(fence.closingStart)}`;
|
|
3299
3305
|
}
|
|
3300
3306
|
function cmdTodoComplete(cwd, filename, options, raw) {
|
|
3301
3307
|
if (!filename) {
|
|
@@ -3313,7 +3319,7 @@ function cmdTodoComplete(cwd, filename, options, raw) {
|
|
|
3313
3319
|
// to a location inside todosRoot (or inside pending/) and would pass
|
|
3314
3320
|
// containment, yet none of them is a bare filename. Reject on basename
|
|
3315
3321
|
// shape FIRST, before any path is even joined — same predicate shape as
|
|
3316
|
-
// findPhaseArtifact in
|
|
3322
|
+
// findPhaseArtifact in gate-predicate.cts. Checking both `/` and
|
|
3317
3323
|
// `\` explicitly (not just path.basename) matters on POSIX, where a
|
|
3318
3324
|
// literal backslash is just an ordinary filename character to
|
|
3319
3325
|
// path.basename but not to path.win32.basename or to the user's intent.
|
|
@@ -3448,6 +3454,9 @@ function cmdStats(cwd, format, raw) {
|
|
|
3448
3454
|
: null;
|
|
3449
3455
|
const milestoneVersion = milestoneDisplay ?? milestone?.version ?? null;
|
|
3450
3456
|
// Phase & plan stats (reuse progress pattern)
|
|
3457
|
+
// #5060: `phaseStatus` here is the internal PHASE_STATUS ladder value, never
|
|
3458
|
+
// a display label — the output projection below is the only place a label
|
|
3459
|
+
// is produced (toDisplayLabel).
|
|
3451
3460
|
const phasesByNumber = new Map();
|
|
3452
3461
|
let totalPlans = 0;
|
|
3453
3462
|
let totalSummaries = 0;
|
|
@@ -3505,11 +3514,14 @@ function cmdStats(cwd, format, raw) {
|
|
|
3505
3514
|
name: phaseName.replace(/\(INSERTED\)/i, '').trim(),
|
|
3506
3515
|
plans: 0,
|
|
3507
3516
|
summaries: 0,
|
|
3508
|
-
|
|
3517
|
+
phaseStatus: phase_status_cjs_1.PHASE_STATUS.NOT_STARTED,
|
|
3509
3518
|
});
|
|
3510
3519
|
}
|
|
3511
3520
|
}
|
|
3512
3521
|
catch { /* intentionally empty */ }
|
|
3522
|
+
// #5118: the first phase whose verification report `status` is outside the
|
|
3523
|
+
// closed set — carried out of the scan below and failed on after it.
|
|
3524
|
+
let statusError;
|
|
3513
3525
|
try {
|
|
3514
3526
|
// #3185 (ADR-3180 Decision 1): route through the single owner. This
|
|
3515
3527
|
// previously applied the milestone window but NOT a directory-level
|
|
@@ -3546,14 +3558,15 @@ function cmdStats(cwd, format, raw) {
|
|
|
3546
3558
|
const afterToken = dir.slice(phaseToken ? phaseToken.length : 0).replace(/^-/, '');
|
|
3547
3559
|
phaseName = afterToken ? afterToken.replace(/-/g, ' ') : '';
|
|
3548
3560
|
}
|
|
3549
|
-
// #3183: canonical plan/summary counts
|
|
3550
|
-
// canonical pairing) from the
|
|
3551
|
-
|
|
3552
|
-
const
|
|
3553
|
-
|
|
3561
|
+
// #3183/#5060: canonical plan/summary counts and lifecycle status
|
|
3562
|
+
// (root+nested, superseded-excluded, canonical pairing) from the
|
|
3563
|
+
// single owner (Phase Status Module, ADR-5057).
|
|
3564
|
+
const ps = (0, phase_status_cjs_1.phaseStatus)(node_path_1.default.join(phasesDir, dir), { convention: phaseIdConvention });
|
|
3565
|
+
statusError = firstStatusError(statusError, ps.value.statusError);
|
|
3566
|
+
const plans = ps.value.planCount;
|
|
3567
|
+
const summaries = ps.value.summaryCount;
|
|
3554
3568
|
totalPlans += plans;
|
|
3555
3569
|
totalSummaries += summaries;
|
|
3556
|
-
const status = determinePhaseStatus(plans, summaries, node_path_1.default.join(phasesDir, dir), 'Not Started');
|
|
3557
3570
|
const normalizedNum = normalizePhaseName(phaseNum);
|
|
3558
3571
|
const existing = phasesByNumber.get(normalizedNum);
|
|
3559
3572
|
phasesByNumber.set(normalizedNum, {
|
|
@@ -3569,13 +3582,26 @@ function cmdStats(cwd, format, raw) {
|
|
|
3569
3582
|
// platforms, so a naive overwrite can report a Complete phase as Not
|
|
3570
3583
|
// Started (or vice versa) depending on read order. The fold picks the
|
|
3571
3584
|
// furthest-along status, matching what an operator expects.
|
|
3572
|
-
|
|
3585
|
+
phaseStatus: existing ? (0, phase_status_cjs_1.foldPhaseStatuses)(existing.phaseStatus, ps.value.status) : ps.value.status,
|
|
3573
3586
|
});
|
|
3574
3587
|
}
|
|
3575
3588
|
}
|
|
3576
3589
|
catch { /* intentionally empty */ }
|
|
3577
|
-
|
|
3578
|
-
|
|
3590
|
+
// #5118: a read-only aggregate over a refused report prints nothing.
|
|
3591
|
+
if (statusError)
|
|
3592
|
+
failOnVerificationStatusError(statusError);
|
|
3593
|
+
const internalPhases = [...phasesByNumber.values()].sort((a, b) => comparePhaseNum(a.number, b.number));
|
|
3594
|
+
const completedPhases = internalPhases.filter(p => p.phaseStatus === phase_status_cjs_1.PHASE_STATUS.COMPLETE).length;
|
|
3595
|
+
// #5060: project the internal PHASE_STATUS ladder to the display label at
|
|
3596
|
+
// the output boundary only — the internal `phaseStatus` field is never emitted.
|
|
3597
|
+
const phases = internalPhases.map(p => ({
|
|
3598
|
+
number: p.number,
|
|
3599
|
+
...(p.display_id ? { display_id: p.display_id } : {}),
|
|
3600
|
+
name: p.name,
|
|
3601
|
+
plans: p.plans,
|
|
3602
|
+
summaries: p.summaries,
|
|
3603
|
+
status: (0, phase_status_cjs_1.toDisplayLabel)(p.phaseStatus, { pendingWord: 'Not Started' }),
|
|
3604
|
+
}));
|
|
3579
3605
|
// #3217 (ADR-3180 §7.6 rule 4): both percentages here are derived from the
|
|
3580
3606
|
// same `phaseScope`-carrying directory enumeration above (Phase 3, #3222) —
|
|
3581
3607
|
// withhold both when that scope is not COMPLETE, same rationale as
|
|
@@ -3901,9 +3927,6 @@ function cmdCommitDocsGuardDisable(cwd, raw) {
|
|
|
3901
3927
|
module.exports = {
|
|
3902
3928
|
effortSurfaceForHost,
|
|
3903
3929
|
groupFilesBySubrepo,
|
|
3904
|
-
determinePhaseStatus,
|
|
3905
|
-
foldPhaseStatus,
|
|
3906
|
-
PHASE_STATUS_PRECEDENCE,
|
|
3907
3930
|
cmdGenerateSlug,
|
|
3908
3931
|
cmdCurrentTimestamp,
|
|
3909
3932
|
cmdListTodos,
|