@opengsd/gsd-core 1.12.0 → 1.14.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-advisor-researcher.compact.md +85 -0
- package/agents/gsd-ai-researcher.compact.md +96 -0
- package/agents/gsd-assumptions-analyzer.compact.md +81 -0
- package/agents/gsd-code-fixer.compact.md +458 -0
- package/agents/gsd-code-fixer.md +5 -5
- package/agents/gsd-code-reviewer.compact.md +269 -0
- package/agents/gsd-code-reviewer.md +15 -3
- package/agents/gsd-codebase-mapper.compact.md +760 -0
- package/agents/gsd-debug-session-manager.compact.md +345 -0
- package/agents/gsd-doc-classifier.compact.md +192 -0
- package/agents/gsd-doc-synthesizer.compact.md +200 -0
- package/agents/gsd-doc-verifier.compact.md +143 -0
- package/agents/gsd-doc-writer.compact.md +440 -0
- package/agents/gsd-dom-verifier.compact.md +138 -0
- package/agents/gsd-domain-researcher.compact.md +141 -0
- package/agents/gsd-eval-auditor.compact.md +160 -0
- package/agents/gsd-eval-planner.compact.md +137 -0
- package/agents/gsd-executor.md +63 -35
- package/agents/gsd-framework-selector.compact.md +82 -0
- package/agents/gsd-integration-checker.compact.md +245 -0
- package/agents/gsd-intel-updater.compact.md +226 -0
- package/agents/gsd-mempalace-curator.compact.md +45 -0
- package/agents/gsd-nyquist-auditor.compact.md +179 -0
- package/agents/gsd-pattern-mapper.compact.md +275 -0
- package/agents/gsd-plan-checker.md +76 -57
- package/agents/gsd-planner.md +14 -0
- package/agents/gsd-project-researcher.compact.md +587 -0
- package/agents/gsd-research-synthesizer.compact.md +212 -0
- package/agents/gsd-roadmapper.compact.md +454 -0
- package/agents/gsd-roadmapper.md +13 -0
- package/agents/gsd-security-auditor.compact.md +162 -0
- package/agents/gsd-ui-auditor.compact.md +404 -0
- package/agents/gsd-ui-checker.compact.md +277 -0
- package/agents/gsd-ui-checker.md +19 -3
- package/agents/gsd-ui-researcher.compact.md +282 -0
- package/agents/gsd-ui-researcher.md +29 -0
- package/agents/gsd-user-profiler.compact.md +108 -0
- package/agents/gsd-verifier.md +23 -1
- package/bin/install.js +444 -134
- package/commands/gsd/cleanup.md +1 -0
- package/commands/gsd/code-review.md +2 -1
- package/commands/gsd/complete-milestone.md +1 -0
- package/commands/gsd/config.md +1 -0
- package/commands/gsd/debug.md +1 -0
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/graphify.md +1 -0
- package/commands/gsd/health.md +1 -0
- package/commands/gsd/mempalace-capture.md +1 -0
- package/commands/gsd/mempalace-recall.md +1 -0
- package/commands/gsd/new-milestone.md +1 -0
- package/commands/gsd/new-project.md +1 -0
- package/commands/gsd/next.md +1 -0
- package/commands/gsd/ns-workflow.md +2 -1
- package/commands/gsd/pause-work.md +1 -0
- package/commands/gsd/phase.md +2 -1
- package/commands/gsd/pr-branch.md +1 -0
- package/commands/gsd/quick-batch.md +105 -0
- package/commands/gsd/resume-work.md +1 -0
- package/commands/gsd/review-backlog.md +1 -0
- package/commands/gsd/settings.md +2 -1
- package/commands/gsd/stats.md +1 -0
- package/commands/gsd/surface.md +18 -8
- package/commands/gsd/thread.md +1 -0
- package/commands/gsd/workspace.md +1 -0
- package/commands/gsd/workstreams.md +1 -0
- package/gsd-core/bin/check-latest-version.cjs +8 -3
- package/gsd-core/bin/gsd-tools.cjs +532 -174
- package/gsd-core/bin/lib/adr-parser.cjs +1 -1
- package/gsd-core/bin/lib/artifacts.cjs +2 -1
- package/gsd-core/bin/lib/audit.cjs +39 -22
- package/gsd-core/bin/lib/broken-windows.cjs +168 -49
- package/gsd-core/bin/lib/capability-activation.cjs +27 -0
- package/gsd-core/bin/lib/capability-lifecycle.cjs +10 -6
- package/gsd-core/bin/lib/capability-loader.cjs +135 -1
- package/gsd-core/bin/lib/capability-registry.cjs +528 -116
- package/gsd-core/bin/lib/capability-source.cjs +19 -2
- package/gsd-core/bin/lib/capability-state.cjs +7 -1
- package/gsd-core/bin/lib/capability-validator.cjs +134 -5
- package/gsd-core/bin/lib/capability-writer.cjs +14 -4
- package/gsd-core/bin/lib/check-command-router.cjs +198 -38
- package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
- package/gsd-core/bin/lib/clusters.cjs +1 -0
- package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
- package/gsd-core/bin/lib/command-aliases.cjs +16 -0
- package/gsd-core/bin/lib/commands.cjs +981 -79
- package/gsd-core/bin/lib/config-loader.cjs +4 -0
- package/gsd-core/bin/lib/config.cjs +153 -38
- package/gsd-core/bin/lib/core-utils.cjs +34 -7
- package/gsd-core/bin/lib/coverage.cjs +1 -1
- package/gsd-core/bin/lib/decisions.cjs +343 -28
- package/gsd-core/bin/lib/edge-probe.cjs +14 -1
- package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
- package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
- package/gsd-core/bin/lib/frontmatter.cjs +137 -23
- package/gsd-core/bin/lib/gap-checker.cjs +22 -13
- package/gsd-core/bin/lib/git-base-branch.cjs +10 -2
- package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
- 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 +54 -11
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +87 -23
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
- package/gsd-core/bin/lib/host-integration.cjs +57 -5
- package/gsd-core/bin/lib/init-command-router.cjs +14 -0
- package/gsd-core/bin/lib/init.cjs +539 -60
- package/gsd-core/bin/lib/install-engine.cjs +199 -14
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +45 -0
- package/gsd-core/bin/lib/install-profiles.cjs +36 -14
- package/gsd-core/bin/lib/installer-migration-report.cjs +1 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +33 -4
- package/gsd-core/bin/lib/io.cjs +35 -0
- package/gsd-core/bin/lib/loop-resolver.cjs +64 -39
- package/gsd-core/bin/lib/markdown-table.cjs +123 -0
- package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
- package/gsd-core/bin/lib/milestone.cjs +41 -10
- package/gsd-core/bin/lib/model-resolver.cjs +101 -10
- package/gsd-core/bin/lib/phase-command-router.cjs +20 -7
- package/gsd-core/bin/lib/phase-id.cjs +412 -31
- package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
- package/gsd-core/bin/lib/phase.cjs +941 -98
- package/gsd-core/bin/lib/plan-document.cjs +10 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +34 -18
- package/gsd-core/bin/lib/planning-snapshot.cjs +206 -30
- package/gsd-core/bin/lib/planning-workspace.cjs +153 -29
- package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
- 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/refactor-trigger-command-router.cjs +61 -2
- package/gsd-core/bin/lib/research-store.cjs +11 -12
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +53 -5
- package/gsd-core/bin/lib/review-lane-invocation.cjs +96 -1
- package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
- package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
- package/gsd-core/bin/lib/roadmap-parser.cjs +555 -41
- package/gsd-core/bin/lib/roadmap.cjs +292 -69
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +260 -43
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +28 -20
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +294 -108
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +408 -47
- package/gsd-core/bin/lib/security.cjs +126 -7
- package/gsd-core/bin/lib/shell-command-projection.cjs +4 -0
- package/gsd-core/bin/lib/smart-entry.cjs +7 -9
- package/gsd-core/bin/lib/state-document.cjs +159 -32
- package/gsd-core/bin/lib/state-md-schema.cjs +44 -27
- package/gsd-core/bin/lib/state-transition.cjs +465 -62
- package/gsd-core/bin/lib/state.cjs +906 -151
- package/gsd-core/bin/lib/surface.cjs +83 -10
- package/gsd-core/bin/lib/task-command-router.cjs +12 -6
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
- package/gsd-core/bin/lib/uat.cjs +1420 -516
- package/gsd-core/bin/lib/update-context.cjs +36 -26
- package/gsd-core/bin/lib/validate.cjs +230 -12
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +316 -23
- package/gsd-core/bin/lib/verify-command-grounding.cjs +1 -1
- package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
- package/gsd-core/bin/lib/verify.cjs +531 -36
- package/gsd-core/bin/lib/workstream-inventory.cjs +21 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +21 -7
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +13 -0
- package/gsd-core/bin/verify-reapply-patches.cjs +507 -81
- package/gsd-core/references/agent-contracts.md +3 -3
- package/gsd-core/references/compact-content-gate.md +66 -0
- package/gsd-core/references/edge-probe.md +17 -13
- package/gsd-core/references/execute-mvp-tdd.md +18 -16
- package/gsd-core/references/execute-phase-response-language.md +6 -0
- package/gsd-core/references/executor-examples.md +42 -0
- package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
- package/gsd-core/references/loop-hook-dispatch.md +18 -0
- package/gsd-core/references/model-profiles.md +12 -3
- package/gsd-core/references/mvp-concepts.md +2 -2
- 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-quick-batch.md +71 -0
- package/gsd-core/references/planner-reviews.md +47 -0
- package/gsd-core/references/planner-revision.md +75 -2
- package/gsd-core/references/planning-config.md +5 -1
- package/gsd-core/references/response-language-directive.md +9 -0
- package/gsd-core/references/revision-loop.md +118 -11
- package/gsd-core/references/tdd.md +17 -9
- package/gsd-core/references/thinking-models-planning.md +18 -2
- package/gsd-core/references/verification-patterns.md +17 -4
- package/gsd-core/references/verifier-evidence-gate.md +160 -0
- package/gsd-core/references/worktree-path-safety.md +112 -2
- package/gsd-core/templates/README.md +7 -1
- package/gsd-core/templates/phase-prompt.md +4 -0
- package/gsd-core/templates/state.md +6 -3
- package/gsd-core/templates/summary.compact.md +212 -0
- package/gsd-core/templates/user-setup.compact.md +199 -0
- package/gsd-core/templates/user-setup.md +0 -9
- package/gsd-core/templates/verification-report.md +5 -0
- package/gsd-core/workflows/add-backlog.md +2 -0
- package/gsd-core/workflows/add-phase.md +2 -0
- package/gsd-core/workflows/add-tests.md +1 -1
- package/gsd-core/workflows/add-todo.md +4 -3
- package/gsd-core/workflows/ai-integration-phase.md +1 -1
- package/gsd-core/workflows/analyze-dependencies.md +2 -0
- package/gsd-core/workflows/audit-fix.md +2 -0
- package/gsd-core/workflows/audit-milestone.md +2 -0
- package/gsd-core/workflows/audit-uat.md +2 -0
- package/gsd-core/workflows/autonomous.md +15 -10
- package/gsd-core/workflows/check-todos.md +5 -3
- package/gsd-core/workflows/cleanup.md +4 -2
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +22 -13
- package/gsd-core/workflows/code-review-fix.md +5 -3
- package/gsd-core/workflows/code-review.md +211 -43
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
- package/gsd-core/workflows/complete-milestone.md +40 -254
- package/gsd-core/workflows/debug.md +1 -1
- package/gsd-core/workflows/diagnose-issues.md +5 -1
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +2 -0
- 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 +2 -0
- 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 +2 -0
- 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.md +1 -1
- package/gsd-core/workflows/discuss-phase-power.md +2 -0
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +43 -13
- package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
- package/gsd-core/workflows/docs-update.md +15 -156
- package/gsd-core/workflows/edit-phase.md +2 -0
- package/gsd-core/workflows/eval-review.md +1 -1
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +20 -3
- package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +24 -3
- package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +8 -2
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -0
- package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
- package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
- package/gsd-core/workflows/execute-phase.md +78 -159
- package/gsd-core/workflows/execute-plan.md +28 -15
- package/gsd-core/workflows/explore.md +2 -0
- package/gsd-core/workflows/extract-learnings.md +2 -0
- package/gsd-core/workflows/fast.md +6 -0
- package/gsd-core/workflows/forensics.md +2 -0
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +1 -1
- 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.compact.md +398 -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 +3 -1
- package/gsd-core/workflows/import.md +3 -3
- package/gsd-core/workflows/inbox.md +1 -1
- package/gsd-core/workflows/ingest-docs.md +1 -1
- package/gsd-core/workflows/insert-phase.md +2 -0
- package/gsd-core/workflows/list-phase-assumptions.md +2 -0
- package/gsd-core/workflows/list-seeds.md +2 -0
- package/gsd-core/workflows/list-workspaces.md +2 -0
- package/gsd-core/workflows/manager.md +3 -3
- package/gsd-core/workflows/map-codebase.md +52 -3
- package/gsd-core/workflows/milestone-summary.md +2 -0
- package/gsd-core/workflows/mvp-phase.md +1 -1
- package/gsd-core/workflows/new-milestone.md +55 -13
- package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
- package/gsd-core/workflows/new-project.md +37 -205
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +2 -0
- 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 +19 -4
- package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -0
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +9 -0
- package/gsd-core/workflows/plan-phase.md +144 -185
- package/gsd-core/workflows/plan-review-convergence.md +102 -10
- package/gsd-core/workflows/plant-seed.md +1 -1
- package/gsd-core/workflows/pr-branch.md +30 -10
- package/gsd-core/workflows/profile-user.md +1 -1
- package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
- package/gsd-core/workflows/progress.md +25 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +37 -2
- package/gsd-core/workflows/quick/steps/research-phase.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 +21 -4
- package/gsd-core/workflows/reapply-patches.md +79 -3
- package/gsd-core/workflows/remove-phase.md +2 -0
- package/gsd-core/workflows/remove-workspace.md +1 -1
- package/gsd-core/workflows/resume-project.md +6 -2
- package/gsd-core/workflows/review.md +215 -10
- package/gsd-core/workflows/scan.md +2 -0
- package/gsd-core/workflows/section-manifest.json +12 -0
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/session-report.md +2 -0
- package/gsd-core/workflows/settings-advanced.md +2 -0
- package/gsd-core/workflows/settings-integrations.md +9 -8
- package/gsd-core/workflows/settings.md +19 -6
- package/gsd-core/workflows/ship.md +10 -10
- package/gsd-core/workflows/sketch-wrap-up.md +2 -0
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/smart-entry.md +1 -1
- package/gsd-core/workflows/spec-phase.md +24 -19
- package/gsd-core/workflows/spike-wrap-up.md +2 -0
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +2 -0
- package/gsd-core/workflows/sync-skills.md +12 -4
- package/gsd-core/workflows/thread.md +2 -0
- package/gsd-core/workflows/transition.md +2 -0
- package/gsd-core/workflows/ui-phase.md +26 -5
- package/gsd-core/workflows/ui-review.md +1 -1
- package/gsd-core/workflows/ultraplan-phase.md +2 -0
- package/gsd-core/workflows/undo.md +1 -1
- package/gsd-core/workflows/update.md +48 -43
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
- package/gsd-core/workflows/verify-work.md +68 -182
- package/hooks/dist/gsd-agent-isolation-guard.js +42 -16
- package/hooks/dist/gsd-check-update-worker.js +19 -2
- package/hooks/dist/gsd-context-monitor.js +371 -27
- package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
- package/hooks/dist/gsd-node-runner.sh +1 -0
- package/hooks/dist/gsd-prompt-guard.js +30 -5
- package/hooks/dist/gsd-read-guard.js +2 -0
- package/hooks/dist/gsd-read-injection-scanner.js +5 -5
- package/hooks/dist/gsd-secret-read-guard.js +1105 -0
- package/hooks/dist/gsd-statusline.js +18 -10
- package/hooks/dist/gsd-validate-commit.sh +474 -7
- package/hooks/dist/gsd-workflow-guard.js +2 -1
- package/hooks/dist/gsd-worktree-path-guard.js +25 -14
- package/hooks/dist/gsd-write-guard.js +46 -1
- package/hooks/dist/lib/dispatch-identity.js +187 -0
- package/hooks/dist/lib/filename-classification.js +64 -0
- package/hooks/dist/lib/git-cmd.js +210 -1
- package/hooks/dist/lib/injection-patterns.js +36 -6
- package/hooks/dist/lib/isolation-deny-reason.js +53 -1
- package/hooks/dist/lib/isolation-sentinel.js +58 -19
- package/hooks/dist/managed-hooks-registry.cjs +1 -0
- package/hooks/gsd-agent-isolation-guard.js +42 -16
- package/hooks/gsd-check-update-worker.js +19 -2
- package/hooks/gsd-context-monitor.js +371 -27
- package/hooks/gsd-cursor-subagent-start.js +34 -14
- package/hooks/gsd-node-runner.sh +1 -0
- package/hooks/gsd-prompt-guard.js +30 -5
- package/hooks/gsd-read-guard.js +2 -0
- package/hooks/gsd-read-injection-scanner.js +5 -5
- package/hooks/gsd-secret-read-guard.js +1105 -0
- package/hooks/gsd-statusline.js +18 -10
- package/hooks/gsd-validate-commit.sh +474 -7
- package/hooks/gsd-workflow-guard.js +2 -1
- package/hooks/gsd-worktree-path-guard.js +25 -14
- package/hooks/gsd-write-guard.js +46 -1
- package/hooks/hooks.json +6 -0
- package/hooks/lib/dispatch-identity.js +187 -0
- package/hooks/lib/filename-classification.js +64 -0
- package/hooks/lib/git-cmd.js +210 -1
- package/hooks/lib/injection-patterns.js +36 -6
- package/hooks/lib/isolation-deny-reason.js +53 -1
- package/hooks/lib/isolation-sentinel.js +58 -19
- package/hooks/managed-hooks-registry.cjs +1 -0
- package/package.json +13 -9
- package/scripts/benchmark-compact-content-variants.cjs +298 -0
- package/scripts/benchmark-compact-content.cjs +368 -0
- package/scripts/build-hooks.js +11 -4
- package/scripts/check-contract-drift.cjs +4 -1
- package/scripts/check-env.cjs +36 -8
- package/scripts/check-glossary-refs.cjs +25 -21
- package/scripts/ci-next-health.cjs +271 -0
- package/scripts/ci-prepare-test-scope.cjs +7 -7
- package/scripts/ci-test-scope.cjs +133 -20
- package/scripts/ci-timeout-report.cjs +1 -1
- package/scripts/diff-touches-shipped-paths.cjs +1 -1
- package/scripts/docs-guard-registry.cjs +17 -2
- package/scripts/gen-adr-index.cjs +8 -2
- package/scripts/gen-inventory-manifest.cjs +12 -0
- package/scripts/gen-loop-host-contract.cjs +67 -15
- package/scripts/gen-platform-conformance-tier.cjs +557 -0
- package/scripts/lib/drift-scan.cjs +1 -1
- package/scripts/lib/macos-conformance-tier.generated.cjs +210 -0
- package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
- package/scripts/lib/platform-conformance-tier.generated.cjs +276 -0
- package/scripts/lib/shellcheck-fetch.cjs +247 -0
- package/scripts/lib/suite-detection.cjs +32 -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-allowed-tools-parity.cjs +221 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +24 -2
- package/scripts/lint-phase-enumeration-drift.cjs +24 -6
- package/scripts/lint-phase-id-drift.cjs +465 -15
- package/scripts/lint-portable-grep.cjs +176 -0
- package/scripts/lint-response-language-coverage.cjs +530 -0
- package/scripts/lint-source-test-name-collision.cjs +1 -1
- package/scripts/lint-test-file-count.allowlist.json +4 -1
- package/scripts/lint-vendored-deps.cjs +128 -17
- package/scripts/lint-workflow-shellcheck-baseline.json +1112 -0
- package/scripts/lint-workflow-shellcheck.cjs +614 -0
- package/scripts/npm-audit-baseline.cjs +376 -0
- package/scripts/prompt-injection-scan.sh +22 -0
- package/scripts/require-issue-link-policy.cjs +16 -1
- package/scripts/workflow-size.cjs +139 -0
- package/skills/gsd-cleanup/SKILL.md +1 -0
- package/skills/gsd-code-review/SKILL.md +2 -1
- package/skills/gsd-complete-milestone/SKILL.md +1 -0
- package/skills/gsd-config/SKILL.md +1 -0
- package/skills/gsd-debug/SKILL.md +1 -0
- package/skills/gsd-execute-phase/SKILL.md +1 -1
- package/skills/gsd-graphify/SKILL.md +1 -0
- package/skills/gsd-health/SKILL.md +1 -0
- package/skills/gsd-mempalace-capture/SKILL.md +1 -0
- package/skills/gsd-mempalace-recall/SKILL.md +1 -0
- package/skills/gsd-new-milestone/SKILL.md +1 -0
- package/skills/gsd-new-project/SKILL.md +1 -0
- package/skills/gsd-next/SKILL.md +1 -0
- package/skills/gsd-ns-workflow/SKILL.md +1 -0
- package/skills/gsd-pause-work/SKILL.md +1 -0
- package/skills/gsd-phase/SKILL.md +2 -1
- package/skills/gsd-pr-branch/SKILL.md +1 -0
- package/skills/gsd-quick-batch/SKILL.md +105 -0
- package/skills/gsd-resume-work/SKILL.md +1 -0
- package/skills/gsd-review-backlog/SKILL.md +1 -0
- package/skills/gsd-settings/SKILL.md +2 -1
- package/skills/gsd-stats/SKILL.md +1 -0
- package/skills/gsd-surface/SKILL.md +18 -8
- package/skills/gsd-thread/SKILL.md +1 -0
- package/skills/gsd-workspace/SKILL.md +1 -0
- package/skills/gsd-workstreams/SKILL.md +1 -0
- package/vscode/package.json +1 -1
- package/gsd-core/templates/claude-md.md +0 -145
- package/gsd-core/templates/codebase/concerns.md +0 -310
- package/gsd-core/templates/codebase/conventions.md +0 -307
- package/gsd-core/templates/codebase/integrations.md +0 -280
- package/gsd-core/templates/codebase/structure.md +0 -285
- package/gsd-core/templates/codebase/testing.md +0 -480
- package/gsd-core/templates/debug-subagent-prompt.md +0 -91
- package/gsd-core/templates/discovery.md +0 -146
|
@@ -30,6 +30,9 @@
|
|
|
30
30
|
* list-todos [area] Count and enumerate pending todos
|
|
31
31
|
* list-seeds [status] List captured seeds (optional status filter)
|
|
32
32
|
* verify-path-exists <path> Check file/directory existence
|
|
33
|
+
* quick-tasks-migrate Migrate STATE.md's "Quick Tasks Completed"
|
|
34
|
+
* table onto the canonical schema (#3730).
|
|
35
|
+
* No-op when absent or already canonical.
|
|
33
36
|
* quick-tasks-append --task <text> Append a row to STATE.md's "Quick Tasks
|
|
34
37
|
* Completed" table (schema-backed via
|
|
35
38
|
* markdown-table.cjs; #2133/ADR-2143).
|
|
@@ -334,6 +337,11 @@ const { routePhaseCommand } = require('./lib/phase-command-router.cjs');
|
|
|
334
337
|
const { routePhasesCommand } = require('./lib/phases-command-router.cjs');
|
|
335
338
|
const { routeValidateCommand } = require('./lib/validate-command-router.cjs');
|
|
336
339
|
const { routeRoadmapCommand } = require('./lib/roadmap-command-router.cjs');
|
|
340
|
+
// #3676 (Phase 4, epic #3344): quick-batch is a first-party, always-on
|
|
341
|
+
// command family (like `/gsd:quick`) — wired directly into
|
|
342
|
+
// HOST_COMMAND_ROUTERS, not the opt-in capability-registry/`activationKey`
|
|
343
|
+
// path graphify uses.
|
|
344
|
+
const { routeQuickBatchCommand } = require('./lib/quick-batch-command-router.cjs');
|
|
337
345
|
const { routeCapabilityCommand } = require('./lib/capability-command-router.cjs');
|
|
338
346
|
const { routeAgentCommand, AGENT_FAILURE_CLASSES } = require('./lib/agent-command-router.cjs');
|
|
339
347
|
const smartEntryMod = require('./lib/smart-entry.cjs');
|
|
@@ -430,8 +438,9 @@ function dispatchCapabilityCommand({ command, args, cwd, raw, error, registry, r
|
|
|
430
438
|
// Step 2: confinement check — belt-and-suspenders even after the basename
|
|
431
439
|
// validation above. Resolved path must be inside libDir (not equal to it,
|
|
432
440
|
// and must start with libDir + sep so "libDir-suffix" can't sneak through).
|
|
433
|
-
const
|
|
434
|
-
|
|
441
|
+
const { tryWithinRootLexical } = require('./lib/security.cjs');
|
|
442
|
+
const resolved = tryWithinRootLexical(m, libDir);
|
|
443
|
+
if (resolved === null || resolved === path.resolve(libDir)) {
|
|
435
444
|
throw new Error('capability module path escapes bin/lib/: ' + JSON.stringify(m));
|
|
436
445
|
}
|
|
437
446
|
// Step 3: require the resolved absolute path — the SAME representation that
|
|
@@ -507,18 +516,19 @@ function defaultRequireFromInstallRoot(installRoot, m) {
|
|
|
507
516
|
if (typeof m !== 'string' || !/^[A-Za-z0-9._-]+\.cjs$/.test(m)) {
|
|
508
517
|
throw new Error('capability module must be a bare .cjs basename: ' + JSON.stringify(m));
|
|
509
518
|
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
const
|
|
513
|
-
if (
|
|
519
|
+
const { tryWithinRoot, tryWithinRootLexical, PathAcceptance } = require('./lib/security.cjs');
|
|
520
|
+
// Lexical containment check: a symlinked ancestor can't widen confinement.
|
|
521
|
+
const lexical = tryWithinRootLexical(m, installRoot);
|
|
522
|
+
if (lexical === null || lexical === path.resolve(installRoot)) {
|
|
514
523
|
throw new Error('capability module path escapes its install root: ' + JSON.stringify(m));
|
|
515
524
|
}
|
|
516
|
-
//
|
|
517
|
-
|
|
518
|
-
|
|
525
|
+
// Realpath/symlink check: the module file itself must not be a symlink
|
|
526
|
+
// pointing outside the root.
|
|
527
|
+
const real = tryWithinRoot(m, installRoot, PathAcceptance.RelativeOnly);
|
|
528
|
+
if (real === null) {
|
|
519
529
|
throw new Error('capability module resolves outside its install root (symlink): ' + JSON.stringify(m));
|
|
520
530
|
}
|
|
521
|
-
return require(
|
|
531
|
+
return require(real);
|
|
522
532
|
}
|
|
523
533
|
|
|
524
534
|
/**
|
|
@@ -776,7 +786,7 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
776
786
|
error,
|
|
777
787
|
output: output,
|
|
778
788
|
});
|
|
779
|
-
if (!handled) config.cmdConfigSet(cwd, args[1], args[2], raw);
|
|
789
|
+
if (!handled) config.cmdConfigSet(cwd, args[1], args[2], raw, { dryRun: args.includes('--dry-run') });
|
|
780
790
|
}
|
|
781
791
|
|
|
782
792
|
function routeConfigSetModelProfile({ args, cwd, raw }) {
|
|
@@ -942,15 +952,27 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
942
952
|
function routeCommit({ args, cwd, raw, error }) {
|
|
943
953
|
const amend = args.includes('--amend');
|
|
944
954
|
const noVerify = args.includes('--no-verify');
|
|
945
|
-
|
|
955
|
+
// #4208: `--files` and `--files-removed` are two path lists, each
|
|
956
|
+
// running from its flag to the NEXT LIST FLAG. A boolean flag
|
|
957
|
+
// inside a list (`--files a --amend b`) is skipped, not a
|
|
958
|
+
// terminator: that is what the previous slice-to-end collection
|
|
959
|
+
// did (it filtered `--` tokens and kept everything else), and a
|
|
960
|
+
// list that stopped at any `--` token silently dropped `b`
|
|
961
|
+
// (review of #4253). The previous form could not carry a second
|
|
962
|
+
// list flag at all, which is the only thing that changed.
|
|
963
|
+
// A REPEATED list flag (`--files a --files b`) merges, as the old
|
|
964
|
+
// slice-to-end parse merged it: every occurrence contributes its
|
|
965
|
+
// run, and none of them ends another's silently.
|
|
966
|
+
const firstListFlag = args.findIndex((a, i) => i > 0 && COMMIT_LIST_FLAGS.has(a));
|
|
946
967
|
// Collect all positional args between command name and first flag,
|
|
947
968
|
// then join them — handles both quoted ("multi word msg") and
|
|
948
969
|
// unquoted (multi word msg) invocations from different shells
|
|
949
|
-
const endIndex =
|
|
970
|
+
const endIndex = firstListFlag !== -1 ? firstListFlag : args.length;
|
|
950
971
|
const messageArgs = args.slice(1, endIndex).filter(a => !a.startsWith('--'));
|
|
951
972
|
const message = messageArgs.join(' ') || undefined;
|
|
952
|
-
const files =
|
|
953
|
-
|
|
973
|
+
const files = collectListFlagValues(args, '--files');
|
|
974
|
+
const filesRemoved = collectListFlagValues(args, '--files-removed');
|
|
975
|
+
commands.cmdCommit(cwd, message, files, raw, amend, noVerify, filesRemoved);
|
|
954
976
|
}
|
|
955
977
|
|
|
956
978
|
function routeCheckCommit({ args, cwd, raw, error }) {
|
|
@@ -1177,6 +1199,34 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1177
1199
|
commands.cmdVerifyPathExists(cwd, args[1], raw);
|
|
1178
1200
|
}
|
|
1179
1201
|
|
|
1202
|
+
function routeQuickTasksMigrate({ cwd, raw }) {
|
|
1203
|
+
// #3730 (option b, maintainer decision 2026-09-02): the supported repair
|
|
1204
|
+
// path for a legacy Quick Tasks table GSD's own pre-registry prose created.
|
|
1205
|
+
// Silent no-op when the section is absent or the table is already canonical
|
|
1206
|
+
// — the quick/fast workflows call this before their first append, so the
|
|
1207
|
+
// migration runs exactly once, on the first quick run, unprompted otherwise.
|
|
1208
|
+
const statePath = path.join(cwd, '.planning', 'STATE.md');
|
|
1209
|
+
if (!fs.existsSync(statePath)) {
|
|
1210
|
+
output({ ok: true, migrated: false, reason: `STATE.md not found at ${statePath}` }, raw);
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1213
|
+
const { migrateQuickTasksTable } = require('./lib/markdown-table.cjs');
|
|
1214
|
+
let report;
|
|
1215
|
+
state.readModifyWriteStateMd(statePath, (content) => {
|
|
1216
|
+
const result = migrateQuickTasksTable(content);
|
|
1217
|
+
if (!result.ok) {
|
|
1218
|
+
throw new ExitError(1, `⚠ quick-tasks-migrate: ${result.reason}`);
|
|
1219
|
+
}
|
|
1220
|
+
report = result.value;
|
|
1221
|
+
return result.value.content;
|
|
1222
|
+
}, cwd, { resync: false });
|
|
1223
|
+
output({
|
|
1224
|
+
ok: true,
|
|
1225
|
+
migrated: report.migrated,
|
|
1226
|
+
...(report.migrated ? { from: report.from, rows: report.rows } : {}),
|
|
1227
|
+
}, raw);
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1180
1230
|
function routeQuickTasksAppend({ args, cwd, raw, error }) {
|
|
1181
1231
|
// #2133 / ADR-2143 §3,§7: schema-backed replacement for fast.md's inline
|
|
1182
1232
|
// `awk NF-2` Quick Tasks column arithmetic. Row construction is delegated
|
|
@@ -1286,7 +1336,8 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1286
1336
|
const fsx = require('node:fs');
|
|
1287
1337
|
const os = require('node:os');
|
|
1288
1338
|
const { REVIEWER_LANES, mergeReviewerLanes } = require('./lib/review-lane-descriptor.cjs');
|
|
1289
|
-
const { resolveLanePlan } = require('./lib/review-lane-invocation.cjs');
|
|
1339
|
+
const { resolveLanePlan, resolveLaneEffort, resolveLaneBudget } = require('./lib/review-lane-invocation.cjs');
|
|
1340
|
+
const modelCatalog = require('./lib/model-catalog.cjs');
|
|
1290
1341
|
const runner = require('./lib/review-lane-runner.cjs');
|
|
1291
1342
|
const cfgLoader = require('./lib/config-loader.cjs');
|
|
1292
1343
|
const capabilityLoader = require('./lib/capability-loader.cjs');
|
|
@@ -1298,18 +1349,20 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1298
1349
|
const sub = args[1];
|
|
1299
1350
|
// Fail fast on an unrecognized subcommand. Without this check, `sub` fell through
|
|
1300
1351
|
// to the `sub !== 'invoke'` usage-error branch far below (after loading the
|
|
1301
|
-
// capability registry AND building a per-lane plan for every lane
|
|
1302
|
-
//
|
|
1303
|
-
//
|
|
1304
|
-
//
|
|
1305
|
-
//
|
|
1306
|
-
//
|
|
1307
|
-
//
|
|
1352
|
+
// capability registry AND building a per-lane plan for every lane) before ever
|
|
1353
|
+
// reporting the error. That made an invalid subcommand slow instead of instant, and
|
|
1354
|
+
// under bench load `review-lane bogus` could exceed a caller's spawn timeout and be
|
|
1355
|
+
// killed before writing anything to stderr — the CI-observed failure was empty stdout
|
|
1356
|
+
// AND stderr, not the expected usage message (#3148). The plan path used to be far
|
|
1357
|
+
// heavier still: it spawned one child `query resolve-execution` process PER LANE to
|
|
1358
|
+
// fetch effort, up to 12 subprocess spawns for the default set. #4255 resolves effort
|
|
1359
|
+
// in-process from the lane's own declaration, so that cost is gone; the fail-fast
|
|
1360
|
+
// check stays because building 12 plans is still work an unrecognized sub should skip.
|
|
1308
1361
|
// `plan`/`invoke` are the only subs that need the expensive plan-building path
|
|
1309
1362
|
// below; `sections`/`flags` return earlier still. Anything else errors here, before
|
|
1310
1363
|
// any of that work starts.
|
|
1311
|
-
if (!['plan', 'invoke', 'sections', 'flags'].includes(sub)) {
|
|
1312
|
-
error("Usage: review-lane <plan|invoke|sections|flags> [--selected a,b] [--run-dir D] [--repo-root R]");
|
|
1364
|
+
if (!['plan', 'invoke', 'sections', 'flags', 'dispatch-step', 'explicit-from-argv'].includes(sub)) {
|
|
1365
|
+
error("Usage: review-lane <plan|invoke|sections|flags|dispatch-step|explicit-from-argv> [--selected a,b] [--run-dir D] [--repo-root R]");
|
|
1313
1366
|
return;
|
|
1314
1367
|
}
|
|
1315
1368
|
const runDir = flag('--run-dir') || '.';
|
|
@@ -1331,6 +1384,86 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1331
1384
|
return cur;
|
|
1332
1385
|
};
|
|
1333
1386
|
|
|
1387
|
+
// Shared by `invoke` and `dispatch-step` (#4209) — both need the SAME bounded
|
|
1388
|
+
// spawn/http/fs seam `runLane` requires (RunnerDeps). Factored out so the two
|
|
1389
|
+
// callers can never disagree about how a lane's binary is resolved or how its
|
|
1390
|
+
// process is bounded; a fix to either reaches both.
|
|
1391
|
+
const buildLaneRunnerDeps = () => ({
|
|
1392
|
+
spawn: (binary, argv, opts) => {
|
|
1393
|
+
// #3086: on Windows, reviewer CLIs (gemini, codex, etc.) are installed
|
|
1394
|
+
// as .cmd shims. spawnSync with a bare name + shell:false fails with
|
|
1395
|
+
// ENOENT (CreateProcess cannot start .cmd). Apply the same #2667 shim
|
|
1396
|
+
// gate used in runWithTimeout: detect .cmd/.bat and mediate through
|
|
1397
|
+
// cmd.exe /d /s /c with an explicit argv array (no shell:true).
|
|
1398
|
+
//
|
|
1399
|
+
// #3275: descriptors declare BARE names, so the gate above never saw an
|
|
1400
|
+
// extension — resolve through the shared PATH+PATHEXT resolver FIRST
|
|
1401
|
+
// (the same one `hasBinary` uses, so probe and spawn can never disagree
|
|
1402
|
+
// about what the lane's binary is). POSIX keeps the bare name: Node's own
|
|
1403
|
+
// PATH search already worked there, and the #3275 acceptance contract
|
|
1404
|
+
// holds macOS/Linux behavior unchanged. A name that resolves to nothing
|
|
1405
|
+
// falls back to the declared name so the ENOENT still surfaces (#3086).
|
|
1406
|
+
// #3411: the resolve-then-mediate pair is one seam call now. Both halves had
|
|
1407
|
+
// private copies here; `projectSpawnInvocation` owns them, so a fix to either
|
|
1408
|
+
// reaches every spawn site instead of only this one.
|
|
1409
|
+
//
|
|
1410
|
+
// Unlike execTool, this lane adopts the RESOLVED path even for a non-batch
|
|
1411
|
+
// binary: that is the behavior #3445 shipped and `deps.hasBinary` answers
|
|
1412
|
+
// from the same resolver, so probe and spawn must agree on the exact file.
|
|
1413
|
+
const { projectSpawnInvocation } = require('./lib/shell-command-projection.cjs');
|
|
1414
|
+
const { command: spawnBinary, args: spawnArgv, windowsVerbatimArguments } = projectSpawnInvocation(binary, argv);
|
|
1415
|
+
const r = cp.spawnSync(spawnBinary, spawnArgv, {
|
|
1416
|
+
input: opts.input,
|
|
1417
|
+
encoding: 'utf8',
|
|
1418
|
+
timeout: opts.timeoutMs,
|
|
1419
|
+
killSignal: 'SIGKILL',
|
|
1420
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
1421
|
+
shell: false, // argv array only — never a shell string (no interpolation of config values)
|
|
1422
|
+
// #2483: a lane's declared env pairs merged OVER this process's environment, for this
|
|
1423
|
+
// child only. Passing a fresh object leaves `process.env` untouched, so nothing leaks
|
|
1424
|
+
// into the orchestrating session or into the next lane.
|
|
1425
|
+
...(opts.env ? { env: { ...process.env, ...opts.env } } : {}),
|
|
1426
|
+
...(windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),
|
|
1427
|
+
});
|
|
1428
|
+
return {
|
|
1429
|
+
status: r.status,
|
|
1430
|
+
stdout: r.stdout || '',
|
|
1431
|
+
stderr: r.stderr || '',
|
|
1432
|
+
errorCode: r.error && r.error.code ? r.error.code : undefined,
|
|
1433
|
+
};
|
|
1434
|
+
},
|
|
1435
|
+
httpJson: async (url, opts) => {
|
|
1436
|
+
try {
|
|
1437
|
+
const res = await fetch(url, {
|
|
1438
|
+
method: opts.method,
|
|
1439
|
+
headers: opts.body ? { 'Content-Type': 'application/json' } : undefined,
|
|
1440
|
+
body: opts.body,
|
|
1441
|
+
signal: AbortSignal.timeout(opts.timeoutMs),
|
|
1442
|
+
});
|
|
1443
|
+
return { ok: res.ok, status: res.status, body: await res.text() };
|
|
1444
|
+
} catch (e) {
|
|
1445
|
+
return { ok: false, status: 0, body: '', error: e && e.message ? e.message : String(e) };
|
|
1446
|
+
}
|
|
1447
|
+
},
|
|
1448
|
+
readFile: (p) => fsx.readFileSync(p, 'utf8'),
|
|
1449
|
+
writeFile: (p, c) => fsx.writeFileSync(p, c, 'utf8'),
|
|
1450
|
+
exists: (p) => fsx.existsSync(p),
|
|
1451
|
+
// PATH scan rather than spawning `command -v` / `where`. Two reasons: it spawns nothing at
|
|
1452
|
+
// all (a probe that costs a process is a probe you avoid running, which is how the original
|
|
1453
|
+
// Kimi probe ended up unbounded), and `shell: true` with an args array is deprecated in
|
|
1454
|
+
// Node 26 (DEP0190) because the arguments are concatenated rather than escaped.
|
|
1455
|
+
//
|
|
1456
|
+
// #3275: the scan lives in `resolveSpawnBinary` now, SHARED with `deps.spawn`
|
|
1457
|
+
// above. Two private copies of "what is this declared binary?" is how the
|
|
1458
|
+
// defect hid: the probe resolved WITH PATHEXT while spawn resolved WITHOUT,
|
|
1459
|
+
// so a lane reported available for a spawn that could never start. One
|
|
1460
|
+
// resolver, both seams — if one changes, the other changes with it.
|
|
1461
|
+
hasBinary: (name) => resolveSpawnBinary(name) !== null,
|
|
1462
|
+
configGet,
|
|
1463
|
+
homeDir: os.homedir(),
|
|
1464
|
+
warn: (m) => process.stderr.write(`${m}\n`),
|
|
1465
|
+
});
|
|
1466
|
+
|
|
1334
1467
|
const selected = (flag('--selected') || '')
|
|
1335
1468
|
.split(',').map((s) => s.trim()).filter(Boolean);
|
|
1336
1469
|
// ADR-2782 D8 (#2927): the lane map is first-party ∪ INSTALLED overlay
|
|
@@ -1358,6 +1491,25 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1358
1491
|
const laneBySlug = new Map(mergedLanes.map((l) => [l.slug, l]));
|
|
1359
1492
|
const chosen = selected.length ? selected : mergedLanes.map((l) => l.slug);
|
|
1360
1493
|
|
|
1494
|
+
// #4209 RQ-02: match a workflow's raw CLI argv (e.g. `--codex`, `--agy`) against the SAME
|
|
1495
|
+
// merged first-party+installed-overlay roster this whole function already built above,
|
|
1496
|
+
// instead of a workflow re-deriving its own copy of `loadRegistry`/`mergeReviewerLanes` via
|
|
1497
|
+
// an inline `node -e` (a rename-only duplicate of the block starting at `mergedLanes =
|
|
1498
|
+
// REVIEWER_LANES` above — `code-review-flags.cjs`'s own header states "this is the canonical
|
|
1499
|
+
// flag-parsing surface — do not replicate inline bash parsing" for exactly this reason).
|
|
1500
|
+
// Everything after `--` is a candidate flag; matched lane slugs print sorted and comma-joined.
|
|
1501
|
+
if (sub === 'explicit-from-argv') {
|
|
1502
|
+
const sepIdx = args.indexOf('--');
|
|
1503
|
+
const candidateArgs = new Set(sepIdx === -1 ? [] : args.slice(sepIdx + 1));
|
|
1504
|
+
const slugs = [];
|
|
1505
|
+
for (const lane of mergedLanes) {
|
|
1506
|
+
const flags = Array.isArray(lane.flags) ? lane.flags : [];
|
|
1507
|
+
if (flags.some((f) => candidateArgs.has(f))) slugs.push(lane.slug);
|
|
1508
|
+
}
|
|
1509
|
+
process.stdout.write([...new Set(slugs)].sort().join(','));
|
|
1510
|
+
return;
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1361
1513
|
if (sub === 'sections') {
|
|
1362
1514
|
const rows = chosen
|
|
1363
1515
|
.map((s) => laneBySlug.get(s))
|
|
@@ -1391,67 +1543,159 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1391
1543
|
return;
|
|
1392
1544
|
}
|
|
1393
1545
|
|
|
1394
|
-
// Effort argv is resolved
|
|
1395
|
-
//
|
|
1396
|
-
//
|
|
1397
|
-
//
|
|
1398
|
-
//
|
|
1399
|
-
//
|
|
1400
|
-
//
|
|
1546
|
+
// Effort argv is resolved from the LANE's own review configuration (#4255), then rendered
|
|
1547
|
+
// through the host's negotiated `effortSurface` so ADR-1239/#2481's trust-boundary invariant
|
|
1548
|
+
// still decides whether an argument is emitted at all and the catalog still owns the syntax.
|
|
1549
|
+
//
|
|
1550
|
+
// What this replaced: a `query resolve-execution gsd-plan-checker --host <slug>` spawn per
|
|
1551
|
+
// lane. The agent id was a hardcoded literal, so the `--host` argument chose only the argv
|
|
1552
|
+
// RENDERING while the LEVEL always came from the installed plan-checker's frontmatter — `low`
|
|
1553
|
+
// under every shipped model profile. Every prompt-fed reviewer therefore ran at a fast
|
|
1554
|
+
// structural verifier's effort, and because the rendered argument is a CLI config override it
|
|
1555
|
+
// silently beat the effort the operator had configured for that CLI. At `low` a large
|
|
1556
|
+
// source-grounded prompt makes a model end its turn with no final message, so the lane came
|
|
1557
|
+
// back empty and the stub read as a crash.
|
|
1401
1558
|
//
|
|
1402
|
-
//
|
|
1403
|
-
//
|
|
1404
|
-
|
|
1405
|
-
// then had to `.split(/\s+/)` that string back apart to get an argv array, and re-splitting a
|
|
1406
|
-
// string the callee just joined is a lossy round trip: any argv element that legitimately
|
|
1407
|
-
// contains a space would come back split into two argv elements, corrupting the very argv it
|
|
1408
|
-
// was rendered to preserve. Reading the UNPICKED object instead gives both `effort_argv` (a
|
|
1409
|
-
// real string array, used verbatim, no re-splitting) and `effort_argv_value` (the bare level,
|
|
1410
|
-
// #2295's `plan.effort`) from the one spawn.
|
|
1411
|
-
const EMPTY_EFFORT = { argv: [], value: null };
|
|
1412
|
-
const effortFor = (slug) => {
|
|
1559
|
+
// `resolveLaneEffort` is pure and lives beside the other lane resolution; this closure only
|
|
1560
|
+
// injects the rendering, which needs the registry and the catalog.
|
|
1561
|
+
const renderLaneEffort = (host, level) => {
|
|
1413
1562
|
try {
|
|
1414
|
-
const
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
);
|
|
1419
|
-
if (r.status !== 0) return EMPTY_EFFORT;
|
|
1420
|
-
let parsed;
|
|
1421
|
-
try {
|
|
1422
|
-
parsed = JSON.parse(String(r.stdout || ''));
|
|
1423
|
-
} catch { return EMPTY_EFFORT; }
|
|
1424
|
-
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) return EMPTY_EFFORT;
|
|
1425
|
-
const argv = Array.isArray(parsed.effort_argv)
|
|
1426
|
-
? parsed.effort_argv.filter((a) => typeof a === 'string' && a !== '')
|
|
1427
|
-
: [];
|
|
1428
|
-
const value = typeof parsed.effort_argv_value === 'string' && parsed.effort_argv_value
|
|
1429
|
-
? parsed.effort_argv_value
|
|
1430
|
-
: null;
|
|
1431
|
-
return { argv, value };
|
|
1432
|
-
} catch { return EMPTY_EFFORT; }
|
|
1563
|
+
const surface = commands.effortSurfaceForHost(cwd, host);
|
|
1564
|
+
const r = modelCatalog.renderEffortArgv(host, level, surface);
|
|
1565
|
+
return { argv: Array.isArray(r && r.argv) ? r.argv : [], value: (r && r.value) || null };
|
|
1566
|
+
} catch { return { argv: [], value: null }; }
|
|
1433
1567
|
};
|
|
1568
|
+
const effortFor = (lane) => resolveLaneEffort(lane, configGet, renderLaneEffort);
|
|
1569
|
+
|
|
1570
|
+
// Per-lane prompt budget: `resolveLaneBudget` (review-lane-invocation.cjs) owns the #2797
|
|
1571
|
+
// resolution semantics (shared with src/reviewer-step-dispatch.cts, #4209 R3 — was two
|
|
1572
|
+
// verbatim copies). Only the budget VALUE is resolved here; assembly/trimming stay in
|
|
1573
|
+
// `prompt-budget`, which the workflow calls, handing the trimmed file back via `--prompt-file`.
|
|
1574
|
+
const budgetFor = (lane) => resolveLaneBudget(lane, configGet);
|
|
1575
|
+
|
|
1576
|
+
// #4209 (ADR-2782 seam) — the ONE interpreter route for a step that declared
|
|
1577
|
+
// `supportsReviewerLanes: true`. Wires `dispatchReviewerLanes` (src/reviewer-step-dispatch.cts)
|
|
1578
|
+
// to the SAME plan/invoke machinery `plan`/`invoke` above use, so a step opting in gets exact
|
|
1579
|
+
// parity with hand-driven `review-lane plan|invoke` rather than a second implementation.
|
|
1580
|
+
// Canonical file paths travel on stdin, never argv (see gsd-core/workflows/code-review.md's
|
|
1581
|
+
// "Files travel on stdin" note) — a 50+-file scope with long paths approaches the Windows
|
|
1582
|
+
// execFileSync argv ceiling, and stdin has no such bound.
|
|
1583
|
+
//
|
|
1584
|
+
// Returns EARLY, like `sections`/`flags` above, rather than falling into the `plans` builder
|
|
1585
|
+
// below: that builder spawns one `effortFor` child process PER LANE IN THE ROSTER (chosen
|
|
1586
|
+
// defaults to every merged lane when nothing is selected), which would burn ~12 wasted spawns
|
|
1587
|
+
// on every dispatch-step call whether or not anything was actually selected. `dispatchReviewerLanes`
|
|
1588
|
+
// builds its own per-SELECTED-lane plan below instead, bounded by the (typically 0-3) explicitly
|
|
1589
|
+
// requested slugs, not the whole roster.
|
|
1590
|
+
if (sub === 'dispatch-step') {
|
|
1591
|
+
const { dispatchReviewerLanes } = require('./lib/reviewer-step-dispatch.cjs');
|
|
1592
|
+
const explicitFlags = (flag('--explicit') || '').split(',').map((s) => s.trim()).filter(Boolean);
|
|
1593
|
+
const depth = flag('--depth') || '';
|
|
1594
|
+
const baseSha = flag('--base-sha') || '';
|
|
1595
|
+
// #4209: this command IS the reusable capability/step-dispatch trait check — see
|
|
1596
|
+
// gsd-core/references/loop-hook-dispatch.md for what supportsReviewerLanes means and why
|
|
1597
|
+
// this is the one place it's resolved. Calls the SAME resolver `loop render-hooks` uses,
|
|
1598
|
+
// `resolveActiveHooksForPoint`, directly in-process — no subprocess, no JSON re-parse, and
|
|
1599
|
+
// no exposure to `io.cjs`'s `@file:` overflow protocol (which only applies to the
|
|
1600
|
+
// rendered-string envelope this path never touches).
|
|
1601
|
+
const capId = flag('--cap-id') || '';
|
|
1602
|
+
const point = flag('--point') || '';
|
|
1603
|
+
let trait = false;
|
|
1604
|
+
if (capId && point) {
|
|
1605
|
+
try {
|
|
1606
|
+
const { resolveActiveHooksForPoint } = loopResolver;
|
|
1607
|
+
const { activeHooks } = resolveActiveHooksForPoint(cwd, point);
|
|
1608
|
+
trait = activeHooks.some((h) => h && h.capId === capId && h.supportsReviewerLanes === true);
|
|
1609
|
+
} catch (e) {
|
|
1610
|
+
process.stderr.write(`Warning: reviewer-lane trait resolution failed for --cap-id ${capId} --point ${point}: ${e && e.message ? e.message : String(e)} — treating as not enabled.\n`);
|
|
1611
|
+
trait = false;
|
|
1612
|
+
}
|
|
1613
|
+
} else if (capId || point) {
|
|
1614
|
+
// #4209 RQ-03: exactly one of the two was passed — a caller with NO capability-step
|
|
1615
|
+
// context at all (neither flag) is the legitimate, silent no-op documented above, but a
|
|
1616
|
+
// caller that named a capability without its point (or vice versa) is misconfigured, not
|
|
1617
|
+
// opted out, and that must not look identical to a correct opt-out on the wire.
|
|
1618
|
+
process.stderr.write(`Warning: --cap-id and --point must both be given to resolve the reviewer-lane trait (got --cap-id=${JSON.stringify(capId)} --point=${JSON.stringify(point)}) — treating as not enabled.\n`);
|
|
1619
|
+
}
|
|
1620
|
+
// No piped stdin (interactive TTY): fail closed to empty paths instead of blocking
|
|
1621
|
+
// indefinitely on a TTY EOF the caller never sends.
|
|
1622
|
+
let stdinPaths = '';
|
|
1623
|
+
if (!process.stdin.isTTY) {
|
|
1624
|
+
try { stdinPaths = fsx.readFileSync(0, 'utf8'); } catch { stdinPaths = ''; }
|
|
1625
|
+
}
|
|
1626
|
+
const paths = stdinPaths.split('\n').map((s) => s.trim()).filter(Boolean);
|
|
1627
|
+
|
|
1628
|
+
// Reuse the exact effort-aware, per-lane plan `plan` builds above (DISP-03: "planned
|
|
1629
|
+
// through the existing `review-lane plan` interface") rather than the interpreter's
|
|
1630
|
+
// simpler default plan callback, which does not resolve per-host effort.
|
|
1631
|
+
const planFn = (lane, ctx) => {
|
|
1632
|
+
const effort = effortFor(lane.slug);
|
|
1633
|
+
return resolveLanePlan({
|
|
1634
|
+
lane, configGet: ctx.configGet, runDir: ctx.runDir, repoRoot: ctx.repoRoot,
|
|
1635
|
+
effortArgs: effort.argv, effortValue: effort.value,
|
|
1636
|
+
});
|
|
1637
|
+
};
|
|
1434
1638
|
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1639
|
+
const runnerDeps = buildLaneRunnerDeps();
|
|
1640
|
+
const invokeFn = async (lane, plan) => {
|
|
1641
|
+
let consentedHost;
|
|
1642
|
+
if (plan.transport === 'openai-http') {
|
|
1643
|
+
try {
|
|
1644
|
+
const consent = require('./lib/capability-consent.cjs');
|
|
1645
|
+
const projectRoot = require('./lib/project-root.cjs').consentProjectRoot(cwd);
|
|
1646
|
+
const capId = String(lane.slug).replace(/_/g, '-');
|
|
1647
|
+
consentedHost = consent.readConsentedReviewerHost({ projectRoot, id: capId });
|
|
1648
|
+
} catch { consentedHost = undefined; }
|
|
1649
|
+
}
|
|
1650
|
+
// DISP-04/05: every selected lane is invoked through this SAME `runner.runLane` seam
|
|
1651
|
+
// `invoke` uses, exactly once (the interpreter's own for-loop over `selection.selected`
|
|
1652
|
+
// never revisits a slug).
|
|
1653
|
+
return runner.runLane(plan, runnerDeps, { consentedHost, explicitlyRequested: true, repoRoot });
|
|
1654
|
+
};
|
|
1655
|
+
|
|
1656
|
+
// `resolveReviewerSelection` only selects an explicit flag present in `detected`
|
|
1657
|
+
// (ADR-2782 D4: absent-safe governs discovery, never explicit selection — a slug the
|
|
1658
|
+
// roster does not declare is rejected here as an explicit-selection error). REAL
|
|
1659
|
+
// host availability (is the CLI actually installed?) is a separate, already-owned
|
|
1660
|
+
// check inside `runner.runLane`'s `probeLane` at invoke time below — duplicating a
|
|
1661
|
+
// second `command -v` probe here would let the two disagree about what "available"
|
|
1662
|
+
// means, which is the exact defect class `resolveSpawnBinary` was consolidated to
|
|
1663
|
+
// prevent (#3275).
|
|
1664
|
+
//
|
|
1665
|
+
// GUARDED ON explicitFlags.length, not unconditional: `resolveReviewerSelection`'s
|
|
1666
|
+
// precedence chain (explicit > --all > review.default_reviewers > all detected) ends,
|
|
1667
|
+
// when none of the first three apply, in `selected = [...detected]` — the SAME
|
|
1668
|
+
// "no flags means every detected reviewer" default `/gsd:review` intentionally uses.
|
|
1669
|
+
// Source review's COMP-01 contract is the opposite: no reviewer-lane flag means inert,
|
|
1670
|
+
// unchanged from before #4209. Passing a non-empty
|
|
1671
|
+
// `detected` unconditionally would silently opt every dispatch-step call with no
|
|
1672
|
+
// `--explicit` into planning+invoking the WHOLE roster via that fallback branch. An
|
|
1673
|
+
// empty `detected` when nothing was asked for makes that fallback resolve to
|
|
1674
|
+
// `[...[]]` = `[]`, so `dispatchReviewerLanes` hits its own `NO_LANES_SELECTED`
|
|
1675
|
+
// early-return before any plan/invoke call — the same fast, inert no-op the caller
|
|
1676
|
+
// gets from an absent `supportsReviewerLanes` trait.
|
|
1677
|
+
const rosterSlugs = explicitFlags.length > 0 ? [...laneBySlug.keys()] : [];
|
|
1678
|
+
|
|
1679
|
+
const dispatchResult = await dispatchReviewerLanes(
|
|
1680
|
+
{
|
|
1681
|
+
trait,
|
|
1682
|
+
selection: { explicitFlags, detected: rosterSlugs },
|
|
1683
|
+
repoRoot,
|
|
1684
|
+
paths,
|
|
1685
|
+
depth,
|
|
1686
|
+
baseSha,
|
|
1687
|
+
runDir,
|
|
1688
|
+
},
|
|
1689
|
+
{
|
|
1690
|
+
getLane: (slug) => laneBySlug.get(slug),
|
|
1691
|
+
configGet,
|
|
1692
|
+
plan: planFn,
|
|
1693
|
+
invoke: invokeFn,
|
|
1694
|
+
},
|
|
1695
|
+
);
|
|
1696
|
+
output(dispatchResult, raw);
|
|
1697
|
+
return;
|
|
1698
|
+
}
|
|
1455
1699
|
|
|
1456
1700
|
const plans = chosen.map((slug) => {
|
|
1457
1701
|
const lane = laneBySlug.get(slug);
|
|
@@ -1462,7 +1706,7 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1462
1706
|
// so losing all of them to one bad manifest is strictly worse. Belt and braces on purpose.
|
|
1463
1707
|
let r;
|
|
1464
1708
|
try {
|
|
1465
|
-
const effort = effortFor(
|
|
1709
|
+
const effort = effortFor(lane);
|
|
1466
1710
|
r = resolveLanePlan({ lane, configGet, runDir, repoRoot, effortArgs: effort.argv, effortValue: effort.value });
|
|
1467
1711
|
} catch (e) {
|
|
1468
1712
|
return { slug, ok: false, reason: 'malformed_lane', detail: `resolver threw: ${e && e.message ? e.message : String(e)}` };
|
|
@@ -1486,7 +1730,7 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1486
1730
|
}
|
|
1487
1731
|
|
|
1488
1732
|
if (sub !== 'invoke') {
|
|
1489
|
-
error("Usage: review-lane <plan|invoke|sections|flags> [--selected a,b] [--run-dir D] [--repo-root R]");
|
|
1733
|
+
error("Usage: review-lane <plan|invoke|sections|flags|dispatch-step|explicit-from-argv> [--selected a,b] [--run-dir D] [--repo-root R]");
|
|
1490
1734
|
return;
|
|
1491
1735
|
}
|
|
1492
1736
|
|
|
@@ -1500,81 +1744,7 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1500
1744
|
|
|
1501
1745
|
// EVERY spawn bounded — `DEFECT.UNBOUNDED-SUBPROCESS` (CONTEXT.md:772). A frozen sync spawn
|
|
1502
1746
|
// cannot be interrupted by --test-force-exit and hangs a whole CI chunk to its 10-minute kill.
|
|
1503
|
-
const deps =
|
|
1504
|
-
spawn: (binary, argv, opts) => {
|
|
1505
|
-
// #3086: on Windows, reviewer CLIs (gemini, codex, etc.) are installed
|
|
1506
|
-
// as .cmd shims. spawnSync with a bare name + shell:false fails with
|
|
1507
|
-
// ENOENT (CreateProcess cannot start .cmd). Apply the same #2667 shim
|
|
1508
|
-
// gate used in runWithTimeout: detect .cmd/.bat and mediate through
|
|
1509
|
-
// cmd.exe /d /s /c with an explicit argv array (no shell:true).
|
|
1510
|
-
//
|
|
1511
|
-
// #3275: descriptors declare BARE names, so the gate above never saw an
|
|
1512
|
-
// extension — resolve through the shared PATH+PATHEXT resolver FIRST
|
|
1513
|
-
// (the same one `hasBinary` uses, so probe and spawn can never disagree
|
|
1514
|
-
// about what the lane's binary is). POSIX keeps the bare name: Node's own
|
|
1515
|
-
// PATH search already worked there, and the #3275 acceptance contract
|
|
1516
|
-
// holds macOS/Linux behavior unchanged. A name that resolves to nothing
|
|
1517
|
-
// falls back to the declared name so the ENOENT still surfaces (#3086).
|
|
1518
|
-
// #3411: the resolve-then-mediate pair is one seam call now. Both halves had
|
|
1519
|
-
// private copies here; `projectSpawnInvocation` owns them, so a fix to either
|
|
1520
|
-
// reaches every spawn site instead of only this one.
|
|
1521
|
-
//
|
|
1522
|
-
// Unlike execTool, this lane adopts the RESOLVED path even for a non-batch
|
|
1523
|
-
// binary: that is the behavior #3445 shipped and `deps.hasBinary` answers
|
|
1524
|
-
// from the same resolver, so probe and spawn must agree on the exact file.
|
|
1525
|
-
const { projectSpawnInvocation } = require('./lib/shell-command-projection.cjs');
|
|
1526
|
-
const { command: spawnBinary, args: spawnArgv, windowsVerbatimArguments } = projectSpawnInvocation(binary, argv);
|
|
1527
|
-
const r = cp.spawnSync(spawnBinary, spawnArgv, {
|
|
1528
|
-
input: opts.input,
|
|
1529
|
-
encoding: 'utf8',
|
|
1530
|
-
timeout: opts.timeoutMs,
|
|
1531
|
-
killSignal: 'SIGKILL',
|
|
1532
|
-
maxBuffer: 64 * 1024 * 1024,
|
|
1533
|
-
shell: false, // argv array only — never a shell string (no interpolation of config values)
|
|
1534
|
-
// #2483: a lane's declared env pairs merged OVER this process's environment, for this
|
|
1535
|
-
// child only. Passing a fresh object leaves `process.env` untouched, so nothing leaks
|
|
1536
|
-
// into the orchestrating session or into the next lane.
|
|
1537
|
-
...(opts.env ? { env: { ...process.env, ...opts.env } } : {}),
|
|
1538
|
-
...(windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),
|
|
1539
|
-
});
|
|
1540
|
-
return {
|
|
1541
|
-
status: r.status,
|
|
1542
|
-
stdout: r.stdout || '',
|
|
1543
|
-
stderr: r.stderr || '',
|
|
1544
|
-
errorCode: r.error && r.error.code ? r.error.code : undefined,
|
|
1545
|
-
};
|
|
1546
|
-
},
|
|
1547
|
-
httpJson: async (url, opts) => {
|
|
1548
|
-
try {
|
|
1549
|
-
const res = await fetch(url, {
|
|
1550
|
-
method: opts.method,
|
|
1551
|
-
headers: opts.body ? { 'Content-Type': 'application/json' } : undefined,
|
|
1552
|
-
body: opts.body,
|
|
1553
|
-
signal: AbortSignal.timeout(opts.timeoutMs),
|
|
1554
|
-
});
|
|
1555
|
-
return { ok: res.ok, status: res.status, body: await res.text() };
|
|
1556
|
-
} catch (e) {
|
|
1557
|
-
return { ok: false, status: 0, body: '', error: e && e.message ? e.message : String(e) };
|
|
1558
|
-
}
|
|
1559
|
-
},
|
|
1560
|
-
readFile: (p) => fsx.readFileSync(p, 'utf8'),
|
|
1561
|
-
writeFile: (p, c) => fsx.writeFileSync(p, c, 'utf8'),
|
|
1562
|
-
exists: (p) => fsx.existsSync(p),
|
|
1563
|
-
// PATH scan rather than spawning `command -v` / `where`. Two reasons: it spawns nothing at
|
|
1564
|
-
// all (a probe that costs a process is a probe you avoid running, which is how the original
|
|
1565
|
-
// Kimi probe ended up unbounded), and `shell: true` with an args array is deprecated in
|
|
1566
|
-
// Node 26 (DEP0190) because the arguments are concatenated rather than escaped.
|
|
1567
|
-
//
|
|
1568
|
-
// #3275: the scan lives in `resolveSpawnBinary` now, SHARED with `deps.spawn`
|
|
1569
|
-
// above. Two private copies of "what is this declared binary?" is how the
|
|
1570
|
-
// defect hid: the probe resolved WITH PATHEXT while spawn resolved WITHOUT,
|
|
1571
|
-
// so a lane reported available for a spawn that could never start. One
|
|
1572
|
-
// resolver, both seams — if one changes, the other changes with it.
|
|
1573
|
-
hasBinary: (name) => resolveSpawnBinary(name) !== null,
|
|
1574
|
-
configGet,
|
|
1575
|
-
homeDir: os.homedir(),
|
|
1576
|
-
warn: (m) => process.stderr.write(`${m}\n`),
|
|
1577
|
-
};
|
|
1747
|
+
const deps = buildLaneRunnerDeps();
|
|
1578
1748
|
|
|
1579
1749
|
// ADR-1517 reviewer instances resolve THROUGH a lane rather than being lanes themselves
|
|
1580
1750
|
// (ADR-2782 D8), so they reuse this seam with three substitutions instead of duplicating the
|
|
@@ -1622,7 +1792,7 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1622
1792
|
`model override (it declares no modelConfigKey). The review will use the CLI's own default.\n`,
|
|
1623
1793
|
);
|
|
1624
1794
|
}
|
|
1625
|
-
const instanceEffort = effortFor(
|
|
1795
|
+
const instanceEffort = effortFor(lane);
|
|
1626
1796
|
const overridden = resolveLanePlan({
|
|
1627
1797
|
lane,
|
|
1628
1798
|
configGet: (k) => (key && k === key ? instanceModel : configGet(k)),
|
|
@@ -1886,6 +2056,112 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1886
2056
|
}
|
|
1887
2057
|
}
|
|
1888
2058
|
|
|
2059
|
+
/**
|
|
2060
|
+
* #3673 (ADR-1239 Phase 1) — typed, PURE-READ query exposing the negotiated
|
|
2061
|
+
* `dispatch.maxConcurrency` numeric sub-field. Sibling of `dispatch-isolation`
|
|
2062
|
+
* above, but — per the #3673 design doc's explicit rejection of a write path
|
|
2063
|
+
* for this query — writes NOTHING: no sentinel file, no side effect at all.
|
|
2064
|
+
* A read-only capacity lookup for a later (Phase 4) scheduler to consult.
|
|
2065
|
+
*
|
|
2066
|
+
* Precedence:
|
|
2067
|
+
* 1. GSD_DISPATCH_MAX_CONCURRENCY, if it parses as a positive safe
|
|
2068
|
+
* integer → source: "live". A malformed value (non-numeric, zero,
|
|
2069
|
+
* negative, fractional) is treated exactly as if the variable were
|
|
2070
|
+
* unset — it falls through to tier 2, never errors.
|
|
2071
|
+
* 2. registry.runtimes[id].runtime.hostIntegration.dispatch.maxConcurrency,
|
|
2072
|
+
* if it is a positive safe integer → source: "descriptor". The
|
|
2073
|
+
* "undocumented" sentinel and any other malformed shape fall through
|
|
2074
|
+
* to tier 3.
|
|
2075
|
+
* 3. 1 (the fail-closed floor) → source: "fallback".
|
|
2076
|
+
*
|
|
2077
|
+
* `isPositiveSafeInteger`, required below from `./lib/host-integration.cjs`,
|
|
2078
|
+
* is the SAME exported predicate src/host-integration.cts's
|
|
2079
|
+
* negotiateHostCapabilities applies to the descriptor value — applied
|
|
2080
|
+
* identically to both the env value and the descriptor value here so the
|
|
2081
|
+
* two tiers can never silently disagree on what counts as valid.
|
|
2082
|
+
*
|
|
2083
|
+
* `reason` names why the WINNING tier (or the fallback) was chosen: "ok" on
|
|
2084
|
+
* the happy path (live or descriptor honored), else "missing" (descriptor
|
|
2085
|
+
* omitted the field), "undocumented" (descriptor sentinel), "non_integer"
|
|
2086
|
+
* (fractional or NaN), "unsafe_integer" (integer but outside
|
|
2087
|
+
* Number.isSafeInteger's range), "non_positive" (zero or negative),
|
|
2088
|
+
* "non_numeric" (string/object/array/null/boolean), or "unknown_runtime"
|
|
2089
|
+
* (no registry entry for the resolved runtime id — resolution itself never
|
|
2090
|
+
* throws; failure fails closed to the same fallback tier).
|
|
2091
|
+
*
|
|
2092
|
+
* Output:
|
|
2093
|
+
* --raw → prints exactly one positive integer, nothing else
|
|
2094
|
+
* --json → prints { runtime, capacity, declared, source, reason }
|
|
2095
|
+
* default → same as --raw
|
|
2096
|
+
*/
|
|
2097
|
+
function routeDispatchCapacity({ args, cwd, raw }) {
|
|
2098
|
+
const { UNDOCUMENTED, isPositiveSafeInteger } = require('./lib/host-integration.cjs');
|
|
2099
|
+
|
|
2100
|
+
let runtimeId = null;
|
|
2101
|
+
let runtimeEntry = null;
|
|
2102
|
+
try {
|
|
2103
|
+
const { resolveRuntime } = require('./lib/runtime-slash.cjs');
|
|
2104
|
+
runtimeId = resolveRuntime(cwd);
|
|
2105
|
+
const registry = require('./lib/capability-registry.cjs');
|
|
2106
|
+
runtimeEntry = registry.runtimes != null ? registry.runtimes[runtimeId] : null;
|
|
2107
|
+
} catch {
|
|
2108
|
+
runtimeEntry = null;
|
|
2109
|
+
}
|
|
2110
|
+
const declared = runtimeEntry?.runtime?.hostIntegration?.dispatch?.maxConcurrency ?? null;
|
|
2111
|
+
|
|
2112
|
+
let liveValue = null;
|
|
2113
|
+
const rawEnv = process.env.GSD_DISPATCH_MAX_CONCURRENCY;
|
|
2114
|
+
if (typeof rawEnv === 'string' && rawEnv.trim().length > 0) {
|
|
2115
|
+
const parsed = Number(rawEnv.trim());
|
|
2116
|
+
if (isPositiveSafeInteger(parsed)) {
|
|
2117
|
+
liveValue = parsed;
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
let capacity;
|
|
2122
|
+
let source;
|
|
2123
|
+
let reason;
|
|
2124
|
+
if (liveValue !== null) {
|
|
2125
|
+
capacity = liveValue;
|
|
2126
|
+
source = 'live';
|
|
2127
|
+
reason = 'ok';
|
|
2128
|
+
} else if (runtimeEntry == null) {
|
|
2129
|
+
capacity = 1;
|
|
2130
|
+
source = 'fallback';
|
|
2131
|
+
reason = 'unknown_runtime';
|
|
2132
|
+
} else if (declared === UNDOCUMENTED) {
|
|
2133
|
+
capacity = 1;
|
|
2134
|
+
source = 'fallback';
|
|
2135
|
+
reason = 'undocumented';
|
|
2136
|
+
} else if (isPositiveSafeInteger(declared)) {
|
|
2137
|
+
capacity = declared;
|
|
2138
|
+
source = 'descriptor';
|
|
2139
|
+
reason = 'ok';
|
|
2140
|
+
} else if (declared === null || declared === undefined) {
|
|
2141
|
+
capacity = 1;
|
|
2142
|
+
source = 'fallback';
|
|
2143
|
+
reason = 'missing';
|
|
2144
|
+
} else if (typeof declared !== 'number') {
|
|
2145
|
+
capacity = 1;
|
|
2146
|
+
source = 'fallback';
|
|
2147
|
+
reason = 'non_numeric';
|
|
2148
|
+
} else if (!Number.isSafeInteger(declared)) {
|
|
2149
|
+
capacity = 1;
|
|
2150
|
+
source = 'fallback';
|
|
2151
|
+
reason = Number.isInteger(declared) ? 'unsafe_integer' : 'non_integer';
|
|
2152
|
+
} else {
|
|
2153
|
+
capacity = 1;
|
|
2154
|
+
source = 'fallback';
|
|
2155
|
+
reason = 'non_positive';
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
if (args.indexOf('--json') !== -1) {
|
|
2159
|
+
output({ runtime: runtimeId, capacity, declared, source, reason }, raw);
|
|
2160
|
+
} else {
|
|
2161
|
+
process.stdout.write(String(capacity));
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
|
|
1889
2165
|
// #3714 follow-up — the dispatch seam gated only on PRESENCE of an explicit
|
|
1890
2166
|
// pin, never on its VALUE, so an Anthropic-flavored global default
|
|
1891
2167
|
// (~/.gsd/defaults.json model_overrides["gsd-executor"] = "sonnet"/"opus"/
|
|
@@ -2571,7 +2847,20 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
2571
2847
|
function routeTodo({ args, cwd, raw, error }) {
|
|
2572
2848
|
const subcommand = args[1];
|
|
2573
2849
|
if (subcommand === 'complete') {
|
|
2574
|
-
|
|
2850
|
+
// #4096: this verb's flag surface is closed. `--dry-run` is parsed
|
|
2851
|
+
// and plumbed (mirroring routeMilestone / #2118), and any OTHER
|
|
2852
|
+
// flag fails loudly instead of being silently ignored — an
|
|
2853
|
+
// accepted-but-ignored safety flag converts a deliberate preview
|
|
2854
|
+
// into the mutation it was meant to avoid. (`--raw` is spliced by
|
|
2855
|
+
// the dispatcher before routing; it stays in the set as a guard
|
|
2856
|
+
// against that splice ever moving.)
|
|
2857
|
+
const TODO_COMPLETE_KNOWN_FLAGS = new Set(['--dry-run', '--raw']);
|
|
2858
|
+
for (const a of args.slice(3)) {
|
|
2859
|
+
if (typeof a === 'string' && a.startsWith('-') && !TODO_COMPLETE_KNOWN_FLAGS.has(a)) {
|
|
2860
|
+
error(`Unknown flag for todo complete: ${a}`, ERROR_REASON.USAGE);
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
commands.cmdTodoComplete(cwd, args[2], { dryRun: args.includes('--dry-run') }, raw);
|
|
2575
2864
|
} else if (subcommand === 'match-phase') {
|
|
2576
2865
|
commands.cmdTodoMatchPhase(cwd, args[2], raw);
|
|
2577
2866
|
} else {
|
|
@@ -3006,6 +3295,7 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
3006
3295
|
const RESTORE_OUTCOME = Object.freeze({
|
|
3007
3296
|
ELIGIBLE: 'eligible',
|
|
3008
3297
|
RESTORED: 'restored',
|
|
3298
|
+
ALREADY_PRESENT: 'already_present',
|
|
3009
3299
|
SKIPPED_DESTINATION_MANAGED: 'skipped_destination_managed',
|
|
3010
3300
|
SKIPPED_DESTINATION_EXISTS: 'skipped_destination_exists',
|
|
3011
3301
|
SKIPPED_COPY_FAILED: 'skipped_copy_failed',
|
|
@@ -3064,18 +3354,30 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
3064
3354
|
return out;
|
|
3065
3355
|
}
|
|
3066
3356
|
|
|
3067
|
-
// Why these three checks rather than security.cjs's `
|
|
3068
|
-
// resolves symlinks with realpathSync and then
|
|
3069
|
-
// whose target sits inside the config dir
|
|
3070
|
-
// wrong — writing through any link
|
|
3071
|
-
//
|
|
3072
|
-
//
|
|
3073
|
-
//
|
|
3357
|
+
// Why these three checks rather than security.cjs's `assertWithinRoot` /
|
|
3358
|
+
// `tryWithinRoot`: that seam resolves symlinks with realpathSync and then
|
|
3359
|
+
// tests containment, so a link whose target sits inside the config dir
|
|
3360
|
+
// passes. For a restore that is still wrong — writing through any link
|
|
3361
|
+
// overwrites whatever it points at instead of materializing a regular file
|
|
3362
|
+
// at the backed-up path. These checks reject links outright, which is
|
|
3363
|
+
// strictly stricter than assertWithinRoot/tryWithinRoot, not a
|
|
3364
|
+
// reimplementation of them. Do not "simplify" this to assertWithinRoot or
|
|
3365
|
+
// tryWithinRoot. Reviewed under epic #4636 Phase 3: the containment
|
|
3366
|
+
// DECISION now routes through the canonical lexical predicate
|
|
3367
|
+
// (`tryWithinRootLexical`, ADR-4650 decision 6); isInsideDir below still
|
|
3368
|
+
// treats target === root as NOT contained via its own extra `!==` check
|
|
3369
|
+
// (unlike every other containment implementation in this repo, which
|
|
3370
|
+
// treats target === root as contained) — that condition is this gate's
|
|
3371
|
+
// own and is layered on top of the shared predicate, not folded into it.
|
|
3074
3372
|
|
|
3075
3373
|
/** True when `target` resolves strictly inside `root`. */
|
|
3076
3374
|
function isInsideDir(root, target) {
|
|
3077
|
-
|
|
3078
|
-
|
|
3375
|
+
// Containment decision: canonical lexical predicate (ADR-4650 decision 6).
|
|
3376
|
+
// The extra `!==` condition is this gate's own: a restore must never
|
|
3377
|
+
// target the config directory itself, only something strictly inside it.
|
|
3378
|
+
if (path.resolve(target) === path.resolve(root)) return false;
|
|
3379
|
+
const { tryWithinRootLexical } = require('./lib/security.cjs');
|
|
3380
|
+
return tryWithinRootLexical(target, root) !== null;
|
|
3079
3381
|
}
|
|
3080
3382
|
|
|
3081
3383
|
/**
|
|
@@ -3273,9 +3575,13 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
3273
3575
|
}
|
|
3274
3576
|
|
|
3275
3577
|
// An identical destination is a no-op restore, not a conflict: re-running
|
|
3276
|
-
// the restore after a successful one must stay quiet and idempotent.
|
|
3578
|
+
// the restore after a successful one must stay quiet and idempotent. It
|
|
3579
|
+
// gets its own outcome so it is excluded from eligible_count — the update
|
|
3580
|
+
// workflow drives its restore question off that count (#4558).
|
|
3581
|
+
let destExists = false;
|
|
3277
3582
|
let destDiffers = false;
|
|
3278
3583
|
if (fs.existsSync(destPath)) {
|
|
3584
|
+
destExists = true;
|
|
3279
3585
|
try {
|
|
3280
3586
|
destDiffers = !fs.readFileSync(destPath).equals(fs.readFileSync(srcPath));
|
|
3281
3587
|
} catch {
|
|
@@ -3290,6 +3596,10 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
3290
3596
|
entries.push({ path: relPath, outcome: RESTORE_OUTCOME.SKIPPED_DESTINATION_EXISTS, warnings });
|
|
3291
3597
|
continue;
|
|
3292
3598
|
}
|
|
3599
|
+
if (destExists) {
|
|
3600
|
+
entries.push({ path: relPath, outcome: RESTORE_OUTCOME.ALREADY_PRESENT, warnings });
|
|
3601
|
+
continue;
|
|
3602
|
+
}
|
|
3293
3603
|
|
|
3294
3604
|
if (!apply) {
|
|
3295
3605
|
entries.push({ path: relPath, outcome: RESTORE_OUTCOME.ELIGIBLE, warnings });
|
|
@@ -4005,6 +4315,31 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
4005
4315
|
* declares that file "All OS-facing I/O; single platform seam", and a private
|
|
4006
4316
|
* duplicate here is what made it untrue.
|
|
4007
4317
|
*/
|
|
4318
|
+
const COMMIT_LIST_FLAGS = new Set(['--files', '--files-removed']);
|
|
4319
|
+
|
|
4320
|
+
// #4208 review: hoisted out of routeCommit's closure so the parser is reachable
|
|
4321
|
+
// from a test. It is the whole of the two-list argument contract, and its edge
|
|
4322
|
+
// cases (a boolean flag inside a run, a repeated list flag, either order) were
|
|
4323
|
+
// already the subject of a review round -- a parser that only the CLI can reach
|
|
4324
|
+
// can only be tested by example, one spawn at a time.
|
|
4325
|
+
//
|
|
4326
|
+
// Every occurrence of `flag` contributes a run; a run ends at the next LIST
|
|
4327
|
+
// flag and skips boolean flags on the way, so no token strictly between one
|
|
4328
|
+
// list flag and the next is ever dropped. Repeated runs of the same flag merge,
|
|
4329
|
+
// as the pre-#4208 slice-to-end parse merged them.
|
|
4330
|
+
function collectListFlagValues(args, flag) {
|
|
4331
|
+
const values = [];
|
|
4332
|
+
args.forEach((a, i) => {
|
|
4333
|
+
if (a !== flag) return;
|
|
4334
|
+
for (const b of args.slice(i + 1)) {
|
|
4335
|
+
if (COMMIT_LIST_FLAGS.has(b)) break;
|
|
4336
|
+
if (b.startsWith('--')) continue;
|
|
4337
|
+
values.push(b);
|
|
4338
|
+
}
|
|
4339
|
+
});
|
|
4340
|
+
return values;
|
|
4341
|
+
}
|
|
4342
|
+
|
|
4008
4343
|
function resolveSpawnBinary(name, platform = process.platform, env = process.env) {
|
|
4009
4344
|
const { resolveExecutableBinary } = require('./lib/shell-command-projection.cjs');
|
|
4010
4345
|
return resolveExecutableBinary(name, { platform, env });
|
|
@@ -4046,6 +4381,21 @@ const HOST_COMMAND_ROUTERS = {
|
|
|
4046
4381
|
// rather than a family — ADR-2346 promotes to a family only at >=3.
|
|
4047
4382
|
'estimate-check': ({ args, cwd, raw }) => estimateCli.cmdEstimateCheck(cwd, args.slice(1), raw),
|
|
4048
4383
|
'estimate-calibration': ({ args, cwd, raw }) => estimateCli.cmdEstimateCalibration(cwd, args.slice(1), raw),
|
|
4384
|
+
// #3418: writes `last_mapped_commit` into every codebase-map document that
|
|
4385
|
+
// exists, closing the loop drift.cjs was built for. A LEAF verb rather than a
|
|
4386
|
+
// `verify` subcommand on purpose -- the verify family is read-only by
|
|
4387
|
+
// contract and this one mutates; ADR-2346 promotes a leaf to a family only at
|
|
4388
|
+
// >=3 verbs, and this is one.
|
|
4389
|
+
'stamp-codebase-map': ({ args, cwd, raw, error }) => {
|
|
4390
|
+
const { files } = parseNamedArgsOrExit(args, { valueFlags: ['files'], positionals: 1 }, error);
|
|
4391
|
+
// A value flag with no value parses to `null`, same as an absent one, so
|
|
4392
|
+
// presence is read off `args`: a bare `--files` (an unquoted empty shell
|
|
4393
|
+
// variable) must hit the empty-filter refusal, not widen to all seven.
|
|
4394
|
+
const only = args.includes('--files')
|
|
4395
|
+
? String(files ?? '').split(',').map((f) => f.trim()).filter(Boolean)
|
|
4396
|
+
: undefined;
|
|
4397
|
+
verify.cmdStampCodebaseMap(cwd, raw, only);
|
|
4398
|
+
},
|
|
4049
4399
|
'estimate-calibrate': ({ args, cwd, raw }) => estimateCli.cmdEstimateCalibrate(cwd, args.slice(1), raw),
|
|
4050
4400
|
'config-new-project': routeConfigNewProject,
|
|
4051
4401
|
'config-path': routeConfigPath,
|
|
@@ -4076,9 +4426,13 @@ const HOST_COMMAND_ROUTERS = {
|
|
|
4076
4426
|
'list-seeds': routeListSeeds,
|
|
4077
4427
|
'verify-path-exists': routeVerifyPathExists,
|
|
4078
4428
|
'quick-tasks-append': routeQuickTasksAppend,
|
|
4429
|
+
'quick-tasks-migrate': routeQuickTasksMigrate,
|
|
4430
|
+
// #3676 (Phase 4, epic #3344): quick-batch coordination verbs.
|
|
4431
|
+
'quick-batch': routeQuickBatchCommand,
|
|
4079
4432
|
'normalize-test-command': routeNormalizeTestCommand,
|
|
4080
4433
|
'dispatch-should-flatten': routeDispatchShouldFlatten,
|
|
4081
4434
|
'dispatch-isolation': routeDispatchIsolation,
|
|
4435
|
+
'dispatch-capacity': routeDispatchCapacity,
|
|
4082
4436
|
'inspect-dispatch-isolation': routeInspectDispatchIsolation,
|
|
4083
4437
|
'record-dispatch-isolation': routeRecordDispatchIsolation,
|
|
4084
4438
|
'resolve-dispatch-type': routeResolveDispatchType,
|
|
@@ -4338,9 +4692,9 @@ const TOP_LEVEL_USAGE = 'Usage: gsd-tools <command> [args] [--raw] [--pick <fiel
|
|
|
4338
4692
|
'from-gsd2, frontmatter, gap-analysis, generate-claude-md, generate-claude-profile, ' +
|
|
4339
4693
|
'generate-dev-preferences, generate-slug, graphify, history-digest, init, intel, ' +
|
|
4340
4694
|
'capability, classify-confidence, git, learnings, list-seeds, list-todos, loop, milestone, package-legitimacy, phase, phase-plan-index, phases, planning, profile-questionnaire, ' +
|
|
4341
|
-
'profile-sample, progress, project-instruction-file, prompt-budget, quick-tasks-append, requirements, research-plan, research-store, resolve-granularity, resolve-model, restore-custom-files, roadmap, runtime-identity, scaffold, smart-entry, state, ' +
|
|
4342
|
-
'config-set-model-profile, dispatch-isolation, dispatch-should-flatten, inspect-dispatch-isolation, record-dispatch-isolation, estimate-calibrate, estimate-calibration, estimate-check, resolve-agent, resolve-dispatch-type, ' +
|
|
4343
|
-
'resolve-execution, review-lane, skill-manifest, skills-root, state-snapshot, stats, summary-extract, teams-status, todo, uat, update-context, verification, websearch, windows, ' +
|
|
4695
|
+
'profile-sample, progress, project-instruction-file, prompt-budget, quick-batch, quick-tasks-append, quick-tasks-migrate, requirements, research-plan, research-store, resolve-granularity, resolve-model, restore-custom-files, roadmap, runtime-identity, scaffold, smart-entry, state, ' +
|
|
4696
|
+
'config-set-model-profile, dispatch-capacity, dispatch-isolation, dispatch-should-flatten, inspect-dispatch-isolation, record-dispatch-isolation, estimate-calibrate, estimate-calibration, estimate-check, resolve-agent, resolve-dispatch-type, ' +
|
|
4697
|
+
'resolve-execution, review-lane, skill-manifest, skills-root, stamp-codebase-map, state-snapshot, stats, summary-extract, teams-status, todo, uat, update-context, verification, websearch, windows, ' +
|
|
4344
4698
|
'task, template, user-story, validate, verify, verify-path-exists, verify-summary, eval, workstream, worktree\n\n' +
|
|
4345
4699
|
'Global flags:\n' +
|
|
4346
4700
|
' --raw Emit raw output without post-processing\n' +
|
|
@@ -4905,6 +5259,10 @@ module.exports = {
|
|
|
4905
5259
|
// #3275: exported for tests — the shared PATH+PATHEXT resolver behind
|
|
4906
5260
|
// review-lane invoke's `deps.spawn` / `deps.hasBinary` seams.
|
|
4907
5261
|
resolveSpawnBinary,
|
|
5262
|
+
// #4208 review: exported for tests — the two-list commit parser is otherwise
|
|
5263
|
+
// reachable only by spawning the CLI, which a property test cannot afford.
|
|
5264
|
+
collectListFlagValues,
|
|
5265
|
+
COMMIT_LIST_FLAGS,
|
|
4908
5266
|
// #3714 follow-up: exported for tests — the dispatch model-pin VALUE
|
|
4909
5267
|
// policy (charset accept/render parity, max-length boundary, leading-char
|
|
4910
5268
|
// anchor) is otherwise unreachable from outside the dispatchOverlayCapabilityCommand closure.
|