@opengsd/gsd-core 1.11.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.opencode/plugins/gsd-core.js +12 -0
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-debug-session-manager.md +1 -1
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-dom-verifier.md +169 -0
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +78 -42
- package/agents/gsd-framework-selector.md +1 -3
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.md +0 -1
- package/agents/gsd-pattern-mapper.md +11 -0
- package/agents/gsd-phase-researcher.md +3 -1
- package/agents/gsd-plan-checker.md +91 -112
- package/agents/gsd-planner.md +20 -4
- 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 +82 -7
- package/agents/gsd-ui-researcher.md +70 -3
- package/agents/gsd-verifier.md +24 -2
- package/bin/install.js +847 -200
- 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/ns-workflow.md +2 -1
- package/commands/gsd/phase.md +1 -1
- package/commands/gsd/quick-batch.md +105 -0
- package/commands/gsd/quick.md +8 -4
- package/commands/gsd/surface.md +18 -8
- package/gsd-core/bin/gsd-tools.cjs +761 -100
- package/gsd-core/bin/lib/active-workstream-store.cjs +8 -0
- package/gsd-core/bin/lib/adr-parser.cjs +13 -7
- package/gsd-core/bin/lib/agent-install-check.cjs +162 -0
- package/gsd-core/bin/lib/api-coverage.cjs +30 -9
- package/gsd-core/bin/lib/artifacts.cjs +2 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +30 -11
- package/gsd-core/bin/lib/audit.cjs +163 -41
- package/gsd-core/bin/lib/broken-windows.cjs +306 -28
- package/gsd-core/bin/lib/capability-activation.cjs +27 -0
- package/gsd-core/bin/lib/capability-lock.cjs +10 -4
- package/gsd-core/bin/lib/capability-registry.cjs +785 -144
- package/gsd-core/bin/lib/capability-state.cjs +25 -4
- package/gsd-core/bin/lib/capability-validator.cjs +321 -18
- package/gsd-core/bin/lib/capability-writer.cjs +14 -4
- package/gsd-core/bin/lib/check-command-router.cjs +229 -6
- package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
- package/gsd-core/bin/lib/cli-exit.cjs +496 -10
- package/gsd-core/bin/lib/clusters.cjs +1 -0
- package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
- package/gsd-core/bin/lib/codex-agent-toml.cjs +410 -4
- package/gsd-core/bin/lib/command-aliases.cjs +16 -0
- package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
- package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
- package/gsd-core/bin/lib/commands.cjs +877 -54
- package/gsd-core/bin/lib/complexity-trigger.cjs +26 -6
- package/gsd-core/bin/lib/config-loader.cjs +121 -29
- package/gsd-core/bin/lib/config.cjs +92 -2
- package/gsd-core/bin/lib/configuration.cjs +129 -37
- package/gsd-core/bin/lib/core-utils.cjs +118 -14
- package/gsd-core/bin/lib/decisions.cjs +213 -1
- package/gsd-core/bin/lib/edge-probe.cjs +23 -2
- 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/file-overlap-partitioner.cjs +74 -0
- package/gsd-core/bin/lib/frontmatter.cjs +975 -326
- package/gsd-core/bin/lib/gap-checker.cjs +41 -8
- package/gsd-core/bin/lib/git-base-branch.cjs +182 -39
- package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +7 -3
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +60 -14
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +75 -22
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +22 -8
- package/gsd-core/bin/lib/health-diagnostic.cjs +23 -3
- package/gsd-core/bin/lib/host-integration.cjs +96 -11
- package/gsd-core/bin/lib/init-command-router.cjs +132 -21
- package/gsd-core/bin/lib/init.cjs +252 -56
- package/gsd-core/bin/lib/install-engine.cjs +252 -15
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +78 -1
- package/gsd-core/bin/lib/install-profiles.cjs +100 -18
- package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
- package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +10 -7
- package/gsd-core/bin/lib/intel.cjs +101 -26
- package/gsd-core/bin/lib/io.cjs +195 -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/loop-resolver.cjs +14 -8
- package/gsd-core/bin/lib/markdown-table.cjs +175 -4
- package/gsd-core/bin/lib/milestone.cjs +112 -7
- package/gsd-core/bin/lib/model-catalog.cjs +177 -19
- package/gsd-core/bin/lib/model-resolver.cjs +10 -28
- package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
- package/gsd-core/bin/lib/phase-command-router.cjs +13 -6
- package/gsd-core/bin/lib/phase-estimation.cjs +17 -8
- package/gsd-core/bin/lib/phase-id.cjs +321 -13
- package/gsd-core/bin/lib/phase-lifecycle.cjs +24 -16
- package/gsd-core/bin/lib/phase-locator.cjs +138 -17
- package/gsd-core/bin/lib/phase.cjs +1175 -115
- package/gsd-core/bin/lib/plan-document.cjs +273 -0
- package/gsd-core/bin/lib/plan-scan.cjs +13 -2
- 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-snapshot.cjs +165 -34
- package/gsd-core/bin/lib/planning-workspace.cjs +159 -28
- package/gsd-core/bin/lib/probe-core.cjs +4 -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/quick-batch-command-router.cjs +285 -0
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
- package/gsd-core/bin/lib/quick-batch.cjs +840 -0
- package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +71 -45
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +62 -14
- package/gsd-core/bin/lib/review-lane-invocation.cjs +73 -1
- package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
- package/gsd-core/bin/lib/roadmap-command-router.cjs +45 -31
- package/gsd-core/bin/lib/roadmap-parser.cjs +577 -41
- package/gsd-core/bin/lib/roadmap.cjs +248 -64
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +329 -41
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +16 -17
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +320 -109
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +487 -83
- package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
- package/gsd-core/bin/lib/runtime-slash.cjs +72 -2
- package/gsd-core/bin/lib/shell-command-projection.cjs +75 -8
- package/gsd-core/bin/lib/smart-entry.cjs +19 -31
- package/gsd-core/bin/lib/spec-section.cjs +12 -7
- package/gsd-core/bin/lib/state-command-router.cjs +47 -18
- package/gsd-core/bin/lib/state-contract.cjs +359 -0
- package/gsd-core/bin/lib/state-document.cjs +216 -5
- package/gsd-core/bin/lib/state-md-schema.cjs +231 -0
- package/gsd-core/bin/lib/state-transition.cjs +850 -145
- package/gsd-core/bin/lib/state.cjs +1629 -287
- package/gsd-core/bin/lib/surface.cjs +33 -10
- 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/tdd-red-evidence.cjs +133 -0
- package/gsd-core/bin/lib/teams-status.cjs +4 -1
- package/gsd-core/bin/lib/uat-predicate.cjs +58 -20
- package/gsd-core/bin/lib/uat.cjs +2542 -387
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
- package/gsd-core/bin/lib/ui-safety-gate.cjs +37 -7
- package/gsd-core/bin/lib/unusable-input.cjs +13 -0
- package/gsd-core/bin/lib/update-context.cjs +6 -2
- package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
- package/gsd-core/bin/lib/validate.cjs +230 -12
- package/gsd-core/bin/lib/vendor/README.md +43 -5
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +287 -13
- package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
- package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
- package/gsd-core/bin/lib/verify.cjs +441 -56
- package/gsd-core/bin/lib/workstream-inventory.cjs +20 -2
- package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
- package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
- package/gsd-core/bin/lib/worktree-safety.cjs +185 -21
- package/gsd-core/bin/shared/config-defaults.manifest.json +7 -1
- package/gsd-core/bin/shared/config-schema.manifest.json +13 -0
- 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/bin/verify-reapply-patches.cjs +70 -3
- package/gsd-core/references/agent-contracts.md +6 -5
- 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 +37 -19
- package/gsd-core/references/decimal-phase-calculation.md +5 -5
- package/gsd-core/references/edge-probe.md +17 -5
- package/gsd-core/references/execute-mvp-tdd.md +18 -18
- package/gsd-core/references/execute-phase-between-wave-reset.md +9 -12
- package/gsd-core/references/execute-phase-response-language.md +6 -0
- package/gsd-core/references/execute-phase-wave-guard.md +11 -9
- package/gsd-core/references/executor-examples.md +42 -0
- package/gsd-core/references/failing-direction.md +78 -0
- package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
- 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 +3 -3
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +22 -0
- package/gsd-core/references/model-profiles.md +1 -1
- package/gsd-core/references/mvp-concepts.md +2 -2
- 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/plan-checker-examples.md +41 -0
- package/gsd-core/references/planner-antipatterns.md +25 -0
- package/gsd-core/references/planner-chunked.md +5 -1
- package/gsd-core/references/planner-coupling.md +42 -0
- package/gsd-core/references/planner-failing-direction.md +53 -0
- package/gsd-core/references/planner-human-verify-mode.md +15 -1
- package/gsd-core/references/planner-quick-batch.md +71 -0
- package/gsd-core/references/planner-reviews.md +47 -0
- package/gsd-core/references/planner-revision.md +76 -3
- package/gsd-core/references/planner-verify-command-grounding.md +17 -0
- package/gsd-core/references/planning-config.md +39 -9
- package/gsd-core/references/response-language-directive.md +9 -0
- package/gsd-core/references/reviewer-instances.md +31 -0
- package/gsd-core/references/revision-loop.md +118 -11
- package/gsd-core/references/runtime-aware-dispatch.md +1 -1
- package/gsd-core/references/tdd.md +15 -12
- 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 +2 -2
- package/gsd-core/references/verifier-evidence-gate.md +160 -0
- package/gsd-core/references/verify-command-path-resolvability.md +42 -0
- package/gsd-core/references/verify-mvp-mode.md +1 -1
- package/gsd-core/references/workstream-flag.md +11 -11
- 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/phase-prompt.md +7 -0
- package/gsd-core/templates/state.md +7 -0
- package/gsd-core/templates/verification-report.md +5 -0
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +3 -1
- package/gsd-core/workflows/add-phase.md +5 -3
- package/gsd-core/workflows/add-tests.md +4 -9
- package/gsd-core/workflows/add-todo.md +2 -2
- package/gsd-core/workflows/ai-integration-phase.md +5 -10
- package/gsd-core/workflows/analyze-dependencies.md +2 -0
- package/gsd-core/workflows/audit-fix.md +14 -3
- package/gsd-core/workflows/audit-milestone.md +11 -9
- package/gsd-core/workflows/audit-uat.md +19 -2
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
- package/gsd-core/workflows/autonomous.md +12 -26
- package/gsd-core/workflows/check-todos.md +2 -2
- package/gsd-core/workflows/cleanup.md +3 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +16 -14
- package/gsd-core/workflows/code-review-fix.md +3 -1
- package/gsd-core/workflows/code-review.md +192 -69
- package/gsd-core/workflows/complete-milestone.md +28 -14
- package/gsd-core/workflows/debug.md +6 -4
- package/gsd-core/workflows/diagnose-issues.md +17 -7
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +3 -1
- package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -7
- package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/text.md +3 -1
- package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
- package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
- package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
- package/gsd-core/workflows/discuss-phase-assumptions.md +3 -3
- package/gsd-core/workflows/discuss-phase-power.md +2 -0
- package/gsd-core/workflows/discuss-phase.md +2 -2
- package/gsd-core/workflows/do.md +46 -19
- package/gsd-core/workflows/docs-update.md +6 -5
- package/gsd-core/workflows/edit-phase.md +3 -1
- package/gsd-core/workflows/eval-review.md +5 -10
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +3 -1
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +129 -11
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +29 -5
- 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 +4 -2
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
- package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
- package/gsd-core/workflows/execute-phase.md +68 -66
- package/gsd-core/workflows/execute-plan.md +25 -20
- package/gsd-core/workflows/explore.md +3 -1
- package/gsd-core/workflows/extract-learnings.md +3 -1
- package/gsd-core/workflows/fast.md +8 -2
- package/gsd-core/workflows/forensics.md +3 -1
- package/gsd-core/workflows/graduation.md +6 -6
- package/gsd-core/workflows/health.md +4 -7
- package/gsd-core/workflows/help/modes/brief.md +2 -0
- package/gsd-core/workflows/help/modes/default.md +2 -0
- package/gsd-core/workflows/help/modes/full.md +12 -0
- package/gsd-core/workflows/help/modes/topic.md +2 -0
- package/gsd-core/workflows/help.md +2 -0
- package/gsd-core/workflows/import.md +17 -14
- package/gsd-core/workflows/inbox.md +5 -6
- package/gsd-core/workflows/ingest-docs.md +45 -12
- package/gsd-core/workflows/insert-phase.md +7 -5
- package/gsd-core/workflows/list-phase-assumptions.md +2 -0
- package/gsd-core/workflows/list-seeds.md +7 -3
- package/gsd-core/workflows/list-workspaces.md +3 -1
- package/gsd-core/workflows/manager.md +15 -26
- package/gsd-core/workflows/map-codebase.md +3 -1
- package/gsd-core/workflows/milestone-summary.md +3 -1
- package/gsd-core/workflows/mvp-phase.md +3 -3
- package/gsd-core/workflows/new-milestone.md +10 -22
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
- package/gsd-core/workflows/new-project.md +17 -29
- package/gsd-core/workflows/new-workspace.md +2 -2
- package/gsd-core/workflows/next.md +4 -2
- package/gsd-core/workflows/node-repair.md +2 -0
- package/gsd-core/workflows/note.md +2 -0
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +20 -5
- 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 +100 -18
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +4 -4
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +12 -3
- package/gsd-core/workflows/plan-phase.md +251 -54
- package/gsd-core/workflows/plan-review-convergence.md +148 -19
- package/gsd-core/workflows/plant-seed.md +3 -3
- package/gsd-core/workflows/pr-branch.md +195 -51
- package/gsd-core/workflows/profile-user.md +17 -15
- package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
- package/gsd-core/workflows/progress.md +52 -15
- package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +38 -5
- package/gsd-core/workflows/quick/steps/quick-verification.md +2 -4
- package/gsd-core/workflows/quick/steps/research-phase.md +5 -7
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
- package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
- package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
- package/gsd-core/workflows/quick-batch.md +203 -0
- package/gsd-core/workflows/quick.md +33 -32
- package/gsd-core/workflows/reapply-patches.md +2 -0
- package/gsd-core/workflows/remove-phase.md +6 -4
- package/gsd-core/workflows/remove-workspace.md +3 -3
- package/gsd-core/workflows/resume-project.md +14 -14
- package/gsd-core/workflows/review.md +404 -21
- package/gsd-core/workflows/scan.md +3 -1
- package/gsd-core/workflows/section-manifest.json +12 -0
- package/gsd-core/workflows/secure-phase.md +3 -3
- package/gsd-core/workflows/session-report.md +2 -0
- package/gsd-core/workflows/settings-advanced.md +9 -9
- package/gsd-core/workflows/settings-integrations.md +66 -32
- package/gsd-core/workflows/settings.md +4 -6
- package/gsd-core/workflows/ship.md +22 -16
- package/gsd-core/workflows/sketch-wrap-up.md +13 -17
- package/gsd-core/workflows/sketch.md +13 -19
- package/gsd-core/workflows/smart-entry.md +4 -6
- package/gsd-core/workflows/spec-phase.md +31 -4
- package/gsd-core/workflows/spike-wrap-up.md +9 -11
- package/gsd-core/workflows/spike.md +21 -32
- package/gsd-core/workflows/stats.md +4 -2
- package/gsd-core/workflows/sync-skills.md +13 -5
- package/gsd-core/workflows/thread.md +13 -7
- package/gsd-core/workflows/transition.md +7 -5
- package/gsd-core/workflows/ui-phase.md +36 -21
- package/gsd-core/workflows/ui-review.md +7 -11
- package/gsd-core/workflows/ultraplan-phase.md +7 -13
- package/gsd-core/workflows/undo.md +9 -17
- package/gsd-core/workflows/update.md +47 -48
- 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 +106 -21
- package/hooks/dist/gsd-agent-isolation-guard.js +77 -38
- package/hooks/dist/gsd-check-update-worker.js +19 -2
- package/hooks/dist/gsd-config-reload.js +18 -12
- package/hooks/dist/gsd-context-monitor.js +302 -22
- package/hooks/dist/gsd-cursor-post-tool.js +3 -1
- package/hooks/dist/gsd-cursor-pre-tool.js +3 -1
- 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 +28 -23
- package/hooks/dist/gsd-cursor-subagent-stop.js +3 -1
- 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 +77 -0
- package/hooks/dist/gsd-phase-boundary.sh +1 -0
- package/hooks/dist/gsd-prompt-guard.js +46 -12
- package/hooks/dist/gsd-read-guard.js +18 -7
- package/hooks/dist/gsd-read-injection-scanner.js +22 -13
- package/hooks/dist/gsd-secret-read-guard.js +1079 -0
- package/hooks/dist/gsd-session-state.sh +1 -0
- package/hooks/dist/gsd-statusline.js +222 -29
- package/hooks/dist/gsd-validate-commit.sh +523 -12
- 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 +36 -17
- 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 +210 -1
- 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 +36 -6
- package/hooks/dist/managed-hooks-registry.cjs +4 -0
- package/hooks/gsd-agent-isolation-guard.js +77 -38
- package/hooks/gsd-check-update-worker.js +19 -2
- package/hooks/gsd-config-reload.js +18 -12
- package/hooks/gsd-context-monitor.js +302 -22
- package/hooks/gsd-cursor-post-tool.js +3 -1
- package/hooks/gsd-cursor-pre-tool.js +3 -1
- package/hooks/gsd-cursor-session-start.js +2 -1
- package/hooks/gsd-cursor-stop.js +2 -1
- package/hooks/gsd-cursor-subagent-start.js +28 -23
- package/hooks/gsd-cursor-subagent-stop.js +3 -1
- package/hooks/gsd-ensure-canonical-path.js +2 -1
- package/hooks/gsd-graphify-update.sh +22 -18
- package/hooks/gsd-node-runner.sh +77 -0
- package/hooks/gsd-phase-boundary.sh +1 -0
- package/hooks/gsd-prompt-guard.js +46 -12
- package/hooks/gsd-read-guard.js +18 -7
- package/hooks/gsd-read-injection-scanner.js +22 -13
- package/hooks/gsd-secret-read-guard.js +1079 -0
- package/hooks/gsd-session-state.sh +1 -0
- package/hooks/gsd-statusline.js +222 -29
- package/hooks/gsd-validate-commit.sh +523 -12
- package/hooks/gsd-windsurf-pre-command.js +16 -11
- package/hooks/gsd-windsurf-pre-write.js +22 -13
- package/hooks/gsd-workflow-guard.js +36 -17
- package/hooks/gsd-worktree-path-guard.js +36 -21
- package/hooks/gsd-write-guard.js +35 -25
- package/hooks/hooks.json +6 -0
- package/hooks/lib/cli-exit.js +560 -0
- package/hooks/lib/exit-code-registry.js +98 -0
- package/hooks/lib/git-cmd.js +210 -1
- package/hooks/lib/git-probe.js +84 -0
- package/hooks/lib/hook-exit.js +81 -0
- package/hooks/lib/injection-patterns.js +36 -6
- package/hooks/managed-hooks-registry.cjs +4 -0
- package/package.json +14 -9
- package/scripts/base64-scan.sh +74 -12
- package/scripts/build-hooks.js +12 -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 +52 -12
- package/scripts/ci-timeout-report.cjs +230 -0
- package/scripts/docs-guard-registry.cjs +406 -0
- package/scripts/gen-capability-registry.cjs +8 -6
- 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-hooks-cli-exit.cjs +239 -0
- package/scripts/gen-install-tree-fixtures.cjs +2 -2
- package/scripts/gen-loop-host-contract.cjs +189 -4
- 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/ci-job-timing.cjs +72 -0
- package/scripts/lib/cli-exit.cjs +546 -44
- package/scripts/lib/drift-scan.cjs +32 -2
- package/scripts/lib/exit-code-registry.cjs +98 -0
- package/scripts/lib/ndjson-reporter.cjs +119 -0
- package/scripts/lib/shellcheck-fetch.cjs +247 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
- 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-docs-guard-registration.cjs +495 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +198 -0
- package/scripts/lint-eslint-glob-coverage.allowlist.json +4 -0
- package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
- package/scripts/lint-health-diagnostic-rule-table.cjs +65 -8
- package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
- package/scripts/lint-phase-enumeration-drift.cjs +45 -14
- package/scripts/lint-phase-id-drift.cjs +133 -8
- package/scripts/lint-planning-prompt-drift.cjs +38 -1
- package/scripts/lint-portable-grep.cjs +176 -0
- package/scripts/lint-removed-but-needed.cjs +184 -16
- package/scripts/lint-response-language-coverage.cjs +524 -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-write-path-drift.cjs +337 -432
- package/scripts/lint-test-file-count.allowlist.json +124 -4
- package/scripts/lint-test-file-count.cjs +25 -3
- package/scripts/lint-unreachable-guard-drift.cjs +51 -64
- package/scripts/lint-vendored-deps.cjs +208 -35
- package/scripts/lint-workflow-shellcheck-baseline.json +1027 -0
- package/scripts/lint-workflow-shellcheck.cjs +614 -0
- package/scripts/mutation-matrix.cjs +599 -50
- package/scripts/npm-audit-baseline.cjs +376 -0
- package/scripts/prompt-injection-scan.sh +83 -14
- package/scripts/require-issue-link-policy.cjs +16 -1
- package/scripts/secret-scan.sh +75 -13
- package/scripts/select-docs-guards.cjs +56 -0
- package/scripts/sync-runtime-launcher.cjs +22 -3
- package/skills/gsd-discuss-phase/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -1
- package/skills/gsd-import/SKILL.md +1 -1
- package/skills/gsd-ns-workflow/SKILL.md +1 -0
- package/skills/gsd-phase/SKILL.md +1 -1
- package/skills/gsd-quick/SKILL.md +8 -4
- package/skills/gsd-quick-batch/SKILL.md +105 -0
- package/skills/gsd-surface/SKILL.md +18 -8
- package/vscode/package.json +1 -1
- package/bin/lib/ui-safety-gate.cjs +0 -109
- package/scripts/lint-emitted-drift-ack.cjs +0 -344
- package/scripts/state-write-path-drift-baseline.json +0 -19
|
@@ -956,6 +956,29 @@ function reanchorBaseline(prev, key, score, opts = {}) {
|
|
|
956
956
|
// ─── Proposal render/parse ──────────────────────────────────────────────────
|
|
957
957
|
const PROPOSAL_JSON_FENCE_OPEN = '````json';
|
|
958
958
|
const PROPOSAL_JSON_FENCE_CLOSE = '````';
|
|
959
|
+
// Reader-side fence tolerance (#3657 — same defect class as the WINDOWS.md
|
|
960
|
+
// ledger): CommonMark formatters (Prettier et al.) narrow the written
|
|
961
|
+
// 4-backtick fence to the shortest legal width (3) whenever the body holds no
|
|
962
|
+
// backtick run, and a canonical-JSON candidates array never does. Locate the
|
|
963
|
+
// block by a line-anchored 3+ fence and close on a run at least as wide
|
|
964
|
+
// (CommonMark: a shorter run does not close). The writer above is unchanged.
|
|
965
|
+
// This module is a leaf (CONTEXT.md — imports only node:fs/node:path), so the
|
|
966
|
+
// span logic is local rather than imported from broken-windows.
|
|
967
|
+
const PROPOSAL_FENCE_OPEN_RE = /^(`{3,})json[ \t]*\r?$/m;
|
|
968
|
+
function locateProposalJsonBlock(text) {
|
|
969
|
+
const open = text.match(PROPOSAL_FENCE_OPEN_RE);
|
|
970
|
+
if (!open || open.index === undefined)
|
|
971
|
+
return null;
|
|
972
|
+
const width = open[1].length;
|
|
973
|
+
const bodyStart = open.index + open[0].length;
|
|
974
|
+
for (const close of text.slice(bodyStart).matchAll(/^(`{3,})[ \t]*\r?$/gm)) {
|
|
975
|
+
if (close[1].length < width)
|
|
976
|
+
continue;
|
|
977
|
+
const bodyEnd = close.index ?? 0;
|
|
978
|
+
return { jsonText: text.slice(bodyStart, bodyStart + bodyEnd).trim() };
|
|
979
|
+
}
|
|
980
|
+
return null;
|
|
981
|
+
}
|
|
959
982
|
function renderProposal(p) {
|
|
960
983
|
const fm = [
|
|
961
984
|
'---',
|
|
@@ -1012,13 +1035,10 @@ function parseProposal(text) {
|
|
|
1012
1035
|
return null;
|
|
1013
1036
|
fm[m[1]] = m[2].trim();
|
|
1014
1037
|
}
|
|
1015
|
-
const
|
|
1016
|
-
if (
|
|
1017
|
-
return null;
|
|
1018
|
-
const jsonEnd = text.indexOf(PROPOSAL_JSON_FENCE_CLOSE, jsonStart + PROPOSAL_JSON_FENCE_OPEN.length);
|
|
1019
|
-
if (jsonEnd === -1)
|
|
1038
|
+
const span = locateProposalJsonBlock(text);
|
|
1039
|
+
if (span === null)
|
|
1020
1040
|
return null;
|
|
1021
|
-
const jsonText =
|
|
1041
|
+
const jsonText = span.jsonText;
|
|
1022
1042
|
let candidates;
|
|
1023
1043
|
try {
|
|
1024
1044
|
candidates = JSON.parse(jsonText);
|
|
@@ -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.
|
|
@@ -305,7 +334,7 @@ function _resetRuntimeWarningCacheForTests() {
|
|
|
305
334
|
// drift in either direction.
|
|
306
335
|
const GLOBAL_DEFAULTS_RESOLUTION_KEYS = [
|
|
307
336
|
'model_profile', 'commit_docs', 'research', 'plan_checker', 'verifier',
|
|
308
|
-
'nyquist_validation', 'post_planning_gaps', 'parallelization', 'text_mode',
|
|
337
|
+
'nyquist_validation', 'post_planning_gaps', 'research_before_questions', 'parallelization', 'text_mode',
|
|
309
338
|
'resolve_model_ids', 'context_window', 'subagent_timeout', 'model_overrides',
|
|
310
339
|
'models', 'granularity', 'granularities', 'planning', 'dynamic_routing',
|
|
311
340
|
'effort', 'fast_mode', 'agent_skills', 'response_language', 'runtime',
|
|
@@ -322,11 +351,14 @@ function _warnShadowedGlobalDefaults(globalDefaults, globalPath) {
|
|
|
322
351
|
// `?? globalDefaults['workflow']?.['post_planning_gaps']` fallback in
|
|
323
352
|
// _globalBaseCfg) — a global file using only the nested form is equally
|
|
324
353
|
// shadowed, so it reports under its dotted name.
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
+
}
|
|
330
362
|
}
|
|
331
363
|
}
|
|
332
364
|
if (shadowed.length === 0)
|
|
@@ -560,8 +592,18 @@ function _warnUnusableConfig(fault) {
|
|
|
560
592
|
* diagnostic names the file. Before this, a trailing comma in config.json was
|
|
561
593
|
* byte-identical to the file not existing: builtin defaults, degraded:false,
|
|
562
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.
|
|
563
602
|
*/
|
|
564
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;
|
|
565
607
|
// NOTE: loadConfigResolved resolves from cwd AS-IS (no walk-up).
|
|
566
608
|
// Callers that need ancestor-anchoring (e.g. cmdAgentSkills) must do so
|
|
567
609
|
// themselves via findProjectRoot() before calling this function.
|
|
@@ -614,24 +656,37 @@ function loadConfigResolved(cwd, options = {}) {
|
|
|
614
656
|
if (rootRead.kind !== 'ok')
|
|
615
657
|
throw new Error('root config absent or unusable');
|
|
616
658
|
rootParsed = rootRead.data;
|
|
617
|
-
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
|
+
}
|
|
618
663
|
if (rootNorms.length > 0) {
|
|
619
664
|
for (const norm of rootNorms) {
|
|
620
665
|
if (norm.requiresFilesystem && !rootNormalized.planning?.['sub_repos']) {
|
|
621
666
|
const detected = getDetectedSubRepos();
|
|
622
667
|
if (detected.length > 0) {
|
|
623
|
-
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)) {
|
|
624
676
|
rootNormalized.planning = {};
|
|
677
|
+
}
|
|
625
678
|
rootNormalized.planning['sub_repos'] = detected;
|
|
626
679
|
rootNormalized.planning['commit_docs'] = false;
|
|
627
680
|
}
|
|
628
681
|
}
|
|
629
682
|
}
|
|
630
683
|
rootParsed = rootNormalized;
|
|
631
|
-
|
|
632
|
-
|
|
684
|
+
if (persist) {
|
|
685
|
+
try {
|
|
686
|
+
(0, shell_command_projection_cjs_1.platformWriteSync)(rootConfigPath, JSON.stringify(rootParsed, null, 2));
|
|
687
|
+
}
|
|
688
|
+
catch { /* ignore */ }
|
|
633
689
|
}
|
|
634
|
-
catch { /* ignore */ }
|
|
635
690
|
}
|
|
636
691
|
else {
|
|
637
692
|
rootParsed = rootNormalized;
|
|
@@ -658,7 +713,10 @@ function loadConfigResolved(cwd, options = {}) {
|
|
|
658
713
|
const fileHadKeys = Object.keys(read.data).length > 0;
|
|
659
714
|
let configDirty = false;
|
|
660
715
|
{
|
|
661
|
-
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
|
+
}
|
|
662
720
|
if (normalizations.length > 0) {
|
|
663
721
|
Object.keys(fileData).forEach(k => delete fileData[k]);
|
|
664
722
|
Object.assign(fileData, normalized);
|
|
@@ -667,7 +725,8 @@ function loadConfigResolved(cwd, options = {}) {
|
|
|
667
725
|
if (norm.requiresFilesystem && !fileData.planning?.['sub_repos']) {
|
|
668
726
|
const detected = getDetectedSubRepos();
|
|
669
727
|
if (detected.length > 0) {
|
|
670
|
-
|
|
728
|
+
// #3760 — see the identical guard on the root-config path above.
|
|
729
|
+
if (!(0, configuration_cjs_1.isConfigSection)(fileData.planning))
|
|
671
730
|
fileData.planning = {};
|
|
672
731
|
fileData.planning['sub_repos'] = detected;
|
|
673
732
|
fileData.planning['commit_docs'] = false;
|
|
@@ -682,14 +741,17 @@ function loadConfigResolved(cwd, options = {}) {
|
|
|
682
741
|
if (detected.length > 0) {
|
|
683
742
|
const sorted = [...currentSubRepos].sort();
|
|
684
743
|
if (JSON.stringify(sorted) !== JSON.stringify(detected)) {
|
|
685
|
-
|
|
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))
|
|
686
748
|
fileData.planning = {};
|
|
687
749
|
fileData.planning['sub_repos'] = detected;
|
|
688
750
|
configDirty = true;
|
|
689
751
|
}
|
|
690
752
|
}
|
|
691
753
|
}
|
|
692
|
-
if (configDirty) {
|
|
754
|
+
if (configDirty && persist) {
|
|
693
755
|
try {
|
|
694
756
|
(0, shell_command_projection_cjs_1.platformWriteSync)(configPath, JSON.stringify(fileData, null, 2));
|
|
695
757
|
}
|
|
@@ -734,17 +796,19 @@ function loadConfigResolved(cwd, options = {}) {
|
|
|
734
796
|
}
|
|
735
797
|
}
|
|
736
798
|
_warnUnknownProfileOverrides(parsed, '.planning/config.json');
|
|
737
|
-
const get = (key, nested) =>
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
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);
|
|
748
812
|
const parallelization = (() => {
|
|
749
813
|
const val = get('parallelization');
|
|
750
814
|
if (typeof val === 'boolean')
|
|
@@ -765,6 +829,9 @@ function loadConfigResolved(cwd, options = {}) {
|
|
|
765
829
|
})(),
|
|
766
830
|
search_gitignored: get('search_gitignored', { section: 'planning', field: 'search_gitignored' }) ?? defaults.search_gitignored,
|
|
767
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'),
|
|
834
|
+
allow_default_branch_commits: getNested('git', 'allow_default_branch_commits'),
|
|
768
835
|
phase_branch_template: get('phase_branch_template', { section: 'git', field: 'phase_branch_template' }) ?? defaults.phase_branch_template,
|
|
769
836
|
milestone_branch_template: get('milestone_branch_template', { section: 'git', field: 'milestone_branch_template' }) ?? defaults.milestone_branch_template,
|
|
770
837
|
quick_branch_template: get('quick_branch_template', { section: 'git', field: 'quick_branch_template' }) ?? defaults.quick_branch_template,
|
|
@@ -778,17 +845,20 @@ function loadConfigResolved(cwd, options = {}) {
|
|
|
778
845
|
firecrawl: get('firecrawl') ?? defaults.firecrawl,
|
|
779
846
|
exa_search: get('exa_search') ?? defaults.exa_search,
|
|
780
847
|
mvp_mode: get('mvp_mode', { section: 'workflow', field: 'mvp_mode' }) ?? false,
|
|
848
|
+
tdd_mode: getNested('workflow', 'tdd_mode') ?? false,
|
|
781
849
|
text_mode: get('text_mode', { section: 'workflow', field: 'text_mode' }) ?? defaults.text_mode,
|
|
782
850
|
auto_advance: get('auto_advance', { section: 'workflow', field: 'auto_advance' }) ?? false,
|
|
783
851
|
_auto_chain_active: get('_auto_chain_active', { section: 'workflow', field: '_auto_chain_active' }) ?? false,
|
|
784
852
|
mode: get('mode') ?? 'interactive',
|
|
785
853
|
sub_repos: get('sub_repos', { section: 'planning', field: 'sub_repos' }) ?? defaults.sub_repos,
|
|
854
|
+
pr_strict: get('pr_strict', { section: 'planning', field: 'pr_strict' }) ?? defaults.pr_strict,
|
|
786
855
|
resolve_model_ids: get('resolve_model_ids') ?? defaults.resolve_model_ids,
|
|
787
856
|
context_window: get('context_window') ?? defaults.context_window,
|
|
788
857
|
phase_naming: get('phase_naming') ?? defaults.phase_naming,
|
|
789
858
|
project_code: get('project_code') ?? defaults.project_code,
|
|
790
859
|
subagent_timeout: get('subagent_timeout', { section: 'workflow', field: 'subagent_timeout' }) ?? defaults.subagent_timeout,
|
|
791
860
|
model_overrides: (parsed['model_overrides']) || null,
|
|
861
|
+
agent_tools: (parsed['agent_tools']) || null,
|
|
792
862
|
models: (parsed['models']) || null,
|
|
793
863
|
granularity: parsed['granularity'] !== undefined ? parsed['granularity'] : null,
|
|
794
864
|
granularities: (parsed['granularities']) || null,
|
|
@@ -813,6 +883,13 @@ function loadConfigResolved(cwd, options = {}) {
|
|
|
813
883
|
claude_md_path: get('claude_md_path') || null,
|
|
814
884
|
claude_md_assembly: (parsed['claude_md_assembly']) || null,
|
|
815
885
|
phase_id_convention: get('phase_id_convention') ?? null,
|
|
886
|
+
// #3691: the documented central review key. Declared here (not federated —
|
|
887
|
+
// it is central, see config-schema.manifest.json validKeys) so the existing
|
|
888
|
+
// `review.*` per-lane keys the federated overlay below adds land as SIBLINGS
|
|
889
|
+
// on this same object rather than being clobbered by it.
|
|
890
|
+
review: {
|
|
891
|
+
max_prompt_tokens: get('max_prompt_tokens', { section: 'review', field: 'max_prompt_tokens' }) ?? defaults.max_prompt_tokens,
|
|
892
|
+
},
|
|
816
893
|
};
|
|
817
894
|
// ADR-857 phase 3b: federated config overlay
|
|
818
895
|
try {
|
|
@@ -873,8 +950,15 @@ function loadConfigResolved(cwd, options = {}) {
|
|
|
873
950
|
// Fix 2: Early intercept — workstream requested but ws config.json absent (or dir absent)
|
|
874
951
|
// AND root config was loaded. Covers BOTH "dir exists, no config.json" AND "dir absent".
|
|
875
952
|
// This delivers the #1366 acceptance criterion: nonexistent GSD_WORKSTREAM yields root, degraded.
|
|
953
|
+
//
|
|
954
|
+
// Both fallback recursions below forward `options` and override ONLY `workstream`.
|
|
955
|
+
// A bare `{ workstream: null }` silently dropped every other option, so a caller's
|
|
956
|
+
// `persist: false` was discarded on exactly this path and the root config was
|
|
957
|
+
// rewritten by a read (#3648, found by external review). The explicit
|
|
958
|
+
// `workstream: null` still wins the `hasOwnProperty` check at the top of this
|
|
959
|
+
// function, so spreading cannot let `workstreamContext` reintroduce a workstream.
|
|
876
960
|
if (wsRequested && rootParsed) {
|
|
877
|
-
const fb = loadConfigResolved(cwd, { workstream: null });
|
|
961
|
+
const fb = loadConfigResolved(cwd, { ...options, workstream: null });
|
|
878
962
|
return fallback({ config: fb.config, source: 'root', degraded: true });
|
|
879
963
|
}
|
|
880
964
|
// Branch B, C, D, E
|
|
@@ -882,7 +966,7 @@ function loadConfigResolved(cwd, options = {}) {
|
|
|
882
966
|
if (rootParsed) {
|
|
883
967
|
// Branch B: workstream requested but ws config.json absent; root config present.
|
|
884
968
|
// (Only reached when wsRequested is false — e.g. ws='' with .planning/workstreams//config.json)
|
|
885
|
-
const fb = loadConfigResolved(cwd, { workstream: null });
|
|
969
|
+
const fb = loadConfigResolved(cwd, { ...options, workstream: null });
|
|
886
970
|
return fallback({ config: fb.config, source: 'root', degraded: true });
|
|
887
971
|
}
|
|
888
972
|
// Branch C: .planning/ exists but no config.json and no root config — federated/builtin defaults
|
|
@@ -920,6 +1004,12 @@ function loadConfigResolved(cwd, options = {}) {
|
|
|
920
1004
|
post_planning_gaps: (globalDefaults['post_planning_gaps'])
|
|
921
1005
|
?? globalDefaults['workflow']?.['post_planning_gaps']
|
|
922
1006
|
?? defaults.post_planning_gaps,
|
|
1007
|
+
// #3894: same nested-alias shape as post_planning_gaps above — the key
|
|
1008
|
+
// was silently dropped from global defaults, so it was unavailable at
|
|
1009
|
+
// user scope AND inert at project scope on the /gsd-quick path.
|
|
1010
|
+
research_before_questions: (globalDefaults['research_before_questions'])
|
|
1011
|
+
?? globalDefaults['workflow']?.['research_before_questions']
|
|
1012
|
+
?? defaults.research_before_questions,
|
|
923
1013
|
parallelization: (globalDefaults['parallelization']) ?? defaults.parallelization,
|
|
924
1014
|
text_mode: (globalDefaults['text_mode']) ?? defaults.text_mode,
|
|
925
1015
|
resolve_model_ids: (globalDefaults['resolve_model_ids']) ?? defaults.resolve_model_ids,
|
|
@@ -979,6 +1069,8 @@ module.exports = {
|
|
|
979
1069
|
CONFIG_DEFAULTS,
|
|
980
1070
|
_getConfigDefault,
|
|
981
1071
|
_getNestedConfigDefault,
|
|
1072
|
+
_getConfigValue,
|
|
1073
|
+
_getConfigNested,
|
|
982
1074
|
_deepMergeConfig,
|
|
983
1075
|
_warnedUnknownConfigKeys,
|
|
984
1076
|
_warnedShadowedGlobalKeys,
|
|
@@ -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',
|
|
@@ -74,6 +84,15 @@ const SCHEMA_DEFAULTS = {
|
|
|
74
84
|
// Derived from the defaults manifest rather than restated, so the manifest
|
|
75
85
|
// stays the single source of truth for the smart-zone budget (#2630).
|
|
76
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,
|
|
77
96
|
};
|
|
78
97
|
/**
|
|
79
98
|
* Resolve a schema-level default for an absent key (#2256). Checks the legacy
|
|
@@ -121,6 +140,35 @@ function validateKnownConfigKeyPath(keyPath) {
|
|
|
121
140
|
error(`Unknown config key: ${keyPath}. Did you mean ${suggested}?`, ERROR_REASON.CONFIG_INVALID_KEY);
|
|
122
141
|
}
|
|
123
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
|
+
}
|
|
124
172
|
function validateShipPrBodySections(value) {
|
|
125
173
|
if (!Array.isArray(value)) {
|
|
126
174
|
error('Invalid ship.pr_body_sections value. Expected a JSON array of section objects.');
|
|
@@ -751,6 +799,11 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
|
|
|
751
799
|
error(`Invalid git.create_tag '${val}'. Must be a boolean (true or false).`);
|
|
752
800
|
}
|
|
753
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
|
+
}
|
|
754
807
|
if (kp === 'ship.pr_body_sections') {
|
|
755
808
|
validateShipPrBodySections(parsedValue);
|
|
756
809
|
}
|
|
@@ -926,6 +979,16 @@ function cmdConfigGet(cwd, keyPath, raw, defaultValue) {
|
|
|
926
979
|
}
|
|
927
980
|
}
|
|
928
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;
|
|
929
992
|
if (err.message.startsWith('No config.json'))
|
|
930
993
|
throw err;
|
|
931
994
|
error('Failed to read config.json: ' + err.message, ERROR_REASON.CONFIG_PARSE_FAILED);
|
|
@@ -1120,16 +1183,42 @@ function cmdConfigPath(cwd, _raw, workstreamContext = null) {
|
|
|
1120
1183
|
*/
|
|
1121
1184
|
function cmdMigrateConfig(cwd, raw) {
|
|
1122
1185
|
const ws = process.env['GSD_WORKSTREAM'] || null;
|
|
1123
|
-
|
|
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
|
+
}
|
|
1124
1198
|
if (raw) {
|
|
1125
|
-
|
|
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) {
|
|
1126
1206
|
const msg = 'No legacy keys found — config is already canonical.';
|
|
1127
1207
|
output(msg, true, msg);
|
|
1128
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
|
+
}
|
|
1129
1217
|
else {
|
|
1130
1218
|
const lines = [
|
|
1131
1219
|
`Migrated: ${String(report.wrote)}`,
|
|
1132
1220
|
...report.normalizations.map(n => ` ${n.from} → ${n.to}`),
|
|
1221
|
+
...declinedLines,
|
|
1133
1222
|
].join('\n');
|
|
1134
1223
|
output(lines, true, lines);
|
|
1135
1224
|
}
|
|
@@ -1151,4 +1240,5 @@ module.exports = {
|
|
|
1151
1240
|
// Exported for programmatic use by capability-writer and tests
|
|
1152
1241
|
setConfigValue,
|
|
1153
1242
|
setConfigValues,
|
|
1243
|
+
isValidProtectedBranches,
|
|
1154
1244
|
};
|