@opengsd/gsd-core 1.15.0 → 1.17.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 -2
- package/.opencode/plugins/gsd-core.js +336 -6
- package/.opencode/plugins/gsd-statusline/tui.tsx +55 -0
- package/GEMINI.md +1 -1
- package/agents/gsd-code-fixer.compact.md +1 -1
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-code-reviewer.compact.md +5 -3
- package/agents/gsd-code-reviewer.md +8 -6
- package/agents/gsd-debug-session-manager.compact.md +7 -1
- package/agents/gsd-debug-session-manager.md +7 -1
- package/agents/gsd-debugger.md +2 -2
- package/agents/gsd-eval-auditor.compact.md +1 -1
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +4 -4
- package/agents/gsd-intel-updater.compact.md +1 -1
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.md +2 -2
- package/agents/gsd-phase-researcher.md +1 -1
- package/agents/gsd-plan-checker.md +7 -2
- package/agents/gsd-planner.md +3 -3
- package/agents/gsd-project-researcher.compact.md +1 -1
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.compact.md +1 -1
- package/agents/gsd-research-synthesizer.md +1 -1
- package/agents/gsd-ui-auditor.compact.md +21 -30
- package/agents/gsd-ui-auditor.md +42 -51
- package/agents/gsd-ui-researcher.compact.md +1 -1
- package/agents/gsd-ui-researcher.md +1 -1
- package/agents/gsd-verifier.md +31 -9
- package/bin/gsd-mcp-server.js +3 -1
- package/bin/install.js +487 -290
- package/commands/gsd/add-tests.md +6 -1
- package/commands/gsd/ai-integration-phase.md +6 -1
- package/commands/gsd/audit-fix.md +5 -0
- package/commands/gsd/audit-milestone.md +6 -1
- package/commands/gsd/autonomous.md +5 -0
- package/commands/gsd/capture.md +8 -4
- package/commands/gsd/code-review.md +7 -2
- package/commands/gsd/complete-milestone.md +4 -0
- package/commands/gsd/config.md +7 -3
- package/commands/gsd/debug.md +11 -7
- package/commands/gsd/discuss-phase.md +7 -3
- package/commands/gsd/docs-update.md +12 -7
- package/commands/gsd/eval-review.md +6 -1
- package/commands/gsd/execute-phase.md +12 -7
- package/commands/gsd/extract-learnings.md +5 -0
- package/commands/gsd/fast.md +4 -0
- package/commands/gsd/forensics.md +5 -1
- package/commands/gsd/graphify.md +10 -6
- package/commands/gsd/health.md +5 -0
- package/commands/gsd/help.md +7 -2
- package/commands/gsd/import.md +7 -3
- package/commands/gsd/inbox.md +5 -0
- package/commands/gsd/ingest-docs.md +5 -1
- package/commands/gsd/manager.md +6 -1
- package/commands/gsd/map-codebase.md +7 -3
- package/commands/gsd/mempalace-capture.md +5 -1
- package/commands/gsd/mempalace-recall.md +5 -1
- package/commands/gsd/milestone-summary.md +5 -1
- package/commands/gsd/mvp-phase.md +8 -3
- package/commands/gsd/new-milestone.md +6 -1
- package/commands/gsd/new-project.md +5 -0
- package/commands/gsd/next.md +6 -1
- package/commands/gsd/ns-context.md +4 -0
- package/commands/gsd/ns-ideate.md +4 -0
- package/commands/gsd/ns-manage.md +4 -0
- package/commands/gsd/ns-project.md +4 -0
- package/commands/gsd/ns-review.md +4 -0
- package/commands/gsd/ns-workflow.md +4 -0
- package/commands/gsd/onboard.md +6 -1
- package/commands/gsd/pause-work.md +5 -1
- package/commands/gsd/phase.md +8 -4
- package/commands/gsd/plan-phase.md +6 -1
- package/commands/gsd/plan-review-convergence.md +5 -1
- package/commands/gsd/pr-branch.md +4 -0
- package/commands/gsd/profile-user.md +5 -1
- package/commands/gsd/progress.md +6 -1
- package/commands/gsd/quick-batch.md +20 -8
- package/commands/gsd/quick.md +12 -7
- package/commands/gsd/review.md +5 -1
- package/commands/gsd/secure-phase.md +6 -1
- package/commands/gsd/ship.md +5 -0
- package/commands/gsd/sketch.md +7 -2
- package/commands/gsd/spec-phase.md +5 -1
- package/commands/gsd/spike.md +8 -3
- package/commands/gsd/surface.md +5 -1
- package/commands/gsd/thread.md +4 -0
- package/commands/gsd/ui-phase.md +6 -1
- package/commands/gsd/ui-review.md +6 -1
- package/commands/gsd/ultraplan-phase.md +5 -1
- package/commands/gsd/undo.md +5 -1
- package/commands/gsd/update.md +6 -2
- package/commands/gsd/validate-phase.md +6 -1
- package/commands/gsd/verify-work.md +6 -1
- package/commands/gsd/workspace.md +7 -3
- package/gsd-core/bin/gsd-tools.cjs +159 -58
- package/gsd-core/bin/lib/active-workstream-store.cjs +15 -0
- package/gsd-core/bin/lib/adr-parser.cjs +37 -5
- package/gsd-core/bin/lib/agent-install-check.cjs +4 -1
- package/gsd-core/bin/lib/audit.cjs +78 -44
- package/gsd-core/bin/lib/broken-windows.cjs +13 -13
- package/gsd-core/bin/lib/capability-activation.cjs +9 -4
- package/gsd-core/bin/lib/capability-registry.cjs +249 -117
- package/gsd-core/bin/lib/capability-validator.cjs +18 -1
- package/gsd-core/bin/lib/capability-writer.cjs +2 -1
- package/gsd-core/bin/lib/check-auto-mode.cjs +35 -0
- package/gsd-core/bin/lib/check-command-router.cjs +164 -1712
- package/gsd-core/bin/lib/cjs-command-router-adapter.cjs +13 -2
- package/gsd-core/bin/lib/cli-exit.cjs +12 -0
- package/gsd-core/bin/lib/codex-agent-toml.cjs +11 -8
- package/gsd-core/bin/lib/command-aliases.cjs +7 -0
- package/gsd-core/bin/lib/command-routing-hub.cjs +48 -1
- package/gsd-core/bin/lib/commands.cjs +201 -178
- package/gsd-core/bin/lib/complexity-trigger.cjs +8 -7
- package/gsd-core/bin/lib/config.cjs +43 -12
- package/gsd-core/bin/lib/core-utils.cjs +6 -1
- package/gsd-core/bin/lib/coverage.cjs +4 -8
- package/gsd-core/bin/lib/decision-coverage-support.cjs +259 -0
- package/gsd-core/bin/lib/drift.cjs +177 -42
- package/gsd-core/bin/lib/frontmatter-fence.cjs +90 -0
- package/gsd-core/bin/lib/frontmatter-splice.cjs +494 -0
- package/gsd-core/bin/lib/frontmatter.cjs +413 -234
- package/gsd-core/bin/lib/gap-checker.cjs +72 -29
- package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +381 -0
- package/gsd-core/bin/lib/gate-args.cjs +53 -0
- package/gsd-core/bin/lib/gate-codebase-drift.cjs +285 -0
- package/gsd-core/bin/lib/gate-config.cjs +46 -0
- package/gsd-core/bin/lib/gate-context-drift.cjs +142 -0
- package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +169 -0
- package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +126 -0
- package/gsd-core/bin/lib/gate-evaluation-scope.cjs +555 -0
- package/gsd-core/bin/lib/gate-evidence.cjs +138 -0
- package/gsd-core/bin/lib/gate-exit.cjs +27 -0
- package/gsd-core/bin/lib/gate-gap-analysis-plan-post.cjs +61 -0
- package/gsd-core/bin/lib/gate-phase-context.cjs +170 -0
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +1 -1
- package/gsd-core/bin/lib/gate-predicate.cjs +165 -0
- package/gsd-core/bin/lib/gate-prohibition-enforcement.cjs +94 -0
- package/gsd-core/bin/lib/gate-schema-drift.cjs +165 -0
- package/gsd-core/bin/lib/gate-tdd-red-evidence.cjs +100 -0
- package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +182 -0
- package/gsd-core/bin/lib/gate-ui-plan.cjs +86 -0
- package/gsd-core/bin/lib/gate-ui-safety.cjs +81 -0
- package/gsd-core/bin/lib/gate-verdict.cjs +64 -0
- package/gsd-core/bin/lib/gate-verify-command-paths.cjs +78 -0
- package/gsd-core/bin/lib/gate-verify-failure-directions.cjs +41 -0
- package/gsd-core/bin/lib/graphify.cjs +3 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +43 -47
- package/gsd-core/bin/lib/health-diagnostic.cjs +45 -8
- package/gsd-core/bin/lib/init.cjs +194 -108
- package/gsd-core/bin/lib/install-engine.cjs +30 -35
- package/gsd-core/bin/lib/install-profiles.cjs +7 -4
- package/gsd-core/bin/lib/install-shadow-report.cjs +12 -0
- package/gsd-core/bin/lib/installer-migrations/011-opencode-mcp-no-prompts.cjs +99 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +54 -1
- package/gsd-core/bin/lib/io.cjs +122 -3
- package/gsd-core/bin/lib/jsonc-edit.cjs +418 -0
- package/gsd-core/bin/lib/loop-host-contract.cjs +2 -1
- package/gsd-core/bin/lib/loop-resolver.cjs +95 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +75 -1
- package/gsd-core/bin/lib/mcp-server.cjs +5 -1
- package/gsd-core/bin/lib/milestone.cjs +19 -1
- package/gsd-core/bin/lib/model-resolver.cjs +18 -18
- package/gsd-core/bin/lib/observability/event.cjs +1 -1
- package/gsd-core/bin/lib/observability/logger.cjs +46 -1
- package/gsd-core/bin/lib/opencode-mcp-entry.cjs +39 -0
- package/gsd-core/bin/lib/opencode-version.cjs +486 -0
- package/gsd-core/bin/lib/pattern.cjs +10 -0
- package/gsd-core/bin/lib/phase-command-router.cjs +11 -4
- package/gsd-core/bin/lib/phase-estimation.cjs +5 -4
- package/gsd-core/bin/lib/phase-id.cjs +1 -1
- package/gsd-core/bin/lib/phase-lifecycle.cjs +9 -2
- package/gsd-core/bin/lib/phase-status.cjs +360 -0
- package/gsd-core/bin/lib/phase.cjs +301 -80
- package/gsd-core/bin/lib/plan-document.cjs +93 -19
- package/gsd-core/bin/lib/plan-drift-guard.cjs +5 -0
- package/gsd-core/bin/lib/planning-document.cjs +273 -40
- package/gsd-core/bin/lib/planning-inspect.cjs +42 -8
- package/gsd-core/bin/lib/planning-snapshot.cjs +18 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +76 -53
- package/gsd-core/bin/lib/pristine-baseline.cjs +10 -0
- package/gsd-core/bin/lib/profile-output.cjs +6 -3
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +0 -55
- package/gsd-core/bin/lib/quick-batch-command-router.cjs +35 -9
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +13 -9
- package/gsd-core/bin/lib/real-home-guard.cjs +9 -1
- package/gsd-core/bin/lib/report-parser.cjs +269 -0
- package/gsd-core/bin/lib/roadmap-command-router.cjs +13 -15
- package/gsd-core/bin/lib/roadmap-parser.cjs +82 -15
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +127 -65
- package/gsd-core/bin/lib/roadmap.cjs +169 -72
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +186 -161
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +2 -1
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +57 -1
- package/gsd-core/bin/lib/runtime-homes.cjs +13 -10
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +365 -594
- package/gsd-core/bin/lib/runtime-name-policy.cjs +142 -27
- package/gsd-core/bin/lib/runtime-slash.cjs +60 -30
- package/gsd-core/bin/lib/shell-command-projection.cjs +37 -2
- package/gsd-core/bin/lib/smart-entry.cjs +111 -56
- package/gsd-core/bin/lib/stale-bake-guard.cjs +32 -48
- package/gsd-core/bin/lib/state-contract.cjs +15 -18
- package/gsd-core/bin/lib/state-document.cjs +100 -22
- package/gsd-core/bin/lib/state-transition.cjs +67 -9
- package/gsd-core/bin/lib/state.cjs +668 -171
- package/gsd-core/bin/lib/surface.cjs +2 -1
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +48 -152
- package/gsd-core/bin/lib/uat-predicate.cjs +313 -35
- package/gsd-core/bin/lib/uat.cjs +424 -7
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +94 -58
- package/gsd-core/bin/lib/update-context.cjs +18 -1
- package/gsd-core/bin/lib/vendor/README.md +31 -9
- package/gsd-core/bin/lib/vendor/saxes.cjs +1934 -0
- package/gsd-core/bin/lib/vendor/saxes.cjs.LICENSE.txt +92 -0
- package/gsd-core/bin/lib/vendor/tap-parser.cjs +8927 -0
- package/gsd-core/bin/lib/vendor/tap-parser.cjs.LICENSE.txt +152 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +1265 -324
- package/gsd-core/bin/lib/verify-command-router.cjs +18 -7
- package/gsd-core/bin/lib/verify.cjs +286 -567
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +14 -6
- package/gsd-core/bin/lib/workstream-inventory.cjs +31 -21
- package/gsd-core/bin/lib/workstream-name-policy.cjs +31 -1
- package/gsd-core/bin/lib/workstream.cjs +11 -2
- package/gsd-core/bin/shared/config-defaults.manifest.json +5 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +4 -0
- package/gsd-core/references/autonomous-smart-discuss.md +2 -1
- package/gsd-core/references/autonomous-ui-design-contract.md +3 -3
- package/gsd-core/references/execute-mvp-tdd.md +5 -10
- package/gsd-core/references/execute-phase-between-wave-reset.md +3 -0
- package/gsd-core/references/execute-phase-response-language.md +1 -1
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +7 -1
- package/gsd-core/references/offer-next.md +1 -1
- package/gsd-core/references/planning-config.md +1 -1
- package/gsd-core/references/spidr-splitting.md +1 -1
- package/gsd-core/references/tdd.md +10 -4
- package/gsd-core/references/verifier-phase-gates.md +5 -2
- package/gsd-core/references/verify-mvp-mode.md +2 -2
- package/gsd-core/references/workstream-flag.md +33 -3
- package/gsd-core/templates/README.md +1 -1
- package/gsd-core/templates/UAT.md +17 -1
- package/gsd-core/templates/config.json +2 -11
- package/gsd-core/templates/verification-report.md +1 -1
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +1 -1
- package/gsd-core/workflows/add-phase.md +8 -7
- package/gsd-core/workflows/add-tests.md +3 -2
- package/gsd-core/workflows/add-todo.md +4 -3
- package/gsd-core/workflows/ai-integration-phase.md +3 -2
- package/gsd-core/workflows/audit-fix.md +1 -1
- package/gsd-core/workflows/audit-milestone.md +4 -3
- package/gsd-core/workflows/audit-uat.md +1 -1
- package/gsd-core/workflows/autonomous.md +28 -18
- package/gsd-core/workflows/check-todos.md +6 -5
- package/gsd-core/workflows/cleanup.md +1 -1
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +20 -13
- package/gsd-core/workflows/code-review-fix.md +5 -4
- package/gsd-core/workflows/code-review.md +100 -91
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +4 -3
- package/gsd-core/workflows/complete-milestone.md +12 -7
- package/gsd-core/workflows/debug.md +30 -5
- package/gsd-core/workflows/diagnose-issues.md +3 -2
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
- package/gsd-core/workflows/discuss-phase.md +4 -3
- package/gsd-core/workflows/do.md +1 -1
- package/gsd-core/workflows/docs-update.md +4 -3
- package/gsd-core/workflows/edit-phase.md +4 -3
- package/gsd-core/workflows/eval-review.md +5 -3
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +4 -2
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +15 -4
- package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +10 -7
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +3 -1
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +2 -2
- 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 +1 -1
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +2 -0
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +187 -0
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +2 -3
- package/gsd-core/workflows/execute-phase.md +81 -171
- package/gsd-core/workflows/execute-plan.md +13 -19
- package/gsd-core/workflows/explore.md +2 -2
- package/gsd-core/workflows/extract-learnings.md +3 -2
- package/gsd-core/workflows/fast.md +1 -1
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +2 -1
- package/gsd-core/workflows/import.md +3 -2
- package/gsd-core/workflows/inbox.md +1 -1
- package/gsd-core/workflows/ingest-docs.md +1 -1
- package/gsd-core/workflows/insert-phase.md +4 -3
- package/gsd-core/workflows/list-seeds.md +1 -1
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +5 -3
- package/gsd-core/workflows/map-codebase.md +4 -3
- package/gsd-core/workflows/milestone-summary.md +3 -2
- package/gsd-core/workflows/mvp-phase.md +14 -14
- package/gsd-core/workflows/new-milestone.md +10 -10
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
- package/gsd-core/workflows/new-project.md +1 -1
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +1 -1
- package/gsd-core/workflows/pause-work.md +2 -2
- package/gsd-core/workflows/plan-phase/detail/elaboration.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +1 -1
- package/gsd-core/workflows/plan-phase.md +27 -18
- package/gsd-core/workflows/plan-review-convergence.md +1 -1
- package/gsd-core/workflows/plant-seed.md +1 -1
- package/gsd-core/workflows/pr-branch.md +1 -1
- package/gsd-core/workflows/profile-user.md +1 -1
- package/gsd-core/workflows/progress.md +19 -49
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +4 -2
- package/gsd-core/workflows/quick/steps/quick-verification.md +4 -4
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +30 -11
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +9 -3
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
- package/gsd-core/workflows/quick-batch.md +15 -10
- package/gsd-core/workflows/quick.md +42 -31
- package/gsd-core/workflows/remove-phase.md +3 -2
- package/gsd-core/workflows/remove-workspace.md +1 -1
- package/gsd-core/workflows/resume-project.md +17 -12
- package/gsd-core/workflows/review.md +3 -2
- package/gsd-core/workflows/scan.md +3 -2
- package/gsd-core/workflows/secure-phase.md +11 -12
- package/gsd-core/workflows/settings-advanced.md +1 -1
- package/gsd-core/workflows/settings-integrations.md +1 -1
- package/gsd-core/workflows/settings.md +1 -1
- package/gsd-core/workflows/ship.md +12 -12
- package/gsd-core/workflows/sketch-wrap-up.md +1 -1
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/smart-entry.md +1 -1
- package/gsd-core/workflows/spec-phase.md +1 -1
- package/gsd-core/workflows/spike-wrap-up.md +1 -1
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +1 -1
- package/gsd-core/workflows/sync-skills.md +1 -1
- package/gsd-core/workflows/thread.md +2 -2
- package/gsd-core/workflows/transition.md +13 -23
- package/gsd-core/workflows/ui-phase.md +5 -4
- package/gsd-core/workflows/ui-review.md +4 -3
- package/gsd-core/workflows/ultraplan-phase.md +3 -2
- package/gsd-core/workflows/undo.md +1 -1
- package/gsd-core/workflows/validate-phase.md +10 -12
- package/gsd-core/workflows/verify-work/detail/elaboration.md +43 -3
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -3
- package/gsd-core/workflows/verify-work.md +68 -59
- package/hooks/dist/gsd-agent-isolation-guard.js +8 -0
- package/hooks/dist/gsd-check-update-worker.js +8 -0
- package/hooks/dist/gsd-check-update.js +34 -20
- package/hooks/dist/gsd-context-monitor.js +45 -13
- package/hooks/dist/gsd-cursor-subagent-start.js +8 -0
- package/hooks/dist/gsd-secret-read-guard.js +263 -16
- package/hooks/dist/gsd-statusline.js +336 -29
- package/hooks/dist/gsd-update-banner.js +8 -0
- package/hooks/dist/gsd-windsurf-pre-write.js +11 -2
- package/hooks/dist/gsd-workflow-guard.js +5 -4
- package/hooks/dist/gsd-worktree-path-guard.js +6 -2
- package/hooks/dist/lib/cli-exit.js +12 -0
- package/hooks/dist/lib/git-probe.js +17 -1
- package/hooks/dist/lib/isolation-sentinel.js +2 -2
- package/hooks/gsd-agent-isolation-guard.js +8 -0
- package/hooks/gsd-check-update-worker.js +8 -0
- package/hooks/gsd-check-update.js +34 -20
- package/hooks/gsd-context-monitor.js +45 -13
- package/hooks/gsd-cursor-subagent-start.js +8 -0
- package/hooks/gsd-secret-read-guard.js +263 -16
- package/hooks/gsd-statusline.js +336 -29
- package/hooks/gsd-update-banner.js +8 -0
- package/hooks/gsd-windsurf-pre-write.js +11 -2
- package/hooks/gsd-workflow-guard.js +5 -4
- package/hooks/gsd-worktree-path-guard.js +6 -2
- package/hooks/hooks.json +5 -5
- package/hooks/lib/cli-exit.js +12 -0
- package/hooks/lib/git-probe.js +17 -1
- package/hooks/lib/isolation-sentinel.js +2 -2
- package/package.json +21 -4
- package/scripts/changeset/parse.cjs +52 -4
- package/scripts/ci-timeout-report.cjs +770 -4
- package/scripts/command-contract-helpers.cjs +12 -8
- package/scripts/docs-guard-registry.cjs +6 -0
- package/scripts/gen-capability-registry.cjs +7 -1
- package/scripts/gen-features.cjs +13 -8
- package/scripts/gen-hooks-cli-exit.cjs +12 -28
- package/scripts/gen-loop-host-contract.cjs +14 -2
- package/scripts/gen-platform-conformance-tier.cjs +187 -1
- package/scripts/gen-plugin-skills.cjs +87 -1
- package/scripts/gen-research-agents.cjs +24 -31
- package/scripts/gen-scripts-cli-exit.cjs +30 -3
- package/scripts/gen-test-timings.cjs +32 -7
- package/scripts/lib/cli-exit.cjs +12 -0
- package/scripts/lib/macos-conformance-tier.generated.cjs +24 -2
- package/scripts/lib/ndjson-reporter.cjs +28 -3
- package/scripts/lib/platform-conformance-tier.generated.cjs +44 -5
- package/scripts/lib/registration-ledger-preload.cjs +155 -0
- package/scripts/lib/vendor-bundle.cjs +59 -0
- package/scripts/lib/vendor-licenses/saxes-6.0.0.txt +64 -0
- 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-completion-predicate-drift.cjs +18 -19
- package/scripts/lint-descriptions.cjs +7 -3
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +10 -0
- package/scripts/lint-eslint-glob-coverage.allowlist.json +20 -0
- package/scripts/lint-frontmatter-fence-drift.cjs +313 -0
- package/scripts/lint-frontmatter-scalar-broad-grep.cjs +122 -16
- package/scripts/lint-phase-enumeration-drift.cjs +12 -8
- package/scripts/lint-phase-id-drift.cjs +29 -0
- package/scripts/lint-planning-document-positive-control.cjs +329 -0
- package/scripts/lint-response-language-coverage.cjs +3 -0
- package/scripts/lint-skill-deps.cjs +7 -3
- package/scripts/lint-test-file-count.allowlist.json +11 -1
- package/scripts/lint-test-file-count.cjs +34 -1
- package/scripts/lint-vendored-deps.cjs +41 -5
- package/scripts/lint-workflow-shellcheck-baseline.json +10 -10
- package/scripts/mutation-matrix.cjs +53 -6
- package/scripts/release-tarball-smoke.cjs +14 -7
- package/scripts/require-issue-link-policy.cjs +6 -2
- package/scripts/sync-runtime-launcher.cjs +184 -2
- package/scripts/verify-npm-publish.cjs +76 -20
- package/skills/gsd-add-tests/SKILL.md +6 -1
- package/skills/gsd-ai-integration-phase/SKILL.md +6 -1
- package/skills/gsd-audit-fix/SKILL.md +5 -0
- package/skills/gsd-audit-milestone/SKILL.md +6 -1
- package/skills/gsd-autonomous/SKILL.md +5 -0
- package/skills/gsd-capture/SKILL.md +8 -4
- package/skills/gsd-code-review/SKILL.md +7 -2
- package/skills/gsd-complete-milestone/SKILL.md +4 -0
- package/skills/gsd-config/SKILL.md +8 -4
- package/skills/gsd-debug/SKILL.md +11 -7
- package/skills/gsd-discuss-phase/SKILL.md +7 -3
- package/skills/gsd-docs-update/SKILL.md +12 -7
- package/skills/gsd-eval-review/SKILL.md +6 -1
- package/skills/gsd-execute-phase/SKILL.md +12 -7
- package/skills/gsd-extract-learnings/SKILL.md +5 -0
- package/skills/gsd-fast/SKILL.md +4 -0
- package/skills/gsd-forensics/SKILL.md +5 -1
- package/skills/gsd-graphify/SKILL.md +10 -6
- package/skills/gsd-health/SKILL.md +5 -0
- package/skills/gsd-help/SKILL.md +7 -2
- package/skills/gsd-import/SKILL.md +7 -3
- package/skills/gsd-inbox/SKILL.md +5 -0
- package/skills/gsd-ingest-docs/SKILL.md +5 -1
- package/skills/gsd-manager/SKILL.md +6 -1
- package/skills/gsd-map-codebase/SKILL.md +7 -3
- package/skills/gsd-mempalace-capture/SKILL.md +5 -1
- package/skills/gsd-mempalace-recall/SKILL.md +5 -1
- package/skills/gsd-milestone-summary/SKILL.md +5 -1
- package/skills/gsd-mvp-phase/SKILL.md +8 -3
- package/skills/gsd-new-milestone/SKILL.md +6 -1
- package/skills/gsd-new-project/SKILL.md +5 -0
- package/skills/gsd-next/SKILL.md +6 -1
- package/skills/gsd-ns-context/SKILL.md +4 -0
- package/skills/gsd-ns-ideate/SKILL.md +4 -0
- package/skills/gsd-ns-manage/SKILL.md +4 -0
- package/skills/gsd-ns-project/SKILL.md +4 -0
- package/skills/gsd-ns-review/SKILL.md +4 -0
- package/skills/gsd-ns-workflow/SKILL.md +4 -0
- package/skills/gsd-onboard/SKILL.md +6 -1
- package/skills/gsd-pause-work/SKILL.md +5 -1
- package/skills/gsd-phase/SKILL.md +8 -4
- package/skills/gsd-plan-phase/SKILL.md +6 -1
- package/skills/gsd-plan-review-convergence/SKILL.md +5 -1
- package/skills/gsd-pr-branch/SKILL.md +4 -0
- package/skills/gsd-profile-user/SKILL.md +5 -1
- package/skills/gsd-progress/SKILL.md +6 -1
- package/skills/gsd-quick/SKILL.md +16 -10
- package/skills/gsd-quick-batch/SKILL.md +20 -8
- package/skills/gsd-review/SKILL.md +5 -1
- package/skills/gsd-review-backlog/SKILL.md +3 -2
- package/skills/gsd-secure-phase/SKILL.md +6 -1
- package/skills/gsd-ship/SKILL.md +5 -0
- package/skills/gsd-sketch/SKILL.md +7 -2
- package/skills/gsd-spec-phase/SKILL.md +5 -1
- package/skills/gsd-spike/SKILL.md +8 -3
- package/skills/gsd-surface/SKILL.md +5 -1
- package/skills/gsd-thread/SKILL.md +4 -0
- package/skills/gsd-ui-phase/SKILL.md +6 -1
- package/skills/gsd-ui-review/SKILL.md +6 -1
- package/skills/gsd-ultraplan-phase/SKILL.md +5 -1
- package/skills/gsd-undo/SKILL.md +5 -1
- package/skills/gsd-update/SKILL.md +6 -2
- package/skills/gsd-validate-phase/SKILL.md +6 -1
- package/skills/gsd-verify-work/SKILL.md +6 -1
- package/skills/gsd-workspace/SKILL.md +7 -3
- package/skills/gsd-workstreams/SKILL.md +6 -6
- package/vscode/package.json +1 -1
- package/gsd-core/workflows/execute-phase/steps/stale-reverification.md +0 -24
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
// Check for GSD updates in background, write result to cache
|
|
4
4
|
// Called by SessionStart hook - runs once per session
|
|
5
5
|
|
|
6
|
+
// #5183: Node's on-disk compile cache, so later runs reuse V8's compiled code
|
|
7
|
+
// for unchanged modules. Only as the entry (a test require() is unaffected),
|
|
8
|
+
// skipped under V8 coverage, optional (Bun stubs it), and never fatal.
|
|
9
|
+
// NODE_DISABLE_COMPILE_CACHE (any value) turns it off.
|
|
10
|
+
if (require.main === module && !process.env.NODE_V8_COVERAGE) {
|
|
11
|
+
try { require('node:module').enableCompileCache?.(); } catch { /* optimization only */ }
|
|
12
|
+
}
|
|
13
|
+
|
|
6
14
|
const fs = require('fs');
|
|
7
15
|
const path = require('path');
|
|
8
16
|
const os = require('os');
|
|
@@ -59,26 +67,32 @@ const cacheFile = path.join(cacheDir, updateCacheFileName);
|
|
|
59
67
|
const projectVersionFile = path.join(projectConfigDir, 'gsd-core', 'VERSION');
|
|
60
68
|
const globalVersionFile = path.join(globalConfigDir, 'gsd-core', 'VERSION');
|
|
61
69
|
|
|
62
|
-
//
|
|
63
|
-
|
|
70
|
+
// This background check is advisory. Cache or worker failures must not make
|
|
71
|
+
// the SessionStart hook fail. Recursive mkdir is safe if another session
|
|
72
|
+
// creates the directory concurrently.
|
|
73
|
+
try {
|
|
64
74
|
fs.mkdirSync(cacheDir, { recursive: true });
|
|
65
|
-
}
|
|
66
75
|
|
|
67
|
-
// Run check in background via a dedicated worker script.
|
|
68
|
-
// Spawning a file (rather than node -e '<inline code>') keeps the worker logic
|
|
69
|
-
// in plain JS with no template-literal regex-escaping concerns, and makes the
|
|
70
|
-
// worker independently testable.
|
|
71
|
-
const workerPath = path.join(__dirname, 'gsd-check-update-worker.js');
|
|
72
|
-
const child = spawn(process.execPath, [workerPath], {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
});
|
|
76
|
+
// Run check in background via a dedicated worker script.
|
|
77
|
+
// Spawning a file (rather than node -e '<inline code>') keeps the worker logic
|
|
78
|
+
// in plain JS with no template-literal regex-escaping concerns, and makes the
|
|
79
|
+
// worker independently testable.
|
|
80
|
+
const workerPath = path.join(__dirname, 'gsd-check-update-worker.js');
|
|
81
|
+
const child = spawn(process.execPath, [workerPath], {
|
|
82
|
+
stdio: 'ignore',
|
|
83
|
+
windowsHide: true,
|
|
84
|
+
detached: true, // Required on Windows for proper process detachment
|
|
85
|
+
env: {
|
|
86
|
+
...process.env,
|
|
87
|
+
GSD_CACHE_FILE: cacheFile,
|
|
88
|
+
GSD_PROJECT_VERSION_FILE: projectVersionFile,
|
|
89
|
+
GSD_GLOBAL_VERSION_FILE: globalVersionFile,
|
|
90
|
+
},
|
|
91
|
+
});
|
|
83
92
|
|
|
84
|
-
child.
|
|
93
|
+
// A spawn failure can also arrive asynchronously on the child process.
|
|
94
|
+
child.on('error', () => {});
|
|
95
|
+
child.unref();
|
|
96
|
+
} catch {
|
|
97
|
+
// Update hints are best effort; a failed check must not interrupt startup.
|
|
98
|
+
}
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
// Thresholds:
|
|
15
15
|
// WARNING (remaining <= 35%): Agent should wrap up current task
|
|
16
16
|
// CRITICAL (remaining <= 25%): Agent should stop immediately and save state
|
|
17
|
+
// "remaining" is of the auto-compact threshold — the statusline puts the
|
|
18
|
+
// bridge on that scale (#4985), so both fire before Claude Code compacts.
|
|
17
19
|
// Both fire-points are overridable per project via .planning/config.json
|
|
18
20
|
// (hooks.context_warning_threshold / hooks.context_critical_threshold, #4285);
|
|
19
21
|
// the values above are the defaults used when the keys are absent or unusable.
|
|
@@ -201,6 +203,29 @@ function writeSentinel(target, payload) {
|
|
|
201
203
|
} catch (e) { /* best effort — see above */ }
|
|
202
204
|
}
|
|
203
205
|
|
|
206
|
+
// The calendar day the CRITICAL auto-record writes into STATE.md (#4905): the
|
|
207
|
+
// operator's LOCAL day through the clock seam, which honors GSD_NOW_MS under
|
|
208
|
+
// GSD_TEST_MODE, like every other date GSD stamps into planning files. The
|
|
209
|
+
// clock is compiled into gsd-core/bin/lib/, so it is reached through the
|
|
210
|
+
// self-healing build seam (#3582). If the library is missing and cannot
|
|
211
|
+
// self-build, the host's local day stands in — still never the UTC day.
|
|
212
|
+
function localDayStamp() {
|
|
213
|
+
try {
|
|
214
|
+
const { ensureRuntimeBuild } = require('../gsd-core/bin/ensure-runtime-build.cjs');
|
|
215
|
+
ensureRuntimeBuild();
|
|
216
|
+
return require('../gsd-core/bin/lib/clock.cjs').realClock.localToday();
|
|
217
|
+
} catch {
|
|
218
|
+
const now = new Date();
|
|
219
|
+
return [now.getFullYear(), String(now.getMonth() + 1).padStart(2, '0'), String(now.getDate()).padStart(2, '0')].join('-');
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// The Stopped At breadcrumb for /gsd:resume-work (#1974). usedPct is coerced to
|
|
224
|
+
// a safe number in case the bridge file is malformed.
|
|
225
|
+
function criticalStoppedAt(usedPct) {
|
|
226
|
+
return `context exhaustion at ${Number(usedPct) || 0}% (${localDayStamp()})`;
|
|
227
|
+
}
|
|
228
|
+
|
|
204
229
|
let input = '';
|
|
205
230
|
// Assigned by main(); the handler below clears it. Declared out here rather
|
|
206
231
|
// than inside main() because the handler closes over it.
|
|
@@ -464,9 +489,7 @@ const handleStdinEnd = () => {
|
|
|
464
489
|
// Using __dirname makes this work on Claude Code, OpenCode, Gemini,
|
|
465
490
|
// Kilo, etc. without hardcoding ~/.claude/.
|
|
466
491
|
const gsdTools = path.join(__dirname, '..', 'gsd-core', 'bin', 'gsd-tools.cjs');
|
|
467
|
-
|
|
468
|
-
const safeUsedPct = Number(usedPct) || 0;
|
|
469
|
-
const stoppedAt = `context exhaustion at ${safeUsedPct}% (${new Date().toISOString().split('T')[0]})`;
|
|
492
|
+
const stoppedAt = criticalStoppedAt(usedPct);
|
|
470
493
|
spawn(
|
|
471
494
|
process.execPath,
|
|
472
495
|
[gsdTools, 'state', 'record-session', '--stopped-at', stoppedAt],
|
|
@@ -479,23 +502,30 @@ const handleStdinEnd = () => {
|
|
|
479
502
|
}
|
|
480
503
|
|
|
481
504
|
// Build advisory warning message (never use imperative commands that
|
|
482
|
-
// override user preferences — see #884)
|
|
505
|
+
// override user preferences — see #884). The percentages are of the
|
|
506
|
+
// auto-compact threshold (#4985); the token counts, when the bridge has
|
|
507
|
+
// them, are the ones /context shows (#2451).
|
|
508
|
+
const kTokens = n => `${Math.round(n / 1000)}k`;
|
|
509
|
+
const tokenDetail = Number.isFinite(metrics.used_tokens) && Number.isFinite(metrics.threshold_tokens)
|
|
510
|
+
? ` (${kTokens(metrics.used_tokens)} of ${kTokens(metrics.threshold_tokens)} usable tokens)`
|
|
511
|
+
: '';
|
|
512
|
+
const usage = `Usage at ${usedPct}%${tokenDetail}. Remaining: ${remaining}%. `;
|
|
483
513
|
let message;
|
|
484
514
|
if (isCritical) {
|
|
485
515
|
message = isGsdActive
|
|
486
|
-
? `CONTEXT CRITICAL:
|
|
516
|
+
? `CONTEXT CRITICAL: ${usage}` +
|
|
487
517
|
'Context is nearly exhausted. Do NOT start new complex work or write handoff files — ' +
|
|
488
518
|
'GSD state is already tracked in STATE.md. Inform the user so they can run ' +
|
|
489
519
|
'/gsd:pause-work at the next natural stopping point.'
|
|
490
|
-
: `CONTEXT CRITICAL:
|
|
520
|
+
: `CONTEXT CRITICAL: ${usage}` +
|
|
491
521
|
'Context is nearly exhausted. Inform the user that context is low and ask how they ' +
|
|
492
522
|
'want to proceed. Do NOT autonomously save state or write handoff files unless the user asks.';
|
|
493
523
|
} else {
|
|
494
524
|
message = isGsdActive
|
|
495
|
-
? `CONTEXT WARNING:
|
|
525
|
+
? `CONTEXT WARNING: ${usage}` +
|
|
496
526
|
'Context is getting limited. Avoid starting new complex work. If not between ' +
|
|
497
527
|
'defined plan steps, inform the user so they can prepare to pause.'
|
|
498
|
-
: `CONTEXT WARNING:
|
|
528
|
+
: `CONTEXT WARNING: ${usage}` +
|
|
499
529
|
'Be aware that context is getting limited. Avoid unnecessary exploration or ' +
|
|
500
530
|
'starting new complex work.';
|
|
501
531
|
}
|
|
@@ -560,8 +590,10 @@ if (require.main === module) {
|
|
|
560
590
|
main();
|
|
561
591
|
}
|
|
562
592
|
|
|
563
|
-
// Exported for
|
|
564
|
-
// test
|
|
565
|
-
// re-hardcoding 35/25 — a test
|
|
566
|
-
// green if the constants were
|
|
567
|
-
|
|
593
|
+
// Exported for tests only. resolveThresholds and the two constants serve the
|
|
594
|
+
// #4285 property test; the constants ride along so a test asserts the fallback
|
|
595
|
+
// pair against the SOURCE of truth rather than re-hardcoding 35/25 — a test
|
|
596
|
+
// carrying its own copy of the defaults would stay green if the constants were
|
|
597
|
+
// edited. criticalStoppedAt lets the #4905 test pin the breadcrumb's day
|
|
598
|
+
// without waiting on the detached record-session writer (#3726).
|
|
599
|
+
module.exports = { resolveThresholds, WARNING_THRESHOLD, CRITICAL_THRESHOLD, criticalStoppedAt };
|
|
@@ -53,6 +53,14 @@
|
|
|
53
53
|
|
|
54
54
|
'use strict';
|
|
55
55
|
|
|
56
|
+
// #5183: Node's on-disk compile cache, so later runs reuse V8's compiled code
|
|
57
|
+
// for unchanged modules. Only as the entry (a test require() is unaffected),
|
|
58
|
+
// skipped under V8 coverage, optional (Bun stubs it), and never fatal.
|
|
59
|
+
// NODE_DISABLE_COMPILE_CACHE (any value) turns it off.
|
|
60
|
+
if (require.main === module && !process.env.NODE_V8_COVERAGE) {
|
|
61
|
+
try { require('node:module').enableCompileCache?.(); } catch { /* optimization only */ }
|
|
62
|
+
}
|
|
63
|
+
|
|
56
64
|
const fs = require('fs');
|
|
57
65
|
const path = require('path');
|
|
58
66
|
const os = require('os');
|
|
@@ -61,7 +61,13 @@
|
|
|
61
61
|
// segment's operands — `[ -f .env ]` and `ls .env*` are existence checks
|
|
62
62
|
// GSD's own agents run — while `cp`/`mv`/`ln`/`git` are deliberately NOT
|
|
63
63
|
// exempt (`cp .env x && cat x` launders the name; `git show HEAD:.env`
|
|
64
|
-
// reads).
|
|
64
|
+
// reads). Narrower carve-outs exempt single operand POSITIONS that are
|
|
65
|
+
// names, never contents, and keep checking the rest of the segment: the
|
|
66
|
+
// `--env-file` value under a container runtime (#4639), the pathspecs of
|
|
67
|
+
// `git check-ignore` / `git ls-files` / `git rm --cached`, and the
|
|
68
|
+
// destination of a one-source `cp`/`mv` (#4856). The #4856 carve-outs
|
|
69
|
+
// fail closed: an option they do not list withdraws them from the segment.
|
|
70
|
+
// A shell interpreter (bash/sh/zsh/dash/ksh/su) has its script scanned
|
|
65
71
|
// whether it arrives via `-c '…'`, a `<( )` file operand, a heredoc /
|
|
66
72
|
// here-string, or a pipe from a knowable `echo`/`printf` source
|
|
67
73
|
// (`echo cat .env | bash`); `eval` scans its joined operands; `source`/`.`
|
|
@@ -91,11 +97,29 @@
|
|
|
91
97
|
// container's own command can print the interpolated environment
|
|
92
98
|
// (`alpine printenv`, `docker compose config`) — the same exposure class
|
|
93
99
|
// as the pre-existing volume-mount gap (`-v .env:/s`); the flag's value
|
|
94
|
-
// itself is a name, never contents.
|
|
100
|
+
// itself is a name, never contents. #4856 adds another: the `cp`/`mv`
|
|
101
|
+
// destination exemption can move a link prepared under a non-secret name
|
|
102
|
+
// onto a secret name (`ln -s .env.example l && mv l .env`), so a later
|
|
103
|
+
// edit of `.env` lands in the link target — a write redirection, the
|
|
104
|
+
// out-of-scope write class, not a read.)
|
|
105
|
+
//
|
|
106
|
+
// A second, unrelated contract is enforced on the Bash leg because this is the
|
|
107
|
+
// one hook that already parses EVERY Bash command (#5214): a Bash command that
|
|
108
|
+
// launches the debugger as a CLI process — `claude … --agent gsd-debugger` — is
|
|
109
|
+
// denied (`debugger-cli-spawn`). The debug session manager is required to spawn
|
|
110
|
+
// gsd-debugger with `Agent(run_in_background=false)` so it can read the
|
|
111
|
+
// debugger's structured return; on a captured 1.15.0 run it routed around that
|
|
112
|
+
// literal and shelled out with the Bash parameter `run_in_background: true`,
|
|
113
|
+
// so no return ever came back and the checkpoint never advanced. Prose in the
|
|
114
|
+
// agent file cannot stop an improvised tool call; a PreToolUse denial can, and
|
|
115
|
+
// Claude Code runs plugin hooks for a subagent's tool calls too. A person
|
|
116
|
+
// running `claude --agent gsd-debugger` in their own terminal never reaches
|
|
117
|
+
// this hook. Only the debugger is named: it is the agent whose blocking return
|
|
118
|
+
// the managed debug loop depends on.
|
|
95
119
|
//
|
|
96
120
|
// Triggers on: Read, Grep, Bash tool calls (Kimi: ReadFile, Grep, Shell)
|
|
97
121
|
// Action: BLOCK (decision: 'block', exit 2) — codes secret-read |
|
|
98
|
-
// glob-too-complex | command-too-large
|
|
122
|
+
// glob-too-complex | command-too-large | debugger-cli-spawn
|
|
99
123
|
// No-op: other tools, non-secret targets, hook errors (fail open — a parser
|
|
100
124
|
// bug in a hook that runs on EVERY Bash call must never brick a
|
|
101
125
|
// session; the crash policy is declared once below).
|
|
@@ -141,6 +165,57 @@ const NON_READING_COMMANDS = new Set([
|
|
|
141
165
|
const CONTAINER_RUNTIMES = new Set(['docker', 'docker-compose', 'podman', 'nerdctl']);
|
|
142
166
|
const ENV_FILE_FLAG_RE = /^--env-file(=|$)/;
|
|
143
167
|
|
|
168
|
+
// #4856: git subcommands that report or drop a path's ignore / tracking
|
|
169
|
+
// status and never print the file, so their pathspec operands are names —
|
|
170
|
+
// the category NON_READING_COMMANDS encodes. Each carries the closed set of
|
|
171
|
+
// its NO-VALUE options (from `git <subcommand> -h`, git 2.49). Any other
|
|
172
|
+
// option withdraws the exemption from the whole segment, so an option value
|
|
173
|
+
// is never mistaken for a pathspec: `-X <file>` reads the file, and
|
|
174
|
+
// `--pathspec-from-file=<file>` echoes its lines in the "did not match"
|
|
175
|
+
// error. `requires` names the option without which the subcommand stays
|
|
176
|
+
// checked (#4856 scopes `git rm` to `--cached`).
|
|
177
|
+
const GIT_PATHSPEC_SUBCOMMANDS = new Map([
|
|
178
|
+
['check-ignore', {
|
|
179
|
+
flags: new Set(['-q', '--quiet', '-v', '--verbose', '--stdin', '-z', '-n', '--non-matching', '--no-index', '--index']),
|
|
180
|
+
}],
|
|
181
|
+
['ls-files', {
|
|
182
|
+
flags: new Set([
|
|
183
|
+
'-z', '-t', '-v', '-f', '-c', '--cached', '-d', '--deleted', '-m', '--modified', '-o', '--others',
|
|
184
|
+
'-i', '--ignored', '-s', '--stage', '-k', '--killed', '-u', '--unmerged', '--directory', '--eol',
|
|
185
|
+
'--no-empty-directory', '--resolve-undo', '--exclude-standard', '--full-name',
|
|
186
|
+
'--recurse-submodules', '--error-unmatch', '--abbrev', '--debug', '--deduplicate', '--sparse',
|
|
187
|
+
]),
|
|
188
|
+
}],
|
|
189
|
+
['rm', {
|
|
190
|
+
flags: new Set(['--cached', '-f', '--force', '-n', '--dry-run', '-q', '--quiet', '-r', '--ignore-unmatch', '--sparse']),
|
|
191
|
+
requires: '--cached',
|
|
192
|
+
}],
|
|
193
|
+
]);
|
|
194
|
+
|
|
195
|
+
// git's global options (its own usage line, git 2.49), needed to locate the
|
|
196
|
+
// subcommand. A value option consumes the next word unless written
|
|
197
|
+
// `--opt=value`; an option in neither set fails closed, so a value is never
|
|
198
|
+
// read as the subcommand (`git -C ls-files show HEAD:.env` runs `show`).
|
|
199
|
+
const GIT_GLOBAL_VALUE_OPTIONS = new Set(['-C', '-c', '--git-dir', '--work-tree', '--namespace', '--config-env']);
|
|
200
|
+
const GIT_GLOBAL_FLAGS = new Set([
|
|
201
|
+
'-p', '--paginate', '-P', '--no-pager', '--no-replace-objects', '--no-lazy-fetch',
|
|
202
|
+
'--no-optional-locks', '--no-advice', '--bare',
|
|
203
|
+
]);
|
|
204
|
+
|
|
205
|
+
// #4856: `cp`/`mv` write their destination and never print it, so a secret
|
|
206
|
+
// name that is ONLY the destination of a one-source copy or move is a name
|
|
207
|
+
// (`cp .env.example .env`). The exemption holds for exactly two operands
|
|
208
|
+
// behind options from this closed set; anything else keeps the whole segment
|
|
209
|
+
// checked — `-t`/`--target-directory` (every operand becomes a source), a
|
|
210
|
+
// backup (`-b`, `--backup`, `-S`: the old secret survives as `.env~`, a name
|
|
211
|
+
// the predicate does not classify), a link (`-l`, `-s`) or `--exchange`
|
|
212
|
+
// (the destination shares or swaps the secret). Options must precede the
|
|
213
|
+
// operands: a trailing one is an option under GNU getopt's permutation but
|
|
214
|
+
// an operand under POSIXLY_CORRECT (`cp a .env -f` then copies `.env` INTO
|
|
215
|
+
// `-f/`), so which word is the destination would hang on an environment
|
|
216
|
+
// the hook cannot see.
|
|
217
|
+
const COPY_NAME_ONLY_FLAGS = new Set(['-f', '--force', '-i', '--interactive', '-n', '--no-clobber', '-v', '--verbose']);
|
|
218
|
+
|
|
144
219
|
// Shell interpreters that run a script from `-c`, a file operand, or stdin
|
|
145
220
|
// (heredoc / here-string / piped `echo`|`printf`). `su` is here for its `-c`
|
|
146
221
|
// form (`su [user] -c 'cmd'`); a bare `su user` resolves to file mode, which
|
|
@@ -714,6 +789,86 @@ function resolveCommand(words) {
|
|
|
714
789
|
return { base: lastSegment(words[idx].text).toLowerCase(), operands: words.slice(idx + 1) };
|
|
715
790
|
}
|
|
716
791
|
|
|
792
|
+
// Operand indices the segment's command consumes as a NAME, never as
|
|
793
|
+
// CONTENTS (#4639, #4856). Only these positions skip the operand check;
|
|
794
|
+
// every other operand is still checked, so a carve-out cannot launder a read
|
|
795
|
+
// elsewhere in the segment.
|
|
796
|
+
function nameOnlyOperandIndices(base, operands) {
|
|
797
|
+
if (CONTAINER_RUNTIMES.has(base)) return envFileValueIndices(operands);
|
|
798
|
+
if (base === 'git') return gitPathspecIndices(operands);
|
|
799
|
+
if (base === 'cp' || base === 'mv') return copyDestinationIndices(operands);
|
|
800
|
+
return new Set();
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// #4639: the `--env-file` word, plus the next operand for the bare flag
|
|
804
|
+
// (`--env-file=<value>` is a single word).
|
|
805
|
+
function envFileValueIndices(operands) {
|
|
806
|
+
const exempt = new Set();
|
|
807
|
+
for (let k = 0; k < operands.length; k++) {
|
|
808
|
+
if (!ENV_FILE_FLAG_RE.test(operands[k].text)) continue;
|
|
809
|
+
exempt.add(k);
|
|
810
|
+
if (!operands[k].text.includes('=')) exempt.add(++k);
|
|
811
|
+
}
|
|
812
|
+
return exempt;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// True when `text` is a listed long option, or a short cluster (`-co`) whose
|
|
816
|
+
// every letter is listed. `--opt=value`, an abbreviation, or a cluster
|
|
817
|
+
// holding a value option (`-ciX`) is not.
|
|
818
|
+
function isListedFlag(text, flags) {
|
|
819
|
+
if (text.startsWith('--')) return flags.has(text);
|
|
820
|
+
return /^-[A-Za-z]+$/.test(text) && [...text.slice(1)].every((ch) => flags.has(`-${ch}`));
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// Index of git's subcommand past its global options, or -1 when an option
|
|
824
|
+
// outside GIT_GLOBAL_VALUE_OPTIONS / GIT_GLOBAL_FLAGS precedes it.
|
|
825
|
+
function gitSubcommandIndex(operands) {
|
|
826
|
+
for (let k = 0; k < operands.length; k++) {
|
|
827
|
+
const t = operands[k].text;
|
|
828
|
+
if (!t.startsWith('-')) return k;
|
|
829
|
+
if (GIT_GLOBAL_VALUE_OPTIONS.has(t)) { k++; continue; }
|
|
830
|
+
if (GIT_GLOBAL_FLAGS.has(t)) continue;
|
|
831
|
+
const eq = t.indexOf('=');
|
|
832
|
+
const isLongValueForm = t.startsWith('--') && eq !== -1 && GIT_GLOBAL_VALUE_OPTIONS.has(t.slice(0, eq));
|
|
833
|
+
if (!isLongValueForm) return -1;
|
|
834
|
+
}
|
|
835
|
+
return -1;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// #4856: the pathspec operands of a GIT_PATHSPEC_SUBCOMMANDS invocation. git
|
|
839
|
+
// permutes options, so one may follow a pathspec; after `--` every word is a
|
|
840
|
+
// pathspec. The subcommand is matched case-sensitively, as git looks it up.
|
|
841
|
+
function gitPathspecIndices(operands) {
|
|
842
|
+
const sub = gitSubcommandIndex(operands);
|
|
843
|
+
const spec = sub === -1 ? undefined : GIT_PATHSPEC_SUBCOMMANDS.get(operands[sub].text);
|
|
844
|
+
if (!spec) return new Set();
|
|
845
|
+
const pathspecs = new Set();
|
|
846
|
+
let required = spec.requires === undefined;
|
|
847
|
+
let endOfOptions = false;
|
|
848
|
+
for (let k = sub + 1; k < operands.length; k++) {
|
|
849
|
+
const t = operands[k].text;
|
|
850
|
+
if (endOfOptions || !t.startsWith('-') || t === '-') pathspecs.add(k);
|
|
851
|
+
else if (t === '--') endOfOptions = true;
|
|
852
|
+
else if (!isListedFlag(t, spec.flags)) return new Set();
|
|
853
|
+
else if (t === spec.requires) required = true;
|
|
854
|
+
}
|
|
855
|
+
return required ? pathspecs : new Set();
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// #4856: the destination of a one-source `cp`/`mv` (see COPY_NAME_ONLY_FLAGS).
|
|
859
|
+
function copyDestinationIndices(operands) {
|
|
860
|
+
const positional = [];
|
|
861
|
+
let endOfOptions = false;
|
|
862
|
+
for (let k = 0; k < operands.length; k++) {
|
|
863
|
+
const t = operands[k].text;
|
|
864
|
+
if (endOfOptions || !t.startsWith('-') || t === '-') positional.push(k);
|
|
865
|
+
else if (positional.length) return new Set(); // an option or `--` after an operand
|
|
866
|
+
else if (t === '--') endOfOptions = true;
|
|
867
|
+
else if (!isListedFlag(t, COPY_NAME_ONLY_FLAGS)) return new Set();
|
|
868
|
+
}
|
|
869
|
+
return positional.length === 2 ? new Set([positional[1]]) : new Set();
|
|
870
|
+
}
|
|
871
|
+
|
|
717
872
|
// The statically-knowable stdin a segment writes: `echo`/`printf` operands
|
|
718
873
|
// joined by a space (for `echo`, leading `-neE` flags dropped). Any other
|
|
719
874
|
// source (`cat gen.sh | bash`, `curl … | sh`) is not knowable → null.
|
|
@@ -854,20 +1009,104 @@ function findSecretRead(command, depth) {
|
|
|
854
1009
|
|
|
855
1010
|
if (NON_READING_COMMANDS.has(base)) continue;
|
|
856
1011
|
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
1012
|
+
const nameOnly = nameOnlyOperandIndices(base, operands);
|
|
1013
|
+
for (let k = 0; k < operands.length; k++) {
|
|
1014
|
+
if (nameOnly.has(k)) continue;
|
|
1015
|
+
if (namesSecret(normalizeOperand(operands[k].text))) return operands[k].text;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
return null;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// ---------------------------------------------------------------------------
|
|
1022
|
+
// gsd-debugger CLI spawn scan (#5214)
|
|
1023
|
+
// ---------------------------------------------------------------------------
|
|
1024
|
+
|
|
1025
|
+
const DEBUGGER_AGENT_NAME = 'gsd-debugger';
|
|
1026
|
+
const CLAUDE_BINARY_RE = /^claude(\.exe)?$/;
|
|
1027
|
+
|
|
1028
|
+
// `gsd-debugger`, or a plugin-namespaced `<plugin>:gsd-debugger`.
|
|
1029
|
+
function isDebuggerAgent(value) {
|
|
1030
|
+
return value.slice(value.lastIndexOf(':') + 1) === DEBUGGER_AGENT_NAME;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
// `timeout [flags] DURATION cmd …` is not in CMD_PREFIXES because its first
|
|
1034
|
+
// operand is a duration, not the command. Returns the command that follows it.
|
|
1035
|
+
function unwrapTimeout(cmd) {
|
|
1036
|
+
let { base, operands } = cmd;
|
|
1037
|
+
while (base === 'timeout') {
|
|
1038
|
+
let k = 0;
|
|
1039
|
+
while (k < operands.length && operands[k].text.startsWith('-')) k++;
|
|
1040
|
+
k++; // the DURATION operand
|
|
1041
|
+
const rest = resolveCommand(operands.slice(k));
|
|
1042
|
+
if (!rest) return null;
|
|
1043
|
+
({ base, operands } = rest);
|
|
1044
|
+
}
|
|
1045
|
+
return { base, operands };
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// Returns the `--agent` value text of a debugger launch, or null.
|
|
1049
|
+
function findDebuggerCliSpawn(command, depth) {
|
|
1050
|
+
const { tokens, nested } = tokenize(command);
|
|
1051
|
+
|
|
1052
|
+
for (const sub of nested) {
|
|
1053
|
+
if (depth < MAX_NESTING_DEPTH) {
|
|
1054
|
+
const hit = findDebuggerCliSpawn(sub, depth + 1);
|
|
1055
|
+
if (hit) return hit;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
const bySeg = new Map();
|
|
1060
|
+
for (const t of tokens) {
|
|
1061
|
+
if (t.kind !== 'word' && t.kind !== 'op') continue;
|
|
1062
|
+
if (!bySeg.has(t.seg)) bySeg.set(t.seg, []);
|
|
1063
|
+
bySeg.get(t.seg).push(t);
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
for (const segTokens of bySeg.values()) {
|
|
1067
|
+
const words = [];
|
|
1068
|
+
for (let k = 0; k < segTokens.length; k++) {
|
|
1069
|
+
const t = segTokens[k];
|
|
1070
|
+
if (t.kind === 'op') {
|
|
1071
|
+
// A redirect consumes its target word; it is never the command.
|
|
1072
|
+
if (!t.dup && segTokens[k + 1] && segTokens[k + 1].kind === 'word') k++;
|
|
1073
|
+
continue;
|
|
1074
|
+
}
|
|
1075
|
+
words.push(t);
|
|
1076
|
+
}
|
|
1077
|
+
if (!words.length) continue;
|
|
1078
|
+
|
|
1079
|
+
const first = resolveCommand(words);
|
|
1080
|
+
const cmd = first && unwrapTimeout(first);
|
|
1081
|
+
if (!cmd) continue;
|
|
1082
|
+
const { base, operands } = cmd;
|
|
1083
|
+
|
|
1084
|
+
if (base === 'eval') {
|
|
1085
|
+
if (depth < MAX_NESTING_DEPTH) {
|
|
1086
|
+
const hit = findDebuggerCliSpawn(operands.map((w) => w.text).join(' '), depth + 1);
|
|
1087
|
+
if (hit) return hit;
|
|
1088
|
+
}
|
|
1089
|
+
continue;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
if (SHELL_INTERPRETERS.has(base)) {
|
|
1093
|
+
const cIdx = operands.findIndex((w) => DASH_C_RE.test(w.text));
|
|
1094
|
+
if (cIdx !== -1 && operands[cIdx + 1] && depth < MAX_NESTING_DEPTH) {
|
|
1095
|
+
const hit = findDebuggerCliSpawn(operands[cIdx + 1].text, depth + 1);
|
|
1096
|
+
if (hit) return hit;
|
|
1097
|
+
}
|
|
1098
|
+
continue;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
if (!CLAUDE_BINARY_RE.test(base)) continue;
|
|
1102
|
+
for (let k = 0; k < operands.length; k++) {
|
|
1103
|
+
const text = operands[k].text;
|
|
1104
|
+
if (text === '--agent') {
|
|
1105
|
+
const value = operands[k + 1];
|
|
1106
|
+
if (value && isDebuggerAgent(value.text)) return value.text;
|
|
1107
|
+
} else if (text.startsWith('--agent=') && isDebuggerAgent(text.slice('--agent='.length))) {
|
|
1108
|
+
return text.slice('--agent='.length);
|
|
869
1109
|
}
|
|
870
|
-
if (namesSecret(normalizeOperand(w.text))) return w.text;
|
|
871
1110
|
}
|
|
872
1111
|
}
|
|
873
1112
|
return null;
|
|
@@ -971,6 +1210,12 @@ function reasonFor(code, tool, target) {
|
|
|
971
1210
|
return `Secret read guard: the Grep glob '${target}' expands to more than ${MAX_GLOB_ALTERNATIVES} ` +
|
|
972
1211
|
'alternatives and cannot be checked for secret-file matches. Use a narrower glob.';
|
|
973
1212
|
}
|
|
1213
|
+
if (code === 'debugger-cli-spawn') {
|
|
1214
|
+
return `Debugger spawn guard: this Bash command launches the debugger as a CLI process ('--agent ${target}'). ` +
|
|
1215
|
+
'A shell-spawned debugger runs detached, returns no structured result, and the debug checkpoint never advances. ' +
|
|
1216
|
+
'Spawn it with the Agent tool instead: Agent(subagent_type="gsd-debugger", run_in_background=false, ...) ' +
|
|
1217
|
+
'and wait for its structured return.';
|
|
1218
|
+
}
|
|
974
1219
|
return `Secret read guard: ${tool} would read '${target}', which matches a protected secret-file ` +
|
|
975
1220
|
`pattern (${PATTERN_TEXT}). Secret values must not be read into the conversation. ` +
|
|
976
1221
|
'If you need a specific value, ask the user for it; if you need the variable NAMES, ' +
|
|
@@ -1122,6 +1367,8 @@ process.stdin.on('end', () => {
|
|
|
1122
1367
|
if (command.length > MAX_COMMAND_LENGTH) emitBlock('command-too-large', tool, '');
|
|
1123
1368
|
const hit = findSecretRead(command, 0);
|
|
1124
1369
|
if (hit !== null) emitBlock('secret-read', tool, hit);
|
|
1370
|
+
const spawn = findDebuggerCliSpawn(command, 0);
|
|
1371
|
+
if (spawn !== null) emitBlock('debugger-cli-spawn', tool, spawn);
|
|
1125
1372
|
allow(undefined);
|
|
1126
1373
|
} catch {
|
|
1127
1374
|
// Fail open — never block valid tool calls due to hook errors.
|