@opengsd/gsd-core 1.11.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.opencode/plugins/gsd-core.js +12 -0
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-debug-session-manager.md +1 -1
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-dom-verifier.md +169 -0
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +78 -42
- package/agents/gsd-framework-selector.md +1 -3
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.md +0 -1
- package/agents/gsd-pattern-mapper.md +11 -0
- package/agents/gsd-phase-researcher.md +3 -1
- package/agents/gsd-plan-checker.md +91 -112
- package/agents/gsd-planner.md +20 -4
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.md +2 -2
- package/agents/gsd-roadmapper.md +15 -11
- package/agents/gsd-ui-checker.md +82 -7
- package/agents/gsd-ui-researcher.md +70 -3
- package/agents/gsd-verifier.md +24 -2
- package/bin/install.js +847 -200
- package/commands/gsd/discuss-phase.md +1 -1
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/import.md +1 -1
- package/commands/gsd/ns-workflow.md +2 -1
- package/commands/gsd/phase.md +1 -1
- package/commands/gsd/quick-batch.md +105 -0
- package/commands/gsd/quick.md +8 -4
- package/commands/gsd/surface.md +18 -8
- package/gsd-core/bin/gsd-tools.cjs +761 -100
- package/gsd-core/bin/lib/active-workstream-store.cjs +8 -0
- package/gsd-core/bin/lib/adr-parser.cjs +13 -7
- package/gsd-core/bin/lib/agent-install-check.cjs +162 -0
- package/gsd-core/bin/lib/api-coverage.cjs +30 -9
- package/gsd-core/bin/lib/artifacts.cjs +2 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +30 -11
- package/gsd-core/bin/lib/audit.cjs +163 -41
- package/gsd-core/bin/lib/broken-windows.cjs +306 -28
- package/gsd-core/bin/lib/capability-activation.cjs +27 -0
- package/gsd-core/bin/lib/capability-lock.cjs +10 -4
- package/gsd-core/bin/lib/capability-registry.cjs +785 -144
- package/gsd-core/bin/lib/capability-state.cjs +25 -4
- package/gsd-core/bin/lib/capability-validator.cjs +321 -18
- package/gsd-core/bin/lib/capability-writer.cjs +14 -4
- package/gsd-core/bin/lib/check-command-router.cjs +229 -6
- package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
- package/gsd-core/bin/lib/cli-exit.cjs +496 -10
- package/gsd-core/bin/lib/clusters.cjs +1 -0
- package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
- package/gsd-core/bin/lib/codex-agent-toml.cjs +410 -4
- package/gsd-core/bin/lib/command-aliases.cjs +16 -0
- package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
- package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
- package/gsd-core/bin/lib/commands.cjs +877 -54
- package/gsd-core/bin/lib/complexity-trigger.cjs +26 -6
- package/gsd-core/bin/lib/config-loader.cjs +121 -29
- package/gsd-core/bin/lib/config.cjs +92 -2
- package/gsd-core/bin/lib/configuration.cjs +129 -37
- package/gsd-core/bin/lib/core-utils.cjs +118 -14
- package/gsd-core/bin/lib/decisions.cjs +213 -1
- package/gsd-core/bin/lib/edge-probe.cjs +23 -2
- package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
- package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
- package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
- package/gsd-core/bin/lib/frontmatter.cjs +975 -326
- package/gsd-core/bin/lib/gap-checker.cjs +41 -8
- package/gsd-core/bin/lib/git-base-branch.cjs +182 -39
- package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +7 -3
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +60 -14
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +75 -22
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +22 -8
- package/gsd-core/bin/lib/health-diagnostic.cjs +23 -3
- package/gsd-core/bin/lib/host-integration.cjs +96 -11
- package/gsd-core/bin/lib/init-command-router.cjs +132 -21
- package/gsd-core/bin/lib/init.cjs +252 -56
- package/gsd-core/bin/lib/install-engine.cjs +252 -15
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +78 -1
- package/gsd-core/bin/lib/install-profiles.cjs +100 -18
- package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
- package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +10 -7
- package/gsd-core/bin/lib/intel.cjs +101 -26
- package/gsd-core/bin/lib/io.cjs +195 -15
- package/gsd-core/bin/lib/learnings.cjs +85 -14
- package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
- package/gsd-core/bin/lib/loop-resolver.cjs +14 -8
- package/gsd-core/bin/lib/markdown-table.cjs +175 -4
- package/gsd-core/bin/lib/milestone.cjs +112 -7
- package/gsd-core/bin/lib/model-catalog.cjs +177 -19
- package/gsd-core/bin/lib/model-resolver.cjs +10 -28
- package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
- package/gsd-core/bin/lib/phase-command-router.cjs +13 -6
- package/gsd-core/bin/lib/phase-estimation.cjs +17 -8
- package/gsd-core/bin/lib/phase-id.cjs +321 -13
- package/gsd-core/bin/lib/phase-lifecycle.cjs +24 -16
- package/gsd-core/bin/lib/phase-locator.cjs +138 -17
- package/gsd-core/bin/lib/phase.cjs +1175 -115
- package/gsd-core/bin/lib/plan-document.cjs +273 -0
- package/gsd-core/bin/lib/plan-scan.cjs +13 -2
- package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +165 -34
- package/gsd-core/bin/lib/planning-workspace.cjs +159 -28
- package/gsd-core/bin/lib/probe-core.cjs +4 -1
- package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
- package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
- package/gsd-core/bin/lib/quick-batch-command-router.cjs +285 -0
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
- package/gsd-core/bin/lib/quick-batch.cjs +840 -0
- package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +71 -45
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +62 -14
- package/gsd-core/bin/lib/review-lane-invocation.cjs +73 -1
- package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
- package/gsd-core/bin/lib/roadmap-command-router.cjs +45 -31
- package/gsd-core/bin/lib/roadmap-parser.cjs +577 -41
- package/gsd-core/bin/lib/roadmap.cjs +248 -64
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +329 -41
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +16 -17
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +320 -109
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +487 -83
- package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
- package/gsd-core/bin/lib/runtime-slash.cjs +72 -2
- package/gsd-core/bin/lib/shell-command-projection.cjs +75 -8
- package/gsd-core/bin/lib/smart-entry.cjs +19 -31
- package/gsd-core/bin/lib/spec-section.cjs +12 -7
- package/gsd-core/bin/lib/state-command-router.cjs +47 -18
- package/gsd-core/bin/lib/state-contract.cjs +359 -0
- package/gsd-core/bin/lib/state-document.cjs +216 -5
- package/gsd-core/bin/lib/state-md-schema.cjs +231 -0
- package/gsd-core/bin/lib/state-transition.cjs +850 -145
- package/gsd-core/bin/lib/state.cjs +1629 -287
- package/gsd-core/bin/lib/surface.cjs +33 -10
- package/gsd-core/bin/lib/task-command-router.cjs +111 -1
- package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
- package/gsd-core/bin/lib/teams-status.cjs +4 -1
- package/gsd-core/bin/lib/uat-predicate.cjs +58 -20
- package/gsd-core/bin/lib/uat.cjs +2542 -387
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
- package/gsd-core/bin/lib/ui-safety-gate.cjs +37 -7
- package/gsd-core/bin/lib/unusable-input.cjs +13 -0
- package/gsd-core/bin/lib/update-context.cjs +6 -2
- package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
- package/gsd-core/bin/lib/validate.cjs +230 -12
- package/gsd-core/bin/lib/vendor/README.md +43 -5
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +287 -13
- package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
- package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
- package/gsd-core/bin/lib/verify.cjs +441 -56
- package/gsd-core/bin/lib/workstream-inventory.cjs +20 -2
- package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
- package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
- package/gsd-core/bin/lib/worktree-safety.cjs +185 -21
- package/gsd-core/bin/shared/config-defaults.manifest.json +7 -1
- package/gsd-core/bin/shared/config-schema.manifest.json +13 -0
- package/gsd-core/bin/shared/exit-codes.json +8 -0
- package/gsd-core/bin/shared/exit-codes.sh +20 -0
- package/gsd-core/bin/shared/model-catalog.json +8 -1
- package/gsd-core/bin/verify-reapply-patches.cjs +70 -3
- package/gsd-core/references/agent-contracts.md +6 -5
- package/gsd-core/references/api-coverage.md +24 -2
- package/gsd-core/references/autonomous-smart-discuss.md +3 -3
- package/gsd-core/references/checkpoints.md +37 -19
- package/gsd-core/references/decimal-phase-calculation.md +5 -5
- package/gsd-core/references/edge-probe.md +17 -5
- package/gsd-core/references/execute-mvp-tdd.md +18 -18
- package/gsd-core/references/execute-phase-between-wave-reset.md +9 -12
- package/gsd-core/references/execute-phase-response-language.md +6 -0
- package/gsd-core/references/execute-phase-wave-guard.md +11 -9
- package/gsd-core/references/executor-examples.md +42 -0
- package/gsd-core/references/failing-direction.md +78 -0
- package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
- package/gsd-core/references/gate-prompts.md +1 -1
- package/gsd-core/references/git-integration.md +5 -5
- package/gsd-core/references/git-planning-commit.md +3 -3
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +22 -0
- package/gsd-core/references/model-profiles.md +1 -1
- package/gsd-core/references/mvp-concepts.md +2 -2
- package/gsd-core/references/nyquist-compliance.md +74 -0
- package/gsd-core/references/offer-next.md +3 -5
- package/gsd-core/references/phase-argument-parsing.md +3 -3
- package/gsd-core/references/plan-checker-examples.md +41 -0
- package/gsd-core/references/planner-antipatterns.md +25 -0
- package/gsd-core/references/planner-chunked.md +5 -1
- package/gsd-core/references/planner-coupling.md +42 -0
- package/gsd-core/references/planner-failing-direction.md +53 -0
- package/gsd-core/references/planner-human-verify-mode.md +15 -1
- package/gsd-core/references/planner-quick-batch.md +71 -0
- package/gsd-core/references/planner-reviews.md +47 -0
- package/gsd-core/references/planner-revision.md +76 -3
- package/gsd-core/references/planner-verify-command-grounding.md +17 -0
- package/gsd-core/references/planning-config.md +39 -9
- package/gsd-core/references/response-language-directive.md +9 -0
- package/gsd-core/references/reviewer-instances.md +31 -0
- package/gsd-core/references/revision-loop.md +118 -11
- package/gsd-core/references/runtime-aware-dispatch.md +1 -1
- package/gsd-core/references/tdd.md +15 -12
- package/gsd-core/references/ui-brand.md +65 -21
- package/gsd-core/references/ui-consideration-probe.md +1 -1
- package/gsd-core/references/universal-anti-patterns.md +2 -2
- package/gsd-core/references/verifier-evidence-gate.md +160 -0
- package/gsd-core/references/verify-command-path-resolvability.md +42 -0
- package/gsd-core/references/verify-mvp-mode.md +1 -1
- package/gsd-core/references/workstream-flag.md +11 -11
- package/gsd-core/templates/README.md +1 -1
- package/gsd-core/templates/SECURITY.md +3 -3
- package/gsd-core/templates/UI-SPEC.md +25 -3
- package/gsd-core/templates/VALIDATION.md +3 -3
- package/gsd-core/templates/phase-prompt.md +7 -0
- package/gsd-core/templates/state.md +7 -0
- package/gsd-core/templates/verification-report.md +5 -0
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +3 -1
- package/gsd-core/workflows/add-phase.md +5 -3
- package/gsd-core/workflows/add-tests.md +4 -9
- package/gsd-core/workflows/add-todo.md +2 -2
- package/gsd-core/workflows/ai-integration-phase.md +5 -10
- package/gsd-core/workflows/analyze-dependencies.md +2 -0
- package/gsd-core/workflows/audit-fix.md +14 -3
- package/gsd-core/workflows/audit-milestone.md +11 -9
- package/gsd-core/workflows/audit-uat.md +19 -2
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
- package/gsd-core/workflows/autonomous.md +12 -26
- package/gsd-core/workflows/check-todos.md +2 -2
- package/gsd-core/workflows/cleanup.md +3 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +16 -14
- package/gsd-core/workflows/code-review-fix.md +3 -1
- package/gsd-core/workflows/code-review.md +192 -69
- package/gsd-core/workflows/complete-milestone.md +28 -14
- package/gsd-core/workflows/debug.md +6 -4
- package/gsd-core/workflows/diagnose-issues.md +17 -7
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +3 -1
- package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -7
- package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/text.md +3 -1
- package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
- package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
- package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
- package/gsd-core/workflows/discuss-phase-assumptions.md +3 -3
- package/gsd-core/workflows/discuss-phase-power.md +2 -0
- package/gsd-core/workflows/discuss-phase.md +2 -2
- package/gsd-core/workflows/do.md +46 -19
- package/gsd-core/workflows/docs-update.md +6 -5
- package/gsd-core/workflows/edit-phase.md +3 -1
- package/gsd-core/workflows/eval-review.md +5 -10
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +3 -1
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +129 -11
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +29 -5
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +4 -2
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
- package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
- package/gsd-core/workflows/execute-phase.md +68 -66
- package/gsd-core/workflows/execute-plan.md +25 -20
- package/gsd-core/workflows/explore.md +3 -1
- package/gsd-core/workflows/extract-learnings.md +3 -1
- package/gsd-core/workflows/fast.md +8 -2
- package/gsd-core/workflows/forensics.md +3 -1
- package/gsd-core/workflows/graduation.md +6 -6
- package/gsd-core/workflows/health.md +4 -7
- package/gsd-core/workflows/help/modes/brief.md +2 -0
- package/gsd-core/workflows/help/modes/default.md +2 -0
- package/gsd-core/workflows/help/modes/full.md +12 -0
- package/gsd-core/workflows/help/modes/topic.md +2 -0
- package/gsd-core/workflows/help.md +2 -0
- package/gsd-core/workflows/import.md +17 -14
- package/gsd-core/workflows/inbox.md +5 -6
- package/gsd-core/workflows/ingest-docs.md +45 -12
- package/gsd-core/workflows/insert-phase.md +7 -5
- package/gsd-core/workflows/list-phase-assumptions.md +2 -0
- package/gsd-core/workflows/list-seeds.md +7 -3
- package/gsd-core/workflows/list-workspaces.md +3 -1
- package/gsd-core/workflows/manager.md +15 -26
- package/gsd-core/workflows/map-codebase.md +3 -1
- package/gsd-core/workflows/milestone-summary.md +3 -1
- package/gsd-core/workflows/mvp-phase.md +3 -3
- package/gsd-core/workflows/new-milestone.md +10 -22
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
- package/gsd-core/workflows/new-project.md +17 -29
- package/gsd-core/workflows/new-workspace.md +2 -2
- package/gsd-core/workflows/next.md +4 -2
- package/gsd-core/workflows/node-repair.md +2 -0
- package/gsd-core/workflows/note.md +2 -0
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +20 -5
- package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +4 -4
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +12 -3
- package/gsd-core/workflows/plan-phase.md +251 -54
- package/gsd-core/workflows/plan-review-convergence.md +148 -19
- package/gsd-core/workflows/plant-seed.md +3 -3
- package/gsd-core/workflows/pr-branch.md +195 -51
- package/gsd-core/workflows/profile-user.md +17 -15
- package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
- package/gsd-core/workflows/progress.md +52 -15
- package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +38 -5
- package/gsd-core/workflows/quick/steps/quick-verification.md +2 -4
- package/gsd-core/workflows/quick/steps/research-phase.md +5 -7
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
- package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
- package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
- package/gsd-core/workflows/quick-batch.md +203 -0
- package/gsd-core/workflows/quick.md +33 -32
- package/gsd-core/workflows/reapply-patches.md +2 -0
- package/gsd-core/workflows/remove-phase.md +6 -4
- package/gsd-core/workflows/remove-workspace.md +3 -3
- package/gsd-core/workflows/resume-project.md +14 -14
- package/gsd-core/workflows/review.md +404 -21
- package/gsd-core/workflows/scan.md +3 -1
- package/gsd-core/workflows/section-manifest.json +12 -0
- package/gsd-core/workflows/secure-phase.md +3 -3
- package/gsd-core/workflows/session-report.md +2 -0
- package/gsd-core/workflows/settings-advanced.md +9 -9
- package/gsd-core/workflows/settings-integrations.md +66 -32
- package/gsd-core/workflows/settings.md +4 -6
- package/gsd-core/workflows/ship.md +22 -16
- package/gsd-core/workflows/sketch-wrap-up.md +13 -17
- package/gsd-core/workflows/sketch.md +13 -19
- package/gsd-core/workflows/smart-entry.md +4 -6
- package/gsd-core/workflows/spec-phase.md +31 -4
- package/gsd-core/workflows/spike-wrap-up.md +9 -11
- package/gsd-core/workflows/spike.md +21 -32
- package/gsd-core/workflows/stats.md +4 -2
- package/gsd-core/workflows/sync-skills.md +13 -5
- package/gsd-core/workflows/thread.md +13 -7
- package/gsd-core/workflows/transition.md +7 -5
- package/gsd-core/workflows/ui-phase.md +36 -21
- package/gsd-core/workflows/ui-review.md +7 -11
- package/gsd-core/workflows/ultraplan-phase.md +7 -13
- package/gsd-core/workflows/undo.md +9 -17
- package/gsd-core/workflows/update.md +47 -48
- package/gsd-core/workflows/validate-phase.md +3 -3
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
- package/gsd-core/workflows/verify-work.md +106 -21
- package/hooks/dist/gsd-agent-isolation-guard.js +77 -38
- package/hooks/dist/gsd-check-update-worker.js +19 -2
- package/hooks/dist/gsd-config-reload.js +18 -12
- package/hooks/dist/gsd-context-monitor.js +302 -22
- package/hooks/dist/gsd-cursor-post-tool.js +3 -1
- package/hooks/dist/gsd-cursor-pre-tool.js +3 -1
- package/hooks/dist/gsd-cursor-session-start.js +2 -1
- package/hooks/dist/gsd-cursor-stop.js +2 -1
- package/hooks/dist/gsd-cursor-subagent-start.js +28 -23
- package/hooks/dist/gsd-cursor-subagent-stop.js +3 -1
- package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
- package/hooks/dist/gsd-graphify-update.sh +22 -18
- package/hooks/dist/gsd-node-runner.sh +77 -0
- package/hooks/dist/gsd-phase-boundary.sh +1 -0
- package/hooks/dist/gsd-prompt-guard.js +46 -12
- package/hooks/dist/gsd-read-guard.js +18 -7
- package/hooks/dist/gsd-read-injection-scanner.js +22 -13
- package/hooks/dist/gsd-secret-read-guard.js +1079 -0
- package/hooks/dist/gsd-session-state.sh +1 -0
- package/hooks/dist/gsd-statusline.js +222 -29
- package/hooks/dist/gsd-validate-commit.sh +523 -12
- package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
- package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
- package/hooks/dist/gsd-workflow-guard.js +36 -17
- package/hooks/dist/gsd-worktree-path-guard.js +36 -21
- package/hooks/dist/gsd-write-guard.js +35 -25
- package/hooks/dist/lib/cli-exit.js +560 -0
- package/hooks/dist/lib/exit-code-registry.js +98 -0
- package/hooks/dist/lib/git-cmd.js +210 -1
- package/hooks/dist/lib/git-probe.js +84 -0
- package/hooks/dist/lib/hook-exit.js +81 -0
- package/hooks/dist/lib/injection-patterns.js +36 -6
- package/hooks/dist/managed-hooks-registry.cjs +4 -0
- package/hooks/gsd-agent-isolation-guard.js +77 -38
- package/hooks/gsd-check-update-worker.js +19 -2
- package/hooks/gsd-config-reload.js +18 -12
- package/hooks/gsd-context-monitor.js +302 -22
- package/hooks/gsd-cursor-post-tool.js +3 -1
- package/hooks/gsd-cursor-pre-tool.js +3 -1
- package/hooks/gsd-cursor-session-start.js +2 -1
- package/hooks/gsd-cursor-stop.js +2 -1
- package/hooks/gsd-cursor-subagent-start.js +28 -23
- package/hooks/gsd-cursor-subagent-stop.js +3 -1
- package/hooks/gsd-ensure-canonical-path.js +2 -1
- package/hooks/gsd-graphify-update.sh +22 -18
- package/hooks/gsd-node-runner.sh +77 -0
- package/hooks/gsd-phase-boundary.sh +1 -0
- package/hooks/gsd-prompt-guard.js +46 -12
- package/hooks/gsd-read-guard.js +18 -7
- package/hooks/gsd-read-injection-scanner.js +22 -13
- package/hooks/gsd-secret-read-guard.js +1079 -0
- package/hooks/gsd-session-state.sh +1 -0
- package/hooks/gsd-statusline.js +222 -29
- package/hooks/gsd-validate-commit.sh +523 -12
- package/hooks/gsd-windsurf-pre-command.js +16 -11
- package/hooks/gsd-windsurf-pre-write.js +22 -13
- package/hooks/gsd-workflow-guard.js +36 -17
- package/hooks/gsd-worktree-path-guard.js +36 -21
- package/hooks/gsd-write-guard.js +35 -25
- package/hooks/hooks.json +6 -0
- package/hooks/lib/cli-exit.js +560 -0
- package/hooks/lib/exit-code-registry.js +98 -0
- package/hooks/lib/git-cmd.js +210 -1
- package/hooks/lib/git-probe.js +84 -0
- package/hooks/lib/hook-exit.js +81 -0
- package/hooks/lib/injection-patterns.js +36 -6
- package/hooks/managed-hooks-registry.cjs +4 -0
- package/package.json +14 -9
- package/scripts/base64-scan.sh +74 -12
- package/scripts/build-hooks.js +12 -0
- package/scripts/check-glossary-refs.cjs +77 -15
- package/scripts/check-mutation-score-ratchet.cjs +156 -0
- package/scripts/ci-check-job-near-cap.cjs +49 -0
- package/scripts/ci-pr-mergeability.cjs +262 -0
- package/scripts/ci-test-scope.cjs +52 -12
- package/scripts/ci-timeout-report.cjs +230 -0
- package/scripts/docs-guard-registry.cjs +406 -0
- package/scripts/gen-capability-registry.cjs +8 -6
- package/scripts/gen-exit-code-docs.cjs +318 -0
- package/scripts/gen-exit-code-registry.cjs +891 -0
- package/scripts/gen-features.cjs +836 -0
- package/scripts/gen-hooks-cli-exit.cjs +239 -0
- package/scripts/gen-install-tree-fixtures.cjs +2 -2
- package/scripts/gen-loop-host-contract.cjs +189 -4
- package/scripts/gen-scripts-cli-exit.cjs +185 -0
- package/scripts/gen-state-md-docs.cjs +727 -0
- package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
- package/scripts/lib/ci-job-timing.cjs +72 -0
- package/scripts/lib/cli-exit.cjs +546 -44
- package/scripts/lib/drift-scan.cjs +32 -2
- package/scripts/lib/exit-code-registry.cjs +98 -0
- package/scripts/lib/ndjson-reporter.cjs +119 -0
- package/scripts/lib/shellcheck-fetch.cjs +247 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
- package/scripts/lint-docs-guard-registration.cjs +495 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +198 -0
- package/scripts/lint-eslint-glob-coverage.allowlist.json +4 -0
- package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
- package/scripts/lint-health-diagnostic-rule-table.cjs +65 -8
- package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
- package/scripts/lint-phase-enumeration-drift.cjs +45 -14
- package/scripts/lint-phase-id-drift.cjs +133 -8
- package/scripts/lint-planning-prompt-drift.cjs +38 -1
- package/scripts/lint-portable-grep.cjs +176 -0
- package/scripts/lint-removed-but-needed.cjs +184 -16
- package/scripts/lint-response-language-coverage.cjs +524 -0
- package/scripts/lint-seam-enforcement.cjs +182 -0
- package/scripts/lint-slug-derivation-drift.cjs +921 -0
- package/scripts/lint-source-test-name-collision.cjs +241 -0
- package/scripts/lint-state-write-path-drift.cjs +337 -432
- package/scripts/lint-test-file-count.allowlist.json +124 -4
- package/scripts/lint-test-file-count.cjs +25 -3
- package/scripts/lint-unreachable-guard-drift.cjs +51 -64
- package/scripts/lint-vendored-deps.cjs +208 -35
- package/scripts/lint-workflow-shellcheck-baseline.json +1027 -0
- package/scripts/lint-workflow-shellcheck.cjs +614 -0
- package/scripts/mutation-matrix.cjs +599 -50
- package/scripts/npm-audit-baseline.cjs +376 -0
- package/scripts/prompt-injection-scan.sh +83 -14
- package/scripts/require-issue-link-policy.cjs +16 -1
- package/scripts/secret-scan.sh +75 -13
- package/scripts/select-docs-guards.cjs +56 -0
- package/scripts/sync-runtime-launcher.cjs +22 -3
- package/skills/gsd-discuss-phase/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -1
- package/skills/gsd-import/SKILL.md +1 -1
- package/skills/gsd-ns-workflow/SKILL.md +1 -0
- package/skills/gsd-phase/SKILL.md +1 -1
- package/skills/gsd-quick/SKILL.md +8 -4
- package/skills/gsd-quick-batch/SKILL.md +105 -0
- package/skills/gsd-surface/SKILL.md +18 -8
- package/vscode/package.json +1 -1
- package/bin/lib/ui-safety-gate.cjs +0 -109
- package/scripts/lint-emitted-drift-ack.cjs +0 -344
- package/scripts/state-write-path-drift-baseline.json +0 -19
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* #4196: npm-audit baseline diff.
|
|
6
|
+
*
|
|
7
|
+
* The #3588 gate (tests/npm-integrity-gate.test.cjs) used to fail on ANY
|
|
8
|
+
* advisory present in the production tree, regardless of whether the PR
|
|
9
|
+
* being checked introduced it. Because npm's advisory database updates
|
|
10
|
+
* continuously and independently of repo state, that made the gate fail
|
|
11
|
+
* for reasons no PR caused -- see #4196 for the incident where PR #4188
|
|
12
|
+
* passed this gate at merge time and failed it ~15 minutes later on the
|
|
13
|
+
* identical commit, purely because a new advisory was disclosed in the
|
|
14
|
+
* interim.
|
|
15
|
+
*
|
|
16
|
+
* This module computes which vulnerable packages are NEW relative to a
|
|
17
|
+
* baseline tree (typically the PR's target branch), so the gate blocks a
|
|
18
|
+
* PR only for advisories it actually introduces -- a pre-existing advisory
|
|
19
|
+
* in an untouched transitive dependency no longer blocks unrelated work.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const fs = require('node:fs');
|
|
23
|
+
const os = require('node:os');
|
|
24
|
+
const path = require('node:path');
|
|
25
|
+
const { execFileSync } = require('node:child_process');
|
|
26
|
+
|
|
27
|
+
// Per-attempt timeout. 60s comfortably covers the worst *working* latency
|
|
28
|
+
// measured against this endpoint (43.41s, see #4260) while staying well
|
|
29
|
+
// under the old single-shot 180s budget once multiplied by AUDIT_MAX_ATTEMPTS.
|
|
30
|
+
const AUDIT_ATTEMPT_TIMEOUT_MS = 60_000;
|
|
31
|
+
// Bounded retry, not unbounded -- a registry-side outage must still fail the
|
|
32
|
+
// gate eventually (see #4260's own caveat: silently disarming a required
|
|
33
|
+
// security check on a transport error is worse than failing it).
|
|
34
|
+
const AUDIT_MAX_ATTEMPTS = 3;
|
|
35
|
+
// Exponential backoff base between attempts (2s, 4s for attempts 1->2, 2->3).
|
|
36
|
+
const AUDIT_BACKOFF_BASE_MS = 2_000;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Synchronous sleep (execFileSync-based retry logic is itself synchronous,
|
|
40
|
+
* so backoff between attempts must be too). Uses Atomics.wait on a throwaway
|
|
41
|
+
* SharedArrayBuffer, the standard Node pattern for a blocking sleep with no
|
|
42
|
+
* external dependency.
|
|
43
|
+
*/
|
|
44
|
+
function sleepSyncMs(ms) {
|
|
45
|
+
const sab = new SharedArrayBuffer(4);
|
|
46
|
+
Atomics.wait(new Int32Array(sab), 0, 0, ms);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* True when `error` represents a child process execFileSync killed via its
|
|
51
|
+
* `timeout` option (e.g. AUDIT_ATTEMPT_TIMEOUT_MS firing against a
|
|
52
|
+
* slow/degraded npm registry) rather than one that exited normally with a
|
|
53
|
+
* non-zero code. Node sets `killed: true` and `signal` to the kill signal in
|
|
54
|
+
* that case (see the child_process docs for execFileSync's `timeout`
|
|
55
|
+
* option); a normal "npm audit found advisories" exit has neither set. The
|
|
56
|
+
* distinction matters because a killed process's stdout is truncated
|
|
57
|
+
* mid-write, not complete JSON -- treating it as recoverable JSON produces a
|
|
58
|
+
* misleading `Unexpected end of JSON input` instead of naming the real
|
|
59
|
+
* cause.
|
|
60
|
+
*/
|
|
61
|
+
function isTimeoutKill(error) {
|
|
62
|
+
return Boolean(error && (error.killed === true || error.signal));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Builds the standard timeout-kill error message, shared by every caller
|
|
67
|
+
* that classifies a killed npm audit process after exhausting retries (see
|
|
68
|
+
* isTimeoutKill, runNpmAuditWithRetry) -- kept in one place so the message
|
|
69
|
+
* can't independently drift between callers, per this repo's Generative Fix
|
|
70
|
+
* Divergence anti-pattern.
|
|
71
|
+
*
|
|
72
|
+
* Includes whatever `error.stderr` execFileSync captured before the last
|
|
73
|
+
* kill (Node buffers stdout/stderr from a killed child in-memory and
|
|
74
|
+
* attaches them to the thrown error; without surfacing it here that data
|
|
75
|
+
* was simply discarded, giving zero diagnostic signal into WHY npm was
|
|
76
|
+
* still running when the timeout fired).
|
|
77
|
+
*/
|
|
78
|
+
function buildTimeoutKillError(cwd, error, attempts = 1) {
|
|
79
|
+
const stderr = error && error.stderr
|
|
80
|
+
? (Buffer.isBuffer(error.stderr) ? error.stderr.toString('utf-8') : String(error.stderr))
|
|
81
|
+
: '';
|
|
82
|
+
const stderrSuffix = stderr.trim()
|
|
83
|
+
? `\n\nCaptured stderr before the last kill:\n${stderr.trim().slice(0, 2000)}`
|
|
84
|
+
: '\n\n(no stderr was captured before the last kill)';
|
|
85
|
+
const attemptsPhrase = attempts > 1
|
|
86
|
+
? `after ${attempts} attempts (each up to ${AUDIT_ATTEMPT_TIMEOUT_MS}ms, with exponential backoff between)`
|
|
87
|
+
: `after ${AUDIT_ATTEMPT_TIMEOUT_MS}ms`;
|
|
88
|
+
return new Error(
|
|
89
|
+
`npm audit timed out ${attemptsPhrase} in ${cwd} -- this is not a JSON ` +
|
|
90
|
+
`parse failure, npm audit did not finish. The npm registry's advisories endpoint ` +
|
|
91
|
+
`may be degraded; check https://status.npmjs.org before assuming a code regression.` +
|
|
92
|
+
stderrSuffix,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const AUDIT_DIFF_REASON = Object.freeze({
|
|
97
|
+
OK_NO_NEW_VULNERABILITIES: 'ok_no_new_vulnerabilities',
|
|
98
|
+
FAIL_NEW_VULNERABLE_PACKAGE: 'fail_new_vulnerable_package',
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Pure diff: which package names are vulnerable in `headVulnerabilities`
|
|
103
|
+
* but were NOT already vulnerable in `baselineVulnerabilities`.
|
|
104
|
+
*
|
|
105
|
+
* Both args are the `.vulnerabilities` object from `npm audit --json`
|
|
106
|
+
* (keyed by package name). Matched by package NAME only -- not by the
|
|
107
|
+
* specific advisory ID or severity -- so a package that stays vulnerable
|
|
108
|
+
* across a *different* newly-disclosed advisory for the same package is
|
|
109
|
+
* still "pre-existing", not new. A package whose vulnerability *worsens*
|
|
110
|
+
* while remaining the same package name is deliberately NOT flagged here;
|
|
111
|
+
* that tradeoff keeps the predicate simple and matched to the #4196
|
|
112
|
+
* incident shape (a transitive dependency neither side of the diff
|
|
113
|
+
* touched). Severity escalation on an already-known-vulnerable package is
|
|
114
|
+
* exactly the kind of thing the scheduled Dependabot channel should catch
|
|
115
|
+
* instead (see #4196, PR #4200's auto-merge workflow).
|
|
116
|
+
*/
|
|
117
|
+
function diffNewVulnerablePackages(baselineVulnerabilities, headVulnerabilities) {
|
|
118
|
+
const baselineNames = new Set(Object.keys(baselineVulnerabilities || {}));
|
|
119
|
+
return Object.keys(headVulnerabilities || {}).filter((name) => !baselineNames.has(name));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Typed verdict wrapping diffNewVulnerablePackages. `ok: false` means the
|
|
124
|
+
* diff (head vs baseline) is non-empty -- this PR/push introduced at
|
|
125
|
+
* least one newly-vulnerable package.
|
|
126
|
+
*/
|
|
127
|
+
function evaluateAuditDiff({ baselineVulnerabilities, headVulnerabilities }) {
|
|
128
|
+
const newlyIntroduced = diffNewVulnerablePackages(baselineVulnerabilities, headVulnerabilities);
|
|
129
|
+
if (newlyIntroduced.length > 0) {
|
|
130
|
+
return { ok: false, reason: AUDIT_DIFF_REASON.FAIL_NEW_VULNERABLE_PACKAGE, newlyIntroduced };
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
ok: true,
|
|
134
|
+
reason: AUDIT_DIFF_REASON.OK_NO_NEW_VULNERABILITIES,
|
|
135
|
+
preExisting: Object.keys(headVulnerabilities || {}),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Runs `npm audit <args> --json` in `cwd` with bounded retry + exponential
|
|
141
|
+
* backoff. A per-attempt timeout-kill (see isTimeoutKill) is retried rather
|
|
142
|
+
* than failed immediately -- #4260: the audit backend has real, independent
|
|
143
|
+
* latency variance from the rest of the registry (measured: 43.41s vs 0.20s
|
|
144
|
+
* for a plain registry fetch, and two outright non-responses in the same
|
|
145
|
+
* window), and a single 180s attempt with no retry meant any one bad moment
|
|
146
|
+
* failed a REQUIRED gate on a transport hiccup, not a real advisory. A
|
|
147
|
+
* non-timeout failure (e.g. a genuine non-zero exit with recoverable stdout,
|
|
148
|
+
* or an unrecoverable error) is NOT retried -- only a confirmed timeout-kill
|
|
149
|
+
* is, since retrying a deterministic failure wastes the budget without
|
|
150
|
+
* changing the outcome.
|
|
151
|
+
*
|
|
152
|
+
* Exhausting all attempts still fails the gate (see #4260's own caveat:
|
|
153
|
+
* silently skipping a required security check on a transport error is a
|
|
154
|
+
* worse failure mode than occasionally re-running CI).
|
|
155
|
+
*/
|
|
156
|
+
function runNpmAuditWithRetry(cwd, args, { execFileSyncImpl = execFileSync, sleepImpl = sleepSyncMs } = {}) {
|
|
157
|
+
const isWindows = process.platform === 'win32';
|
|
158
|
+
const npmCandidates = isWindows ? ['npm.cmd', 'npm'] : ['npm'];
|
|
159
|
+
let lastTimeoutError = null;
|
|
160
|
+
|
|
161
|
+
for (let attempt = 1; attempt <= AUDIT_MAX_ATTEMPTS; attempt += 1) {
|
|
162
|
+
let out;
|
|
163
|
+
let timedOutThisAttempt = false;
|
|
164
|
+
let lastErr = null;
|
|
165
|
+
|
|
166
|
+
for (const npmCmd of npmCandidates) {
|
|
167
|
+
try {
|
|
168
|
+
out = execFileSyncImpl(npmCmd, args, {
|
|
169
|
+
cwd,
|
|
170
|
+
encoding: 'utf-8',
|
|
171
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
172
|
+
timeout: AUDIT_ATTEMPT_TIMEOUT_MS,
|
|
173
|
+
shell: isWindows,
|
|
174
|
+
});
|
|
175
|
+
lastErr = null;
|
|
176
|
+
break;
|
|
177
|
+
} catch (e) {
|
|
178
|
+
if (isTimeoutKill(e)) {
|
|
179
|
+
timedOutThisAttempt = true;
|
|
180
|
+
lastErr = e;
|
|
181
|
+
break; // do not burn the alt npm-candidate slot on a timeout; go to the next retry attempt instead
|
|
182
|
+
}
|
|
183
|
+
// `npm audit` exits non-zero when advisories are present; the JSON is
|
|
184
|
+
// still on stdout in that case. Recover and let the caller classify —
|
|
185
|
+
// but only when stdout actually CARRIES the JSON: an audit that was
|
|
186
|
+
// killed or aborted can exit non-zero with EMPTY stdout, and accepting
|
|
187
|
+
// the empty string here surfaces as a bare `SyntaxError: Unexpected
|
|
188
|
+
// end of JSON input` at the parse below, hiding the captured error
|
|
189
|
+
// (observed on CI 2026-09-03, both lanes; cause undetermined).
|
|
190
|
+
const recovered = e && typeof e.stdout !== 'undefined' && e.stdout !== null
|
|
191
|
+
? (Buffer.isBuffer(e.stdout) ? e.stdout.toString('utf-8') : String(e.stdout))
|
|
192
|
+
: '';
|
|
193
|
+
if (recovered.trim()) {
|
|
194
|
+
out = recovered;
|
|
195
|
+
lastErr = null;
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
lastErr = e;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (timedOutThisAttempt) {
|
|
203
|
+
lastTimeoutError = lastErr;
|
|
204
|
+
if (attempt < AUDIT_MAX_ATTEMPTS) {
|
|
205
|
+
sleepImpl(AUDIT_BACKOFF_BASE_MS * (2 ** (attempt - 1)));
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
throw buildTimeoutKillError(cwd, lastTimeoutError, attempt);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (lastErr) throw lastErr; // non-timeout failure: fail immediately, no retry
|
|
212
|
+
|
|
213
|
+
const parsed = JSON.parse(out);
|
|
214
|
+
if (parsed && parsed.metadata && parsed.metadata.vulnerabilities) {
|
|
215
|
+
return parsed;
|
|
216
|
+
}
|
|
217
|
+
throw new Error(`Unexpected npm audit JSON shape in ${cwd}: missing metadata.vulnerabilities`);
|
|
218
|
+
}
|
|
219
|
+
// Unreachable (the loop always returns or throws), but keep a fallback
|
|
220
|
+
// throw so a future refactor mistake fails loudly instead of returning
|
|
221
|
+
// undefined.
|
|
222
|
+
throw buildTimeoutKillError(cwd, lastTimeoutError, AUDIT_MAX_ATTEMPTS);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Runs `npm audit --package-lock-only --omit=dev --json` in `cwd` and
|
|
227
|
+
* returns the parsed JSON, or `null` if `cwd` has no package.json or no
|
|
228
|
+
* package-lock.json (not an auditable tree -- callers treat this as
|
|
229
|
+
* "skip"/"no baseline available", not an error).
|
|
230
|
+
*
|
|
231
|
+
* `--package-lock-only` deliberately avoids requiring `node_modules/` to
|
|
232
|
+
* be installed: it lets a baseline tree be audited from nothing but an
|
|
233
|
+
* extracted package.json + package-lock.json (see extractBaselineTree),
|
|
234
|
+
* without a second full `npm ci`.
|
|
235
|
+
*/
|
|
236
|
+
function runPackageLockAudit(cwd, opts = {}) {
|
|
237
|
+
if (!fs.existsSync(path.join(cwd, 'package.json'))) return null;
|
|
238
|
+
if (!fs.existsSync(path.join(cwd, 'package-lock.json'))) return null;
|
|
239
|
+
return runNpmAuditWithRetry(cwd, ['audit', '--package-lock-only', '--omit=dev', '--json'], opts);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Runs `npm audit --omit=dev --json` against the REAL installed node_modules
|
|
244
|
+
* tree in `cwd` (unlike runPackageLockAudit's --package-lock-only mode,
|
|
245
|
+
* which only needs package-lock.json on disk). Returns `null` if `cwd` has
|
|
246
|
+
* no package.json or no node_modules/ (not an auditable/installed tree --
|
|
247
|
+
* callers treat this as "skip", not an error).
|
|
248
|
+
*/
|
|
249
|
+
function runInstalledTreeAudit(cwd, opts = {}) {
|
|
250
|
+
if (!fs.existsSync(path.join(cwd, 'package.json'))) return null;
|
|
251
|
+
if (!fs.existsSync(path.join(cwd, 'node_modules'))) return null;
|
|
252
|
+
return runNpmAuditWithRetry(cwd, ['audit', '--omit=dev', '--json'], opts);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Extracts package.json + package-lock.json (optionally under `subdir`,
|
|
257
|
+
* e.g. 'sdk') from `ref` at git object level into a fresh temp directory --
|
|
258
|
+
* no working-tree checkout, no `node_modules` install. Returns the temp
|
|
259
|
+
* dir path, or `null` if `ref` cannot be resolved locally (e.g. a shallow
|
|
260
|
+
* clone that never fetched it) or either file is absent at that ref --
|
|
261
|
+
* callers treat `null` as "no baseline available", not an error.
|
|
262
|
+
*/
|
|
263
|
+
function extractBaselineTree(ref, repoRoot, subdir = '') {
|
|
264
|
+
const rel = (name) => (subdir ? path.posix.join(subdir, name) : name);
|
|
265
|
+
let pkgJson;
|
|
266
|
+
let lockJson;
|
|
267
|
+
try {
|
|
268
|
+
pkgJson = execFileSync('git', ['show', `${ref}:${rel('package.json')}`], {
|
|
269
|
+
cwd: repoRoot,
|
|
270
|
+
encoding: 'utf-8',
|
|
271
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
272
|
+
});
|
|
273
|
+
lockJson = execFileSync('git', ['show', `${ref}:${rel('package-lock.json')}`], {
|
|
274
|
+
cwd: repoRoot,
|
|
275
|
+
encoding: 'utf-8',
|
|
276
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
277
|
+
});
|
|
278
|
+
} catch {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-audit-baseline-'));
|
|
282
|
+
fs.writeFileSync(path.join(dir, 'package.json'), pkgJson);
|
|
283
|
+
fs.writeFileSync(path.join(dir, 'package-lock.json'), lockJson);
|
|
284
|
+
return dir;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// All-zeros is git's documented sentinel for "this ref did not exist
|
|
288
|
+
// before this push" (a brand-new branch's first push) -- never a real
|
|
289
|
+
// commit to diff against.
|
|
290
|
+
const NULL_SHA = '0000000000000000000000000000000000000000';
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Resolves the git ref to diff against, in priority order:
|
|
294
|
+
* 1. AUDIT_BASELINE_REF env var -- the primary mechanism. CI sets this
|
|
295
|
+
* explicitly (see .github/workflows/test.yml) to github.event.pull_
|
|
296
|
+
* request.base.sha on a pull_request event, or github.event.before on
|
|
297
|
+
* a push event -- both pinned, race-free values Git/GitHub track for
|
|
298
|
+
* exactly this purpose. Prefer this over anything below whenever the
|
|
299
|
+
* caller can provide it.
|
|
300
|
+
* 2. GITHUB_BASE_REF (GitHub Actions sets this on pull_request events)
|
|
301
|
+
* resolved against the LIVE origin/<branch> tip. Only reached if
|
|
302
|
+
* AUDIT_BASELINE_REF wasn't set -- e.g. a workflow that forgot to
|
|
303
|
+
* wire it. origin/<branch> can advance mid-run (see the GSD_EMITTED_
|
|
304
|
+
* BASE precedent in test.yml), so this is a degraded fallback, not
|
|
305
|
+
* the intended path for pull_request events in this repo's own CI.
|
|
306
|
+
* 3. On a `push` event, `HEAD~1` -- correct ONLY when the push added
|
|
307
|
+
* exactly one commit (true for a squash-merge or a single ordinary
|
|
308
|
+
* commit). This repo also allows rebase-merge (allow_rebase_merge:
|
|
309
|
+
* true), which can land a PR as several discrete commits in one
|
|
310
|
+
* push -- HEAD~1 then lands on an EARLIER commit in the same push,
|
|
311
|
+
* which may already contain a vulnerable package that commit itself
|
|
312
|
+
* introduced, silently marking it "pre-existing". CI never reaches
|
|
313
|
+
* this branch (AUDIT_BASELINE_REF is always set by test.yml for
|
|
314
|
+
* push events); it exists only for out-of-band invocations (e.g.
|
|
315
|
+
* gsd-test) that don't set any of the above.
|
|
316
|
+
* 4. `origin/next`, else a plain local branch named `next` (gsd-test's
|
|
317
|
+
* sandbox fetches the base as a local branch, not a remote-tracking
|
|
318
|
+
* ref -- see gsd-test-merges-into-LOCAL-base-branch in this repo's
|
|
319
|
+
* own operational notes), if either exists locally (this repo's
|
|
320
|
+
* integration branch -- matches DEFAULT_BASE in
|
|
321
|
+
* scripts/changeset/lint.cjs).
|
|
322
|
+
* Returns '' if none resolve -- callers fall back to strict zero-tolerance
|
|
323
|
+
* rather than silently skipping the gate.
|
|
324
|
+
*/
|
|
325
|
+
function resolveBaselineRef(repoRoot) {
|
|
326
|
+
if (process.env.AUDIT_BASELINE_REF) return process.env.AUDIT_BASELINE_REF;
|
|
327
|
+
if (process.env.GITHUB_BASE_REF) return `origin/${process.env.GITHUB_BASE_REF}`;
|
|
328
|
+
if (process.env.GITHUB_EVENT_NAME === 'push') {
|
|
329
|
+
try {
|
|
330
|
+
const parent = execFileSync('git', ['rev-parse', '--verify', 'HEAD~1'], {
|
|
331
|
+
cwd: repoRoot,
|
|
332
|
+
encoding: 'utf-8',
|
|
333
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
334
|
+
}).trim();
|
|
335
|
+
if (parent && parent !== NULL_SHA) return parent;
|
|
336
|
+
} catch {
|
|
337
|
+
// not enough history; fall through
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
try {
|
|
341
|
+
execFileSync('git', ['rev-parse', '--verify', 'origin/next'], {
|
|
342
|
+
cwd: repoRoot,
|
|
343
|
+
stdio: 'ignore',
|
|
344
|
+
});
|
|
345
|
+
return 'origin/next';
|
|
346
|
+
} catch {
|
|
347
|
+
// fall through to a plain local branch
|
|
348
|
+
}
|
|
349
|
+
try {
|
|
350
|
+
execFileSync('git', ['rev-parse', '--verify', 'next'], {
|
|
351
|
+
cwd: repoRoot,
|
|
352
|
+
stdio: 'ignore',
|
|
353
|
+
});
|
|
354
|
+
return 'next';
|
|
355
|
+
} catch {
|
|
356
|
+
return '';
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
module.exports = {
|
|
361
|
+
AUDIT_DIFF_REASON,
|
|
362
|
+
diffNewVulnerablePackages,
|
|
363
|
+
evaluateAuditDiff,
|
|
364
|
+
runPackageLockAudit,
|
|
365
|
+
runInstalledTreeAudit,
|
|
366
|
+
runNpmAuditWithRetry,
|
|
367
|
+
extractBaselineTree,
|
|
368
|
+
resolveBaselineRef,
|
|
369
|
+
isTimeoutKill,
|
|
370
|
+
buildTimeoutKillError,
|
|
371
|
+
sleepSyncMs,
|
|
372
|
+
AUDIT_ATTEMPT_TIMEOUT_MS,
|
|
373
|
+
AUDIT_MAX_ATTEMPTS,
|
|
374
|
+
AUDIT_BACKOFF_BASE_MS,
|
|
375
|
+
NULL_SHA,
|
|
376
|
+
};
|
|
@@ -6,12 +6,29 @@
|
|
|
6
6
|
# scripts/prompt-injection-scan.sh --file path/to/file # Scan a single file
|
|
7
7
|
# scripts/prompt-injection-scan.sh --dir agents/ # Scan all files in a directory
|
|
8
8
|
#
|
|
9
|
-
# Exit codes:
|
|
10
|
-
# 0
|
|
11
|
-
# 1
|
|
12
|
-
#
|
|
9
|
+
# Exit codes (ADR-3889, #3908 — registered in gsd-core/bin/shared/exit-codes.json):
|
|
10
|
+
# 0 = clean
|
|
11
|
+
# 1 = findings detected
|
|
12
|
+
# $EXIT_USAGE (64) = usage error (bad argv, missing --file/--dir target)
|
|
13
|
+
# $EXIT_NO_INPUT (66) = ran; scope established; zero files in scope (genuinely empty)
|
|
14
|
+
# $EXIT_UNAVAILABLE (69) = could not establish scope (bad ref, not a repo, unreadable dir)
|
|
13
15
|
set -euo pipefail
|
|
14
16
|
|
|
17
|
+
# ─── Exit-code registry (ADR-3889, #3908) ────────────────────────────────────
|
|
18
|
+
# Resolved relative to THIS script's location, not the caller's cwd. Loud,
|
|
19
|
+
# non-zero failure if the fragment is missing — never fall back to a guessed
|
|
20
|
+
# literal integer, and never let a missing registry silently degrade to
|
|
21
|
+
# exit 0.
|
|
22
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
23
|
+
EXIT_CODES_SH="$SCRIPT_DIR/../gsd-core/bin/shared/exit-codes.sh"
|
|
24
|
+
if [[ ! -f "$EXIT_CODES_SH" ]]; then
|
|
25
|
+
echo "prompt-injection-scan: FATAL: exit-code registry not found at $EXIT_CODES_SH" >&2
|
|
26
|
+
echo " Regenerate with: node scripts/gen-exit-code-registry.cjs --write" >&2
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
29
|
+
# shellcheck disable=SC1090
|
|
30
|
+
. "$EXIT_CODES_SH"
|
|
31
|
+
|
|
15
32
|
# ─── Patterns ────────────────────────────────────────────────────────────────
|
|
16
33
|
# Each pattern is a POSIX extended regex. Keep alphabetized by category.
|
|
17
34
|
#
|
|
@@ -154,6 +171,14 @@ ALLOWLIST=(
|
|
|
154
171
|
# allowlist cannot reach it either, because the literal `child_process` is
|
|
155
172
|
# not adjacent to `.exec`. See the note at the pattern itself.
|
|
156
173
|
'tests/continuation-grammar-parity.test.cjs'
|
|
174
|
+
# #3676 row 11b — quick-batch's task-list parser must treat a
|
|
175
|
+
# prompt-injection-shaped task description as inert data, never
|
|
176
|
+
# interpreted. The fixture has to be a real "ignore all previous
|
|
177
|
+
# instructions…" phrase or the test asserts nothing: it is the payload the
|
|
178
|
+
# parser is required to carry byte-for-byte through createBatch and STATE
|
|
179
|
+
# rendering, never a command. Same DEFECT.PROMPT-INJECTION-SCAN-COLLISION
|
|
180
|
+
# class as the input-validator fixtures above.
|
|
181
|
+
'tests/quick-batch.test.cjs'
|
|
157
182
|
)
|
|
158
183
|
|
|
159
184
|
is_allowlisted() {
|
|
@@ -175,33 +200,73 @@ collect_files() {
|
|
|
175
200
|
case "$mode" in
|
|
176
201
|
--diff)
|
|
177
202
|
local base="${1:-origin/main}"
|
|
178
|
-
#
|
|
179
|
-
|
|
180
|
-
|
|
203
|
+
# Run git separately from the filter pipe so its OWN exit status (not
|
|
204
|
+
# grep's) decides whether the diff could be established. stdout and
|
|
205
|
+
# stderr are captured SEPARATELY (never merged with `2>&1`) so that a
|
|
206
|
+
# warning git writes to stderr on an otherwise successful diff can
|
|
207
|
+
# never be mistaken for a filename in the file list. On failure the
|
|
208
|
+
# captured stderr is emitted as the diagnostic; on success it is
|
|
209
|
+
# forwarded as a warning, never folded into the file list.
|
|
210
|
+
# `|| true` on the filter below is CORRECT (not gratuitous): `grep -E`
|
|
211
|
+
# exits 1 when nothing matches the scannable extensions (e.g. a diff
|
|
212
|
+
# touching only non-scannable file types), which is a legitimate empty
|
|
213
|
+
# result, not a failure to run.
|
|
214
|
+
local raw status err_file
|
|
215
|
+
err_file=$(mktemp)
|
|
216
|
+
raw=$(git diff --name-only --diff-filter=ACMR "$base"...HEAD 2>"$err_file")
|
|
217
|
+
status=$?
|
|
218
|
+
if (( status != 0 )); then
|
|
219
|
+
cat "$err_file" >&2
|
|
220
|
+
rm -f "$err_file"
|
|
221
|
+
exit "$EXIT_UNAVAILABLE"
|
|
222
|
+
fi
|
|
223
|
+
if [[ -s "$err_file" ]]; then
|
|
224
|
+
echo "Warning: git diff emitted stderr output:" >&2
|
|
225
|
+
cat "$err_file" >&2
|
|
226
|
+
fi
|
|
227
|
+
rm -f "$err_file"
|
|
228
|
+
printf '%s\n' "$raw" | grep -E '\.(md|cjs|js|json|yml|yaml|sh)$' || true
|
|
181
229
|
;;
|
|
182
230
|
--file)
|
|
183
231
|
if [[ -f "$1" ]]; then
|
|
184
232
|
echo "$1"
|
|
185
233
|
else
|
|
186
234
|
echo "Error: file not found: $1" >&2
|
|
187
|
-
exit
|
|
235
|
+
exit "$EXIT_USAGE"
|
|
188
236
|
fi
|
|
189
237
|
;;
|
|
190
238
|
--dir)
|
|
191
239
|
local dir="$1"
|
|
192
240
|
if [[ ! -d "$dir" ]]; then
|
|
193
241
|
echo "Error: directory not found: $dir" >&2
|
|
194
|
-
exit
|
|
242
|
+
exit "$EXIT_USAGE"
|
|
243
|
+
fi
|
|
244
|
+
# Same treatment as --diff: a `find` that fails (e.g. permission
|
|
245
|
+
# denied) must not be reported as an empty directory, and stdout/stderr
|
|
246
|
+
# are captured separately so a stderr warning never enters the file list.
|
|
247
|
+
local raw status err_file
|
|
248
|
+
err_file=$(mktemp)
|
|
249
|
+
raw=$(find "$dir" -type f \( -name '*.md' -o -name '*.cjs' -o -name '*.js' -o -name '*.json' -o -name '*.yml' -o -name '*.yaml' -o -name '*.sh' \) \
|
|
250
|
+
! -path '*/node_modules/*' ! -path '*/.git/*' ! -path '*/dist/*' 2>"$err_file")
|
|
251
|
+
status=$?
|
|
252
|
+
if (( status != 0 )); then
|
|
253
|
+
cat "$err_file" >&2
|
|
254
|
+
rm -f "$err_file"
|
|
255
|
+
exit "$EXIT_UNAVAILABLE"
|
|
256
|
+
fi
|
|
257
|
+
if [[ -s "$err_file" ]]; then
|
|
258
|
+
echo "Warning: find emitted stderr output:" >&2
|
|
259
|
+
cat "$err_file" >&2
|
|
195
260
|
fi
|
|
196
|
-
|
|
197
|
-
|
|
261
|
+
rm -f "$err_file"
|
|
262
|
+
printf '%s\n' "$raw"
|
|
198
263
|
;;
|
|
199
264
|
--stdin)
|
|
200
265
|
cat
|
|
201
266
|
;;
|
|
202
267
|
*)
|
|
203
268
|
echo "Usage: $0 --diff [base] | --file <path> | --dir <path> | --stdin" >&2
|
|
204
|
-
exit
|
|
269
|
+
exit "$EXIT_USAGE"
|
|
205
270
|
;;
|
|
206
271
|
esac
|
|
207
272
|
}
|
|
@@ -240,7 +305,7 @@ scan_file() {
|
|
|
240
305
|
main() {
|
|
241
306
|
if [[ $# -eq 0 ]]; then
|
|
242
307
|
echo "Usage: $0 --diff [base] | --file <path> | --dir <path>" >&2
|
|
243
|
-
exit
|
|
308
|
+
exit "$EXIT_USAGE"
|
|
244
309
|
fi
|
|
245
310
|
|
|
246
311
|
local mode="$1"
|
|
@@ -250,8 +315,12 @@ main() {
|
|
|
250
315
|
files=$(collect_files "$mode" "$@")
|
|
251
316
|
|
|
252
317
|
if [[ -z "$files" ]]; then
|
|
318
|
+
# collect_files already exited (UNAVAILABLE/USAGE) for anything that
|
|
319
|
+
# could not establish scope. Reaching here with an empty result means
|
|
320
|
+
# scope WAS established and is genuinely empty — that is NO_INPUT, not a
|
|
321
|
+
# silent clean pass.
|
|
253
322
|
echo "prompt-injection-scan: no files to scan"
|
|
254
|
-
exit
|
|
323
|
+
exit "$EXIT_NO_INPUT"
|
|
255
324
|
fi
|
|
256
325
|
|
|
257
326
|
local total=0
|
|
@@ -30,6 +30,7 @@ const { runMain } = require('./lib/cli-exit.cjs');
|
|
|
30
30
|
const ISSUE_LINK_REASON = Object.freeze({
|
|
31
31
|
OK_CLOSING_KEYWORD: 'ok_closing_keyword',
|
|
32
32
|
OK_BACKMERGE_EXEMPT: 'ok_backmerge_exempt',
|
|
33
|
+
OK_DEPENDABOT_EXEMPT: 'ok_dependabot_exempt',
|
|
33
34
|
OK_FOLLOWUP_REFERENCE: 'ok_followup_reference',
|
|
34
35
|
FAIL_NO_ISSUE_REFERENCE: 'fail_no_issue_reference',
|
|
35
36
|
FAIL_REFERENCE_NEEDS_CLOSING: 'fail_reference_needs_closing',
|
|
@@ -41,6 +42,14 @@ const ISSUE_LINK_REASON = Object.freeze({
|
|
|
41
42
|
// ONLY when combined with `sameRepo === true` below (see header comment).
|
|
42
43
|
const BACKMERGE_BRANCH_PREFIX = 'chore/backmerge-main-to-next-';
|
|
43
44
|
|
|
45
|
+
// #4196: Dependabot has no mechanism to link a PR it opens to a repo issue —
|
|
46
|
+
// its alerts live in the Security tab, not as issues, so there is nothing
|
|
47
|
+
// for it to reference. `pr.user.login` is authenticated by GitHub (not
|
|
48
|
+
// forgeable by a crafted title/branch), so this is safe without a sameRepo
|
|
49
|
+
// conjunct: no external actor can make GitHub report this login for a PR
|
|
50
|
+
// they opened.
|
|
51
|
+
const DEPENDABOT_LOGIN = 'dependabot[bot]';
|
|
52
|
+
|
|
44
53
|
// A follow-up-only PR (references an issue without closing it) is only
|
|
45
54
|
// allowed to skip the closing keyword when every changed file is a test or
|
|
46
55
|
// doc file — i.e. it cannot be the PR that actually implements the fix.
|
|
@@ -122,7 +131,11 @@ function allPathsAreTestsOrDocs(changedFiles) {
|
|
|
122
131
|
});
|
|
123
132
|
}
|
|
124
133
|
|
|
125
|
-
function evaluateIssueLink({ prBody, headRef, sameRepo, changedFiles, changedFilesTotal }) {
|
|
134
|
+
function evaluateIssueLink({ prBody, headRef, sameRepo, authorLogin, changedFiles, changedFilesTotal }) {
|
|
135
|
+
if (authorLogin === DEPENDABOT_LOGIN) {
|
|
136
|
+
return { ok: true, reason: ISSUE_LINK_REASON.OK_DEPENDABOT_EXEMPT };
|
|
137
|
+
}
|
|
138
|
+
|
|
126
139
|
if (hasClosingKeyword(prBody)) {
|
|
127
140
|
return { ok: true, reason: ISSUE_LINK_REASON.OK_CLOSING_KEYWORD };
|
|
128
141
|
}
|
|
@@ -159,6 +172,7 @@ function main() {
|
|
|
159
172
|
prBody: process.env.PR_BODY || '',
|
|
160
173
|
headRef: process.env.HEAD_REF || '',
|
|
161
174
|
sameRepo: process.env.SAME_REPO === 'true',
|
|
175
|
+
authorLogin: process.env.PR_AUTHOR_LOGIN || '',
|
|
162
176
|
changedFiles,
|
|
163
177
|
changedFilesTotal,
|
|
164
178
|
});
|
|
@@ -179,6 +193,7 @@ if (require.main === module) runMain(main);
|
|
|
179
193
|
module.exports = {
|
|
180
194
|
ISSUE_LINK_REASON,
|
|
181
195
|
BACKMERGE_BRANCH_PREFIX,
|
|
196
|
+
DEPENDABOT_LOGIN,
|
|
182
197
|
EXEMPT_PATH_PREFIXES,
|
|
183
198
|
EXCLUDED_ROOT_DOCS,
|
|
184
199
|
CLOSING_KEYWORD_REGEX,
|