@webpresso/agent-kit 3.1.28 → 3.2.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.
Potentially problematic release.
This version of @webpresso/agent-kit might be problematic. Click here for more details.
- package/README.md +53 -15
- package/bin/_run.js +179 -19
- package/catalog/AGENTS.md.tpl +6 -6
- package/catalog/agent/agents/implementer.md +45 -0
- package/catalog/agent/rules/ci-cost-local-first.md +139 -0
- package/catalog/agent/rules/ci-test-perf.md +5 -1
- package/catalog/agent/rules/cmd-execution.md +9 -0
- package/catalog/agent/rules/parallel-orchestration-quota.md +100 -0
- package/catalog/agent/rules/pre-implementation.md +30 -2
- package/catalog/agent/rules/supported-agent-clis.md +9 -0
- package/catalog/agent/rules/test-scan-perf.md +80 -0
- package/catalog/agent/rules/typescript-version.md +50 -0
- package/catalog/agent/rules/workflow-skills-routing.md +4 -2
- package/catalog/agent/skills/codex/SKILL.md +25 -1
- package/catalog/agent/skills/fix/SKILL.md +3 -0
- package/catalog/agent/skills/verify/SKILL.md +22 -2
- package/catalog/docs/templates/blueprint.md +1 -1
- package/dist/esm/audit/agents.js +1 -0
- package/dist/esm/audit/blueprint-lifecycle-sql.d.ts +12 -0
- package/dist/esm/audit/blueprint-lifecycle-sql.js +23 -2
- package/dist/esm/audit/blueprint-pr-coverage.js +72 -7
- package/dist/esm/audit/ci-guardrails-wiring.d.ts +22 -0
- package/dist/esm/audit/ci-guardrails-wiring.js +90 -0
- package/dist/esm/audit/ci-test-perf.js +30 -0
- package/dist/esm/audit/command-surface.js +4 -2
- package/dist/esm/audit/host-substitution-risk.d.ts +2 -0
- package/dist/esm/audit/host-substitution-risk.js +256 -0
- package/dist/esm/audit/registry.d.ts +4 -4
- package/dist/esm/audit/registry.js +11 -1
- package/dist/esm/audit/roadmap-links.js +6 -0
- package/dist/esm/audit/test-scan-perf.d.ts +18 -0
- package/dist/esm/audit/test-scan-perf.js +275 -0
- package/dist/esm/audit/toolchain-isolation.js +1 -2
- package/dist/esm/audit/typescript-version.d.ts +2 -0
- package/dist/esm/audit/typescript-version.js +123 -0
- package/dist/esm/blueprint/core/parser.js +43 -1
- package/dist/esm/blueprint/core/validation/criteria.d.ts +14 -0
- package/dist/esm/blueprint/core/validation/criteria.js +41 -2
- package/dist/esm/blueprint/core/validation/state.js +25 -14
- package/dist/esm/blueprint/db/migrations/run.js +4 -2
- package/dist/esm/blueprint/lifecycle/audit.d.ts +20 -0
- package/dist/esm/blueprint/lifecycle/audit.js +65 -10
- package/dist/esm/blueprint/lifecycle/canonicalize.d.ts +6 -0
- package/dist/esm/blueprint/lifecycle/canonicalize.js +135 -0
- package/dist/esm/blueprint/lifecycle/engine.d.ts +9 -0
- package/dist/esm/blueprint/lifecycle/engine.js +53 -6
- package/dist/esm/blueprint/lifecycle/local.d.ts +2 -1
- package/dist/esm/blueprint/lifecycle/local.js +19 -3
- package/dist/esm/blueprint/lifecycle/review-provenance.d.ts +6 -0
- package/dist/esm/blueprint/lifecycle/review-provenance.js +118 -12
- package/dist/esm/blueprint/markdown/helpers.js +6 -2
- package/dist/esm/blueprint/service/BlueprintCreationService.d.ts +1 -0
- package/dist/esm/blueprint/service/BlueprintCreationService.js +53 -4
- package/dist/esm/blueprint/service/blueprint-tech-debt-links.js +16 -2
- package/dist/esm/blueprint/trust/command-runner.d.ts +26 -0
- package/dist/esm/blueprint/trust/command-runner.js +148 -0
- package/dist/esm/blueprint/trust/gates.d.ts +7 -0
- package/dist/esm/blueprint/trust/gates.js +20 -0
- package/dist/esm/blueprint/trust/promotion.d.ts +8 -0
- package/dist/esm/blueprint/trust/promotion.js +62 -112
- package/dist/esm/blueprint/trust/scaffold.d.ts +19 -0
- package/dist/esm/blueprint/trust/scaffold.js +64 -0
- package/dist/esm/blueprint/trust/validator.d.ts +1 -0
- package/dist/esm/blueprint/trust/validator.js +7 -1
- package/dist/esm/blueprint/utils/archive.js +27 -5
- package/dist/esm/blueprint/utils/document-paths.d.ts +18 -0
- package/dist/esm/blueprint/utils/document-paths.js +45 -12
- package/dist/esm/build/cli-mcp-parity.js +10 -0
- package/dist/esm/ci/act-replay.js +110 -7
- package/dist/esm/ci/act-runner.d.ts +4 -0
- package/dist/esm/ci/act-runner.js +85 -26
- package/dist/esm/ci/act-worktree-git.d.ts +11 -0
- package/dist/esm/ci/act-worktree-git.js +219 -0
- package/dist/esm/cli/bundle/commands/blueprint.d.ts +1 -0
- package/dist/esm/cli/bundle/commands/blueprint.js +1 -0
- package/dist/esm/cli/cli.d.ts +1 -1
- package/dist/esm/cli/cli.js +34 -0
- package/dist/esm/cli/commands/agent-launch.d.ts +6 -1
- package/dist/esm/cli/commands/agent-launch.js +82 -4
- package/dist/esm/cli/commands/audit-core.d.ts +1 -1
- package/dist/esm/cli/commands/audit-core.js +8 -1
- package/dist/esm/cli/commands/audit.js +20 -5
- package/dist/esm/cli/commands/blueprint/execution.js +18 -6
- package/dist/esm/cli/commands/blueprint/mutations.js +83 -47
- package/dist/esm/cli/commands/blueprint/router-dispatch.d.ts +2 -1
- package/dist/esm/cli/commands/blueprint/router-dispatch.js +20 -1
- package/dist/esm/cli/commands/blueprint/router-output.d.ts +21 -0
- package/dist/esm/cli/commands/blueprint/router-output.js +48 -17
- package/dist/esm/cli/commands/blueprint/router.d.ts +7 -0
- package/dist/esm/cli/commands/blueprint/router.js +40 -3
- package/dist/esm/cli/commands/ci-preflight.d.ts +13 -2
- package/dist/esm/cli/commands/ci-preflight.js +6 -3
- package/dist/esm/cli/commands/ci.d.ts +8 -1
- package/dist/esm/cli/commands/ci.js +80 -21
- package/dist/esm/cli/commands/compile.js +5 -31
- package/dist/esm/cli/commands/dash/delivery-status.d.ts +1 -0
- package/dist/esm/cli/commands/dash/delivery-status.js +11 -4
- package/dist/esm/cli/commands/dash/keymap.js +1 -0
- package/dist/esm/cli/commands/dash/launch-preferences.d.ts +15 -0
- package/dist/esm/cli/commands/dash/launch-preferences.js +46 -17
- package/dist/esm/cli/commands/dash/plan-once-label.d.ts +7 -0
- package/dist/esm/cli/commands/dash/plan-once-label.js +5 -0
- package/dist/esm/cli/commands/dash/plan-once-membership.d.ts +67 -0
- package/dist/esm/cli/commands/dash/plan-once-membership.js +151 -0
- package/dist/esm/cli/commands/dash/plan-once-merge.d.ts +143 -0
- package/dist/esm/cli/commands/dash/plan-once-merge.js +238 -0
- package/dist/esm/cli/commands/dash/plan-once.d.ts +22 -2
- package/dist/esm/cli/commands/dash/plan-once.js +41 -4
- package/dist/esm/cli/commands/dash/tui/attention-hud.d.ts +9 -0
- package/dist/esm/cli/commands/dash/tui/attention-hud.js +9 -0
- package/dist/esm/cli/commands/dash/tui/chat-bridge.d.ts +41 -0
- package/dist/esm/cli/commands/dash/tui/chat-bridge.js +73 -0
- package/dist/esm/cli/commands/dash/tui/chat-mode.d.ts +47 -0
- package/dist/esm/cli/commands/dash/tui/chat-mode.js +98 -0
- package/dist/esm/cli/commands/dash/tui/frame.d.ts +3 -0
- package/dist/esm/cli/commands/dash/tui/frame.js +3 -1
- package/dist/esm/cli/commands/dash/tui/palette.d.ts +1 -1
- package/dist/esm/cli/commands/dash/tui/palette.js +14 -0
- package/dist/esm/cli/commands/dash/tui/runtime.js +239 -4
- package/dist/esm/cli/commands/dash/tui/state.d.ts +41 -0
- package/dist/esm/cli/commands/dash/tui/state.js +257 -4
- package/dist/esm/cli/commands/doctor.js +5 -0
- package/dist/esm/cli/commands/format.js +0 -2
- package/dist/esm/cli/commands/hook.d.ts +5 -0
- package/dist/esm/cli/commands/hook.js +15 -9
- package/dist/esm/cli/commands/init/config.d.ts +0 -1
- package/dist/esm/cli/commands/init/config.js +3 -6
- package/dist/esm/cli/commands/init/convergence-apply.d.ts +24 -0
- package/dist/esm/cli/commands/init/convergence-apply.js +221 -0
- package/dist/esm/cli/commands/init/convergence-state.d.ts +13 -0
- package/dist/esm/cli/commands/init/convergence-state.js +54 -0
- package/dist/esm/cli/commands/init/convergence.d.ts +23 -0
- package/dist/esm/cli/commands/init/convergence.js +57 -0
- package/dist/esm/cli/commands/init/git-convergence.d.ts +19 -0
- package/dist/esm/cli/commands/init/git-convergence.js +168 -0
- package/dist/esm/cli/commands/init/gitignore-patcher.js +2 -0
- package/dist/esm/cli/commands/init/grok-privacy-smoke.d.ts +52 -0
- package/dist/esm/cli/commands/init/grok-privacy-smoke.js +91 -0
- package/dist/esm/cli/commands/init/host-native-proof.d.ts +18 -0
- package/dist/esm/cli/commands/init/host-native-proof.js +88 -0
- package/dist/esm/cli/commands/init/host-visibility.d.ts +2 -2
- package/dist/esm/cli/commands/init/host-visibility.js +94 -5
- package/dist/esm/cli/commands/init/index.d.ts +1 -0
- package/dist/esm/cli/commands/init/index.js +326 -173
- package/dist/esm/cli/commands/init/mcp-spec.d.ts +57 -0
- package/dist/esm/cli/commands/init/mcp-spec.js +273 -0
- package/dist/esm/cli/commands/init/merge.d.ts +1 -0
- package/dist/esm/cli/commands/init/merge.js +30 -1
- package/dist/esm/cli/commands/init/plugin-cache-prune.d.ts +0 -3
- package/dist/esm/cli/commands/init/plugin-cache-prune.js +0 -1
- package/dist/esm/cli/commands/init/scaffold-agents-md.d.ts +1 -1
- package/dist/esm/cli/commands/init/scaffold-agents-md.js +9 -3
- package/dist/esm/cli/commands/init/scaffold-base-kit.d.ts +11 -0
- package/dist/esm/cli/commands/init/scaffold-base-kit.js +56 -1
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/claude.d.ts +5 -6
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/claude.js +6 -13
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.d.ts +14 -8
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/grok.js +41 -24
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.d.ts +2 -0
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/index.js +51 -23
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/ir.d.ts +0 -2
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/ir.js +0 -2
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/merge.d.ts +6 -3
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/merge.js +39 -26
- package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.d.ts +12 -31
- package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +297 -173
- package/dist/esm/cli/commands/init/scaffolders/file-transaction.d.ts +7 -0
- package/dist/esm/cli/commands/init/scaffolders/file-transaction.js +58 -0
- package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.d.ts +38 -0
- package/dist/esm/cli/commands/init/scaffolders/grok-mcp/index.js +73 -0
- package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.d.ts +21 -0
- package/dist/esm/cli/commands/init/scaffolders/opencode-plugin/index.js +559 -63
- package/dist/esm/cli/commands/init/untracked-collision.d.ts +14 -0
- package/dist/esm/cli/commands/init/untracked-collision.js +42 -0
- package/dist/esm/cli/commands/logs.d.ts +15 -0
- package/dist/esm/cli/commands/logs.js +192 -3
- package/dist/esm/cli/commands/opencode-rotate.d.ts +49 -0
- package/dist/esm/cli/commands/opencode-rotate.js +135 -0
- package/dist/esm/cli/commands/public-readiness.d.ts +11 -0
- package/dist/esm/cli/commands/public-readiness.js +42 -0
- package/dist/esm/cli/commands/quality-log-store.d.ts +1 -0
- package/dist/esm/cli/commands/quality-log-store.js +187 -51
- package/dist/esm/cli/commands/review.d.ts +35 -3
- package/dist/esm/cli/commands/review.js +734 -217
- package/dist/esm/cli/commands/run-wait.d.ts +2 -0
- package/dist/esm/cli/commands/run-wait.js +122 -0
- package/dist/esm/cli/commands/secrets.d.ts +1 -0
- package/dist/esm/cli/commands/secrets.js +4 -1
- package/dist/esm/cli/commands/setup-help.js +1 -1
- package/dist/esm/cli/commands/setup-repair-help.js +1 -1
- package/dist/esm/cli/commands/worktree/router-dispatch.js +13 -8
- package/dist/esm/cli/direct-provider-launch.js +31 -3
- package/dist/esm/cli/optional-tool-freshness.d.ts +27 -0
- package/dist/esm/cli/optional-tool-freshness.js +219 -9
- package/dist/esm/daemon/domains/git-actions.d.ts +29 -0
- package/dist/esm/daemon/domains/git-actions.js +136 -0
- package/dist/esm/daemon/domains/run-registry-interop.d.ts +16 -0
- package/dist/esm/daemon/domains/run-registry-interop.js +45 -0
- package/dist/esm/daemon/domains/sessions-search.d.ts +11 -0
- package/dist/esm/daemon/domains/sessions-search.js +29 -0
- package/dist/esm/daemon/handlers/blueprints-handlers.d.ts +5 -0
- package/dist/esm/daemon/handlers/blueprints-handlers.js +36 -0
- package/dist/esm/daemon/handlers/deploy-handlers.d.ts +6 -0
- package/dist/esm/daemon/handlers/deploy-handlers.js +5 -0
- package/dist/esm/daemon/handlers/fleet-handlers.d.ts +15 -0
- package/dist/esm/daemon/handlers/fleet-handlers.js +91 -0
- package/dist/esm/daemon/handlers/git-handlers.d.ts +6 -0
- package/dist/esm/daemon/handlers/git-handlers.js +27 -0
- package/dist/esm/daemon/handlers/product.d.ts +7 -0
- package/dist/esm/daemon/handlers/product.js +10 -119
- package/dist/esm/daemon/handlers/sessions-handlers.d.ts +12 -0
- package/dist/esm/daemon/handlers/sessions-handlers.js +31 -0
- package/dist/esm/daemon/handlers/shared.d.ts +5 -0
- package/dist/esm/daemon/handlers/shared.js +16 -0
- package/dist/esm/daemon/methods.d.ts +8 -1
- package/dist/esm/daemon/methods.js +7 -0
- package/dist/esm/daemon/protocol/contract.d.ts +43 -0
- package/dist/esm/daemon/protocol/contract.js +26 -0
- package/dist/esm/daemon/protocol/index.d.ts +1 -1
- package/dist/esm/daemon/protocol/index.js +1 -1
- package/dist/esm/hooks/__conformance__/matrix.d.ts +6 -0
- package/dist/esm/hooks/__conformance__/matrix.js +137 -0
- package/dist/esm/hooks/conformance/batch.js +5 -0
- package/dist/esm/hooks/doctor.d.ts +15 -0
- package/dist/esm/hooks/doctor.js +79 -8
- package/dist/esm/hooks/permission-request/index.js +9 -3
- package/dist/esm/hooks/permission-request/policy.d.ts +9 -0
- package/dist/esm/hooks/permission-request/policy.js +13 -0
- package/dist/esm/hooks/pretool-guard/dev-routing.d.ts +9 -0
- package/dist/esm/hooks/pretool-guard/dev-routing.js +41 -5
- package/dist/esm/hooks/pretool-guard/logger.d.ts +7 -0
- package/dist/esm/hooks/pretool-guard/logger.js +16 -0
- package/dist/esm/hooks/pretool-guard/runner.d.ts +6 -0
- package/dist/esm/hooks/pretool-guard/runner.js +68 -16
- package/dist/esm/hooks/pretool-guard/validators/blueprint.js +30 -10
- package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +58 -4
- package/dist/esm/hooks/pretool-guard/validators/index.d.ts +7 -0
- package/dist/esm/hooks/pretool-guard/validators/index.js +21 -0
- package/dist/esm/hooks/pretool-guard/validators/mcp-redirect.d.ts +0 -1
- package/dist/esm/hooks/pretool-guard/validators/mcp-redirect.js +8 -5
- package/dist/esm/hooks/pretool-guard/validators/path-contract.d.ts +19 -0
- package/dist/esm/hooks/pretool-guard/validators/path-contract.js +25 -8
- package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.d.ts +9 -7
- package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.js +126 -14
- package/dist/esm/hooks/shared/guard-outcome.d.ts +149 -0
- package/dist/esm/hooks/shared/guard-outcome.js +215 -0
- package/dist/esm/hooks/shared/hook-bootstrap.d.ts +26 -0
- package/dist/esm/hooks/shared/hook-bootstrap.js +64 -1
- package/dist/esm/hooks/shared/types.d.ts +8 -5
- package/dist/esm/hooks/shared/types.js +23 -8
- package/dist/esm/hooks/stop/qa-changed-files.d.ts +11 -0
- package/dist/esm/hooks/stop/qa-changed-files.js +74 -0
- package/dist/esm/mcp/blueprint/_shared/lifecycle.js +14 -1
- package/dist/esm/mcp/blueprint/handlers/document-mutations.js +78 -4
- package/dist/esm/mcp/blueprint/handlers/review-log.js +8 -1
- package/dist/esm/mcp/blueprint/handlers/task-advance.js +113 -54
- package/dist/esm/mcp/blueprint/handlers/task-verify.js +39 -19
- package/dist/esm/mcp/blueprint/registration.js +3 -2
- package/dist/esm/mcp/server.d.ts +1 -0
- package/dist/esm/mcp/server.js +27 -0
- package/dist/esm/mcp/tools/_names.d.ts +1 -1
- package/dist/esm/mcp/tools/_names.js +5 -0
- package/dist/esm/mcp/tools/_registry.js +10 -0
- package/dist/esm/mcp/tools/_shared/audit-kinds.d.ts +1 -1
- package/dist/esm/mcp/tools/_shared/result.js +44 -1
- package/dist/esm/mcp/tools/audit.d.ts +5 -1
- package/dist/esm/mcp/tools/audit.js +40 -0
- package/dist/esm/mcp/tools/audits.d.ts +4 -0
- package/dist/esm/mcp/tools/ci-act.js +9 -11
- package/dist/esm/mcp/tools/ci-preflight.d.ts +50 -0
- package/dist/esm/mcp/tools/ci-preflight.js +183 -0
- package/dist/esm/mcp/tools/pr-upsert.js +39 -1
- package/dist/esm/mcp/tools/review-run.d.ts +28 -0
- package/dist/esm/mcp/tools/review-run.js +216 -0
- package/dist/esm/mcp/tools/run-wait.d.ts +86 -0
- package/dist/esm/mcp/tools/run-wait.js +593 -0
- package/dist/esm/mcp/tools/session-id.d.ts +3 -0
- package/dist/esm/mcp/tools/session-id.js +45 -0
- package/dist/esm/mcp/tools/session-info.d.ts +3 -0
- package/dist/esm/mcp/tools/session-info.js +49 -0
- package/dist/esm/output-transforms/audit.d.ts +23 -0
- package/dist/esm/output-transforms/audit.js +47 -0
- package/dist/esm/output-transforms/index.js +2 -0
- package/dist/esm/paths/state-root.js +3 -0
- package/dist/esm/review/authority.js +30 -7
- package/dist/esm/review/availability.d.ts +22 -0
- package/dist/esm/review/availability.js +123 -23
- package/dist/esm/review/delivery-verifier.d.ts +27 -0
- package/dist/esm/review/delivery-verifier.js +115 -0
- package/dist/esm/review/events.d.ts +3 -2
- package/dist/esm/review/events.js +23 -3
- package/dist/esm/review/execution/adapters.js +26 -9
- package/dist/esm/review/execution/output-policy.d.ts +2 -0
- package/dist/esm/review/execution/output-policy.js +12 -0
- package/dist/esm/review/execution/review-checkout.js +87 -6
- package/dist/esm/review/execution/supervisor.js +5 -0
- package/dist/esm/review/execution/types.d.ts +8 -0
- package/dist/esm/review/opencode-account.d.ts +24 -0
- package/dist/esm/review/opencode-account.js +68 -0
- package/dist/esm/review/opencode-auth.d.ts +33 -0
- package/dist/esm/review/opencode-auth.js +79 -0
- package/dist/esm/review/subject.d.ts +27 -0
- package/dist/esm/review/subject.js +179 -32
- package/dist/esm/runtime/executor.d.ts +1 -0
- package/dist/esm/runtime/executor.js +2 -1
- package/dist/esm/runtime/opencode-account-materializer.d.ts +34 -0
- package/dist/esm/runtime/opencode-account-materializer.js +33 -0
- package/dist/esm/secret-gate/runner.d.ts +1 -0
- package/dist/esm/secret-gate/runner.js +4 -1
- package/dist/esm/status/snapshot.js +30 -19
- package/dist/esm/test/duration-sequencer.d.ts +20 -0
- package/dist/esm/test/duration-sequencer.js +54 -0
- package/dist/esm/test/shard-durations.json +922 -0
- package/dist/esm/test/worker-budget.d.ts +6 -0
- package/dist/esm/test/worker-budget.js +59 -1
- package/dist/esm/test-helpers/global-setup.d.ts +1 -1
- package/dist/esm/test-helpers/global-setup.js +68 -24
- package/dist/esm/ultragoal/service.d.ts +7 -0
- package/dist/esm/ultragoal/service.js +42 -2
- package/dist/esm/utils/write-json-file.js +3 -1
- package/dist/esm/worktrees/codex-project-trust.d.ts +15 -0
- package/dist/esm/worktrees/codex-project-trust.js +56 -0
- package/dist/esm/worktrees/current.d.ts +1 -0
- package/dist/esm/worktrees/current.js +1 -0
- package/dist/esm/worktrees/project-agent-surfaces.d.ts +1 -0
- package/dist/esm/worktrees/project-agent-surfaces.js +2 -1
- package/dist/esm/worktrees/registry.d.ts +8 -0
- package/dist/esm/worktrees/registry.js +18 -3
- package/package.json +18 -15
|
@@ -25,13 +25,15 @@ const VALIDATOR_NAME = "worktree-discipline";
|
|
|
25
25
|
* effective cwd elsewhere (e.g. CI) → ALLOW (rule scoped to local ~/repos primaries)
|
|
26
26
|
* ```
|
|
27
27
|
*
|
|
28
|
-
* The "effective cwd" is the tool's ambient `input.cwd`, lowered
|
|
29
|
-
* success-gated
|
|
30
|
-
* `cd <worktree> && git commit
|
|
31
|
-
* EVERY `git -C <dir>` flag on the
|
|
32
|
-
* cumulatively).
|
|
33
|
-
*
|
|
34
|
-
*
|
|
28
|
+
* The "effective cwd" is the tool's ambient `input.cwd`, lowered by a
|
|
29
|
+
* success-gated `&&` chain that governs the forbidden git op, e.g.
|
|
30
|
+
* `cd <worktree> && git commit`, `cd <worktree> && ./bin/wp … && git commit`,
|
|
31
|
+
* or `(cd <worktree> && git commit)`, then by EVERY `git -C <dir>` flag on the
|
|
32
|
+
* matched invocation (git applies them cumulatively). Intermediate non-cwd
|
|
33
|
+
* commands after a proven `cd` leave the chain cwd unchanged. Unsupported
|
|
34
|
+
* control flow (`;` / `|` / braces), option-bearing `cd`, `cd -`, or
|
|
35
|
+
* unresolvable `cd`/`-C` targets fail closed because they could hide a
|
|
36
|
+
* primary-checkout mutation. Prefer `git -C <worktree> ...` or running the
|
|
35
37
|
* command with the tool cwd already set to the worktree.
|
|
36
38
|
*
|
|
37
39
|
* Known best-effort limits (acceptable for an agent guard with a documented
|
|
@@ -634,12 +636,13 @@ const DIR_TOKEN = String.raw `(?:"[^"]+"|'[^']+'|[^\s;&|(){}]+)`;
|
|
|
634
636
|
const QUOTED_ARG = String.raw `(?:"[^"]+"|'[^']+'|\S+)`;
|
|
635
637
|
const CD_TARGET = String.raw `(?:--\s+)?(${DIR_TOKEN}|-)`;
|
|
636
638
|
const CD_LEADER = String.raw `(?:[A-Za-z_]\w*=\S*\s+)*(?:command\s+|builtin\s+)?(?:cd|pushd)\s+`;
|
|
637
|
-
//
|
|
638
|
-
//
|
|
639
|
-
|
|
639
|
+
// Pure cd/pushd segment that updates the success-gated cwd (optional leading env /
|
|
640
|
+
// command|builtin wrapper). Trailing whitespace is allowed; nothing else.
|
|
641
|
+
const PURE_CD_SEGMENT = new RegExp(String.raw `^\s*${CD_LEADER}${CD_TARGET}\s*$`, "u");
|
|
642
|
+
// Legacy pure trailing-cd chain (also used for subshell-local `(cd X && git …)`).
|
|
640
643
|
const CD_CHAIN = new RegExp(String.raw `(?:^|[;&|(){}]\s*)?((?:\s*${CD_LEADER}${CD_TARGET}\s*&&)+)\s*$`, "u");
|
|
641
644
|
const CD_IN_CHAIN = new RegExp(String.raw `(?:^|&&)\s*${CD_LEADER}${CD_TARGET}\s*(?=&&)`, "gu");
|
|
642
|
-
const CWD_WORD = new RegExp(String.raw `(?:^|[;{}()&|]\s*|\s+)(?:[A-Za-z_]\w*=\S*\s+)*(?:command\s+|builtin\s+)?(?:cd|pushd)\b`, "gu");
|
|
645
|
+
const CWD_WORD = new RegExp(String.raw `(?:^|[;{}()&|]\s*|\s+)(?:[A-Za-z_]\w*=\S*\s+)*(?:command\s+|builtin\s+)?(?:cd|pushd|popd)\b`, "gu");
|
|
643
646
|
const DASH_C = new RegExp(String.raw `-C\s+(${DIR_TOKEN})`, "gu");
|
|
644
647
|
function isInsideSingleOrDoubleQuotes(command, index) {
|
|
645
648
|
let quote = null;
|
|
@@ -667,6 +670,27 @@ function hasUnquotedCwdCommand(command) {
|
|
|
667
670
|
}
|
|
668
671
|
return false;
|
|
669
672
|
}
|
|
673
|
+
/** True when unquoted `;` `|` `{` `}` appear (breaks a pure `&&` agent chain). */
|
|
674
|
+
function hasUnquotedUnsafeAndChainControl(command) {
|
|
675
|
+
for (let i = 0; i < command.length; i += 1) {
|
|
676
|
+
const ch = command[i];
|
|
677
|
+
if (ch !== ";" && ch !== "|" && ch !== "{" && ch !== "}" && ch !== "\n")
|
|
678
|
+
continue;
|
|
679
|
+
if (!isInsideSingleOrDoubleQuotes(command, i))
|
|
680
|
+
return true;
|
|
681
|
+
}
|
|
682
|
+
return false;
|
|
683
|
+
}
|
|
684
|
+
function hasUnquotedParen(command) {
|
|
685
|
+
for (let i = 0; i < command.length; i += 1) {
|
|
686
|
+
const ch = command[i];
|
|
687
|
+
if (ch !== "(" && ch !== ")")
|
|
688
|
+
continue;
|
|
689
|
+
if (!isInsideSingleOrDoubleQuotes(command, i))
|
|
690
|
+
return true;
|
|
691
|
+
}
|
|
692
|
+
return false;
|
|
693
|
+
}
|
|
670
694
|
/** Apply one dir token to `cwd`, or signal ambiguity if it cannot be statically resolved. */
|
|
671
695
|
function applyDir(token, cwd) {
|
|
672
696
|
if (!token)
|
|
@@ -683,11 +707,85 @@ function isTrustedCwdChainStart(commandPrefix, chainStart) {
|
|
|
683
707
|
const before = commandPrefix.slice(0, chainStart).trimEnd();
|
|
684
708
|
return before.endsWith("&&") || before.endsWith("(") || before.endsWith("{");
|
|
685
709
|
}
|
|
686
|
-
|
|
710
|
+
/**
|
|
711
|
+
* Split a pure `&&` command prefix into segments. Returns null when the prefix
|
|
712
|
+
* uses `;` / `|` / braces / newlines we refuse to model.
|
|
713
|
+
*/
|
|
714
|
+
function splitSuccessGatedAndSegments(commandPrefix) {
|
|
715
|
+
if (!commandPrefix.trim())
|
|
716
|
+
return [];
|
|
717
|
+
if (hasUnquotedUnsafeAndChainControl(commandPrefix))
|
|
718
|
+
return null;
|
|
719
|
+
const segments = [];
|
|
720
|
+
let current = "";
|
|
721
|
+
let quote = null;
|
|
722
|
+
let escaped = false;
|
|
723
|
+
for (let i = 0; i < commandPrefix.length; i += 1) {
|
|
724
|
+
const ch = commandPrefix[i] ?? "";
|
|
725
|
+
if (escaped) {
|
|
726
|
+
current += ch;
|
|
727
|
+
escaped = false;
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
if (quote !== "'" && ch === "\\") {
|
|
731
|
+
escaped = true;
|
|
732
|
+
current += ch;
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
if ((ch === '"' || ch === "'") && (quote === null || quote === ch)) {
|
|
736
|
+
quote = quote === ch ? null : ch;
|
|
737
|
+
current += ch;
|
|
738
|
+
continue;
|
|
739
|
+
}
|
|
740
|
+
if (!quote && ch === "&" && commandPrefix[i + 1] === "&") {
|
|
741
|
+
if (current.trim())
|
|
742
|
+
segments.push(current);
|
|
743
|
+
current = "";
|
|
744
|
+
i += 1;
|
|
745
|
+
continue;
|
|
746
|
+
}
|
|
747
|
+
current += ch;
|
|
748
|
+
}
|
|
749
|
+
if (quote || escaped)
|
|
750
|
+
return null;
|
|
751
|
+
if (current.trim())
|
|
752
|
+
segments.push(current);
|
|
753
|
+
return segments;
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Walk pure `&&` segments: resolvable cd/pushd updates cwd; other segments must
|
|
757
|
+
* not smuggle unquoted cwd changes. Enables
|
|
758
|
+
* `cd <managed-worktree> && ./bin/wp … && git commit`.
|
|
759
|
+
*/
|
|
760
|
+
function applyAndSegmentCwdChain(commandPrefix, baseCwd) {
|
|
761
|
+
const segments = splitSuccessGatedAndSegments(commandPrefix);
|
|
762
|
+
if (segments === null)
|
|
763
|
+
return { ambiguous: true };
|
|
764
|
+
if (segments.length === 0)
|
|
765
|
+
return { ambiguous: false, cwd: baseCwd };
|
|
766
|
+
let cwd = baseCwd;
|
|
767
|
+
for (const segment of segments) {
|
|
768
|
+
const pureCd = PURE_CD_SEGMENT.exec(segment);
|
|
769
|
+
if (pureCd) {
|
|
770
|
+
const next = applyDir(pureCd[1], cwd);
|
|
771
|
+
if (next === "ambiguous")
|
|
772
|
+
return { ambiguous: true };
|
|
773
|
+
cwd = next.cwd;
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
if (hasUnquotedCwdCommand(segment))
|
|
777
|
+
return { ambiguous: true };
|
|
778
|
+
}
|
|
779
|
+
return { ambiguous: false, cwd };
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Legacy trailing pure-cd chain (and subshell-local `(cd X && git …)` forms).
|
|
783
|
+
* Kept for paren-scoped ops; intermediate non-cd commands are handled by
|
|
784
|
+
* {@link applyAndSegmentCwdChain} when no unquoted parens are present.
|
|
785
|
+
*/
|
|
786
|
+
function applyLegacyTrailingCdChain(commandPrefix, baseCwd) {
|
|
687
787
|
const chain = CD_CHAIN.exec(commandPrefix);
|
|
688
788
|
const chainStart = chain?.index ?? commandPrefix.length;
|
|
689
|
-
// If there is any other unquoted cwd-changing syntax before this simple chain,
|
|
690
|
-
// we cannot prove the chain governs the op. Fail closed.
|
|
691
789
|
if (hasUnquotedCwdCommand(commandPrefix.slice(0, chainStart)))
|
|
692
790
|
return { ambiguous: true };
|
|
693
791
|
if (!chain)
|
|
@@ -703,6 +801,20 @@ function applySuccessGatedCwdChain(commandPrefix, baseCwd) {
|
|
|
703
801
|
}
|
|
704
802
|
return { ambiguous: false, cwd };
|
|
705
803
|
}
|
|
804
|
+
/**
|
|
805
|
+
* Success-gated cwd chain for a forbidden git op's command prefix.
|
|
806
|
+
*
|
|
807
|
+
* Prefer the `&&`-segment walk when the prefix has no unquoted parentheses so
|
|
808
|
+
* agents can run tools between a managed `cd` and a mutation. When parentheses
|
|
809
|
+
* appear (subshell-local ops), fall back to the legacy trailing-cd chain so
|
|
810
|
+
* non-persistent `(cd …) && git …` stays fail-closed.
|
|
811
|
+
*/
|
|
812
|
+
function applySuccessGatedCwdChain(commandPrefix, baseCwd) {
|
|
813
|
+
if (!hasUnquotedParen(commandPrefix)) {
|
|
814
|
+
return applyAndSegmentCwdChain(commandPrefix, baseCwd);
|
|
815
|
+
}
|
|
816
|
+
return applyLegacyTrailingCdChain(commandPrefix, baseCwd);
|
|
817
|
+
}
|
|
706
818
|
/**
|
|
707
819
|
* The directory the git op actually runs in: `baseCwd` lowered by the
|
|
708
820
|
* success-gated cwd chain that directly governs this op, then through EVERY
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed guard-outcome vocabulary for the pretool-guard and permission-request
|
|
3
|
+
* hook surfaces.
|
|
4
|
+
*
|
|
5
|
+
* Two orthogonal axes describe every guard decision:
|
|
6
|
+
*
|
|
7
|
+
* - `GUARD_EFFECT` — what actually reaches the host on the wire.
|
|
8
|
+
* - `GUARD_INTENT` — why the guard produced that effect.
|
|
9
|
+
*
|
|
10
|
+
* They are deliberately separate because the earlier flat Proceed/Deny/Guide
|
|
11
|
+
* draft was not mutually exclusive ("Guide" still emits a deny; a host
|
|
12
|
+
* pass-through is not "Proceed"). The two-axis model is exhaustive and honest
|
|
13
|
+
* about what the host receives versus what the policy meant.
|
|
14
|
+
*
|
|
15
|
+
* Every guard denial carries a stable `GUARD_<CODE>` that survives the
|
|
16
|
+
* host-displayed wire reason (see {@link formatGuardReason}). Consumers and
|
|
17
|
+
* audits key on the code, not on fragile English wording, and the code is
|
|
18
|
+
* identical across every Tier-1 host.
|
|
19
|
+
*
|
|
20
|
+
* Precedent for the as-const vocabulary shape: `HOOK_STATUS`
|
|
21
|
+
* (src/hooks/shared/vocabulary.ts).
|
|
22
|
+
*/
|
|
23
|
+
export declare const GUARD_EFFECT: {
|
|
24
|
+
/** A deny envelope reaches the host (exit 2 / permissionDecision:"deny"). */
|
|
25
|
+
readonly block: "block";
|
|
26
|
+
/** The host is told to proceed (permission allow), or nothing is denied. */
|
|
27
|
+
readonly allow: "allow";
|
|
28
|
+
/** No wire change; the host's own UI/policy stays authoritative. */
|
|
29
|
+
readonly defer: "defer";
|
|
30
|
+
};
|
|
31
|
+
export type GuardEffect = (typeof GUARD_EFFECT)[keyof typeof GUARD_EFFECT];
|
|
32
|
+
export declare const GUARD_INTENT: {
|
|
33
|
+
/** Block-and-hold: the caller must comply; no automatic reroute. */
|
|
34
|
+
readonly enforce: "enforce";
|
|
35
|
+
/** Actively reroute the caller onto a sanctioned sandbox/MCP lane. */
|
|
36
|
+
readonly redirect: "redirect";
|
|
37
|
+
/** Non-blocking guidance surfaced in logs only. */
|
|
38
|
+
readonly advise: "advise";
|
|
39
|
+
/** Defer to the host default (pass-through); guard expresses no opinion. */
|
|
40
|
+
readonly hostDefault: "hostDefault";
|
|
41
|
+
};
|
|
42
|
+
export type GuardIntent = (typeof GUARD_INTENT)[keyof typeof GUARD_INTENT];
|
|
43
|
+
export type GuardOutcome = {
|
|
44
|
+
readonly effect: GuardEffect;
|
|
45
|
+
readonly intent: GuardIntent;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Closed set of guard reason codes, one per stable policy invariant (a single
|
|
49
|
+
* validator enforcing multiple invariants may map to multiple codes). Each code
|
|
50
|
+
* matches `/^GUARD_[A-Z0-9_]+$/u` and carries its {@link GuardOutcome}.
|
|
51
|
+
*
|
|
52
|
+
* Per-validator `GUARD_<NAME>_UNEXPECTED` fallback codes are NOT listed here;
|
|
53
|
+
* they are a deterministically-derived open family (see
|
|
54
|
+
* {@link validatorUnexpectedCode}) so that adding a validator never leaves a
|
|
55
|
+
* failure unclassified.
|
|
56
|
+
*/
|
|
57
|
+
export declare const GUARD_REASON_CODES: {
|
|
58
|
+
readonly GUARD_RAW_GIT_MUTATION: GuardOutcome;
|
|
59
|
+
readonly GUARD_MANUAL_PR_WAIT: GuardOutcome;
|
|
60
|
+
readonly GUARD_PR_DISCLOSURE: GuardOutcome;
|
|
61
|
+
readonly GUARD_WRAPPED_WP: GuardOutcome;
|
|
62
|
+
readonly GUARD_SOURCE_ENTRYPOINT: GuardOutcome;
|
|
63
|
+
readonly GUARD_DEV_TOOL_REDIRECT: GuardOutcome;
|
|
64
|
+
readonly GUARD_DATA_HEAVY_SANDBOX: GuardOutcome;
|
|
65
|
+
readonly GUARD_SESSION_MEMORY_REDIRECT: GuardOutcome;
|
|
66
|
+
readonly GUARD_PROMPT_INTENT: GuardOutcome;
|
|
67
|
+
readonly GUARD_PRETOOL_FAILURE: GuardOutcome;
|
|
68
|
+
readonly GUARD_FORBIDDEN_COMMAND: GuardOutcome;
|
|
69
|
+
readonly GUARD_DANGEROUS_COMMAND: GuardOutcome;
|
|
70
|
+
readonly GUARD_BLUEPRINT_INVALID: GuardOutcome;
|
|
71
|
+
readonly GUARD_BLUEPRINT_FIRST: GuardOutcome;
|
|
72
|
+
readonly GUARD_DOCS_GOVERNANCE: GuardOutcome;
|
|
73
|
+
readonly GUARD_PLAN_FRONTMATTER: GuardOutcome;
|
|
74
|
+
readonly GUARD_COMPLEXITY: GuardOutcome;
|
|
75
|
+
readonly GUARD_PACKAGE_IMPORTS: GuardOutcome;
|
|
76
|
+
readonly GUARD_FILE_CONVENTIONS: GuardOutcome;
|
|
77
|
+
readonly GUARD_COMMAND_FILE: GuardOutcome;
|
|
78
|
+
readonly GUARD_TEST_QUALITY: GuardOutcome;
|
|
79
|
+
readonly GUARD_UX_QUALITY: GuardOutcome;
|
|
80
|
+
readonly GUARD_WORKTREE_DISCIPLINE: GuardOutcome;
|
|
81
|
+
readonly GUARD_PERMISSION_ALLOWED: GuardOutcome;
|
|
82
|
+
readonly GUARD_PERMISSION_DENIED: GuardOutcome;
|
|
83
|
+
readonly GUARD_HOST_DEFAULT: GuardOutcome;
|
|
84
|
+
};
|
|
85
|
+
export type GuardReasonCode = keyof typeof GUARD_REASON_CODES;
|
|
86
|
+
/** Deterministically-derived per-validator fallback code family. */
|
|
87
|
+
export type GuardUnexpectedCode = `GUARD_${string}_UNEXPECTED`;
|
|
88
|
+
/** Any accepted guard reason token: a registered code or an UNEXPECTED fallback. */
|
|
89
|
+
export type GuardReasonToken = GuardReasonCode | GuardUnexpectedCode;
|
|
90
|
+
/**
|
|
91
|
+
* Map a pretool-guard validator name to its canonical invariant code. Runner
|
|
92
|
+
* derivation falls back to {@link validatorUnexpectedCode} when a validator is
|
|
93
|
+
* not listed here, so a newly-added validator still produces a classified,
|
|
94
|
+
* pattern-valid code.
|
|
95
|
+
*/
|
|
96
|
+
export declare const GUARD_VALIDATOR_CODES: Readonly<Record<string, GuardReasonCode>>;
|
|
97
|
+
/**
|
|
98
|
+
* Deterministic, length-bounded per-validator fallback code, e.g. `test-quality`
|
|
99
|
+
* -> `GUARD_TEST_QUALITY_UNEXPECTED`. Always {@link GUARD_INTENT.enforce}. The
|
|
100
|
+
* name segment is capped so even a pathologically long validator name yields a
|
|
101
|
+
* token well under the wire budget.
|
|
102
|
+
*/
|
|
103
|
+
export declare function validatorUnexpectedCode(validatorName: string): GuardUnexpectedCode;
|
|
104
|
+
/**
|
|
105
|
+
* Narrow to a registered guard reason code. This is SOUND: it returns true
|
|
106
|
+
* only for actual keys of the closed {@link GUARD_REASON_CODES} set. A
|
|
107
|
+
* per-validator `GUARD_<NAME>_UNEXPECTED` fallback is NOT a registered code —
|
|
108
|
+
* use {@link isGuardReasonToken} for the broader accepted family.
|
|
109
|
+
*/
|
|
110
|
+
export declare function isGuardReasonCode(value: string): value is GuardReasonCode;
|
|
111
|
+
/**
|
|
112
|
+
* Narrow to any accepted guard reason token: a registered code or a well-formed
|
|
113
|
+
* `GUARD_<NAME>_UNEXPECTED` fallback. A fallback token is only accepted when it
|
|
114
|
+
* fits the wire budget as a prefix (token + ": " <= GUARD_REASON_BUDGET), so an
|
|
115
|
+
* accepted token can never force formatGuardReason to truncate its own prefix —
|
|
116
|
+
* consistent with the length-bounded {@link validatorUnexpectedCode}.
|
|
117
|
+
*/
|
|
118
|
+
export declare function isGuardReasonToken(value: string): value is GuardReasonToken;
|
|
119
|
+
/** Resolve the outcome for a token; UNEXPECTED fallbacks are always block+enforce. */
|
|
120
|
+
export declare function outcomeForCode(code: string): GuardOutcome;
|
|
121
|
+
/** Total wire budget (in Unicode code points) for a host-displayed deny reason. */
|
|
122
|
+
export declare const GUARD_REASON_BUDGET = 80;
|
|
123
|
+
/** Truncate to at most `max` Unicode code points (never splits a surrogate pair). */
|
|
124
|
+
export declare function truncateCodePoints(value: string, max: number): string;
|
|
125
|
+
/**
|
|
126
|
+
* Truncate a wire reason to the {@link GUARD_REASON_BUDGET} in Unicode code
|
|
127
|
+
* points (never splitting a surrogate pair). Truncates from the end, so a
|
|
128
|
+
* leading `GUARD_<CODE>: ` prefix always survives. Shared by buildDenyEnvelope
|
|
129
|
+
* so the defensive budget matches formatGuardReason exactly.
|
|
130
|
+
*/
|
|
131
|
+
export declare function truncateReasonToBudget(reason: string): string;
|
|
132
|
+
/**
|
|
133
|
+
* Produce the host-displayed wire reason `"<CODE>: <message>"`.
|
|
134
|
+
*
|
|
135
|
+
* The code prefix is reserved FIRST, then the message is sanitized (whitespace
|
|
136
|
+
* collapsed) and truncated by Unicode code points so the total never exceeds
|
|
137
|
+
* {@link GUARD_REASON_BUDGET} and no emoji/surrogate pair is split. The code
|
|
138
|
+
* therefore always survives the host budget — that survival is the entire
|
|
139
|
+
* cross-host stdout integration.
|
|
140
|
+
*
|
|
141
|
+
* `code` is a typed {@link GuardReasonToken}, so an over-budget prefix is not a
|
|
142
|
+
* reachable input (all registered codes and validator fallbacks are far under
|
|
143
|
+
* 80). If one somehow occurs, the prefix is preserved and the message dropped
|
|
144
|
+
* rather than corrupting the code.
|
|
145
|
+
*
|
|
146
|
+
* A message that already begins with the same `CODE: ` prefix is not
|
|
147
|
+
* double-prefixed (idempotent for re-formatted reasons).
|
|
148
|
+
*/
|
|
149
|
+
export declare function formatGuardReason(code: GuardReasonToken, message: string): string;
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed guard-outcome vocabulary for the pretool-guard and permission-request
|
|
3
|
+
* hook surfaces.
|
|
4
|
+
*
|
|
5
|
+
* Two orthogonal axes describe every guard decision:
|
|
6
|
+
*
|
|
7
|
+
* - `GUARD_EFFECT` — what actually reaches the host on the wire.
|
|
8
|
+
* - `GUARD_INTENT` — why the guard produced that effect.
|
|
9
|
+
*
|
|
10
|
+
* They are deliberately separate because the earlier flat Proceed/Deny/Guide
|
|
11
|
+
* draft was not mutually exclusive ("Guide" still emits a deny; a host
|
|
12
|
+
* pass-through is not "Proceed"). The two-axis model is exhaustive and honest
|
|
13
|
+
* about what the host receives versus what the policy meant.
|
|
14
|
+
*
|
|
15
|
+
* Every guard denial carries a stable `GUARD_<CODE>` that survives the
|
|
16
|
+
* host-displayed wire reason (see {@link formatGuardReason}). Consumers and
|
|
17
|
+
* audits key on the code, not on fragile English wording, and the code is
|
|
18
|
+
* identical across every Tier-1 host.
|
|
19
|
+
*
|
|
20
|
+
* Precedent for the as-const vocabulary shape: `HOOK_STATUS`
|
|
21
|
+
* (src/hooks/shared/vocabulary.ts).
|
|
22
|
+
*/
|
|
23
|
+
export const GUARD_EFFECT = {
|
|
24
|
+
/** A deny envelope reaches the host (exit 2 / permissionDecision:"deny"). */
|
|
25
|
+
block: "block",
|
|
26
|
+
/** The host is told to proceed (permission allow), or nothing is denied. */
|
|
27
|
+
allow: "allow",
|
|
28
|
+
/** No wire change; the host's own UI/policy stays authoritative. */
|
|
29
|
+
defer: "defer",
|
|
30
|
+
};
|
|
31
|
+
export const GUARD_INTENT = {
|
|
32
|
+
/** Block-and-hold: the caller must comply; no automatic reroute. */
|
|
33
|
+
enforce: "enforce",
|
|
34
|
+
/** Actively reroute the caller onto a sanctioned sandbox/MCP lane. */
|
|
35
|
+
redirect: "redirect",
|
|
36
|
+
/** Non-blocking guidance surfaced in logs only. */
|
|
37
|
+
advise: "advise",
|
|
38
|
+
/** Defer to the host default (pass-through); guard expresses no opinion. */
|
|
39
|
+
hostDefault: "hostDefault",
|
|
40
|
+
};
|
|
41
|
+
const BLOCK_ENFORCE = { effect: GUARD_EFFECT.block, intent: GUARD_INTENT.enforce };
|
|
42
|
+
const BLOCK_REDIRECT = { effect: GUARD_EFFECT.block, intent: GUARD_INTENT.redirect };
|
|
43
|
+
const ALLOW_ENFORCE = { effect: GUARD_EFFECT.allow, intent: GUARD_INTENT.enforce };
|
|
44
|
+
const DEFER_HOST = { effect: GUARD_EFFECT.defer, intent: GUARD_INTENT.hostDefault };
|
|
45
|
+
/**
|
|
46
|
+
* Closed set of guard reason codes, one per stable policy invariant (a single
|
|
47
|
+
* validator enforcing multiple invariants may map to multiple codes). Each code
|
|
48
|
+
* matches `/^GUARD_[A-Z0-9_]+$/u` and carries its {@link GuardOutcome}.
|
|
49
|
+
*
|
|
50
|
+
* Per-validator `GUARD_<NAME>_UNEXPECTED` fallback codes are NOT listed here;
|
|
51
|
+
* they are a deterministically-derived open family (see
|
|
52
|
+
* {@link validatorUnexpectedCode}) so that adding a validator never leaves a
|
|
53
|
+
* failure unclassified.
|
|
54
|
+
*/
|
|
55
|
+
export const GUARD_REASON_CODES = {
|
|
56
|
+
// --- pretool-guard dev-routing (block + redirect: each names a wp_* lane) --
|
|
57
|
+
GUARD_RAW_GIT_MUTATION: BLOCK_REDIRECT,
|
|
58
|
+
GUARD_MANUAL_PR_WAIT: BLOCK_REDIRECT,
|
|
59
|
+
GUARD_PR_DISCLOSURE: BLOCK_REDIRECT,
|
|
60
|
+
GUARD_WRAPPED_WP: BLOCK_REDIRECT,
|
|
61
|
+
GUARD_SOURCE_ENTRYPOINT: BLOCK_REDIRECT,
|
|
62
|
+
GUARD_DEV_TOOL_REDIRECT: BLOCK_REDIRECT,
|
|
63
|
+
GUARD_DATA_HEAVY_SANDBOX: BLOCK_REDIRECT,
|
|
64
|
+
GUARD_SESSION_MEMORY_REDIRECT: BLOCK_REDIRECT,
|
|
65
|
+
// --- pretool-guard prompt routing (block + enforce: no tool redirect) ------
|
|
66
|
+
GUARD_PROMPT_INTENT: BLOCK_ENFORCE,
|
|
67
|
+
// --- pretool-guard fail-closed error path (block + enforce) ----------------
|
|
68
|
+
GUARD_PRETOOL_FAILURE: BLOCK_ENFORCE,
|
|
69
|
+
// --- pretool-guard validators (block + enforce) ---------------------------
|
|
70
|
+
GUARD_FORBIDDEN_COMMAND: BLOCK_ENFORCE,
|
|
71
|
+
GUARD_DANGEROUS_COMMAND: BLOCK_ENFORCE,
|
|
72
|
+
GUARD_BLUEPRINT_INVALID: BLOCK_ENFORCE,
|
|
73
|
+
GUARD_BLUEPRINT_FIRST: BLOCK_ENFORCE,
|
|
74
|
+
GUARD_DOCS_GOVERNANCE: BLOCK_ENFORCE,
|
|
75
|
+
GUARD_PLAN_FRONTMATTER: BLOCK_ENFORCE,
|
|
76
|
+
GUARD_COMPLEXITY: BLOCK_ENFORCE,
|
|
77
|
+
GUARD_PACKAGE_IMPORTS: BLOCK_ENFORCE,
|
|
78
|
+
GUARD_FILE_CONVENTIONS: BLOCK_ENFORCE,
|
|
79
|
+
GUARD_COMMAND_FILE: BLOCK_ENFORCE,
|
|
80
|
+
GUARD_TEST_QUALITY: BLOCK_ENFORCE,
|
|
81
|
+
GUARD_UX_QUALITY: BLOCK_ENFORCE,
|
|
82
|
+
GUARD_WORKTREE_DISCIPLINE: BLOCK_ENFORCE,
|
|
83
|
+
// --- permission-request (host-boundary decisions) -------------------------
|
|
84
|
+
GUARD_PERMISSION_ALLOWED: ALLOW_ENFORCE,
|
|
85
|
+
GUARD_PERMISSION_DENIED: BLOCK_ENFORCE,
|
|
86
|
+
GUARD_HOST_DEFAULT: DEFER_HOST,
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Map a pretool-guard validator name to its canonical invariant code. Runner
|
|
90
|
+
* derivation falls back to {@link validatorUnexpectedCode} when a validator is
|
|
91
|
+
* not listed here, so a newly-added validator still produces a classified,
|
|
92
|
+
* pattern-valid code.
|
|
93
|
+
*/
|
|
94
|
+
export const GUARD_VALIDATOR_CODES = {
|
|
95
|
+
"forbidden-commands": "GUARD_FORBIDDEN_COMMAND",
|
|
96
|
+
"dangerous-commands": "GUARD_DANGEROUS_COMMAND",
|
|
97
|
+
blueprint: "GUARD_BLUEPRINT_INVALID",
|
|
98
|
+
"blueprint-first": "GUARD_BLUEPRINT_FIRST",
|
|
99
|
+
"docs-governance": "GUARD_DOCS_GOVERNANCE",
|
|
100
|
+
"plan-frontmatter": "GUARD_PLAN_FRONTMATTER",
|
|
101
|
+
complexity: "GUARD_COMPLEXITY",
|
|
102
|
+
"package-imports": "GUARD_PACKAGE_IMPORTS",
|
|
103
|
+
"file-conventions": "GUARD_FILE_CONVENTIONS",
|
|
104
|
+
"command-file": "GUARD_COMMAND_FILE",
|
|
105
|
+
"test-quality": "GUARD_TEST_QUALITY",
|
|
106
|
+
"ux-quality": "GUARD_UX_QUALITY",
|
|
107
|
+
"worktree-discipline": "GUARD_WORKTREE_DISCIPLINE",
|
|
108
|
+
};
|
|
109
|
+
const GUARD_CODE_RE = /^GUARD_[A-Z0-9_]+$/u;
|
|
110
|
+
const GUARD_UNEXPECTED_RE = /^GUARD_[A-Z0-9_]+_UNEXPECTED$/u;
|
|
111
|
+
/**
|
|
112
|
+
* Max code-point length of the derived validator-name segment in an UNEXPECTED
|
|
113
|
+
* fallback. Bounds the whole token to `GUARD_` (6) + 48 + `_UNEXPECTED` (11) =
|
|
114
|
+
* 65 code points, comfortably under {@link GUARD_REASON_BUDGET}, so the fallback
|
|
115
|
+
* path can never blow the 80-code-point wire budget.
|
|
116
|
+
*/
|
|
117
|
+
const UNEXPECTED_NAME_MAX = 48;
|
|
118
|
+
/**
|
|
119
|
+
* Deterministic, length-bounded per-validator fallback code, e.g. `test-quality`
|
|
120
|
+
* -> `GUARD_TEST_QUALITY_UNEXPECTED`. Always {@link GUARD_INTENT.enforce}. The
|
|
121
|
+
* name segment is capped so even a pathologically long validator name yields a
|
|
122
|
+
* token well under the wire budget.
|
|
123
|
+
*/
|
|
124
|
+
export function validatorUnexpectedCode(validatorName) {
|
|
125
|
+
const screaming = validatorName
|
|
126
|
+
.toUpperCase()
|
|
127
|
+
.replace(/[^A-Z0-9]+/gu, "_")
|
|
128
|
+
.replace(/^_+|_+$/gu, "")
|
|
129
|
+
.slice(0, UNEXPECTED_NAME_MAX)
|
|
130
|
+
.replace(/_+$/gu, "");
|
|
131
|
+
return `GUARD_${screaming || "UNKNOWN"}_UNEXPECTED`;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Narrow to a registered guard reason code. This is SOUND: it returns true
|
|
135
|
+
* only for actual keys of the closed {@link GUARD_REASON_CODES} set. A
|
|
136
|
+
* per-validator `GUARD_<NAME>_UNEXPECTED` fallback is NOT a registered code —
|
|
137
|
+
* use {@link isGuardReasonToken} for the broader accepted family.
|
|
138
|
+
*/
|
|
139
|
+
export function isGuardReasonCode(value) {
|
|
140
|
+
return Object.prototype.hasOwnProperty.call(GUARD_REASON_CODES, value);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Narrow to any accepted guard reason token: a registered code or a well-formed
|
|
144
|
+
* `GUARD_<NAME>_UNEXPECTED` fallback. A fallback token is only accepted when it
|
|
145
|
+
* fits the wire budget as a prefix (token + ": " <= GUARD_REASON_BUDGET), so an
|
|
146
|
+
* accepted token can never force formatGuardReason to truncate its own prefix —
|
|
147
|
+
* consistent with the length-bounded {@link validatorUnexpectedCode}.
|
|
148
|
+
*/
|
|
149
|
+
export function isGuardReasonToken(value) {
|
|
150
|
+
if (isGuardReasonCode(value))
|
|
151
|
+
return true;
|
|
152
|
+
if (!(GUARD_UNEXPECTED_RE.test(value) && GUARD_CODE_RE.test(value)))
|
|
153
|
+
return false;
|
|
154
|
+
// Count Unicode code points to match formatGuardReason's budget exactly
|
|
155
|
+
// (the regex already constrains tokens to ASCII, so this is belt-and-suspenders).
|
|
156
|
+
return [...value].length + ": ".length <= GUARD_REASON_BUDGET;
|
|
157
|
+
}
|
|
158
|
+
/** Resolve the outcome for a token; UNEXPECTED fallbacks are always block+enforce. */
|
|
159
|
+
export function outcomeForCode(code) {
|
|
160
|
+
if (isGuardReasonCode(code)) {
|
|
161
|
+
return GUARD_REASON_CODES[code];
|
|
162
|
+
}
|
|
163
|
+
return BLOCK_ENFORCE;
|
|
164
|
+
}
|
|
165
|
+
/** Total wire budget (in Unicode code points) for a host-displayed deny reason. */
|
|
166
|
+
export const GUARD_REASON_BUDGET = 80;
|
|
167
|
+
/** Truncate to at most `max` Unicode code points (never splits a surrogate pair). */
|
|
168
|
+
export function truncateCodePoints(value, max) {
|
|
169
|
+
if (max <= 0)
|
|
170
|
+
return "";
|
|
171
|
+
const points = Array.from(value);
|
|
172
|
+
return points.length <= max ? value : points.slice(0, max).join("");
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Truncate a wire reason to the {@link GUARD_REASON_BUDGET} in Unicode code
|
|
176
|
+
* points (never splitting a surrogate pair). Truncates from the end, so a
|
|
177
|
+
* leading `GUARD_<CODE>: ` prefix always survives. Shared by buildDenyEnvelope
|
|
178
|
+
* so the defensive budget matches formatGuardReason exactly.
|
|
179
|
+
*/
|
|
180
|
+
export function truncateReasonToBudget(reason) {
|
|
181
|
+
return truncateCodePoints(reason, GUARD_REASON_BUDGET);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Produce the host-displayed wire reason `"<CODE>: <message>"`.
|
|
185
|
+
*
|
|
186
|
+
* The code prefix is reserved FIRST, then the message is sanitized (whitespace
|
|
187
|
+
* collapsed) and truncated by Unicode code points so the total never exceeds
|
|
188
|
+
* {@link GUARD_REASON_BUDGET} and no emoji/surrogate pair is split. The code
|
|
189
|
+
* therefore always survives the host budget — that survival is the entire
|
|
190
|
+
* cross-host stdout integration.
|
|
191
|
+
*
|
|
192
|
+
* `code` is a typed {@link GuardReasonToken}, so an over-budget prefix is not a
|
|
193
|
+
* reachable input (all registered codes and validator fallbacks are far under
|
|
194
|
+
* 80). If one somehow occurs, the prefix is preserved and the message dropped
|
|
195
|
+
* rather than corrupting the code.
|
|
196
|
+
*
|
|
197
|
+
* A message that already begins with the same `CODE: ` prefix is not
|
|
198
|
+
* double-prefixed (idempotent for re-formatted reasons).
|
|
199
|
+
*/
|
|
200
|
+
export function formatGuardReason(code, message) {
|
|
201
|
+
const prefix = `${code}: `;
|
|
202
|
+
const collapsed = message.replace(/\s+/gu, " ").trim();
|
|
203
|
+
const withoutDuplicatePrefix = collapsed.startsWith(prefix)
|
|
204
|
+
? collapsed.slice(prefix.length)
|
|
205
|
+
: collapsed;
|
|
206
|
+
const prefixPoints = Array.from(prefix).length;
|
|
207
|
+
const room = GUARD_REASON_BUDGET - prefixPoints;
|
|
208
|
+
if (room <= 0) {
|
|
209
|
+
// Unreachable for real tokens (all registered codes and bounded UNEXPECTED
|
|
210
|
+
// fallbacks are far under budget). If a caller ever hands in an oversized
|
|
211
|
+
// token, the 80-code-point budget still wins: truncate code-point-safe.
|
|
212
|
+
return truncateCodePoints(prefix, GUARD_REASON_BUDGET);
|
|
213
|
+
}
|
|
214
|
+
return `${prefix}${truncateCodePoints(withoutDuplicatePrefix, room)}`;
|
|
215
|
+
}
|
|
@@ -8,6 +8,32 @@ export declare function writeHookOutput(output: string): void;
|
|
|
8
8
|
* Cross-platform: no-ops on Windows (fd redirect not portable there).
|
|
9
9
|
*/
|
|
10
10
|
export declare function suppressStderr(): void;
|
|
11
|
+
/**
|
|
12
|
+
* Capture intentional guard feedback at an in-process execution boundary.
|
|
13
|
+
*
|
|
14
|
+
* Production launchers leave this unset and write to the real fd 2. The
|
|
15
|
+
* conformance batch installs a row-scoped capture so direct fd delivery stays
|
|
16
|
+
* attributable to the row that emitted it.
|
|
17
|
+
*/
|
|
18
|
+
export declare function installGuardStderrCapture(capture: (text: string) => void): () => void;
|
|
19
|
+
/**
|
|
20
|
+
* Suppress only DECORATIVE JS stderr (console.error / process.stderr.write
|
|
21
|
+
* diagnostics) while keeping the real fd 2 open. Unlike {@link suppressStderr}
|
|
22
|
+
* (which closes fd 2 to also block native stderr), this preserves the host's
|
|
23
|
+
* stderr channel so a PreToolUse deny can deliver its coded reason there —
|
|
24
|
+
* Claude reads STDERR, not stdout, as the blocking feedback on an exit-2 deny.
|
|
25
|
+
*
|
|
26
|
+
* Use this instead of {@link suppressStderr} on the pretool-guard path, which
|
|
27
|
+
* must both hide noise AND surface the coded deny reason on stderr.
|
|
28
|
+
*/
|
|
29
|
+
export declare function suppressDecorativeStderr(): void;
|
|
30
|
+
/**
|
|
31
|
+
* Write intentional guard feedback (a coded deny reason) to the REAL stderr —
|
|
32
|
+
* the channel Claude reads on an exit-2 PreToolUse deny. When decorative stderr
|
|
33
|
+
* is suppressed (fd 2 still open), write directly to fd 2 to bypass the swallow;
|
|
34
|
+
* otherwise use the normal `process.stderr`. Never throws.
|
|
35
|
+
*/
|
|
36
|
+
export declare function writeGuardStderr(text: string): void;
|
|
11
37
|
/**
|
|
12
38
|
* Read all stdin bytes and return as UTF-8 string.
|
|
13
39
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { closeSync, openSync } from "node:fs";
|
|
1
|
+
import { closeSync, openSync, writeSync } from "node:fs";
|
|
2
2
|
import { rememberHookStdin } from "#hooks/logs/store.js";
|
|
3
3
|
export function writeHookOutput(output) {
|
|
4
4
|
process.stdout.write(output);
|
|
@@ -22,6 +22,69 @@ export function suppressStderr() {
|
|
|
22
22
|
// If stderr is already closed or /dev/null unavailable, ignore silently
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* True once {@link suppressDecorativeStderr} has swallowed decorative JS stderr
|
|
27
|
+
* while keeping the real fd 2 open, so {@link writeGuardStderr} delivers the
|
|
28
|
+
* coded reason to the real stderr via a direct fd write.
|
|
29
|
+
*/
|
|
30
|
+
let decorativeStderrSuppressed = false;
|
|
31
|
+
let guardStderrCapture = null;
|
|
32
|
+
/**
|
|
33
|
+
* Capture intentional guard feedback at an in-process execution boundary.
|
|
34
|
+
*
|
|
35
|
+
* Production launchers leave this unset and write to the real fd 2. The
|
|
36
|
+
* conformance batch installs a row-scoped capture so direct fd delivery stays
|
|
37
|
+
* attributable to the row that emitted it.
|
|
38
|
+
*/
|
|
39
|
+
export function installGuardStderrCapture(capture) {
|
|
40
|
+
const previous = guardStderrCapture;
|
|
41
|
+
guardStderrCapture = capture;
|
|
42
|
+
return () => {
|
|
43
|
+
if (guardStderrCapture === capture)
|
|
44
|
+
guardStderrCapture = previous;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Suppress only DECORATIVE JS stderr (console.error / process.stderr.write
|
|
49
|
+
* diagnostics) while keeping the real fd 2 open. Unlike {@link suppressStderr}
|
|
50
|
+
* (which closes fd 2 to also block native stderr), this preserves the host's
|
|
51
|
+
* stderr channel so a PreToolUse deny can deliver its coded reason there —
|
|
52
|
+
* Claude reads STDERR, not stdout, as the blocking feedback on an exit-2 deny.
|
|
53
|
+
*
|
|
54
|
+
* Use this instead of {@link suppressStderr} on the pretool-guard path, which
|
|
55
|
+
* must both hide noise AND surface the coded deny reason on stderr.
|
|
56
|
+
*/
|
|
57
|
+
export function suppressDecorativeStderr() {
|
|
58
|
+
if (process.platform === "win32")
|
|
59
|
+
return;
|
|
60
|
+
if (decorativeStderrSuppressed)
|
|
61
|
+
return;
|
|
62
|
+
const swallow = (() => true);
|
|
63
|
+
process.stderr.write = swallow;
|
|
64
|
+
decorativeStderrSuppressed = true;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Write intentional guard feedback (a coded deny reason) to the REAL stderr —
|
|
68
|
+
* the channel Claude reads on an exit-2 PreToolUse deny. When decorative stderr
|
|
69
|
+
* is suppressed (fd 2 still open), write directly to fd 2 to bypass the swallow;
|
|
70
|
+
* otherwise use the normal `process.stderr`. Never throws.
|
|
71
|
+
*/
|
|
72
|
+
export function writeGuardStderr(text) {
|
|
73
|
+
try {
|
|
74
|
+
if (guardStderrCapture) {
|
|
75
|
+
guardStderrCapture(text);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (decorativeStderrSuppressed) {
|
|
79
|
+
writeSync(2, text);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
process.stderr.write(text);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// Never block the hook on feedback delivery.
|
|
86
|
+
}
|
|
87
|
+
}
|
|
25
88
|
/**
|
|
26
89
|
* Read all stdin bytes and return as UTF-8 string.
|
|
27
90
|
*/
|