@opengsd/gsd-core 1.10.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-debug-session-manager.md +12 -1
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-doc-synthesizer.md +2 -4
- package/agents/gsd-dom-verifier.md +169 -0
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +22 -14
- package/agents/gsd-framework-selector.md +1 -3
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.md +5 -3
- package/agents/gsd-pattern-mapper.md +11 -0
- package/agents/gsd-phase-researcher.md +23 -2
- package/agents/gsd-plan-checker.md +50 -53
- package/agents/gsd-planner.md +50 -50
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.md +2 -2
- package/agents/gsd-roadmapper.md +15 -11
- package/agents/gsd-ui-checker.md +63 -4
- package/agents/gsd-ui-researcher.md +41 -3
- package/agents/gsd-user-profiler.md +3 -0
- package/agents/gsd-verifier.md +13 -4
- package/bin/install.js +1448 -1103
- package/commands/gsd/code-review.md +1 -1
- package/commands/gsd/discuss-phase.md +1 -1
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/import.md +1 -1
- package/commands/gsd/map-codebase.md +1 -1
- package/commands/gsd/mempalace-capture.md +1 -1
- package/commands/gsd/mempalace-recall.md +1 -1
- package/commands/gsd/new-milestone.md +1 -1
- package/commands/gsd/quick.md +9 -5
- package/commands/gsd/review-backlog.md +2 -1
- package/commands/gsd/verify-work.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +1035 -138
- package/gsd-core/bin/lib/active-workstream-store.cjs +146 -22
- package/gsd-core/bin/lib/adr-parser.cjs +13 -7
- package/gsd-core/bin/lib/agent-install-check.cjs +392 -32
- package/gsd-core/bin/lib/api-coverage.cjs +33 -14
- package/gsd-core/bin/lib/artifacts.cjs +5 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +32 -15
- package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
- package/gsd-core/bin/lib/audit.cjs +1026 -268
- package/gsd-core/bin/lib/broken-windows.cjs +306 -28
- package/gsd-core/bin/lib/capability-consent.cjs +149 -15
- package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
- package/gsd-core/bin/lib/capability-lock.cjs +10 -4
- package/gsd-core/bin/lib/capability-registry.cjs +845 -130
- package/gsd-core/bin/lib/capability-source.cjs +92 -0
- package/gsd-core/bin/lib/capability-state.cjs +18 -3
- package/gsd-core/bin/lib/capability-trust.cjs +444 -25
- package/gsd-core/bin/lib/capability-validator.cjs +700 -40
- package/gsd-core/bin/lib/capability-writer.cjs +3 -2
- package/gsd-core/bin/lib/check-command-router.cjs +216 -42
- package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
- package/gsd-core/bin/lib/cli-exit.cjs +496 -10
- package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
- package/gsd-core/bin/lib/codex-agent-toml.cjs +735 -0
- package/gsd-core/bin/lib/command-aliases.cjs +22 -0
- package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
- package/gsd-core/bin/lib/command-roster.cjs +44 -1
- package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
- package/gsd-core/bin/lib/commands.cjs +1172 -108
- package/gsd-core/bin/lib/commonjs-marker.cjs +12 -6
- package/gsd-core/bin/lib/complexity-trigger.cjs +1192 -0
- package/gsd-core/bin/lib/config-loader.cjs +187 -23
- package/gsd-core/bin/lib/config.cjs +102 -3
- package/gsd-core/bin/lib/configuration.cjs +129 -37
- package/gsd-core/bin/lib/core-utils.cjs +208 -33
- package/gsd-core/bin/lib/decisions.cjs +23 -0
- package/gsd-core/bin/lib/edge-probe.cjs +9 -1
- package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
- package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
- package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
- package/gsd-core/bin/lib/frontmatter.cjs +899 -229
- package/gsd-core/bin/lib/gap-checker.cjs +95 -10
- package/gsd-core/bin/lib/git-base-branch.cjs +276 -39
- package/gsd-core/bin/lib/gsd2-import.cjs +10 -1
- package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +149 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +268 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +187 -0
- package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +451 -0
- package/gsd-core/bin/lib/host-integration.cjs +39 -6
- package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
- package/gsd-core/bin/lib/init-command-router.cjs +118 -21
- package/gsd-core/bin/lib/init.cjs +439 -168
- package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
- package/gsd-core/bin/lib/install-engine.cjs +811 -259
- package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +235 -0
- package/gsd-core/bin/lib/install-profiles.cjs +212 -61
- package/gsd-core/bin/lib/install-scope.cjs +270 -0
- package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
- package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
- package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
- package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +148 -38
- package/gsd-core/bin/lib/intel.cjs +101 -26
- package/gsd-core/bin/lib/io.cjs +170 -15
- package/gsd-core/bin/lib/learnings.cjs +85 -14
- package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +2 -1
- package/gsd-core/bin/lib/markdown-table.cjs +183 -22
- package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
- package/gsd-core/bin/lib/milestone.cjs +842 -73
- package/gsd-core/bin/lib/model-catalog.cjs +232 -16
- package/gsd-core/bin/lib/model-resolver.cjs +193 -68
- package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
- package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
- package/gsd-core/bin/lib/pattern.cjs +122 -0
- package/gsd-core/bin/lib/phase-estimation.cjs +18 -9
- package/gsd-core/bin/lib/phase-id.cjs +514 -40
- package/gsd-core/bin/lib/phase-lifecycle.cjs +52 -19
- package/gsd-core/bin/lib/phase-locator.cjs +262 -34
- package/gsd-core/bin/lib/phase.cjs +1038 -214
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +72 -1
- package/gsd-core/bin/lib/plan-document.cjs +263 -0
- package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
- package/gsd-core/bin/lib/plan-scan.cjs +98 -3
- package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
- package/gsd-core/bin/lib/planning-scope.cjs +31 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +894 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +112 -6
- package/gsd-core/bin/lib/probe-core.cjs +5 -2
- package/gsd-core/bin/lib/profile-output.cjs +1 -1
- package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
- package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
- package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +766 -0
- package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +11 -6
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +22 -13
- package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +421 -66
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
- package/gsd-core/bin/lib/roadmap-command-router.cjs +59 -11
- package/gsd-core/bin/lib/roadmap-parser.cjs +1006 -184
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
- package/gsd-core/bin/lib/roadmap.cjs +442 -96
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +702 -52
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +459 -55
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
- package/gsd-core/bin/lib/runtime-homes.cjs +69 -3
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +402 -58
- package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
- package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
- package/gsd-core/bin/lib/runtime-slash.cjs +96 -8
- package/gsd-core/bin/lib/security.cjs +104 -5
- package/gsd-core/bin/lib/shell-command-projection.cjs +342 -7
- package/gsd-core/bin/lib/smart-entry.cjs +133 -23
- package/gsd-core/bin/lib/spec-section.cjs +12 -7
- package/gsd-core/bin/lib/state-command-router.cjs +52 -19
- package/gsd-core/bin/lib/state-contract.cjs +359 -0
- package/gsd-core/bin/lib/state-document.cjs +338 -8
- package/gsd-core/bin/lib/state-md-schema.cjs +221 -0
- package/gsd-core/bin/lib/state-transition.cjs +846 -176
- package/gsd-core/bin/lib/state.cjs +2589 -369
- package/gsd-core/bin/lib/surface.cjs +33 -11
- package/gsd-core/bin/lib/task-command-router.cjs +111 -1
- package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
- package/gsd-core/bin/lib/teams-status.cjs +4 -1
- package/gsd-core/bin/lib/text-lines.cjs +80 -0
- package/gsd-core/bin/lib/token-scanner.cjs +76 -0
- package/gsd-core/bin/lib/uat-predicate.cjs +67 -23
- package/gsd-core/bin/lib/uat.cjs +1761 -167
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
- package/gsd-core/bin/lib/ui-safety-gate.cjs +51 -12
- package/gsd-core/bin/lib/unusable-input.cjs +37 -0
- package/gsd-core/bin/lib/update-context.cjs +8 -2
- package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
- package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
- package/gsd-core/bin/lib/validate.cjs +20 -6
- package/gsd-core/bin/lib/vendor/README.md +75 -0
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
- package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
- package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +272 -9
- package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
- package/gsd-core/bin/lib/verify.cjs +453 -918
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +53 -32
- package/gsd-core/bin/lib/workstream-inventory.cjs +63 -10
- package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
- package/gsd-core/bin/lib/workstream.cjs +2 -2
- package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
- package/gsd-core/bin/lib/worktree-safety.cjs +341 -18
- package/gsd-core/bin/shared/config-defaults.manifest.json +8 -1
- package/gsd-core/bin/shared/config-schema.manifest.json +12 -1
- package/gsd-core/bin/shared/exit-codes.json +8 -0
- package/gsd-core/bin/shared/exit-codes.sh +20 -0
- package/gsd-core/bin/shared/model-catalog.json +8 -1
- package/gsd-core/references/agent-contracts.md +44 -26
- package/gsd-core/references/api-coverage.md +24 -2
- package/gsd-core/references/autonomous-smart-discuss.md +3 -3
- package/gsd-core/references/checkpoints.md +39 -21
- package/gsd-core/references/context-budget.md +1 -1
- package/gsd-core/references/decimal-phase-calculation.md +5 -5
- package/gsd-core/references/dispatch-isolation-gate.md +138 -0
- package/gsd-core/references/doc-conflict-engine.md +1 -1
- package/gsd-core/references/edge-probe.md +8 -0
- package/gsd-core/references/execute-mvp-tdd.md +4 -6
- package/gsd-core/references/execute-phase-between-wave-reset.md +15 -14
- package/gsd-core/references/execute-phase-context-guard.md +1 -1
- package/gsd-core/references/execute-phase-response-language.md +1 -1
- package/gsd-core/references/execute-phase-wave-guard.md +17 -11
- package/gsd-core/references/failing-direction.md +78 -0
- package/gsd-core/references/gate-prompts.md +1 -1
- package/gsd-core/references/git-integration.md +5 -5
- package/gsd-core/references/git-planning-commit.md +5 -4
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +61 -2
- package/gsd-core/references/model-profiles.md +12 -4
- package/gsd-core/references/mvp-concepts.md +9 -9
- package/gsd-core/references/nyquist-compliance.md +74 -0
- package/gsd-core/references/offer-next.md +3 -5
- package/gsd-core/references/phase-argument-parsing.md +3 -3
- package/gsd-core/references/planner-failing-direction.md +53 -0
- package/gsd-core/references/planner-guidance.md +3 -9
- package/gsd-core/references/planner-human-verify-mode.md +15 -1
- package/gsd-core/references/planner-preconditions.md +1 -1
- package/gsd-core/references/planner-reviews.md +1 -1
- package/gsd-core/references/planner-revision.md +1 -1
- package/gsd-core/references/planner-verify-command-grounding.md +17 -0
- package/gsd-core/references/planning-config.md +44 -13
- package/gsd-core/references/reviewer-instances.md +31 -0
- package/gsd-core/references/revision-loop.md +1 -1
- package/gsd-core/references/runtime-aware-dispatch.md +1 -1
- package/gsd-core/references/specless-probe-fallback.md +1 -1
- package/gsd-core/references/tdd.md +1 -3
- package/gsd-core/references/ui-brand.md +65 -21
- package/gsd-core/references/ui-consideration-probe.md +1 -1
- package/gsd-core/references/universal-anti-patterns.md +5 -5
- package/gsd-core/references/verifier-phase-gates.md +192 -0
- package/gsd-core/references/verify-command-path-resolvability.md +42 -0
- package/gsd-core/references/verify-mvp-mode.md +2 -2
- package/gsd-core/references/workstream-flag.md +33 -17
- package/gsd-core/templates/README.md +1 -1
- package/gsd-core/templates/SECURITY.md +3 -3
- package/gsd-core/templates/UI-SPEC.md +25 -3
- package/gsd-core/templates/VALIDATION.md +3 -3
- package/gsd-core/templates/discussion-log.md +1 -1
- package/gsd-core/templates/phase-prompt.md +5 -4
- package/gsd-core/templates/state.md +11 -4
- package/gsd-core/templates/verification-report.md +9 -1
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +1 -1
- package/gsd-core/workflows/add-phase.md +3 -3
- package/gsd-core/workflows/add-tests.md +3 -8
- package/gsd-core/workflows/add-todo.md +1 -1
- package/gsd-core/workflows/ai-integration-phase.md +13 -20
- package/gsd-core/workflows/audit-fix.md +12 -3
- package/gsd-core/workflows/audit-milestone.md +9 -9
- package/gsd-core/workflows/audit-uat.md +17 -2
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
- package/gsd-core/workflows/autonomous.md +11 -27
- package/gsd-core/workflows/check-todos.md +1 -1
- package/gsd-core/workflows/cleanup.md +64 -5
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +14 -4
- package/gsd-core/workflows/code-review-fix.md +38 -11
- package/gsd-core/workflows/code-review.md +159 -52
- package/gsd-core/workflows/complete-milestone.md +151 -23
- package/gsd-core/workflows/debug.md +12 -8
- package/gsd-core/workflows/diagnose-issues.md +47 -15
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -8
- package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/text.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
- package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +3 -6
- package/gsd-core/workflows/docs-update.md +5 -4
- package/gsd-core/workflows/edit-phase.md +27 -2
- package/gsd-core/workflows/eval-review.md +7 -14
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +142 -15
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +24 -4
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
- package/gsd-core/workflows/execute-phase.md +72 -100
- package/gsd-core/workflows/execute-plan.md +52 -15
- package/gsd-core/workflows/explore.md +131 -4
- package/gsd-core/workflows/extract-learnings.md +1 -1
- package/gsd-core/workflows/fast.md +10 -2
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +5 -5
- package/gsd-core/workflows/health.md +76 -10
- package/gsd-core/workflows/import.md +18 -15
- package/gsd-core/workflows/inbox.md +4 -5
- package/gsd-core/workflows/ingest-docs.md +49 -16
- package/gsd-core/workflows/insert-phase.md +5 -5
- package/gsd-core/workflows/list-seeds.md +5 -3
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +12 -23
- package/gsd-core/workflows/map-codebase.md +1 -1
- package/gsd-core/workflows/milestone-summary.md +1 -1
- package/gsd-core/workflows/mvp-phase.md +8 -5
- package/gsd-core/workflows/new-milestone.md +22 -29
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
- package/gsd-core/workflows/new-project.md +26 -40
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +14 -2
- package/gsd-core/workflows/pause-work.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -4
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +3 -3
- package/gsd-core/workflows/plan-phase.md +162 -59
- package/gsd-core/workflows/plan-review-convergence.md +96 -11
- package/gsd-core/workflows/plant-seed.md +2 -2
- package/gsd-core/workflows/pr-branch.md +187 -51
- package/gsd-core/workflows/profile-user.md +16 -14
- package/gsd-core/workflows/progress.md +61 -18
- package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +5 -7
- package/gsd-core/workflows/quick/steps/quick-verification.md +28 -9
- package/gsd-core/workflows/quick/steps/research-phase.md +4 -6
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
- package/gsd-core/workflows/quick.md +55 -44
- package/gsd-core/workflows/remove-phase.md +4 -4
- package/gsd-core/workflows/remove-workspace.md +2 -2
- package/gsd-core/workflows/resume-project.md +8 -12
- package/gsd-core/workflows/review.md +219 -20
- package/gsd-core/workflows/scan.md +1 -1
- package/gsd-core/workflows/secure-phase.md +3 -3
- package/gsd-core/workflows/session-report.md +2 -1
- package/gsd-core/workflows/settings-advanced.md +7 -9
- package/gsd-core/workflows/settings-integrations.md +64 -31
- package/gsd-core/workflows/settings.md +69 -7
- package/gsd-core/workflows/ship.md +116 -50
- package/gsd-core/workflows/sketch-wrap-up.md +11 -17
- package/gsd-core/workflows/sketch.md +12 -18
- package/gsd-core/workflows/smart-entry.md +3 -5
- package/gsd-core/workflows/spec-phase.md +53 -13
- package/gsd-core/workflows/spike-wrap-up.md +7 -11
- package/gsd-core/workflows/spike.md +20 -31
- package/gsd-core/workflows/stats.md +2 -2
- package/gsd-core/workflows/sync-skills.md +64 -9
- package/gsd-core/workflows/thread.md +11 -7
- package/gsd-core/workflows/transition.md +49 -14
- package/gsd-core/workflows/ui-phase.md +15 -21
- package/gsd-core/workflows/ui-review.md +8 -12
- package/gsd-core/workflows/ultraplan-phase.md +5 -13
- package/gsd-core/workflows/undo.md +8 -16
- package/gsd-core/workflows/update.md +7 -11
- package/gsd-core/workflows/validate-phase.md +3 -3
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
- package/gsd-core/workflows/verify-work.md +66 -25
- package/hooks/dist/gsd-agent-isolation-guard.js +158 -30
- package/hooks/dist/gsd-check-update-worker.js +56 -13
- package/hooks/dist/gsd-check-update.js +19 -1
- package/hooks/dist/gsd-config-reload.js +18 -12
- package/hooks/dist/gsd-context-monitor.js +19 -10
- package/hooks/dist/gsd-cursor-post-tool.js +3 -1
- package/hooks/dist/gsd-cursor-pre-tool.js +2 -3
- package/hooks/dist/gsd-cursor-session-start.js +2 -1
- package/hooks/dist/gsd-cursor-stop.js +2 -1
- package/hooks/dist/gsd-cursor-subagent-start.js +83 -3
- package/hooks/dist/gsd-cursor-subagent-stop.js +6 -3
- package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
- package/hooks/dist/gsd-graphify-update.sh +22 -18
- package/hooks/dist/gsd-node-runner.sh +76 -0
- package/hooks/dist/gsd-phase-boundary.sh +1 -0
- package/hooks/dist/gsd-prompt-guard.js +37 -27
- package/hooks/dist/gsd-read-guard.js +16 -7
- package/hooks/dist/gsd-read-injection-scanner.js +55 -32
- package/hooks/dist/gsd-session-state.sh +1 -0
- package/hooks/dist/gsd-statusline.js +231 -24
- package/hooks/dist/gsd-update-banner.js +22 -1
- package/hooks/dist/gsd-validate-commit.sh +80 -6
- package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
- package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
- package/hooks/dist/gsd-workflow-guard.js +162 -46
- package/hooks/dist/gsd-worktree-path-guard.js +36 -21
- package/hooks/dist/gsd-write-guard.js +35 -25
- package/hooks/dist/lib/cli-exit.js +560 -0
- package/hooks/dist/lib/exit-code-registry.js +98 -0
- package/hooks/dist/lib/git-cmd.js +92 -59
- package/hooks/dist/lib/git-probe.js +84 -0
- package/hooks/dist/lib/hook-exit.js +81 -0
- package/hooks/dist/lib/injection-patterns.js +45 -0
- package/hooks/dist/lib/isolation-deny-reason.js +39 -0
- package/hooks/dist/lib/isolation-sentinel.js +9 -0
- package/hooks/dist/managed-hooks-registry.cjs +3 -0
- package/hooks/gsd-agent-isolation-guard.js +158 -30
- package/hooks/gsd-check-update-worker.js +56 -13
- package/hooks/gsd-check-update.js +19 -1
- package/hooks/gsd-config-reload.js +18 -12
- package/hooks/gsd-context-monitor.js +19 -10
- package/hooks/gsd-cursor-post-tool.js +3 -1
- package/hooks/gsd-cursor-pre-tool.js +2 -3
- package/hooks/gsd-cursor-session-start.js +2 -1
- package/hooks/gsd-cursor-stop.js +2 -1
- package/hooks/gsd-cursor-subagent-start.js +83 -3
- package/hooks/gsd-cursor-subagent-stop.js +6 -3
- package/hooks/gsd-ensure-canonical-path.js +2 -1
- package/hooks/gsd-graphify-update.sh +22 -18
- package/hooks/gsd-node-runner.sh +76 -0
- package/hooks/gsd-phase-boundary.sh +1 -0
- package/hooks/gsd-prompt-guard.js +37 -27
- package/hooks/gsd-read-guard.js +16 -7
- package/hooks/gsd-read-injection-scanner.js +55 -32
- package/hooks/gsd-session-state.sh +1 -0
- package/hooks/gsd-statusline.js +231 -24
- package/hooks/gsd-update-banner.js +22 -1
- package/hooks/gsd-validate-commit.sh +80 -6
- package/hooks/gsd-windsurf-pre-command.js +16 -11
- package/hooks/gsd-windsurf-pre-write.js +22 -13
- package/hooks/gsd-workflow-guard.js +162 -46
- package/hooks/gsd-worktree-path-guard.js +36 -21
- package/hooks/gsd-write-guard.js +35 -25
- package/hooks/lib/cli-exit.js +560 -0
- package/hooks/lib/exit-code-registry.js +98 -0
- package/hooks/lib/git-cmd.js +92 -59
- package/hooks/lib/git-probe.js +84 -0
- package/hooks/lib/hook-exit.js +81 -0
- package/hooks/lib/injection-patterns.js +45 -0
- package/hooks/lib/isolation-deny-reason.js +39 -0
- package/hooks/lib/isolation-sentinel.js +9 -0
- package/hooks/managed-hooks-registry.cjs +3 -0
- package/package.json +28 -11
- package/pi/gsd.cjs +19 -5
- package/scripts/base64-scan.sh +74 -12
- package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
- package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
- package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
- package/scripts/build-hooks.js +5 -0
- package/scripts/changeset/lint.cjs +60 -5
- package/scripts/check-alias-drift.cjs +7 -43
- package/scripts/check-contract-drift.cjs +297 -0
- package/scripts/check-glossary-refs.cjs +77 -15
- package/scripts/check-mutation-score-ratchet.cjs +156 -0
- package/scripts/ci-check-job-near-cap.cjs +49 -0
- package/scripts/ci-pr-mergeability.cjs +262 -0
- package/scripts/ci-test-scope.cjs +64 -14
- package/scripts/ci-timeout-report.cjs +230 -0
- package/scripts/command-contract-helpers.cjs +903 -1
- package/scripts/docs-guard-registry.cjs +396 -0
- package/scripts/gen-adr-index.cjs +728 -38
- package/scripts/gen-capability-registry.cjs +11 -21
- package/scripts/gen-context-index.cjs +2 -11
- package/scripts/gen-exit-code-docs.cjs +318 -0
- package/scripts/gen-exit-code-registry.cjs +891 -0
- package/scripts/gen-features.cjs +836 -0
- package/scripts/gen-health-docs.cjs +390 -0
- package/scripts/gen-hooks-cli-exit.cjs +239 -0
- package/scripts/gen-install-tree-fixtures.cjs +2 -2
- package/scripts/gen-inventory-manifest.cjs +50 -4
- package/scripts/gen-loop-host-contract.cjs +138 -25
- package/scripts/gen-registry.cjs +3 -14
- package/scripts/gen-scripts-cli-exit.cjs +185 -0
- package/scripts/gen-state-md-docs.cjs +727 -0
- package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
- package/scripts/lib/alias-drift-families.cjs +46 -0
- package/scripts/lib/ci-job-timing.cjs +72 -0
- package/scripts/lib/cli-exit.cjs +546 -44
- package/scripts/lib/drift-scan.cjs +308 -0
- package/scripts/lib/exit-code-registry.cjs +98 -0
- package/scripts/lib/ndjson-reporter.cjs +119 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +1 -26
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
- package/scripts/lint-canary-version-leak.cjs +73 -0
- package/scripts/lint-command-contract.cjs +96 -13
- package/scripts/lint-completion-predicate-drift.cjs +933 -0
- package/scripts/lint-completion-ratio-drift.cjs +214 -0
- package/scripts/lint-default-flip-documentation.cjs +193 -0
- package/scripts/lint-docs-guard-registration.cjs +495 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +193 -0
- package/scripts/lint-eslint-glob-coverage.allowlist.json +38 -0
- package/scripts/lint-eslint-glob-coverage.cjs +340 -0
- package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
- package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
- package/scripts/lint-health-diagnostic-rule-table.cjs +461 -0
- package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
- package/scripts/lint-milestone-window-drift.cjs +468 -0
- package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
- package/scripts/lint-phase-enumeration-drift.cjs +492 -0
- package/scripts/lint-plan-count-drift.cjs +318 -0
- package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
- package/scripts/lint-planning-prompt-drift.cjs +471 -0
- package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
- package/scripts/lint-regression-test-names.cjs +15 -13
- package/scripts/lint-removed-but-needed.cjs +488 -0
- package/scripts/lint-seam-enforcement.cjs +182 -0
- package/scripts/lint-slug-derivation-drift.cjs +921 -0
- package/scripts/lint-source-test-name-collision.cjs +241 -0
- package/scripts/lint-state-field-drift.cjs +805 -0
- package/scripts/lint-state-write-path-drift.cjs +950 -0
- package/scripts/lint-test-file-count.allowlist.json +137 -8
- package/scripts/lint-test-file-count.cjs +25 -3
- package/scripts/lint-unreachable-guard-drift.cjs +830 -0
- package/scripts/lint-vendored-deps.cjs +297 -0
- package/scripts/mutation-matrix.cjs +599 -50
- package/scripts/pr-changed-files.cjs +63 -0
- package/scripts/pr-template-policy.cjs +14 -4
- package/scripts/prompt-injection-scan.sh +100 -14
- package/scripts/require-issue-link-policy.cjs +192 -0
- package/scripts/secret-scan.sh +75 -13
- package/scripts/select-docs-guards.cjs +56 -0
- package/scripts/sync-runtime-launcher.cjs +24 -7
- package/skills/gsd-autonomous/SKILL.md +0 -1
- package/skills/gsd-code-review/SKILL.md +1 -1
- package/skills/gsd-discuss-phase/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -2
- package/skills/gsd-import/SKILL.md +1 -1
- package/skills/gsd-map-codebase/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +1 -1
- package/skills/gsd-mempalace-recall/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-next/SKILL.md +0 -1
- package/skills/gsd-plan-phase/SKILL.md +0 -1
- package/skills/gsd-progress/SKILL.md +0 -1
- package/skills/gsd-quick/SKILL.md +9 -5
- package/skills/gsd-review-backlog/SKILL.md +2 -1
- package/skills/gsd-stats/SKILL.md +0 -1
- package/skills/gsd-verify-work/SKILL.md +1 -1
- package/vscode/package.json +1 -1
- package/bin/lib/ui-safety-gate.cjs +0 -107
- package/gsd-core/workflows/discovery-phase.md +0 -298
- package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
- package/gsd-core/workflows/verify-phase.md +0 -574
- package/scripts/affected-tests-lib.cjs +0 -554
- package/scripts/lint-allow-test-rule-refs.cjs +0 -162
- package/scripts/lint-emitted-drift-ack.cjs +0 -344
- package/scripts/run-affected-tests.cjs +0 -7
- package/scripts/run-tests.cjs +0 -1051
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
*
|
|
14
14
|
* Dependencies (leaf modules only):
|
|
15
15
|
* - node:fs / node:os / node:path (stdlib)
|
|
16
|
-
* - ./configuration.cjs (normalizeLegacyKeys, CONFIG_DEFAULTS as CANONICAL_CONFIG_DEFAULTS)
|
|
16
|
+
* - ./configuration.cjs (normalizeLegacyKeys, isConfigSection, CONFIG_DEFAULTS as CANONICAL_CONFIG_DEFAULTS)
|
|
17
|
+
* - ./unusable-input.cjs (warnUnusableInput, UNUSABLE_REASON — #3760)
|
|
17
18
|
* - ./config-schema.cjs (VALID_CONFIG_KEYS, DYNAMIC_KEY_PATTERNS)
|
|
18
19
|
* - ./planning-workspace.cjs (planningDir, planningRoot)
|
|
19
20
|
* - ./shell-command-projection.cjs (execGit, platformWriteSync, platformReadSync)
|
|
@@ -39,6 +40,12 @@ const configuration_cjs_1 = require("./configuration.cjs");
|
|
|
39
40
|
const configSchema = require("./config-schema.cjs");
|
|
40
41
|
const { VALID_CONFIG_KEYS, DYNAMIC_KEY_PATTERNS, isCentralConfigKey: _isCentralConfigKeyFn } = configSchema;
|
|
41
42
|
const model_catalog_cjs_1 = require("./model-catalog.cjs");
|
|
43
|
+
// #3760: the ADR-1411 out-of-band diagnostic seam. loadConfig returns `.config`
|
|
44
|
+
// alone, so an in-band `skipped` record would be unreachable to nearly every
|
|
45
|
+
// caller — "a reason no caller reads is an unreachable field" (ADR-1411).
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
47
|
+
const unusableInputModule = require("./unusable-input.cjs");
|
|
48
|
+
const { UNUSABLE_REASON: _UNUSABLE_REASON, warnUnusableInput: _warnUnusableInput } = unusableInputModule;
|
|
42
49
|
// ─── Federated Config (ADR-857 phase 3b) ─────────────────────────────────────
|
|
43
50
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
44
51
|
const federatedConfigModule = require("./federated-config.cjs");
|
|
@@ -77,6 +84,7 @@ function _resetFederatedRegistryForTests() {
|
|
|
77
84
|
* - git.* → flat git keys (branching_strategy, templates)
|
|
78
85
|
* - workflow.* → flat names (research, verifier, …)
|
|
79
86
|
* - planning.sub_repos → sub_repos
|
|
87
|
+
* - planning.pr_strict → pr_strict
|
|
80
88
|
* - planning.commit_docs / search_gitignored → top-level flat keys
|
|
81
89
|
*/
|
|
82
90
|
// CANONICAL_CONFIG_DEFAULTS is typed as Record<string, unknown> from configuration.cjs;
|
|
@@ -91,6 +99,23 @@ function _getNestedConfigDefault(section, field) {
|
|
|
91
99
|
}
|
|
92
100
|
return undefined;
|
|
93
101
|
}
|
|
102
|
+
/** Shared flat-then-nested config lookup; exported for parity tests. */
|
|
103
|
+
function _getConfigValue(parsed, key, nested) {
|
|
104
|
+
if (parsed[key] !== undefined)
|
|
105
|
+
return parsed[key];
|
|
106
|
+
if (nested && parsed[nested.section] && typeof parsed[nested.section] === 'object' && parsed[nested.section] !== null) {
|
|
107
|
+
return parsed[nested.section][nested.field];
|
|
108
|
+
}
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
/** Shared nested-only config lookup; exported for parity tests. */
|
|
112
|
+
function _getConfigNested(parsed, section, field) {
|
|
113
|
+
const sec = parsed[section];
|
|
114
|
+
if (sec !== null && typeof sec === 'object' && !Array.isArray(sec)) {
|
|
115
|
+
return sec[field];
|
|
116
|
+
}
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
94
119
|
const CONFIG_DEFAULTS = {
|
|
95
120
|
model_profile: _getConfigDefault('model_profile'),
|
|
96
121
|
commit_docs: _getConfigDefault('commit_docs'),
|
|
@@ -111,6 +136,7 @@ const CONFIG_DEFAULTS = {
|
|
|
111
136
|
exa_search: _getConfigDefault('exa_search'),
|
|
112
137
|
text_mode: _getNestedConfigDefault('workflow', 'text_mode'),
|
|
113
138
|
sub_repos: _getNestedConfigDefault('planning', 'sub_repos'),
|
|
139
|
+
pr_strict: _getNestedConfigDefault('planning', 'pr_strict'),
|
|
114
140
|
resolve_model_ids: _getConfigDefault('resolve_model_ids'),
|
|
115
141
|
context_window: _getConfigDefault('context_window'),
|
|
116
142
|
phase_naming: _getConfigDefault('phase_naming'),
|
|
@@ -120,7 +146,10 @@ const CONFIG_DEFAULTS = {
|
|
|
120
146
|
security_asvs_level: _getNestedConfigDefault('workflow', 'security_asvs_level'),
|
|
121
147
|
security_block_on: _getNestedConfigDefault('workflow', 'security_block_on'),
|
|
122
148
|
post_planning_gaps: _getNestedConfigDefault('workflow', 'post_planning_gaps'),
|
|
149
|
+
research_before_questions: _getNestedConfigDefault('workflow', 'research_before_questions'), // #3894
|
|
123
150
|
smart_zone_tokens: _getNestedConfigDefault('workflow', 'smart_zone_tokens'),
|
|
151
|
+
inline_plan_threshold: _getNestedConfigDefault('workflow', 'inline_plan_threshold'), // #3801
|
|
152
|
+
max_prompt_tokens: _getNestedConfigDefault('review', 'max_prompt_tokens'),
|
|
124
153
|
};
|
|
125
154
|
/**
|
|
126
155
|
* Deep-merge two plain config objects. `overlay` wins on key conflict.
|
|
@@ -290,6 +319,59 @@ function _resetRuntimeWarningCacheForTests() {
|
|
|
290
319
|
_warnedConfigKeys.clear();
|
|
291
320
|
_warnedUnknownConfigKeys.clear();
|
|
292
321
|
_warnedUnusableConfig.clear();
|
|
322
|
+
_warnedShadowedGlobalKeys.clear();
|
|
323
|
+
}
|
|
324
|
+
// ─── #3532 (10b): shadowed global-defaults diagnostic ────────────────────────
|
|
325
|
+
// The keys Branch D's `_globalBaseCfg` demonstrably honors from
|
|
326
|
+
// ~/.gsd/defaults.json when no project config exists. Under a project
|
|
327
|
+
// .planning/config.json (Branch A — every real project) the global file is
|
|
328
|
+
// never opened, so each of these set globally is silently inert for resolution.
|
|
329
|
+
// `effort` is in Branch D's honored set but is EXCLUDED from the shadow warning:
|
|
330
|
+
// the install-time effort sync (readGsdEffectiveEffortConfig) DOES merge the
|
|
331
|
+
// global file, so warning on it would be false for the channel users actually
|
|
332
|
+
// control via `effort sync`. Keep this list in lockstep with `_globalBaseCfg`
|
|
333
|
+
// below — the per-key canary in tests/config-loader.test.cjs fails first on
|
|
334
|
+
// drift in either direction.
|
|
335
|
+
const GLOBAL_DEFAULTS_RESOLUTION_KEYS = [
|
|
336
|
+
'model_profile', 'commit_docs', 'research', 'plan_checker', 'verifier',
|
|
337
|
+
'nyquist_validation', 'post_planning_gaps', 'research_before_questions', 'parallelization', 'text_mode',
|
|
338
|
+
'resolve_model_ids', 'context_window', 'subagent_timeout', 'model_overrides',
|
|
339
|
+
'models', 'granularity', 'granularities', 'planning', 'dynamic_routing',
|
|
340
|
+
'effort', 'fast_mode', 'agent_skills', 'response_language', 'runtime',
|
|
341
|
+
'model_profile_overrides', 'model_policy',
|
|
342
|
+
];
|
|
343
|
+
// Module-level dedup keyed on the SORTED shadowed-key set: a later call with
|
|
344
|
+
// the same shadowed set stays quiet, while a config that grows a new shadowed
|
|
345
|
+
// key re-arms the warning. Stronger than _warnedUnknownConfigKeys (which keys
|
|
346
|
+
// on insertion order) — same discipline, order-independent key.
|
|
347
|
+
const _warnedShadowedGlobalKeys = new Set();
|
|
348
|
+
function _warnShadowedGlobalDefaults(globalDefaults, globalPath) {
|
|
349
|
+
const shadowed = GLOBAL_DEFAULTS_RESOLUTION_KEYS.filter(k => k !== 'effort' && Object.prototype.hasOwnProperty.call(globalDefaults, k));
|
|
350
|
+
// Branch D also honors the nested alias workflow.post_planning_gaps (the
|
|
351
|
+
// `?? globalDefaults['workflow']?.['post_planning_gaps']` fallback in
|
|
352
|
+
// _globalBaseCfg) — a global file using only the nested form is equally
|
|
353
|
+
// shadowed, so it reports under its dotted name.
|
|
354
|
+
// #3894: research_before_questions gets the same nested-alias reporting.
|
|
355
|
+
const nestedAliasKeys = ['post_planning_gaps', 'research_before_questions'];
|
|
356
|
+
const wf = globalDefaults['workflow'];
|
|
357
|
+
if (wf && typeof wf === 'object' && !Array.isArray(wf)) {
|
|
358
|
+
for (const k of nestedAliasKeys) {
|
|
359
|
+
if (!shadowed.includes(k) && Object.prototype.hasOwnProperty.call(wf, k)) {
|
|
360
|
+
shadowed.push(`workflow.${k}`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (shadowed.length === 0)
|
|
365
|
+
return;
|
|
366
|
+
const dedupKey = shadowed.slice().sort().join(',');
|
|
367
|
+
if (_warnedShadowedGlobalKeys.has(dedupKey))
|
|
368
|
+
return;
|
|
369
|
+
_warnedShadowedGlobalKeys.add(dedupKey);
|
|
370
|
+
try {
|
|
371
|
+
process.stderr.write(`gsd-tools: warning: ${globalPath} sets ${shadowed.join(', ')} but a project config ` +
|
|
372
|
+
`takes precedence here — those global keys are ignored for model resolution. (#3532)\n`);
|
|
373
|
+
}
|
|
374
|
+
catch { /* stderr might be closed in some test harnesses */ }
|
|
293
375
|
}
|
|
294
376
|
// ─── FIX 2: Federated overlay helpers ────────────────────────────────────────
|
|
295
377
|
/**
|
|
@@ -510,8 +592,18 @@ function _warnUnusableConfig(fault) {
|
|
|
510
592
|
* diagnostic names the file. Before this, a trailing comma in config.json was
|
|
511
593
|
* byte-identical to the file not existing: builtin defaults, degraded:false,
|
|
512
594
|
* and the user's entire configuration silently discarded.
|
|
595
|
+
*
|
|
596
|
+
* `options.persist: false` (#3648) suppresses the two normalize-then-write-back
|
|
597
|
+
* side effects below. Resolution is otherwise identical — same precedence, same
|
|
598
|
+
* returned object — the migrated shape simply stays in memory. Callers that only
|
|
599
|
+
* ASK the config something (a predicate, a status readout) pass it so that a read
|
|
600
|
+
* cannot dirty the working tree; the ~30 callers that omit it keep persisting, so
|
|
601
|
+
* a legacy config is still migrated exactly once by ordinary use.
|
|
513
602
|
*/
|
|
514
603
|
function loadConfigResolved(cwd, options = {}) {
|
|
604
|
+
// Opt-OUT, not opt-in: omitting the option must preserve the historical
|
|
605
|
+
// write-back for every existing caller.
|
|
606
|
+
const persist = options['persist'] !== false;
|
|
515
607
|
// NOTE: loadConfigResolved resolves from cwd AS-IS (no walk-up).
|
|
516
608
|
// Callers that need ancestor-anchoring (e.g. cmdAgentSkills) must do so
|
|
517
609
|
// themselves via findProjectRoot() before calling this function.
|
|
@@ -564,24 +656,37 @@ function loadConfigResolved(cwd, options = {}) {
|
|
|
564
656
|
if (rootRead.kind !== 'ok')
|
|
565
657
|
throw new Error('root config absent or unusable');
|
|
566
658
|
rootParsed = rootRead.data;
|
|
567
|
-
const { parsed: rootNormalized, normalizations: rootNorms } = (0, configuration_cjs_1.normalizeLegacyKeys)(rootParsed);
|
|
659
|
+
const { parsed: rootNormalized, normalizations: rootNorms, skipped: rootSkipped } = (0, configuration_cjs_1.normalizeLegacyKeys)(rootParsed);
|
|
660
|
+
if (rootSkipped.length > 0) {
|
|
661
|
+
_warnUnusableInput({ reason: _UNUSABLE_REASON.CONFIG_SECTION_NOT_OBJECT, source: rootConfigPath });
|
|
662
|
+
}
|
|
568
663
|
if (rootNorms.length > 0) {
|
|
569
664
|
for (const norm of rootNorms) {
|
|
570
665
|
if (norm.requiresFilesystem && !rootNormalized.planning?.['sub_repos']) {
|
|
571
666
|
const detected = getDetectedSubRepos();
|
|
572
667
|
if (detected.length > 0) {
|
|
573
|
-
if (!
|
|
668
|
+
// #3760: `if (!planning) planning = {}` treated a non-empty STRING as an
|
|
669
|
+
// already-present section, and the next line then assigned onto a
|
|
670
|
+
// primitive — a strict-mode TypeError the enclosing catch swallowed,
|
|
671
|
+
// discarding the user's whole config. `requiresFilesystem` now only
|
|
672
|
+
// reaches here when the section is absent or an object (configuration.cts
|
|
673
|
+
// block 3 refuses otherwise and reports it via `skipped`), so this
|
|
674
|
+
// narrowing chooses between merge and create and never discards.
|
|
675
|
+
if (!(0, configuration_cjs_1.isConfigSection)(rootNormalized.planning)) {
|
|
574
676
|
rootNormalized.planning = {};
|
|
677
|
+
}
|
|
575
678
|
rootNormalized.planning['sub_repos'] = detected;
|
|
576
679
|
rootNormalized.planning['commit_docs'] = false;
|
|
577
680
|
}
|
|
578
681
|
}
|
|
579
682
|
}
|
|
580
683
|
rootParsed = rootNormalized;
|
|
581
|
-
|
|
582
|
-
|
|
684
|
+
if (persist) {
|
|
685
|
+
try {
|
|
686
|
+
(0, shell_command_projection_cjs_1.platformWriteSync)(rootConfigPath, JSON.stringify(rootParsed, null, 2));
|
|
687
|
+
}
|
|
688
|
+
catch { /* ignore */ }
|
|
583
689
|
}
|
|
584
|
-
catch { /* ignore */ }
|
|
585
690
|
}
|
|
586
691
|
else {
|
|
587
692
|
rootParsed = rootNormalized;
|
|
@@ -608,7 +713,10 @@ function loadConfigResolved(cwd, options = {}) {
|
|
|
608
713
|
const fileHadKeys = Object.keys(read.data).length > 0;
|
|
609
714
|
let configDirty = false;
|
|
610
715
|
{
|
|
611
|
-
const { parsed: normalized, normalizations } = (0, configuration_cjs_1.normalizeLegacyKeys)(fileData);
|
|
716
|
+
const { parsed: normalized, normalizations, skipped } = (0, configuration_cjs_1.normalizeLegacyKeys)(fileData);
|
|
717
|
+
if (skipped.length > 0) {
|
|
718
|
+
_warnUnusableInput({ reason: _UNUSABLE_REASON.CONFIG_SECTION_NOT_OBJECT, source: configPath });
|
|
719
|
+
}
|
|
612
720
|
if (normalizations.length > 0) {
|
|
613
721
|
Object.keys(fileData).forEach(k => delete fileData[k]);
|
|
614
722
|
Object.assign(fileData, normalized);
|
|
@@ -617,7 +725,8 @@ function loadConfigResolved(cwd, options = {}) {
|
|
|
617
725
|
if (norm.requiresFilesystem && !fileData.planning?.['sub_repos']) {
|
|
618
726
|
const detected = getDetectedSubRepos();
|
|
619
727
|
if (detected.length > 0) {
|
|
620
|
-
|
|
728
|
+
// #3760 — see the identical guard on the root-config path above.
|
|
729
|
+
if (!(0, configuration_cjs_1.isConfigSection)(fileData.planning))
|
|
621
730
|
fileData.planning = {};
|
|
622
731
|
fileData.planning['sub_repos'] = detected;
|
|
623
732
|
fileData.planning['commit_docs'] = false;
|
|
@@ -632,14 +741,17 @@ function loadConfigResolved(cwd, options = {}) {
|
|
|
632
741
|
if (detected.length > 0) {
|
|
633
742
|
const sorted = [...currentSubRepos].sort();
|
|
634
743
|
if (JSON.stringify(sorted) !== JSON.stringify(detected)) {
|
|
635
|
-
|
|
744
|
+
// #3760 — reachable only when `planning` already yielded a non-empty
|
|
745
|
+
// sub_repos array, so it is an object here; the narrowing keeps the
|
|
746
|
+
// assignment total rather than relying on that from three frames away.
|
|
747
|
+
if (!(0, configuration_cjs_1.isConfigSection)(fileData.planning))
|
|
636
748
|
fileData.planning = {};
|
|
637
749
|
fileData.planning['sub_repos'] = detected;
|
|
638
750
|
configDirty = true;
|
|
639
751
|
}
|
|
640
752
|
}
|
|
641
753
|
}
|
|
642
|
-
if (configDirty) {
|
|
754
|
+
if (configDirty && persist) {
|
|
643
755
|
try {
|
|
644
756
|
(0, shell_command_projection_cjs_1.platformWriteSync)(configPath, JSON.stringify(fileData, null, 2));
|
|
645
757
|
}
|
|
@@ -684,17 +796,19 @@ function loadConfigResolved(cwd, options = {}) {
|
|
|
684
796
|
}
|
|
685
797
|
}
|
|
686
798
|
_warnUnknownProfileOverrides(parsed, '.planning/config.json');
|
|
687
|
-
const get = (key, nested) =>
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
799
|
+
const get = (key, nested) => _getConfigValue(parsed, key, nested);
|
|
800
|
+
/**
|
|
801
|
+
* Nested-ONLY read — no top-level fallback (#3648).
|
|
802
|
+
*
|
|
803
|
+
* `get()`'s flat-then-nested order exists for keys that have a legacy flat
|
|
804
|
+
* spelling `normalizeLegacyKeys` migrates (`branching_strategy`,
|
|
805
|
+
* `base_branch`, …); for those, honouring the flat key is back-compat. A key
|
|
806
|
+
* introduced with no legacy form has nothing to be compatible WITH, so
|
|
807
|
+
* routing it through `get()` would invent an undocumented top-level alias
|
|
808
|
+
* that silently outranks the canonical nested key. Use this instead for new
|
|
809
|
+
* `<section>.<field>` keys (round-4 external review).
|
|
810
|
+
*/
|
|
811
|
+
const getNested = (section, field) => _getConfigNested(parsed, section, field);
|
|
698
812
|
const parallelization = (() => {
|
|
699
813
|
const val = get('parallelization');
|
|
700
814
|
if (typeof val === 'boolean')
|
|
@@ -715,6 +829,8 @@ function loadConfigResolved(cwd, options = {}) {
|
|
|
715
829
|
})(),
|
|
716
830
|
search_gitignored: get('search_gitignored', { section: 'planning', field: 'search_gitignored' }) ?? defaults.search_gitignored,
|
|
717
831
|
branching_strategy: get('branching_strategy', { section: 'git', field: 'branching_strategy' }) ?? defaults.branching_strategy,
|
|
832
|
+
base_branch: get('base_branch', { section: 'git', field: 'base_branch' }),
|
|
833
|
+
protected_branches: getNested('git', 'protected_branches'),
|
|
718
834
|
phase_branch_template: get('phase_branch_template', { section: 'git', field: 'phase_branch_template' }) ?? defaults.phase_branch_template,
|
|
719
835
|
milestone_branch_template: get('milestone_branch_template', { section: 'git', field: 'milestone_branch_template' }) ?? defaults.milestone_branch_template,
|
|
720
836
|
quick_branch_template: get('quick_branch_template', { section: 'git', field: 'quick_branch_template' }) ?? defaults.quick_branch_template,
|
|
@@ -733,6 +849,7 @@ function loadConfigResolved(cwd, options = {}) {
|
|
|
733
849
|
_auto_chain_active: get('_auto_chain_active', { section: 'workflow', field: '_auto_chain_active' }) ?? false,
|
|
734
850
|
mode: get('mode') ?? 'interactive',
|
|
735
851
|
sub_repos: get('sub_repos', { section: 'planning', field: 'sub_repos' }) ?? defaults.sub_repos,
|
|
852
|
+
pr_strict: get('pr_strict', { section: 'planning', field: 'pr_strict' }) ?? defaults.pr_strict,
|
|
736
853
|
resolve_model_ids: get('resolve_model_ids') ?? defaults.resolve_model_ids,
|
|
737
854
|
context_window: get('context_window') ?? defaults.context_window,
|
|
738
855
|
phase_naming: get('phase_naming') ?? defaults.phase_naming,
|
|
@@ -751,11 +868,25 @@ function loadConfigResolved(cwd, options = {}) {
|
|
|
751
868
|
fast_mode: (parsed['fast_mode']) || null,
|
|
752
869
|
agent_skills: (parsed['agent_skills']) || {},
|
|
753
870
|
agent_skills_security: (parsed['agent_skills_security']) || null,
|
|
871
|
+
// #3587: phase_commit_docs.<phase-id> — a dynamic-key family shaped like
|
|
872
|
+
// agent_skills above (`{ "<phase-id>": boolean }`). Must be threaded here
|
|
873
|
+
// explicitly: `_baseConfig` is a hand-maintained allowlist, so a key that
|
|
874
|
+
// is only in config-schema.manifest.json's dynamicKeyPatterns (and not
|
|
875
|
+
// projected here) is silently dropped on read — the exact `features`-key
|
|
876
|
+
// failure mode this module's own A3 test guards against.
|
|
877
|
+
phase_commit_docs: (parsed['phase_commit_docs']) || {},
|
|
754
878
|
manager: (parsed['manager']) || {},
|
|
755
879
|
response_language: get('response_language') || null,
|
|
756
880
|
claude_md_path: get('claude_md_path') || null,
|
|
757
881
|
claude_md_assembly: (parsed['claude_md_assembly']) || null,
|
|
758
882
|
phase_id_convention: get('phase_id_convention') ?? null,
|
|
883
|
+
// #3691: the documented central review key. Declared here (not federated —
|
|
884
|
+
// it is central, see config-schema.manifest.json validKeys) so the existing
|
|
885
|
+
// `review.*` per-lane keys the federated overlay below adds land as SIBLINGS
|
|
886
|
+
// on this same object rather than being clobbered by it.
|
|
887
|
+
review: {
|
|
888
|
+
max_prompt_tokens: get('max_prompt_tokens', { section: 'review', field: 'max_prompt_tokens' }) ?? defaults.max_prompt_tokens,
|
|
889
|
+
},
|
|
759
890
|
};
|
|
760
891
|
// ADR-857 phase 3b: federated config overlay
|
|
761
892
|
try {
|
|
@@ -777,6 +908,22 @@ function loadConfigResolved(cwd, options = {}) {
|
|
|
777
908
|
// A1 vs A2: disambiguate by whether a real workstream was requested.
|
|
778
909
|
// Fix 4: empty-string ws ('') resolves the root path → source:'root'.
|
|
779
910
|
const source = wsRequested ? 'workstream' : 'root';
|
|
911
|
+
// #3532 (10b): a parsed project config means Branch D never runs, so every
|
|
912
|
+
// key ~/.gsd/defaults.json sets that Branch D would honor is silently inert
|
|
913
|
+
// here. Observation only — one deduped stderr warning; precedence is
|
|
914
|
+
// untouched. Faults in the global file stay silent in this branch (the
|
|
915
|
+
// project config governs; the nearer file is the actionable one).
|
|
916
|
+
try {
|
|
917
|
+
const shadowHome = process.env['GSD_HOME'] || node_os_1.default.homedir();
|
|
918
|
+
const shadowPath = node_path_1.default.join(shadowHome, '.gsd', 'defaults.json');
|
|
919
|
+
const shadowRead = _readConfigFile(shadowPath);
|
|
920
|
+
if (shadowRead.kind === 'ok') {
|
|
921
|
+
_warnShadowedGlobalDefaults(shadowRead.data, shadowPath);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
catch {
|
|
925
|
+
// Observation only — never let the diagnostic perturb resolution.
|
|
926
|
+
}
|
|
780
927
|
// This config parsed — but a DIFFERENT file on the resolution path may not
|
|
781
928
|
// have. A workstream config that loads cleanly while the root config it
|
|
782
929
|
// inherits from is corrupt is still a degraded resolution: the root's
|
|
@@ -800,8 +947,15 @@ function loadConfigResolved(cwd, options = {}) {
|
|
|
800
947
|
// Fix 2: Early intercept — workstream requested but ws config.json absent (or dir absent)
|
|
801
948
|
// AND root config was loaded. Covers BOTH "dir exists, no config.json" AND "dir absent".
|
|
802
949
|
// This delivers the #1366 acceptance criterion: nonexistent GSD_WORKSTREAM yields root, degraded.
|
|
950
|
+
//
|
|
951
|
+
// Both fallback recursions below forward `options` and override ONLY `workstream`.
|
|
952
|
+
// A bare `{ workstream: null }` silently dropped every other option, so a caller's
|
|
953
|
+
// `persist: false` was discarded on exactly this path and the root config was
|
|
954
|
+
// rewritten by a read (#3648, found by external review). The explicit
|
|
955
|
+
// `workstream: null` still wins the `hasOwnProperty` check at the top of this
|
|
956
|
+
// function, so spreading cannot let `workstreamContext` reintroduce a workstream.
|
|
803
957
|
if (wsRequested && rootParsed) {
|
|
804
|
-
const fb = loadConfigResolved(cwd, { workstream: null });
|
|
958
|
+
const fb = loadConfigResolved(cwd, { ...options, workstream: null });
|
|
805
959
|
return fallback({ config: fb.config, source: 'root', degraded: true });
|
|
806
960
|
}
|
|
807
961
|
// Branch B, C, D, E
|
|
@@ -809,7 +963,7 @@ function loadConfigResolved(cwd, options = {}) {
|
|
|
809
963
|
if (rootParsed) {
|
|
810
964
|
// Branch B: workstream requested but ws config.json absent; root config present.
|
|
811
965
|
// (Only reached when wsRequested is false — e.g. ws='' with .planning/workstreams//config.json)
|
|
812
|
-
const fb = loadConfigResolved(cwd, { workstream: null });
|
|
966
|
+
const fb = loadConfigResolved(cwd, { ...options, workstream: null });
|
|
813
967
|
return fallback({ config: fb.config, source: 'root', degraded: true });
|
|
814
968
|
}
|
|
815
969
|
// Branch C: .planning/ exists but no config.json and no root config — federated/builtin defaults
|
|
@@ -847,6 +1001,12 @@ function loadConfigResolved(cwd, options = {}) {
|
|
|
847
1001
|
post_planning_gaps: (globalDefaults['post_planning_gaps'])
|
|
848
1002
|
?? globalDefaults['workflow']?.['post_planning_gaps']
|
|
849
1003
|
?? defaults.post_planning_gaps,
|
|
1004
|
+
// #3894: same nested-alias shape as post_planning_gaps above — the key
|
|
1005
|
+
// was silently dropped from global defaults, so it was unavailable at
|
|
1006
|
+
// user scope AND inert at project scope on the /gsd-quick path.
|
|
1007
|
+
research_before_questions: (globalDefaults['research_before_questions'])
|
|
1008
|
+
?? globalDefaults['workflow']?.['research_before_questions']
|
|
1009
|
+
?? defaults.research_before_questions,
|
|
850
1010
|
parallelization: (globalDefaults['parallelization']) ?? defaults.parallelization,
|
|
851
1011
|
text_mode: (globalDefaults['text_mode']) ?? defaults.text_mode,
|
|
852
1012
|
resolve_model_ids: (globalDefaults['resolve_model_ids']) ?? defaults.resolve_model_ids,
|
|
@@ -906,8 +1066,12 @@ module.exports = {
|
|
|
906
1066
|
CONFIG_DEFAULTS,
|
|
907
1067
|
_getConfigDefault,
|
|
908
1068
|
_getNestedConfigDefault,
|
|
1069
|
+
_getConfigValue,
|
|
1070
|
+
_getConfigNested,
|
|
909
1071
|
_deepMergeConfig,
|
|
910
1072
|
_warnedUnknownConfigKeys,
|
|
1073
|
+
_warnedShadowedGlobalKeys,
|
|
1074
|
+
GLOBAL_DEFAULTS_RESOLUTION_KEYS,
|
|
911
1075
|
_warnUnknownProfileOverrides,
|
|
912
1076
|
_resetRuntimeWarningCacheForTests,
|
|
913
1077
|
_warnedConfigKeys,
|
|
@@ -16,6 +16,9 @@ const node_os_1 = __importDefault(require("node:os"));
|
|
|
16
16
|
const io = require("./io.cjs");
|
|
17
17
|
const { output, error, ERROR_REASON } = io;
|
|
18
18
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
19
|
+
const cliExitMod = require("./cli-exit.cjs");
|
|
20
|
+
const { ExitError } = cliExitMod;
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
19
22
|
const configLoader = require("./config-loader.cjs");
|
|
20
23
|
const { CONFIG_DEFAULTS } = configLoader;
|
|
21
24
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
@@ -31,6 +34,13 @@ const { VALID_CONFIG_KEYS, isValidConfigKey, getCapabilityConfigSchema } = confi
|
|
|
31
34
|
const secrets_cjs_1 = require("./secrets.cjs");
|
|
32
35
|
const review_reviewer_selection_cjs_1 = require("./review-reviewer-selection.cjs");
|
|
33
36
|
const configuration_cjs_1 = require("./configuration.cjs");
|
|
37
|
+
// #3760: the ADR-1411 out-of-band diagnostic. It lives here rather than inside
|
|
38
|
+
// `migrateOnDisk` because `configuration.cjs` must stay loadable from an install
|
|
39
|
+
// layout holding only itself plus its manifests (#3571) — see the note at the top
|
|
40
|
+
// of configuration.cts.
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
42
|
+
const unusableInputModule = require("./unusable-input.cjs");
|
|
43
|
+
const { UNUSABLE_REASON, warnUnusableInput } = unusableInputModule;
|
|
34
44
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
35
45
|
const CONFIG_KEY_SUGGESTIONS = {
|
|
36
46
|
'workflow.nyquist_validation_enabled': 'workflow.nyquist_validation',
|
|
@@ -68,9 +78,21 @@ const SCHEMA_DEFAULTS = {
|
|
|
68
78
|
'planner.stall_detect_interval_minutes': 5,
|
|
69
79
|
'planner.stall_threshold_minutes': 10,
|
|
70
80
|
'git.create_tag': true,
|
|
81
|
+
// #1689: per-plan agent_hint executor routing — default-on. A no-op for plans
|
|
82
|
+
// without an agent_hint field, so existing dispatch is byte-identical.
|
|
83
|
+
'workflow.agent_hint_routing': true,
|
|
71
84
|
// Derived from the defaults manifest rather than restated, so the manifest
|
|
72
85
|
// stays the single source of truth for the smart-zone budget (#2630).
|
|
73
86
|
'workflow.smart_zone_tokens': CONFIG_DEFAULTS.smart_zone_tokens,
|
|
87
|
+
// #2971: /gsd:pr-branch reads this key directly; an absent key must resolve to the
|
|
88
|
+
// manifest default rather than "Key not found". Derived from the defaults manifest so
|
|
89
|
+
// the manifest stays the single source of truth.
|
|
90
|
+
'planning.pr_strict': CONFIG_DEFAULTS.pr_strict,
|
|
91
|
+
// #3801: execute-plan reads this key on every run; an absent key must resolve
|
|
92
|
+
// to the manifest default (2) rather than "Key not Found" — previously the
|
|
93
|
+
// effective default existed only as the workflow's shell fallback and the
|
|
94
|
+
// docs disagreed (settings-advanced said 3). Manifest stays the one owner.
|
|
95
|
+
'workflow.inline_plan_threshold': CONFIG_DEFAULTS.inline_plan_threshold,
|
|
74
96
|
};
|
|
75
97
|
/**
|
|
76
98
|
* Resolve a schema-level default for an absent key (#2256). Checks the legacy
|
|
@@ -118,6 +140,35 @@ function validateKnownConfigKeyPath(keyPath) {
|
|
|
118
140
|
error(`Unknown config key: ${keyPath}. Did you mean ${suggested}?`, ERROR_REASON.CONFIG_INVALID_KEY);
|
|
119
141
|
}
|
|
120
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* Is `value` an acceptable `git.protected_branches` list (#3552)?
|
|
145
|
+
*
|
|
146
|
+
* A non-empty array whose every element is a string with non-whitespace
|
|
147
|
+
* content. Exported so a property test can pin this predicate against the
|
|
148
|
+
* resolver's own per-entry filter in `git-base-branch.cts` — `config-set` must
|
|
149
|
+
* only accept lists the resolver will honour in full, with nothing rejected.
|
|
150
|
+
* The two are deliberately different shapes (all-or-nothing here, per-entry
|
|
151
|
+
* there, because a direct file edit bypasses this check), so nothing keeps them
|
|
152
|
+
* agreeing except a test that asks both.
|
|
153
|
+
*/
|
|
154
|
+
function isValidProtectedBranches(value) {
|
|
155
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
156
|
+
return false;
|
|
157
|
+
const entries = value;
|
|
158
|
+
// Index, do NOT use `.every()`. `.every()` SKIPS holes, so a sparse array
|
|
159
|
+
// (`["main", , "develop"]`) passed this check while the resolver's `for...of`
|
|
160
|
+
// — which yields `undefined` for a hole — rejected that element. The two
|
|
161
|
+
// surfaces then disagreed about the same value. JSON cannot express a hole,
|
|
162
|
+
// so neither surface meets one in production, but "unreachable" is not a
|
|
163
|
+
// reason to leave two definitions of the same predicate contradicting each
|
|
164
|
+
// other (round-4 external review).
|
|
165
|
+
for (let i = 0; i < entries.length; i += 1) {
|
|
166
|
+
const branch = entries[i];
|
|
167
|
+
if (typeof branch !== 'string' || branch.trim().length === 0)
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
121
172
|
function validateShipPrBodySections(value) {
|
|
122
173
|
if (!Array.isArray(value)) {
|
|
123
174
|
error('Invalid ship.pr_body_sections value. Expected a JSON array of section objects.');
|
|
@@ -652,7 +703,7 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
|
|
|
652
703
|
const val = value;
|
|
653
704
|
validateKnownConfigKeyPath(kp);
|
|
654
705
|
if (!isValidConfigKey(kp, cwd)) {
|
|
655
|
-
error(`Unknown config key: "${kp}". Valid keys: ${[...VALID_CONFIG_KEYS].sort().join(', ')}, agent_skills.<agent-type>, features.<feature_name>`, ERROR_REASON.CONFIG_INVALID_KEY);
|
|
706
|
+
error(`Unknown config key: "${kp}". Valid keys: ${[...VALID_CONFIG_KEYS].sort().join(', ')}, agent_skills.<agent-type>, features.<feature_name>, phase_commit_docs.<phase-id>`, ERROR_REASON.CONFIG_INVALID_KEY);
|
|
656
707
|
}
|
|
657
708
|
// Parse value (handle booleans, numbers, and JSON arrays/objects)
|
|
658
709
|
let parsedValue = val;
|
|
@@ -736,12 +787,23 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
|
|
|
736
787
|
error(`Invalid workflow.post_planning_gaps '${val}'. Must be a boolean (true or false).`);
|
|
737
788
|
}
|
|
738
789
|
}
|
|
790
|
+
// Per-plan executor routing via agent_hint frontmatter (#1689)
|
|
791
|
+
if (kp === 'workflow.agent_hint_routing') {
|
|
792
|
+
if (typeof parsedValue !== 'boolean') {
|
|
793
|
+
error(`Invalid workflow.agent_hint_routing '${val}'. Must be a boolean (true or false).`);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
739
796
|
// #3086 — git.create_tag: boolean only
|
|
740
797
|
if (kp === 'git.create_tag') {
|
|
741
798
|
if (typeof parsedValue !== 'boolean') {
|
|
742
799
|
error(`Invalid git.create_tag '${val}'. Must be a boolean (true or false).`);
|
|
743
800
|
}
|
|
744
801
|
}
|
|
802
|
+
if (kp === 'git.protected_branches') {
|
|
803
|
+
if (!isValidProtectedBranches(parsedValue)) {
|
|
804
|
+
error(`Invalid git.protected_branches '${val}'. Must be a non-empty array of non-empty branch names.`);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
745
807
|
if (kp === 'ship.pr_body_sections') {
|
|
746
808
|
validateShipPrBodySections(parsedValue);
|
|
747
809
|
}
|
|
@@ -917,6 +979,16 @@ function cmdConfigGet(cwd, keyPath, raw, defaultValue) {
|
|
|
917
979
|
}
|
|
918
980
|
}
|
|
919
981
|
catch (err) {
|
|
982
|
+
// ADR-3889: error() now throws ExitError (carries no message) instead of
|
|
983
|
+
// calling process.exit() directly. The message-sniffing check below
|
|
984
|
+
// (`.startsWith('No config.json')`) can never match an ExitError raised
|
|
985
|
+
// by the "no config.json" error() call above it — ExitError.message
|
|
986
|
+
// defaults to `process exit ${code}` when no message is passed — so
|
|
987
|
+
// without this unconditional guard that ExitError falls through and gets
|
|
988
|
+
// re-wrapped as a WRONG reason (CONFIG_PARSE_FAILED instead of
|
|
989
|
+
// CONFIG_NO_FILE) with a nonsense message, plus a duplicate stderr write.
|
|
990
|
+
if (err instanceof ExitError)
|
|
991
|
+
throw err;
|
|
920
992
|
if (err.message.startsWith('No config.json'))
|
|
921
993
|
throw err;
|
|
922
994
|
error('Failed to read config.json: ' + err.message, ERROR_REASON.CONFIG_PARSE_FAILED);
|
|
@@ -1111,16 +1183,42 @@ function cmdConfigPath(cwd, _raw, workstreamContext = null) {
|
|
|
1111
1183
|
*/
|
|
1112
1184
|
function cmdMigrateConfig(cwd, raw) {
|
|
1113
1185
|
const ws = process.env['GSD_WORKSTREAM'] || null;
|
|
1114
|
-
|
|
1186
|
+
// #3749: resolve the migration target through the project-aware resolver so
|
|
1187
|
+
// GSD_PROJECT scopes the write; migrateOnDisk itself cannot (see its
|
|
1188
|
+
// configPathOverride note).
|
|
1189
|
+
const scopedConfigPath = node_path_1.default.join(planningDir(cwd, ws || undefined), 'config.json');
|
|
1190
|
+
const report = (0, configuration_cjs_1.migrateOnDisk)(cwd, ws || undefined, scopedConfigPath);
|
|
1191
|
+
// #3760: deduplicated on (path, reason), so a repeated invocation stays quiet.
|
|
1192
|
+
if (report.skipped.length > 0) {
|
|
1193
|
+
warnUnusableInput({
|
|
1194
|
+
reason: UNUSABLE_REASON.CONFIG_SECTION_NOT_OBJECT,
|
|
1195
|
+
source: node_path_1.default.join(planningDir(cwd, ws || undefined), 'config.json'),
|
|
1196
|
+
});
|
|
1197
|
+
}
|
|
1115
1198
|
if (raw) {
|
|
1116
|
-
|
|
1199
|
+
// #3760: a refused migration is NOT an already-canonical config. Reporting
|
|
1200
|
+
// "no legacy keys found" when a legacy key was found and declined would send
|
|
1201
|
+
// the user away believing there is nothing to fix — and the thing to fix is
|
|
1202
|
+
// the one thing only they can fix, by hand.
|
|
1203
|
+
const declined = report.skipped;
|
|
1204
|
+
const declinedLines = declined.map(s => ` ${s.from} → ${s.to} SKIPPED: '${s.section}' holds a ${s.sectionType}, not an object`);
|
|
1205
|
+
if (!report.migrated && declined.length === 0) {
|
|
1117
1206
|
const msg = 'No legacy keys found — config is already canonical.';
|
|
1118
1207
|
output(msg, true, msg);
|
|
1119
1208
|
}
|
|
1209
|
+
else if (!report.migrated) {
|
|
1210
|
+
const lines = [
|
|
1211
|
+
'Not migrated — every legacy key found was left in place:',
|
|
1212
|
+
...declinedLines,
|
|
1213
|
+
'Fix the section by hand, then re-run. Nothing was written.',
|
|
1214
|
+
].join('\n');
|
|
1215
|
+
output(lines, true, lines);
|
|
1216
|
+
}
|
|
1120
1217
|
else {
|
|
1121
1218
|
const lines = [
|
|
1122
1219
|
`Migrated: ${String(report.wrote)}`,
|
|
1123
1220
|
...report.normalizations.map(n => ` ${n.from} → ${n.to}`),
|
|
1221
|
+
...declinedLines,
|
|
1124
1222
|
].join('\n');
|
|
1125
1223
|
output(lines, true, lines);
|
|
1126
1224
|
}
|
|
@@ -1142,4 +1240,5 @@ module.exports = {
|
|
|
1142
1240
|
// Exported for programmatic use by capability-writer and tests
|
|
1143
1241
|
setConfigValue,
|
|
1144
1242
|
setConfigValues,
|
|
1243
|
+
isValidProtectedBranches,
|
|
1145
1244
|
};
|