@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
|
@@ -27,12 +27,13 @@
|
|
|
27
27
|
* "failed" and "ran cleanly with nothing to report" IS the defect this epic closes (#2494/#2605).
|
|
28
28
|
*/
|
|
29
29
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
-
exports.MODEL_VALUE_MAX = exports.BANNER_SCAN_LINES = exports.UNRESOLVED_MODEL = exports.MODEL_SOURCE = void 0;
|
|
30
|
+
exports.ANTIGRAVITY_FAILURE_MODE = exports.MODEL_VALUE_MAX = exports.BANNER_SCAN_LINES = exports.UNRESOLVED_MODEL = exports.MODEL_SOURCE = void 0;
|
|
31
31
|
exports.parseModelBanner = parseModelBanner;
|
|
32
32
|
exports.parseTranscriptModel = parseTranscriptModel;
|
|
33
33
|
exports.checkEgressHost = checkEgressHost;
|
|
34
34
|
exports.probeLane = probeLane;
|
|
35
35
|
exports.writeReviewOrStub = writeReviewOrStub;
|
|
36
|
+
exports.emptyOutputDiagnosis = emptyOutputDiagnosis;
|
|
36
37
|
exports.handleOpencodeOutput = handleOpencodeOutput;
|
|
37
38
|
exports.antigravityWatermark = antigravityWatermark;
|
|
38
39
|
exports.antigravityTranscriptFallback = antigravityTranscriptFallback;
|
|
@@ -40,6 +41,7 @@ exports.antigravityModel = antigravityModel;
|
|
|
40
41
|
exports.resolveSpawnModel = resolveSpawnModel;
|
|
41
42
|
exports.antigravityPrompt = antigravityPrompt;
|
|
42
43
|
exports.antigravityArgv = antigravityArgv;
|
|
44
|
+
exports.antigravityFailureMode = antigravityFailureMode;
|
|
43
45
|
exports.antigravityDiagnostic = antigravityDiagnostic;
|
|
44
46
|
exports.stampBlindReview = stampBlindReview;
|
|
45
47
|
exports.stampUngroundedReview = stampUngroundedReview;
|
|
@@ -354,8 +356,15 @@ async function probeLane(plan, deps) {
|
|
|
354
356
|
* `extraDiagnostics` carries the raw HTTP response body for the OpenAI-compatible lanes: an error
|
|
355
357
|
* from such a server arrives with HTTP 4xx/5xx and the JSON in the BODY, so stderr alone is empty
|
|
356
358
|
* and the body is the only evidence.
|
|
359
|
+
*
|
|
360
|
+
* `outcome` (#4255) carries the spawn's exit status so the stub can say WHICH empty it is. The
|
|
361
|
+
* header alone cannot: a crash, a timeout kill and a model that ended its turn without writing a
|
|
362
|
+
* final message all reach here as the same zero bytes, and the third is what a too-low reasoning
|
|
363
|
+
* effort produces on a large source-grounded prompt. A clean exit inside the timeout with no
|
|
364
|
+
* output is a stopped-short model, and the stub now says so — with the effort it ran at, which is
|
|
365
|
+
* the value the operator would change.
|
|
357
366
|
*/
|
|
358
|
-
function writeReviewOrStub(plan, content, deps, extraDiagnostics) {
|
|
367
|
+
function writeReviewOrStub(plan, content, deps, extraDiagnostics, outcome) {
|
|
359
368
|
if (!(0, review_lane_invocation_cjs_1.isEmptyReview)(content)) {
|
|
360
369
|
deps.writeFile(plan.reviewPath, content.endsWith('\n') ? content : `${content}\n`);
|
|
361
370
|
return { stubbed: false };
|
|
@@ -364,9 +373,53 @@ function writeReviewOrStub(plan, content, deps, extraDiagnostics) {
|
|
|
364
373
|
const parts = [`${plan.slug} review failed or returned empty output. stderr:`, stderr];
|
|
365
374
|
if (extraDiagnostics)
|
|
366
375
|
parts.push('Raw response body:', extraDiagnostics);
|
|
376
|
+
parts.push(emptyOutputDiagnosis(plan, outcome));
|
|
367
377
|
deps.writeFile(plan.reviewPath, `${parts.join('\n')}\n`);
|
|
368
378
|
return { stubbed: true };
|
|
369
379
|
}
|
|
380
|
+
/**
|
|
381
|
+
* One line naming the effort the lane ran at and how the process ended (#4255).
|
|
382
|
+
*
|
|
383
|
+
* Kept out of the header so the `failed or returned empty output` string every downstream reader
|
|
384
|
+
* greps for is untouched — this is an added line, not a reworded one.
|
|
385
|
+
*/
|
|
386
|
+
function emptyOutputDiagnosis(plan, outcome) {
|
|
387
|
+
// `effort` lives on the spawn plan only. An HTTP lane reaches a server directly and has no
|
|
388
|
+
// reviewer CLI at all, so naming one there would be a lie about what ran (Codex review of
|
|
389
|
+
// #4255) — the two transports get different, accurate wording.
|
|
390
|
+
const spawned = plan.transport === 'spawn';
|
|
391
|
+
const level = spawned ? plan.effort : null;
|
|
392
|
+
const effort = level
|
|
393
|
+
? `ran at effort=${level}`
|
|
394
|
+
: spawned
|
|
395
|
+
? "ran with no effort argument, so the reviewer CLI's own configuration applied"
|
|
396
|
+
: 'is an HTTP lane and carries no reasoning-effort setting';
|
|
397
|
+
if (!outcome)
|
|
398
|
+
return `Diagnosis: ${plan.slug} ${effort}.`;
|
|
399
|
+
// Four endings, not two. `status` is null for BOTH a timeout kill and a process that never
|
|
400
|
+
// ran or died on a signal (ENOENT, SIGKILL) — reporting the latter as "status null" said
|
|
401
|
+
// nothing, and folding them together would attach the stopped-short hint to a crash.
|
|
402
|
+
const timedOut = outcome.errorCode === 'ETIMEDOUT';
|
|
403
|
+
const neverRan = !timedOut && outcome.status === null;
|
|
404
|
+
const cleanExit = outcome.status === 0;
|
|
405
|
+
const ending = timedOut
|
|
406
|
+
? 'was killed by the outer timeout'
|
|
407
|
+
: neverRan
|
|
408
|
+
? `did not exit normally (${outcome.errorCode ?? 'killed by a signal'})`
|
|
409
|
+
: cleanExit
|
|
410
|
+
? 'exited cleanly inside the timeout'
|
|
411
|
+
: `exited with status ${String(outcome.status)}`;
|
|
412
|
+
// Hedged deliberately. A clean exit with no output is CONSISTENT with a model ending its turn
|
|
413
|
+
// without a final message — which is what too low an effort produces on a large prompt — but it
|
|
414
|
+
// is equally consistent with the CLI writing its output somewhere this lane did not read. The
|
|
415
|
+
// line points at the likeliest cause without asserting it.
|
|
416
|
+
const tail = cleanExit
|
|
417
|
+
? ' — a clean exit that produced no output is most often a model ending its turn without'
|
|
418
|
+
+ ' writing a final message rather than a crash; if this lane carries a reasoning effort,'
|
|
419
|
+
+ ' raising it is the usual fix.'
|
|
420
|
+
: '.';
|
|
421
|
+
return `Diagnosis: ${plan.slug} ${effort} and ${ending}${tail}`;
|
|
422
|
+
}
|
|
370
423
|
/* ------------------------------------------------------------------ *
|
|
371
424
|
* Handlers (D6) — named first-party code, never conditionals in data
|
|
372
425
|
* ------------------------------------------------------------------ */
|
|
@@ -677,9 +730,13 @@ function antigravityPrompt(promptPath, repoRoot) {
|
|
|
677
730
|
* lane that fails to start is worse than one that runs on the prompt anchor alone.
|
|
678
731
|
* 2. The self-report prompt variant above, swapped in for the standard file-ref text.
|
|
679
732
|
*
|
|
680
|
-
* Both are argv shape, so they belong here rather than in the descriptor: expressing "add this
|
|
681
|
-
* only if the binary's --help mentions it" as data would need a conditional, which is
|
|
682
|
-
* what the named-handler seam exists to absorb (ADR-2782 D6).
|
|
733
|
+
* Both are argv shape, so they belong here rather than in the descriptor: expressing "add this
|
|
734
|
+
* flag only if the binary's --help mentions it" as data would need a conditional, which is
|
|
735
|
+
* precisely what the named-handler seam exists to absorb (ADR-2782 D6). The native
|
|
736
|
+
* `--print-timeout` VALUE (#3274) is NOT this handler's job — `resolveLanePlan`
|
|
737
|
+
* (review-lane-invocation.cts) resolves the `{{nativeTimeout}}` ARGV_PLACEHOLDER itself, exactly
|
|
738
|
+
* like `{{model}}`/`{{effort}}`/`{{output}}`/`{{prompt}}`, so `plan.argv` arrives here already fully
|
|
739
|
+
* resolved.
|
|
683
740
|
*/
|
|
684
741
|
function antigravityArgv(argv, promptPath, repoRoot, deps) {
|
|
685
742
|
const standard = (0, review_lane_invocation_cjs_1.fileRefPrompt)(promptPath, repoRoot);
|
|
@@ -710,8 +767,54 @@ function antigravityArgv(argv, promptPath, repoRoot, deps) {
|
|
|
710
767
|
*
|
|
711
768
|
* Mode 3 (a pre-session stall, which `--print-timeout` cannot bound because it cannot fire before a
|
|
712
769
|
* session exists) leaves no log line at all, so its tell is stated rather than searched for.
|
|
770
|
+
*
|
|
771
|
+
* #3996 mode 4 (a headless tool-permission denial) exits 0 with empty stdout and a POPULATED
|
|
772
|
+
* transcript, and reports the cause only on stderr — so the stub carries the run's stderr
|
|
773
|
+
* (`evidence.stderr`, the same `plan.errPath` content the generic stub reads), and the mode-3
|
|
774
|
+
* tell is stated only when `antigravityFailureMode` rules a session out (a fresh conv-id or
|
|
775
|
+
* transcript growth past the watermark means one verifiably started). Asserting mode 3 over a
|
|
776
|
+
* transcript this code just read inverts who is better placed to know.
|
|
713
777
|
*/
|
|
714
|
-
|
|
778
|
+
/**
|
|
779
|
+
* What actually failed, as a typed fact rather than a guess baked into prose. #3996.
|
|
780
|
+
*
|
|
781
|
+
* The session-started question is decidable at diagnostic time with the same staleness rule the
|
|
782
|
+
* layer-2 fallback uses: re-resolve the workspace's CURRENT conv-id and compare against the
|
|
783
|
+
* pre-spawn watermark. A conv-id that changed, or a transcript that grew past the watermark
|
|
784
|
+
* line count, means THIS invocation demonstrably reached a session — a pre-launch stall is
|
|
785
|
+
* ruled out. An unchanged conv-id with no growth means nothing new happened, which is the
|
|
786
|
+
* #2073 mode-3 shape even when a PRIOR session's transcript still exists on disk (existence
|
|
787
|
+
* alone would mis-diagnose mode 3 as mode 4 in any workspace with history).
|
|
788
|
+
*/
|
|
789
|
+
exports.ANTIGRAVITY_FAILURE_MODE = Object.freeze({
|
|
790
|
+
/** A session ran this invocation (fresh conv-id, or transcript growth) but no review was recovered. */
|
|
791
|
+
SESSION_STARTED: 'session_started',
|
|
792
|
+
/** No session this invocation — the #2073 mode-3 stall tell is the right signpost. */
|
|
793
|
+
PRE_SESSION_STALL: 'pre_session_stall',
|
|
794
|
+
});
|
|
795
|
+
function antigravityFailureMode(workspace, mark, deps) {
|
|
796
|
+
const convId = resolveWorkspaceConvId(workspace, deps);
|
|
797
|
+
if (!convId)
|
|
798
|
+
return exports.ANTIGRAVITY_FAILURE_MODE.PRE_SESSION_STALL;
|
|
799
|
+
// A different conv-id than the watermark saw = a fresh session this run, transcript or not.
|
|
800
|
+
if (convId !== mark.convId)
|
|
801
|
+
return exports.ANTIGRAVITY_FAILURE_MODE.SESSION_STARTED;
|
|
802
|
+
const tx = transcriptPath(deps.homeDir, convId);
|
|
803
|
+
if (!deps.exists(tx))
|
|
804
|
+
return exports.ANTIGRAVITY_FAILURE_MODE.PRE_SESSION_STALL;
|
|
805
|
+
try {
|
|
806
|
+
const lines = deps.readFile(tx).split(/\r?\n/).filter((l) => l.trim()).length;
|
|
807
|
+
return lines > mark.lines
|
|
808
|
+
? exports.ANTIGRAVITY_FAILURE_MODE.SESSION_STARTED
|
|
809
|
+
: exports.ANTIGRAVITY_FAILURE_MODE.PRE_SESSION_STALL;
|
|
810
|
+
}
|
|
811
|
+
catch {
|
|
812
|
+
// #3118 fail-closed shape: the transcript indisputably exists but cannot be read — do not
|
|
813
|
+
// assert the stall case over a file this code cannot check.
|
|
814
|
+
return exports.ANTIGRAVITY_FAILURE_MODE.SESSION_STARTED;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
function antigravityDiagnostic(deps, evidence = {}) {
|
|
715
818
|
const lines = [
|
|
716
819
|
'Antigravity review failed or returned empty output.',
|
|
717
820
|
];
|
|
@@ -731,8 +834,28 @@ function antigravityDiagnostic(deps) {
|
|
|
731
834
|
/* an unreadable log is not worth failing the lane over */
|
|
732
835
|
}
|
|
733
836
|
}
|
|
734
|
-
|
|
735
|
-
|
|
837
|
+
// #3996: the stub carries agy's stderr, as the generic lane stub already does — mode 4 (a
|
|
838
|
+
// headless tool-permission denial) exits 0 with empty stdout and a populated transcript, and
|
|
839
|
+
// the stderr line is the only signal that names its cause.
|
|
840
|
+
const stderr = (evidence.stderr ?? '').trim();
|
|
841
|
+
if (stderr)
|
|
842
|
+
lines.push('stderr:', stderr);
|
|
843
|
+
// #3996: mode 3's tell is stated only when its precondition holds — the mode is computed from
|
|
844
|
+
// the watermark (#3996 mode 3 in a workspace with history is a no-growth transcript, not an
|
|
845
|
+
// absent one), never guessed from prose.
|
|
846
|
+
if (evidence.mode === exports.ANTIGRAVITY_FAILURE_MODE.SESSION_STARTED) {
|
|
847
|
+
lines.push('An agy session started this run but no review was recovered, so a pre-launch stall is ' +
|
|
848
|
+
'ruled out.' +
|
|
849
|
+
(stderr
|
|
850
|
+
? ' See stderr above; a headless run that was auto-denied a tool permission reports ' +
|
|
851
|
+
'the cause and its fix there.'
|
|
852
|
+
: ' agy reported nothing on its error stream; inspect the transcript under ' +
|
|
853
|
+
'~/.gemini/antigravity-cli/brain/<conv-id>/ for where the session stopped.'));
|
|
854
|
+
}
|
|
855
|
+
else {
|
|
856
|
+
lines.push('If no agy run started, that is the pre-session-stall case: check whether a new ' +
|
|
857
|
+
'~/.gemini/antigravity-cli/brain/<conv-id>/ dir appeared within ~30s of launch.');
|
|
858
|
+
}
|
|
736
859
|
return lines.join('\n');
|
|
737
860
|
}
|
|
738
861
|
/**
|
|
@@ -948,7 +1071,10 @@ function runSpawnLane(plan, deps, repoRoot) {
|
|
|
948
1071
|
// pinned model that 404s server-side and exits 0 with empty output, so the model IS the
|
|
949
1072
|
// diagnosis — dropping it here would throw away the one piece of evidence the stub exists
|
|
950
1073
|
// to preserve.
|
|
951
|
-
deps.writeFile(plan.reviewPath, `${antigravityDiagnostic(deps
|
|
1074
|
+
deps.writeFile(plan.reviewPath, `${antigravityDiagnostic(deps, {
|
|
1075
|
+
stderr: errContent,
|
|
1076
|
+
mode: antigravityFailureMode(repoRoot, mark, deps),
|
|
1077
|
+
})}\n`);
|
|
952
1078
|
return { slug: plan.slug, ok: true, stubbed: true, model };
|
|
953
1079
|
}
|
|
954
1080
|
}
|
|
@@ -959,7 +1085,7 @@ function runSpawnLane(plan, deps, repoRoot) {
|
|
|
959
1085
|
// folded in as a diff observation, and the citation check must not change that surface.
|
|
960
1086
|
if (plan.evidenceClass !== 'diff-only')
|
|
961
1087
|
review = stampUngroundedReview(review);
|
|
962
|
-
const { stubbed } = writeReviewOrStub(plan, review, deps, extra);
|
|
1088
|
+
const { stubbed } = writeReviewOrStub(plan, review, deps, extra, out);
|
|
963
1089
|
return { slug: plan.slug, ok: true, stubbed, model };
|
|
964
1090
|
}
|
|
965
1091
|
async function runHttpLane(plan, deps) {
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Reviewer Step Dispatch (#4209 Phase 1 Plan 2, ADR-2782 seam).
|
|
4
|
+
*
|
|
5
|
+
* ONE interpreter for "a step declares `supportsReviewerLanes: true`" — see
|
|
6
|
+
* `gsd-core/references/loop-hook-dispatch.md` for the canonical explanation of the trait and how
|
|
7
|
+
* `review-lane dispatch-step` re-derives it. This module trusts `trait` exactly as given: it is
|
|
8
|
+
* the CALLER's job to have derived it correctly. Every direct or lifecycle caller routes through
|
|
9
|
+
* `dispatchReviewerLanes` so selection/plan/invoke logic is owned once, not re-derived per
|
|
10
|
+
* feature. This module owns NONE of those primitives — it wires `resolveReviewerSelection`
|
|
11
|
+
* (selection) and `resolveLanePlan` (planning), the same building blocks
|
|
12
|
+
* `gsd-core/bin/gsd-tools.cjs`'s `review-lane plan` subcommand uses. Invocation (`runLane`) needs
|
|
13
|
+
* OS-aware spawn/probe plumbing this module does not own, so `deps.invoke` is the one required,
|
|
14
|
+
* caller-supplied seam (wired for real in `gsd-core/bin/gsd-tools.cjs`'s `review-lane
|
|
15
|
+
* dispatch-step` route).
|
|
16
|
+
*
|
|
17
|
+
* Fail-closed contract:
|
|
18
|
+
* - Trait not exactly `true`, or nothing selected → inert. Zero plan/invoke calls.
|
|
19
|
+
* - Missing/unsafe request-level input (paths escaping `repoRoot`, absent depth/base SHA) stops
|
|
20
|
+
* the WHOLE dispatch before any lane is planned or invoked.
|
|
21
|
+
* - An explicitly requested lane the selector could not resolve does not silently narrow the
|
|
22
|
+
* result to only what worked: lanes that DID resolve still run and their results are kept,
|
|
23
|
+
* but the aggregate `ok` is `false` so no caller mistakes a partial run for a clean one.
|
|
24
|
+
* - Once a lane is planned, a per-lane plan/budget/invoke failure never displaces or cancels a
|
|
25
|
+
* sibling lane already run.
|
|
26
|
+
* - A lane whose `promptChannel` is `'none'` (it reviews the working tree on its own terms, fed
|
|
27
|
+
* nothing — e.g. `coderabbit`) cannot receive the bounded prompt below and is rejected before
|
|
28
|
+
* plan/invoke, same as an unresolved slug.
|
|
29
|
+
*
|
|
30
|
+
* The bounded source-review prompt built here is METADATA ONLY — repository root, canonical
|
|
31
|
+
* file paths, review depth, base SHA, and four fixed prohibitions. It never embeds file
|
|
32
|
+
* contents. If the assembled prompt exceeds a lane's resolved budget, that lane's dispatch
|
|
33
|
+
* hard-fails before `invoke` runs for it — no silent truncation of the file list.
|
|
34
|
+
*/
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.SOURCE_REVIEW_PROHIBITIONS = exports.DISPATCH_REASON = void 0;
|
|
40
|
+
exports.buildSourceReviewPrompt = buildSourceReviewPrompt;
|
|
41
|
+
exports.dispatchReviewerLanes = dispatchReviewerLanes;
|
|
42
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
43
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
44
|
+
const prompt_budget_cjs_1 = require("./prompt-budget.cjs");
|
|
45
|
+
const security_cjs_1 = require("./security.cjs");
|
|
46
|
+
const review_lane_invocation_cjs_1 = require("./review-lane-invocation.cjs");
|
|
47
|
+
const review_reviewer_selection_cjs_1 = require("./review-reviewer-selection.cjs");
|
|
48
|
+
/** Closed set of request-level (not per-lane) halt reasons. Mirrors `LANE_UNAVAILABLE`'s shape. */
|
|
49
|
+
exports.DISPATCH_REASON = Object.freeze({
|
|
50
|
+
TRAIT_NOT_ENABLED: 'trait_not_enabled',
|
|
51
|
+
NO_LANES_SELECTED: 'no_lanes_selected',
|
|
52
|
+
SELECTION_FAILED: 'selection_failed',
|
|
53
|
+
INVALID_PATHS: 'invalid_paths',
|
|
54
|
+
PATH_ESCAPES_REPO_ROOT: 'path_escapes_repo_root',
|
|
55
|
+
MISSING_PROVENANCE: 'missing_provenance',
|
|
56
|
+
INVALID_PROVENANCE: 'invalid_provenance',
|
|
57
|
+
PROMPT_WRITE_FAILED: 'prompt_write_failed',
|
|
58
|
+
});
|
|
59
|
+
/** Fixed, non-negotiable prompt constraints (SAFE-03..SAFE-06). Order is the display order. */
|
|
60
|
+
exports.SOURCE_REVIEW_PROHIBITIONS = Object.freeze([
|
|
61
|
+
'Do not modify any source file.',
|
|
62
|
+
'Do not run tests.',
|
|
63
|
+
'Do not start background processes.',
|
|
64
|
+
'Do not poll or wait — return findings from a single read-only pass.',
|
|
65
|
+
]);
|
|
66
|
+
// #4209 RQ-04: a control character (newline, CR, NUL, ...) in ANY string this module embeds
|
|
67
|
+
// into the external prompt (`buildSourceReviewPrompt`) lets it inject a fabricated section —
|
|
68
|
+
// not just via `paths` (agy-F1's original finding), since `depth`, `baseSha`, and `repoRoot` land
|
|
69
|
+
// in that same markdown. Every embedded string is checked against this ONE shared boundary.
|
|
70
|
+
const CONTROL_CHAR = /[\x00-\x1f\x7f\u2028\u2029]/;
|
|
71
|
+
function defaultWritePromptFile(filePath, content) {
|
|
72
|
+
node_fs_1.default.writeFileSync(filePath, content, 'utf8');
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Validate that every path is a non-empty string resolving INSIDE `repoRoot` — blocks `..`
|
|
76
|
+
* traversal and absolute paths pointing elsewhere before any lane sees them.
|
|
77
|
+
*/
|
|
78
|
+
function validatePaths(repoRoot, paths) {
|
|
79
|
+
if (!Array.isArray(paths) || paths.length === 0) {
|
|
80
|
+
return { ok: false, reason: exports.DISPATCH_REASON.INVALID_PATHS };
|
|
81
|
+
}
|
|
82
|
+
const root = node_path_1.default.resolve(String(repoRoot ?? ''));
|
|
83
|
+
// repoRoot itself may be a symlink (e.g. a `/tmp`-based worktree on macOS, where `/tmp` is
|
|
84
|
+
// itself a symlink to `/private/tmp`) — realpath it once so the per-path comparison below
|
|
85
|
+
// compares like with like, not a resolved child path against an unresolved root.
|
|
86
|
+
let realRoot;
|
|
87
|
+
try {
|
|
88
|
+
realRoot = node_fs_1.default.realpathSync(root);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
realRoot = root;
|
|
92
|
+
}
|
|
93
|
+
// #4209 agy-F1: a control character (newline, CR, NUL, ...) in a path lets a maliciously
|
|
94
|
+
// named repo file inject a fabricated section into the markdown prompt built from `paths`
|
|
95
|
+
// below (buildSourceReviewPrompt) — reject it here, at the shared trust boundary, rather than
|
|
96
|
+
// relying on the incidental quoting `git diff --name-only` happens to apply upstream.
|
|
97
|
+
for (const p of paths) {
|
|
98
|
+
if (typeof p !== 'string' || p.length === 0 || CONTROL_CHAR.test(p)) {
|
|
99
|
+
return { ok: false, reason: exports.DISPATCH_REASON.INVALID_PATHS };
|
|
100
|
+
}
|
|
101
|
+
// ADR-4650 decision 6: lexical family — this is the first of the two
|
|
102
|
+
// deliberate halves (#4209 WR-05); the ENOENT-tolerant realpath half
|
|
103
|
+
// below cannot be folded into a single `tryWithinRoot` call (its
|
|
104
|
+
// ancestor-walk would accept a deleted path via the nearest existing
|
|
105
|
+
// ancestor, not the explicit `continue` this code requires).
|
|
106
|
+
const resolved = node_path_1.default.resolve(root, p);
|
|
107
|
+
if ((0, security_cjs_1.tryWithinRootLexical)(p, root) === null) {
|
|
108
|
+
return { ok: false, reason: exports.DISPATCH_REASON.PATH_ESCAPES_REPO_ROOT };
|
|
109
|
+
}
|
|
110
|
+
// #4209 WR-05: `path.resolve` is lexical only — a symlink whose OWN path sits inside
|
|
111
|
+
// repoRoot can still point outside it, passing the check above while listing an
|
|
112
|
+
// out-of-repo file for the external lane to read. `fs.realpathSync` follows the link;
|
|
113
|
+
// ENOENT is expected and benign here (a `git diff --name-only` path can legitimately name
|
|
114
|
+
// a file already deleted in a stale worktree) and is not itself an escape.
|
|
115
|
+
let real;
|
|
116
|
+
try {
|
|
117
|
+
real = node_fs_1.default.realpathSync(resolved);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if ((0, security_cjs_1.tryWithinRootLexical)(real, realRoot) === null) {
|
|
123
|
+
return { ok: false, reason: exports.DISPATCH_REASON.PATH_ESCAPES_REPO_ROOT };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return { ok: true };
|
|
127
|
+
}
|
|
128
|
+
// `resolveLaneBudget` (review-lane-invocation.cjs) resolves the number; `null` and a resolved
|
|
129
|
+
// `0` both mean unbounded (#2797) — the caller's overflow check must test both `!== null` and
|
|
130
|
+
// `!== 0`. See the call site below.
|
|
131
|
+
/**
|
|
132
|
+
* One-line depth definition for an external reviewer lane, condensed from `<depth_levels>` in
|
|
133
|
+
* `agents/gsd-code-reviewer.md` (#4209 review: a bare `quick`/`standard`/`deep` label means
|
|
134
|
+
* nothing to a third-party CLI that never sees that agent's system prompt — unlike the internal
|
|
135
|
+
* reviewer, whose own persona fully defines these three terms). Every category named here must
|
|
136
|
+
* stay a strict subset of what `<depth_levels>` actually does — `tests/reviewer-step-dispatch
|
|
137
|
+
* .test.cjs`'s "depthMeaning tracks depth_levels" tests assert each case against the real agent
|
|
138
|
+
* file, not just against this function, so the two cannot silently drift again. An unrecognised
|
|
139
|
+
* depth normalizes to `standard`'s text, matching `agents/gsd-code-reviewer.md`'s own "if depth
|
|
140
|
+
* is not one of quick/standard/deep, default to standard" rule — the raw label is not repeated
|
|
141
|
+
* here since `buildSourceReviewPrompt` already states it once, verbatim, earlier in the prompt.
|
|
142
|
+
*/
|
|
143
|
+
function depthMeaning(depth) {
|
|
144
|
+
switch (depth) {
|
|
145
|
+
case 'quick':
|
|
146
|
+
return 'pattern-scan without reading full file contents: hardcoded secrets, dangerous functions, debug artifacts, empty catch blocks, commented-out code';
|
|
147
|
+
case 'standard':
|
|
148
|
+
return 'read each changed file in context for bugs, security, and quality problems; cross-reference imports and exports';
|
|
149
|
+
case 'deep':
|
|
150
|
+
return 'standard, plus cross-file analysis: trace call chains, check type consistency at API boundaries, verify error propagation, check state mutation consistency, detect circular dependencies';
|
|
151
|
+
default:
|
|
152
|
+
return depthMeaning('standard');
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Build the bounded source-review prompt. Metadata only — repoRoot, paths, depth, base SHA, and
|
|
157
|
+
* the four fixed prohibitions. NEVER embeds file contents.
|
|
158
|
+
*/
|
|
159
|
+
function buildSourceReviewPrompt(input) {
|
|
160
|
+
// Base SHA is identical for every file and already stated once above — repeating it per line
|
|
161
|
+
// (as an earlier version of this prompt did) wastes real tokens at O(files), for zero
|
|
162
|
+
// information gain, on every dispatched lane.
|
|
163
|
+
const fileLines = input.paths.map((p) => `- ${p}`).join('\n');
|
|
164
|
+
const ruleLines = exports.SOURCE_REVIEW_PROHIBITIONS.map((r, i) => `${i + 1}. ${r}`).join('\n');
|
|
165
|
+
return [
|
|
166
|
+
'## Source Review Request',
|
|
167
|
+
'',
|
|
168
|
+
`Repository root: ${input.repoRoot}`,
|
|
169
|
+
`Review depth: ${input.depth}`,
|
|
170
|
+
`Base SHA: ${input.baseSha}`,
|
|
171
|
+
'',
|
|
172
|
+
'Review the changes introduced in each file below relative to the base SHA above, at the',
|
|
173
|
+
`requested depth (${depthMeaning(input.depth)}). Report every bug, security issue, and`,
|
|
174
|
+
'code-quality problem you find. For every claim you make, cite the exact file path and line',
|
|
175
|
+
'number(s) it applies to — a claim with no file:line citation cannot be independently',
|
|
176
|
+
're-verified and will be discarded by the consolidating reviewer. Performance issues',
|
|
177
|
+
'(O(n²), memory leaks) are out of scope unless also correctness issues (e.g. an infinite',
|
|
178
|
+
'loop) — do not flag them otherwise.',
|
|
179
|
+
'',
|
|
180
|
+
'### Files in scope',
|
|
181
|
+
fileLines,
|
|
182
|
+
'',
|
|
183
|
+
'### Rules',
|
|
184
|
+
ruleLines,
|
|
185
|
+
].join('\n');
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Dispatch every selected reviewer lane for one opted-in step. See module docstring for scope.
|
|
189
|
+
*/
|
|
190
|
+
async function dispatchReviewerLanes(input, deps) {
|
|
191
|
+
if (input.trait !== true) {
|
|
192
|
+
return { dispatched: false, ok: true, reason: exports.DISPATCH_REASON.TRAIT_NOT_ENABLED, results: [] };
|
|
193
|
+
}
|
|
194
|
+
const resolveSelection = deps.resolveSelection ?? review_reviewer_selection_cjs_1.resolveReviewerSelection;
|
|
195
|
+
const selection = resolveSelection(input.selection);
|
|
196
|
+
if (selection.selected.length === 0) {
|
|
197
|
+
// Distinguish "explicitly requested but every candidate was unavailable" (a real failure —
|
|
198
|
+
// `errors` is non-empty) from "nothing was ever requested" (a clean, inert no-op).
|
|
199
|
+
const reason = selection.errors.length > 0
|
|
200
|
+
? exports.DISPATCH_REASON.SELECTION_FAILED
|
|
201
|
+
: exports.DISPATCH_REASON.NO_LANES_SELECTED;
|
|
202
|
+
return { dispatched: false, ok: selection.errors.length === 0, reason, selection, results: [] };
|
|
203
|
+
}
|
|
204
|
+
const pathCheck = validatePaths(input.repoRoot, input.paths);
|
|
205
|
+
if (!pathCheck.ok) {
|
|
206
|
+
return { dispatched: false, ok: false, reason: pathCheck.reason, selection, results: [] };
|
|
207
|
+
}
|
|
208
|
+
// #4209 RQ-04: depth/baseSha/repoRoot/runDir land in the SAME markdown prompt `paths` does
|
|
209
|
+
// (buildSourceReviewPrompt, `dispatchReviewerLanes`'s `runDir`-derived promptPath write) — a
|
|
210
|
+
// control character in any of them is the identical injection vector agy-F1 found in `paths`,
|
|
211
|
+
// so this trust boundary must reject it here too, not just for the file list.
|
|
212
|
+
if (typeof input.depth !== 'string' || input.depth.length === 0
|
|
213
|
+
|| typeof input.baseSha !== 'string' || input.baseSha.length === 0
|
|
214
|
+
|| typeof input.repoRoot !== 'string' || input.repoRoot.length === 0
|
|
215
|
+
|| typeof input.runDir !== 'string' || input.runDir.length === 0) {
|
|
216
|
+
return { dispatched: false, ok: false, reason: exports.DISPATCH_REASON.MISSING_PROVENANCE, selection, results: [] };
|
|
217
|
+
}
|
|
218
|
+
// #4209 WR-04: a present-but-malicious field (control character) is a different failure mode
|
|
219
|
+
// than an absent one — MISSING_PROVENANCE above means "the caller never supplied this"; this
|
|
220
|
+
// branch means "the caller supplied something and it's an injection attempt," which a caller
|
|
221
|
+
// handling the two reasons differently (e.g. surfacing one as a config problem, the other as
|
|
222
|
+
// a security event) must be able to tell apart.
|
|
223
|
+
if (CONTROL_CHAR.test(input.depth) || CONTROL_CHAR.test(input.baseSha)
|
|
224
|
+
|| CONTROL_CHAR.test(input.repoRoot) || CONTROL_CHAR.test(input.runDir)) {
|
|
225
|
+
return { dispatched: false, ok: false, reason: exports.DISPATCH_REASON.INVALID_PROVENANCE, selection, results: [] };
|
|
226
|
+
}
|
|
227
|
+
// #4209 WR-03 (considered, declined): gating `depth` to code-review's quick/standard/deep
|
|
228
|
+
// enum here would reject the deliberately capability-neutral case this function supports —
|
|
229
|
+
// see "a second, unrelated synthetic step context dispatches through the same function
|
|
230
|
+
// identically" below, which passes a wholly different depth vocabulary on purpose to prove
|
|
231
|
+
// this dispatcher has no code-review-specific special-casing. `depthMeaning()`'s `standard`
|
|
232
|
+
// fallback for an off-enum value is accepted, not a bug, for that reason.
|
|
233
|
+
const { configGet, getLane, plan } = deps;
|
|
234
|
+
const writePromptFile = deps.writePromptFile ?? defaultWritePromptFile;
|
|
235
|
+
const prompt = buildSourceReviewPrompt(input);
|
|
236
|
+
const estimatedTokens = (0, prompt_budget_cjs_1.estimateTokens)(prompt);
|
|
237
|
+
// Written once, before any lane's plan() runs: `promptPath` is derived from `runDir` alone
|
|
238
|
+
// (see `artifactPaths`), constant across every lane in this dispatch by construction — there
|
|
239
|
+
// is no per-lane variance to defend against, so writing it per-lane (as an earlier version of
|
|
240
|
+
// this function did) was pure redundancy, not a real safeguard.
|
|
241
|
+
// A hoisted, whole-dispatch write (see the doc comment above) that throws must not escape as
|
|
242
|
+
// an uncaught exception — no lane can succeed anyway if the shared prompt file was never
|
|
243
|
+
// written, so this is a dispatch-level halt like `validatePaths`/`MISSING_PROVENANCE` above,
|
|
244
|
+
// not a per-lane failure.
|
|
245
|
+
try {
|
|
246
|
+
writePromptFile((0, review_lane_invocation_cjs_1.artifactPaths)(input.runDir, '').promptPath, prompt);
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
return { dispatched: false, ok: false, reason: exports.DISPATCH_REASON.PROMPT_WRITE_FAILED, selection, results: [] };
|
|
250
|
+
}
|
|
251
|
+
const results = [];
|
|
252
|
+
// Never narrow the requested set: an explicit reviewer the selector could not resolve is
|
|
253
|
+
// already surfaced in `selection.errors` — reflect that in the aggregate `ok` even though
|
|
254
|
+
// lanes that DID resolve still run below and keep their own results.
|
|
255
|
+
let anyFailed = selection.errors.length > 0;
|
|
256
|
+
// Tracks whether any lane actually reached plan() — `dispatched` must stay false when every
|
|
257
|
+
// selected slug turned out to be unresolvable, even though a `results` entry was still pushed.
|
|
258
|
+
let planned = false;
|
|
259
|
+
for (const slug of selection.selected) {
|
|
260
|
+
const lane = getLane(slug);
|
|
261
|
+
if (!lane) {
|
|
262
|
+
results.push({ slug, ok: false, reason: 'malformed_lane', detail: 'no such declared lane' });
|
|
263
|
+
anyFailed = true;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
// #4209 review: a `promptChannel: 'none'` lane (coderabbit) is fed nothing and reviews
|
|
267
|
+
// whatever it independently sees fit (its own working-tree diff, review.md:367) rather than
|
|
268
|
+
// the bounded `paths`/`depth`/`baseSha` scope `buildSourceReviewPrompt` promises — silently
|
|
269
|
+
// dispatching it here would violate this interpreter's own scoped, metadata-only review
|
|
270
|
+
// contract. Reject before plan()/invoke() rather than let the mismatch surface as an
|
|
271
|
+
// unexplained out-of-scope review.
|
|
272
|
+
if (lane.transport === 'spawn' && lane.invoke.promptChannel === 'none') {
|
|
273
|
+
results.push({
|
|
274
|
+
slug,
|
|
275
|
+
ok: false,
|
|
276
|
+
reason: 'prompt_channel_unsupported',
|
|
277
|
+
detail: `lane '${slug}' declares promptChannel 'none' and cannot receive a scoped source-review prompt`,
|
|
278
|
+
});
|
|
279
|
+
anyFailed = true;
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
// A single throwing plan()/invoke() must not take down every sibling lane already collected
|
|
283
|
+
// in `results` — same rationale as gsd-tools.cjs's resolveLanePlan guard
|
|
284
|
+
// (#2494/#2605/#1698/#1936/#2073/#2176/#2589/#2794): belt and braces on purpose.
|
|
285
|
+
let planOutcome;
|
|
286
|
+
try {
|
|
287
|
+
planOutcome = plan(lane, { configGet, runDir: input.runDir, repoRoot: input.repoRoot });
|
|
288
|
+
}
|
|
289
|
+
catch (e) {
|
|
290
|
+
results.push({ slug, ok: false, reason: 'malformed_lane', detail: e instanceof Error ? e.message : String(e) });
|
|
291
|
+
anyFailed = true;
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
if (!planOutcome.ok) {
|
|
295
|
+
results.push({ slug, ok: false, reason: planOutcome.reason, detail: planOutcome.detail });
|
|
296
|
+
anyFailed = true;
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
const budget = (0, review_lane_invocation_cjs_1.resolveLaneBudget)(lane, configGet);
|
|
300
|
+
if (budget !== null && budget !== 0 && estimatedTokens > budget) {
|
|
301
|
+
results.push({
|
|
302
|
+
slug,
|
|
303
|
+
ok: false,
|
|
304
|
+
reason: 'budget_exceeded',
|
|
305
|
+
detail: `estimated ${estimatedTokens} tokens exceeds resolved budget ${budget} for lane '${slug}'`,
|
|
306
|
+
});
|
|
307
|
+
anyFailed = true;
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
planned = true;
|
|
311
|
+
let invokeOutcome;
|
|
312
|
+
try {
|
|
313
|
+
invokeOutcome = await deps.invoke(lane, planOutcome.plan);
|
|
314
|
+
}
|
|
315
|
+
catch (e) {
|
|
316
|
+
results.push({ slug, ok: false, reason: 'invoke_failed', detail: e instanceof Error ? e.message : String(e) });
|
|
317
|
+
anyFailed = true;
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
if (!invokeOutcome.ok)
|
|
321
|
+
anyFailed = true;
|
|
322
|
+
results.push({
|
|
323
|
+
slug,
|
|
324
|
+
ok: invokeOutcome.ok,
|
|
325
|
+
reason: invokeOutcome.reason,
|
|
326
|
+
detail: invokeOutcome.detail,
|
|
327
|
+
reviewPath: invokeOutcome.reviewPath,
|
|
328
|
+
errPath: invokeOutcome.errPath,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
return {
|
|
332
|
+
dispatched: planned,
|
|
333
|
+
ok: !anyFailed,
|
|
334
|
+
selection,
|
|
335
|
+
results,
|
|
336
|
+
};
|
|
337
|
+
}
|