@toolu/opencode 6.8.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/generated/GENERATED-NOTES.md +16 -0
- package/generated/agents/toolu--architect.md +52 -0
- package/generated/agents/toolu--deep-explore.md +65 -0
- package/generated/agents/toolu--implementer.md +50 -0
- package/generated/agents/toolu--quick-task.md +54 -0
- package/generated/agents/toolu--research-agent.md +101 -0
- package/generated/commands/toolu--commit.md +8 -0
- package/generated/commands/toolu--review-and-commit.md +8 -0
- package/generated/opencode.toolu.json +116 -0
- package/generated/skills/toolu--brainstorm/SKILL.md +57 -0
- package/generated/skills/toolu--brainstorm/references/design-questions.md +36 -0
- package/generated/skills/toolu--commit--skill/SKILL.md +11 -0
- package/generated/skills/toolu--debug/SKILL.md +54 -0
- package/generated/skills/toolu--deep-research/SKILL.md +51 -0
- package/generated/skills/toolu--execution/SKILL.md +77 -0
- package/generated/skills/toolu--orchestrator/SKILL.md +145 -0
- package/generated/skills/toolu--orchestrator/references/delegation-cost.md +104 -0
- package/generated/skills/toolu--orchestrator/references/model-routing.md +112 -0
- package/generated/skills/toolu--plan/SKILL.md +56 -0
- package/generated/skills/toolu--plan/references/ledger.md +18 -0
- package/generated/skills/toolu--plan-review/SKILL.md +48 -0
- package/generated/skills/toolu--review-and-commit--skill/SKILL.md +10 -0
- package/generated/skills/toolu--setup/SKILL.md +24 -0
- package/generated/skills/toolu--setup/scripts/setup.sh +206 -0
- package/generated/skills/toolu--spec/SKILL.md +67 -0
- package/generated/skills/toolu--spec-review/SKILL.md +50 -0
- package/generated/skills/toolu--test/SKILL.md +49 -0
- package/package.json +51 -0
- package/plugins/agent-browser/.claude-plugin/plugin.json +18 -0
- package/plugins/agent-browser/.codex-plugin/plugin.json +7 -0
- package/plugins/agent-browser/README.md +18 -0
- package/plugins/agent-browser/hooks/hooks.json +15 -0
- package/plugins/agent-browser/hooks/session-start.sh +42 -0
- package/plugins/agent-browser/skills/agent-browser/SKILL.md +77 -0
- package/plugins/agent-browser/skills/agent-browser/scripts/agent-browser.sh +80 -0
- package/plugins/ast-grep/.claude-plugin/plugin.json +18 -0
- package/plugins/ast-grep/.codex-plugin/plugin.json +7 -0
- package/plugins/ast-grep/README.md +21 -0
- package/plugins/ast-grep/hooks/hooks.json +15 -0
- package/plugins/ast-grep/hooks/post-tools.d/byte-savings.sh +67 -0
- package/plugins/ast-grep/hooks/pre-tools.d/search-nudge.sh +170 -0
- package/plugins/ast-grep/hooks/register.sh +68 -0
- package/plugins/ast-grep/scripts/byte-savings-report.sh +28 -0
- package/plugins/ast-grep/skills/ast-grep/SKILL.md +127 -0
- package/plugins/ast-grep/skills/ast-grep/references/ast-grep-advanced.md +44 -0
- package/plugins/ast-grep/skills/ast-grep/scripts/ast-grep.sh +103 -0
- package/plugins/context7/.claude-plugin/plugin.json +18 -0
- package/plugins/context7/.codex-plugin/plugin.json +7 -0
- package/plugins/context7/README.md +19 -0
- package/plugins/context7/hooks/hooks.json +15 -0
- package/plugins/context7/hooks/session-start.sh +42 -0
- package/plugins/context7/skills/context7/SKILL.md +64 -0
- package/plugins/context7/skills/context7/scripts/search.sh +151 -0
- package/plugins/exa-search/.claude-plugin/plugin.json +18 -0
- package/plugins/exa-search/.codex-plugin/plugin.json +7 -0
- package/plugins/exa-search/README.md +19 -0
- package/plugins/exa-search/hooks/hooks.json +15 -0
- package/plugins/exa-search/hooks/session-start.sh +34 -0
- package/plugins/exa-search/skills/exa-search/SKILL.md +91 -0
- package/plugins/exa-search/skills/exa-search/scripts/search.sh +221 -0
- package/plugins/jev/.claude-plugin/plugin.json +19 -0
- package/plugins/jev/.codex-plugin/plugin.json +7 -0
- package/plugins/jev/README.md +61 -0
- package/plugins/jev/hooks/hooks.json +25 -0
- package/plugins/jev/hooks/lib/common.sh +49 -0
- package/plugins/jev/hooks/session-start.sh +45 -0
- package/plugins/jev/hooks/user-prompt-submit.sh +41 -0
- package/plugins/jev/skills/jev/SKILL.md +81 -0
- package/plugins/jev/skills/jev/evals/README.md +98 -0
- package/plugins/jev/skills/jev/references/problem-solving.md +198 -0
- package/plugins/jev/skills/jev/scripts/jev.sh +405 -0
- package/plugins/jira/.claude-plugin/plugin.json +18 -0
- package/plugins/jira/.codex-plugin/plugin.json +7 -0
- package/plugins/jira/README.md +42 -0
- package/plugins/jira/hooks/hooks.json +15 -0
- package/plugins/jira/hooks/session-start.sh +34 -0
- package/plugins/jira/skills/jira/SKILL.md +123 -0
- package/plugins/jira/skills/jira/scripts/jira.sh +70 -0
- package/plugins/jira/skills/jira/scripts/lib/adf.sh +17 -0
- package/plugins/jira/skills/jira/scripts/lib/attachment.sh +94 -0
- package/plugins/jira/skills/jira/scripts/lib/board.sh +58 -0
- package/plugins/jira/skills/jira/scripts/lib/http.sh +184 -0
- package/plugins/jira/skills/jira/scripts/lib/issue.sh +159 -0
- package/plugins/jira/skills/jira/scripts/lib/paginate.sh +45 -0
- package/plugins/jira/skills/jira/scripts/lib/plan-parse.sh +70 -0
- package/plugins/jira/skills/jira/scripts/lib/plan-run.sh +115 -0
- package/plugins/jira/skills/jira/scripts/lib/plan-store.sh +168 -0
- package/plugins/jira/skills/jira/scripts/lib/plan.sh +108 -0
- package/plugins/jira/skills/jira/scripts/lib/project.sh +42 -0
- package/plugins/jira/skills/jira/scripts/lib/raw.sh +15 -0
- package/plugins/jira/skills/jira/scripts/lib/search.sh +41 -0
- package/plugins/jira/skills/jira/scripts/lib/sprint.sh +88 -0
- package/plugins/jira/skills/jira/scripts/lib/user.sh +45 -0
- package/plugins/jira/skills/jira/scripts/lib/worklog.sh +56 -0
- package/plugins/pr-babysit/.claude-plugin/plugin.json +25 -0
- package/plugins/pr-babysit/.codex-plugin/plugin.json +7 -0
- package/plugins/pr-babysit/README.md +38 -0
- package/plugins/pr-babysit/commands/babysit.md +13 -0
- package/plugins/pr-babysit/hooks/check-toolu.sh +20 -0
- package/plugins/pr-babysit/hooks/hooks.json +15 -0
- package/plugins/pr-babysit/scripts/babysit-tick.sh +133 -0
- package/plugins/pr-babysit/scripts/collect-pr.sh +224 -0
- package/plugins/pr-babysit/scripts/lib/common.sh +146 -0
- package/plugins/pr-babysit/scripts/lib/gh.sh +150 -0
- package/plugins/pr-babysit/scripts/lib/lock.sh +84 -0
- package/plugins/pr-babysit/scripts/lib/normalize.sh +98 -0
- package/plugins/pr-babysit/scripts/lib/state.sh +37 -0
- package/plugins/pr-babysit/scripts/parse-verdict.sh +171 -0
- package/plugins/pr-babysit/scripts/record.sh +69 -0
- package/plugins/pr-babysit/scripts/reduce-state.sh +245 -0
- package/plugins/pr-babysit/scripts/reply-thread.sh +90 -0
- package/plugins/pr-babysit/scripts/resolve-thread.sh +71 -0
- package/plugins/pr-babysit/skills/babysit/SKILL.md +31 -0
- package/plugins/pr-babysit/skills/babysit/references/helper.md +209 -0
- package/plugins/pr-babysit/workflows/babysit.md +722 -0
- package/plugins/python-quality/.claude-plugin/plugin.json +24 -0
- package/plugins/python-quality/.codex-plugin/plugin.json +6 -0
- package/plugins/python-quality/README.md +23 -0
- package/plugins/python-quality/hooks/check-toolu.sh +20 -0
- package/plugins/python-quality/hooks/concerns/00-preamble.sh +85 -0
- package/plugins/python-quality/hooks/concerns/10-size-file.sh +6 -0
- package/plugins/python-quality/hooks/concerns/20-tests.sh +37 -0
- package/plugins/python-quality/hooks/concerns/30-suppression.sh +33 -0
- package/plugins/python-quality/hooks/concerns/50-size-fn.sh +49 -0
- package/plugins/python-quality/hooks/concerns/70-no-mocks.sh +76 -0
- package/plugins/python-quality/hooks/concerns/90-docs.sh +52 -0
- package/plugins/python-quality/hooks/concerns/99-finalize.sh +36 -0
- package/plugins/python-quality/hooks/hooks.json +19 -0
- package/plugins/python-quality/hooks/register.sh +65 -0
- package/plugins/rust-quality/.claude-plugin/plugin.json +24 -0
- package/plugins/rust-quality/.codex-plugin/plugin.json +6 -0
- package/plugins/rust-quality/README.md +24 -0
- package/plugins/rust-quality/hooks/check-toolu.sh +20 -0
- package/plugins/rust-quality/hooks/concerns/00-preamble.sh +93 -0
- package/plugins/rust-quality/hooks/concerns/10-size-file.sh +10 -0
- package/plugins/rust-quality/hooks/concerns/20-tests.sh +84 -0
- package/plugins/rust-quality/hooks/concerns/30-suppression.sh +26 -0
- package/plugins/rust-quality/hooks/concerns/40-unsafe.sh +39 -0
- package/plugins/rust-quality/hooks/concerns/50-size-fn.sh +38 -0
- package/plugins/rust-quality/hooks/concerns/55-size-impl.sh +28 -0
- package/plugins/rust-quality/hooks/concerns/60-error-handling.sh +149 -0
- package/plugins/rust-quality/hooks/concerns/70-no-mocks.sh +139 -0
- package/plugins/rust-quality/hooks/concerns/90-docs.sh +32 -0
- package/plugins/rust-quality/hooks/concerns/99-finalize.sh +36 -0
- package/plugins/rust-quality/hooks/hooks.json +19 -0
- package/plugins/rust-quality/hooks/register.sh +65 -0
- package/plugins/statusline/.claude-plugin/plugin.json +19 -0
- package/plugins/statusline/.codex-plugin/plugin.json +7 -0
- package/plugins/statusline/README.md +108 -0
- package/plugins/statusline/commands/setup.md +30 -0
- package/plugins/statusline/hooks/hooks.json +15 -0
- package/plugins/statusline/hooks/session-start.sh +45 -0
- package/plugins/statusline/scripts/collect-status.sh +147 -0
- package/plugins/statusline/scripts/setup.sh +93 -0
- package/plugins/statusline/scripts/status.sh +55 -0
- package/plugins/statusline/skills/status/SKILL.md +13 -0
- package/plugins/statusline/statusline.sh +177 -0
- package/plugins/toolu/.claude-plugin/plugin.json +19 -0
- package/plugins/toolu/.codex-plugin/plugin.json +7 -0
- package/plugins/toolu/README.md +35 -0
- package/plugins/toolu/agents/.gitkeep +0 -0
- package/plugins/toolu/agents/architect.md +52 -0
- package/plugins/toolu/agents/deep-explore.md +65 -0
- package/plugins/toolu/agents/implementer.md +50 -0
- package/plugins/toolu/agents/quick-task.md +54 -0
- package/plugins/toolu/agents/research-agent.md +107 -0
- package/plugins/toolu/assets/agents/architect.toml +9 -0
- package/plugins/toolu/assets/agents/deep-explore.toml +9 -0
- package/plugins/toolu/assets/agents/implementer.toml +9 -0
- package/plugins/toolu/assets/agents/quick-task.toml +9 -0
- package/plugins/toolu/assets/agents/research-agent.toml +9 -0
- package/plugins/toolu/commands/.gitkeep +0 -0
- package/plugins/toolu/commands/commit.md +5 -0
- package/plugins/toolu/commands/review-and-commit.md +5 -0
- package/plugins/toolu/hooks/docs/gates.md +157 -0
- package/plugins/toolu/hooks/docs/model-routing.md +14 -0
- package/plugins/toolu/hooks/docs/post-compaction.md +3 -0
- package/plugins/toolu/hooks/docs/push-review.md +123 -0
- package/plugins/toolu/hooks/docs/session-start-python.md +3 -0
- package/plugins/toolu/hooks/docs/session-start-rust.md +4 -0
- package/plugins/toolu/hooks/docs/session-start-ts.md +3 -0
- package/plugins/toolu/hooks/docs/session-start.md +16 -0
- package/plugins/toolu/hooks/hooks.json +76 -0
- package/plugins/toolu/hooks/lib/config.sh +355 -0
- package/plugins/toolu/hooks/lib/detect.sh +929 -0
- package/plugins/toolu/hooks/lib/diff-sha.sh +32 -0
- package/plugins/toolu/hooks/lib/dispatch.sh +365 -0
- package/plugins/toolu/hooks/lib/docs-sync-config.sh +81 -0
- package/plugins/toolu/hooks/lib/edit-records.sh +157 -0
- package/plugins/toolu/hooks/lib/gate-file.sh +201 -0
- package/plugins/toolu/hooks/lib/gate-mode.sh +244 -0
- package/plugins/toolu/hooks/lib/host.sh +205 -0
- package/plugins/toolu/hooks/lib/permissions.sh +125 -0
- package/plugins/toolu/hooks/lib/plan-ledger-parse.sh +219 -0
- package/plugins/toolu/hooks/lib/plan-ledger-preflight.sh +137 -0
- package/plugins/toolu/hooks/lib/plan-ledger.sh +749 -0
- package/plugins/toolu/hooks/lib/push-waiver.sh +116 -0
- package/plugins/toolu/hooks/lib/quality-config.sh +184 -0
- package/plugins/toolu/hooks/lib/registry.sh +72 -0
- package/plugins/toolu/hooks/lib/state-sweeper.sh +194 -0
- package/plugins/toolu/hooks/lib/telemetry.sh +94 -0
- package/plugins/toolu/hooks/lib/verdict.sh +477 -0
- package/plugins/toolu/hooks/post-tools/mod.sh +42 -0
- package/plugins/toolu/hooks/post-tools/modules/gate-status.sh +140 -0
- package/plugins/toolu/hooks/post-tools/modules/push-waiver.sh +97 -0
- package/plugins/toolu/hooks/pre-compact.sh +18 -0
- package/plugins/toolu/hooks/pre-tools/agent-tier.sh +111 -0
- package/plugins/toolu/hooks/pre-tools/mod.sh +37 -0
- package/plugins/toolu/hooks/pre-tools/modules/bash-commands.sh +187 -0
- package/plugins/toolu/hooks/pre-tools/modules/code-edit-rules.sh +78 -0
- package/plugins/toolu/hooks/pre-tools/modules/commit-gate.sh +70 -0
- package/plugins/toolu/hooks/pre-tools/modules/docs-sync.sh +137 -0
- package/plugins/toolu/hooks/pre-tools/modules/mcp-blocker.sh +151 -0
- package/plugins/toolu/hooks/pre-tools/modules/plan-ledger.sh +201 -0
- package/plugins/toolu/hooks/pre-tools/modules/protected-files.sh +177 -0
- package/plugins/toolu/hooks/pre-tools/modules/push-review.sh +273 -0
- package/plugins/toolu/hooks/pre-tools/modules/quality-gate.sh +87 -0
- package/plugins/toolu/hooks/session-start.sh +379 -0
- package/plugins/toolu/hooks/user-prompt-submit.sh +197 -0
- package/plugins/toolu/scripts/context-budget.sh +111 -0
- package/plugins/toolu/scripts/debug-log.sh +98 -0
- package/plugins/toolu/scripts/debug-stack.sh +108 -0
- package/plugins/toolu/scripts/debug-testfail.sh +97 -0
- package/plugins/toolu/settings/.gitkeep +0 -0
- package/plugins/toolu/settings/README.md +127 -0
- package/plugins/toolu/settings/bash-allowlist.txt +12 -0
- package/plugins/toolu/settings/bash-denylist.txt +25 -0
- package/plugins/toolu/settings/code-edit-rules.json +67 -0
- package/plugins/toolu/settings/commit-prefixes.txt +12 -0
- package/plugins/toolu/settings/mcp-blocklist.txt +8 -0
- package/plugins/toolu/settings/permissions.fragment.json +36 -0
- package/plugins/toolu/settings/protected-files.txt +30 -0
- package/plugins/toolu/settings/rust-unsafe-exemptions.txt +5 -0
- package/plugins/toolu/settings/toolu.config.example.json +130 -0
- package/plugins/toolu/skills/.gitkeep +0 -0
- package/plugins/toolu/skills/brainstorm/SKILL.md +57 -0
- package/plugins/toolu/skills/brainstorm/references/design-questions.md +36 -0
- package/plugins/toolu/skills/commit/SKILL.md +11 -0
- package/plugins/toolu/skills/debug/SKILL.md +54 -0
- package/plugins/toolu/skills/debug/evals/README.md +17 -0
- package/plugins/toolu/skills/debug/evals/cases.json +24 -0
- package/plugins/toolu/skills/debug/evals/results.json +34 -0
- package/plugins/toolu/skills/deep-research/SKILL.md +51 -0
- package/plugins/toolu/skills/execution/SKILL.md +77 -0
- package/plugins/toolu/skills/orchestrator/SKILL.md +145 -0
- package/plugins/toolu/skills/orchestrator/references/delegation-cost.md +104 -0
- package/plugins/toolu/skills/orchestrator/references/model-routing.md +112 -0
- package/plugins/toolu/skills/plan/SKILL.md +56 -0
- package/plugins/toolu/skills/plan/references/ledger.md +18 -0
- package/plugins/toolu/skills/plan-review/SKILL.md +48 -0
- package/plugins/toolu/skills/review-and-commit/SKILL.md +10 -0
- package/plugins/toolu/skills/setup/SKILL.md +24 -0
- package/plugins/toolu/skills/setup/scripts/setup.sh +206 -0
- package/plugins/toolu/skills/spec/SKILL.md +67 -0
- package/plugins/toolu/skills/spec-review/SKILL.md +50 -0
- package/plugins/toolu/skills/test/SKILL.md +49 -0
- package/plugins/toolu/workflows/commit.md +21 -0
- package/plugins/toolu/workflows/host-mapping.md +22 -0
- package/plugins/toolu/workflows/review-and-commit.md +54 -0
- package/plugins/toolu/workflows/semantic-judgments.md +35 -0
- package/plugins/toolu-review/.claude-plugin/plugin.json +18 -0
- package/plugins/toolu-review/.codex-plugin/plugin.json +7 -0
- package/plugins/toolu-review/README.md +17 -0
- package/plugins/toolu-review/hooks/hooks.json +15 -0
- package/plugins/toolu-review/hooks/session-start.sh +34 -0
- package/plugins/toolu-review/skills/review/SKILL.md +90 -0
- package/plugins/toolu-review/skills/review/scripts/write-state.sh +189 -0
- package/plugins/ts-quality/.claude-plugin/plugin.json +24 -0
- package/plugins/ts-quality/.codex-plugin/plugin.json +6 -0
- package/plugins/ts-quality/README.md +24 -0
- package/plugins/ts-quality/hooks/check-toolu.sh +20 -0
- package/plugins/ts-quality/hooks/concerns/00-preamble.sh +123 -0
- package/plugins/ts-quality/hooks/concerns/10-imports.sh +33 -0
- package/plugins/ts-quality/hooks/concerns/15-type-as.sh +9 -0
- package/plugins/ts-quality/hooks/concerns/20-tests.sh +26 -0
- package/plugins/ts-quality/hooks/concerns/25-size-file.sh +40 -0
- package/plugins/ts-quality/hooks/concerns/30-size-fn.sh +53 -0
- package/plugins/ts-quality/hooks/concerns/35-react-hooks.sh +7 -0
- package/plugins/ts-quality/hooks/concerns/40-factory.sh +5 -0
- package/plugins/ts-quality/hooks/concerns/45-typeguard.sh +6 -0
- package/plugins/ts-quality/hooks/concerns/50-type-dup.sh +16 -0
- package/plugins/ts-quality/hooks/concerns/55-naming.sh +11 -0
- package/plugins/ts-quality/hooks/concerns/60-console.sh +6 -0
- package/plugins/ts-quality/hooks/concerns/65-suppression.sh +12 -0
- package/plugins/ts-quality/hooks/concerns/70-ui-confirm.sh +8 -0
- package/plugins/ts-quality/hooks/concerns/72-ui-radix.sh +12 -0
- package/plugins/ts-quality/hooks/concerns/74-react-props.sh +6 -0
- package/plugins/ts-quality/hooks/concerns/76-toast.sh +10 -0
- package/plugins/ts-quality/hooks/concerns/78-error-ast.sh +242 -0
- package/plugins/ts-quality/hooks/concerns/80-throw-literal.sh +23 -0
- package/plugins/ts-quality/hooks/concerns/85-no-mocks.sh +109 -0
- package/plugins/ts-quality/hooks/concerns/90-duplication.sh +30 -0
- package/plugins/ts-quality/hooks/concerns/92-docs.sh +36 -0
- package/plugins/ts-quality/hooks/concerns/94-handler.sh +15 -0
- package/plugins/ts-quality/hooks/concerns/99-finalize.sh +46 -0
- package/plugins/ts-quality/hooks/hooks.json +19 -0
- package/plugins/ts-quality/hooks/register.sh +65 -0
- package/src/adapter/__tests__/evaluate.test.ts +82 -0
- package/src/adapter/__tests__/permission-map.test.ts +91 -0
- package/src/adapter/evaluate.ts +54 -0
- package/src/adapter/permission-map.ts +177 -0
- package/src/bootstrap/__tests__/bootstrap.test.ts +148 -0
- package/src/bootstrap/entrypoint.ts +18 -0
- package/src/bootstrap/index.ts +5 -0
- package/src/bootstrap/readiness.ts +59 -0
- package/src/bootstrap/result.ts +21 -0
- package/src/bootstrap/runtime.ts +99 -0
- package/src/bootstrap/test-helpers.ts +25 -0
- package/src/host/__tests__/host.test.ts +37 -0
- package/src/host/detect.ts +46 -0
- package/src/host/index.ts +10 -0
- package/src/host/roots.ts +59 -0
- package/src/host/types.ts +14 -0
- package/src/inventory/__tests__/inventory.test.ts +69 -0
- package/src/inventory/index.ts +9 -0
- package/src/inventory/manifest.ts +46 -0
- package/src/inventory/scan.ts +66 -0
- package/src/inventory/selection.ts +92 -0
- package/src/inventory/types.ts +22 -0
- package/src/lifecycle/__tests__/lifecycle.test.ts +7 -0
- package/src/lifecycle/index.ts +2 -0
- package/src/lifecycle/table.ts +42 -0
- package/src/plugin/__tests__/plugin-stub.test.ts +6 -0
- package/src/plugin/plugin-stub.ts +7 -0
- package/src/plugin/toolu.ts +142 -0
- package/src/preflight/__tests__/preflight.test.ts +20 -0
- package/src/preflight/check.ts +75 -0
- package/src/preflight/index.ts +2 -0
- package/src/select/__tests__/select.test.ts +52 -0
- package/src/select/index.ts +2 -0
- package/src/select/resolve.ts +87 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Post-tool: turn "the user said yes" into a durable push-review waiver.
|
|
3
|
+
#
|
|
4
|
+
# push-review.sh (PreToolUse) can only ask; it never learns the answer. This
|
|
5
|
+
# module is the other half: PostToolUse runs only when the tool actually ran,
|
|
6
|
+
# so its presence IS the yes. It promotes the pending marker push-review left
|
|
7
|
+
# behind into a waiver for that exact diff.
|
|
8
|
+
#
|
|
9
|
+
# Two guards keep a waiver honest:
|
|
10
|
+
# * the diff SHA must still match what was asked about — a pending marker
|
|
11
|
+
# from older code is never cashed in by a push of different code;
|
|
12
|
+
# * the push must have SUCCEEDED. PostToolUse fires when a tool ran, not when
|
|
13
|
+
# it worked, and a rejected non-fast-forward push has not been reviewed by
|
|
14
|
+
# anybody. On failure the pending marker survives for the retry.
|
|
15
|
+
#
|
|
16
|
+
# Inputs (from the parent dispatcher post-tools/mod.sh, via `export`):
|
|
17
|
+
# $tool_name - name of the tool being invoked
|
|
18
|
+
# $input - raw JSON payload on stdin
|
|
19
|
+
|
|
20
|
+
# pipefail for parity with the other gate modules (push-review.sh,
|
|
21
|
+
# plan-ledger.sh, docs-sync.sh all use exactly this).
|
|
22
|
+
#
|
|
23
|
+
# Deliberately NOT -e and NOT -u, for the same underlying reason: both options
|
|
24
|
+
# apply to the shared libraries this module SOURCES, which are written for the
|
|
25
|
+
# shell they are sourced into rather than for a strict one.
|
|
26
|
+
#
|
|
27
|
+
# -e: this module's control flow uses non-zero returns as signal —
|
|
28
|
+
# push_waiver_promote returns 1 whenever there is nothing to promote,
|
|
29
|
+
# which is the normal case on most pushes.
|
|
30
|
+
# -u: the libraries use the guard-then-assign idiom, reading a variable
|
|
31
|
+
# before it is ever assigned. hooks/lib/detect.sh:307 is one of several:
|
|
32
|
+
# [ -n "$root" ] || root=$(git rev-parse --show-toplevel 2>/dev/null)
|
|
33
|
+
# Under nounset that aborts with "root: unbound variable" before this
|
|
34
|
+
# module reaches its own work, so a real push silently fails to record
|
|
35
|
+
# its waiver. Verified: adding -u fails
|
|
36
|
+
# __tests__/push-waiver.bats "the module never writes to stdout".
|
|
37
|
+
#
|
|
38
|
+
# Making these strict is a repo-wide change to how the shared libraries are
|
|
39
|
+
# written, not a per-module flag.
|
|
40
|
+
set -o pipefail
|
|
41
|
+
|
|
42
|
+
: "${tool_name:=}"
|
|
43
|
+
: "${input:=}"
|
|
44
|
+
|
|
45
|
+
# Cursor Agent uses tool_name "Shell"; Claude Code uses "Bash".
|
|
46
|
+
[[ "$tool_name" != "Bash" && "$tool_name" != "Shell" ]] && exit 0
|
|
47
|
+
|
|
48
|
+
command -v jq >/dev/null 2>&1 || exit 0
|
|
49
|
+
command -v git >/dev/null 2>&1 || exit 0
|
|
50
|
+
|
|
51
|
+
_toolu_lib="${TOOLU_LIB_DIR:-${BASH_SOURCE%/*}/../../lib}"
|
|
52
|
+
# shellcheck source=../../lib/detect.sh
|
|
53
|
+
. "$_toolu_lib/detect.sh"
|
|
54
|
+
# shellcheck source=../../lib/diff-sha.sh
|
|
55
|
+
. "$_toolu_lib/diff-sha.sh"
|
|
56
|
+
# shellcheck source=../../lib/push-waiver.sh
|
|
57
|
+
. "$_toolu_lib/push-waiver.sh"
|
|
58
|
+
|
|
59
|
+
command=$(echo "$input" | jq -r '.tool_input.command // ""' 2>/dev/null || echo "")
|
|
60
|
+
is_git_push "$command" || exit 0
|
|
61
|
+
|
|
62
|
+
# Exit-code resolution mirrors post-tools/modules/gate-status.sh so both
|
|
63
|
+
# modules read a host's response shape the same way.
|
|
64
|
+
exit_code=$(echo "$input" | jq -r '.tool_response.metadata.exit_code // .tool_response.exit_code // .tool_output.exit_code // empty' 2>/dev/null || echo "")
|
|
65
|
+
if [[ -z "$exit_code" || "$exit_code" == "null" ]]; then
|
|
66
|
+
tool_output_raw=$(echo "$input" | jq -r '.tool_output // empty' 2>/dev/null || echo "")
|
|
67
|
+
if [[ -n "$tool_output_raw" ]]; then
|
|
68
|
+
exit_code=$(echo "$tool_output_raw" | jq -r '.exitCode // .exit_code // empty' 2>/dev/null || echo "")
|
|
69
|
+
fi
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
# A reported non-zero exit means the push did not land: leave the pending
|
|
73
|
+
# marker alone so the retry still asks.
|
|
74
|
+
if [[ -n "$exit_code" && "$exit_code" != "null" && "$exit_code" != "0" ]]; then
|
|
75
|
+
exit 0
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
# An interrupted tool call did not complete either.
|
|
79
|
+
interrupted=$(echo "$input" | jq -r '.tool_response.interrupted // false' 2>/dev/null || echo "false")
|
|
80
|
+
[[ "$interrupted" == "true" ]] && exit 0
|
|
81
|
+
|
|
82
|
+
# Same target-repo resolution as the pre-tool gate: `git -C <worktree> push`
|
|
83
|
+
# is judged on the worktree's own branch and state.
|
|
84
|
+
repo_root=$(push_target_root "$command")
|
|
85
|
+
current_branch=$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
|
|
86
|
+
[[ -z "$current_branch" || "$current_branch" == "HEAD" ]] && exit 0
|
|
87
|
+
slug=$(branch_slug "$current_branch")
|
|
88
|
+
|
|
89
|
+
base_branch="${PUSH_REVIEW_BASE:-$(detect_base_branch "$repo_root")}"
|
|
90
|
+
current_diff_sha=$(toolu_diff_sha "$repo_root" "$base_branch")
|
|
91
|
+
[[ -z "$current_diff_sha" ]] && exit 0
|
|
92
|
+
|
|
93
|
+
# Nothing pending, or pending for other code: promote is a no-op and says so
|
|
94
|
+
# by returning non-zero. Either way this module has no output.
|
|
95
|
+
push_waiver_promote "$repo_root" "$slug" "$current_diff_sha" || exit 0
|
|
96
|
+
|
|
97
|
+
exit 0
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# PreCompact hook
|
|
3
|
+
|
|
4
|
+
HOOK_DIR="$(dirname "$0")"
|
|
5
|
+
|
|
6
|
+
# shellcheck source=lib/config.sh
|
|
7
|
+
. "$HOOK_DIR/lib/config.sh"
|
|
8
|
+
|
|
9
|
+
# Drain stdin with a shell builtin so Codex does not see EPIPE from a fast exit.
|
|
10
|
+
while IFS= read -r; do :; done
|
|
11
|
+
|
|
12
|
+
if ! toolu_enabled hooks pre-compact; then
|
|
13
|
+
exit 0
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
# PreCompact has no shared context-output shape, so a successful no-op is silent.
|
|
17
|
+
|
|
18
|
+
exit 0
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Agent/Task model-tier telemetry + advisory. Standalone PreToolUse hook — NOT
|
|
3
|
+
# under pre-tools/modules/ (mod.sh only dispatches that directory, so a
|
|
4
|
+
# sibling script here is invisible to it and, like mcp-blocker.sh, must read
|
|
5
|
+
# its own stdin instead of relying on the parent dispatcher's `export`s).
|
|
6
|
+
#
|
|
7
|
+
# Registered in hooks.json under matcher "spawn_agent|Agent|Task" — deliberately loose
|
|
8
|
+
# (it also matches TaskCreate/TaskUpdate/etc.); the real guard is the exact
|
|
9
|
+
# tool_name equality check below (resolves spec OQ-1).
|
|
10
|
+
#
|
|
11
|
+
# Behavior:
|
|
12
|
+
# - Anything but tool_name == "spawn_agent", "Agent", or "Task" -> no-op.
|
|
13
|
+
# - Always append a `delegation` telemetry event: {model, subagent_type,
|
|
14
|
+
# step_id, step_model}, all null when not applicable. The plan-ledger join
|
|
15
|
+
# (step_id/step_model) only happens when a ledger file for the current
|
|
16
|
+
# branch exists (cheap stat first): step_id is the running step's id, else
|
|
17
|
+
# the ledger's `next`; step_model is that step's declared `model` field.
|
|
18
|
+
# - Nudge/deny ONLY when the joined step names a non-null model AND the
|
|
19
|
+
# call supplied a `model` param AND they differ. A missing call `model`
|
|
20
|
+
# means tier-inherit and is always legitimate — never nudged, never
|
|
21
|
+
# denied. Controlled by `agentTier.mode`: advise (default, emits
|
|
22
|
+
# additionalContext) | block (deny) | off (silent).
|
|
23
|
+
# - All failure paths (no jq, unreadable/malformed ledger, no git repo)
|
|
24
|
+
# fail open (exit 0); a telemetry bug must never block a delegation.
|
|
25
|
+
|
|
26
|
+
set -o pipefail
|
|
27
|
+
|
|
28
|
+
_toolu_lib="${TOOLU_LIB_DIR:-${BASH_SOURCE%/*}/../lib}"
|
|
29
|
+
# shellcheck source=../lib/detect.sh
|
|
30
|
+
. "$_toolu_lib/detect.sh"
|
|
31
|
+
# shellcheck source=../lib/config.sh
|
|
32
|
+
. "$_toolu_lib/config.sh"
|
|
33
|
+
# shellcheck source=../lib/telemetry.sh
|
|
34
|
+
. "$_toolu_lib/telemetry.sh"
|
|
35
|
+
# shellcheck source=../lib/gate-mode.sh
|
|
36
|
+
. "$_toolu_lib/gate-mode.sh"
|
|
37
|
+
|
|
38
|
+
command -v jq >/dev/null 2>&1 || exit 0
|
|
39
|
+
|
|
40
|
+
_input=$(cat 2>/dev/null || true)
|
|
41
|
+
|
|
42
|
+
tool_name=$(jq -r '.tool_name // empty' <<<"$_input" 2>/dev/null || true)
|
|
43
|
+
|
|
44
|
+
# Exact-equality self-filter: the hooks.json matcher is deliberately loose
|
|
45
|
+
# (spawn_agent|Agent|Task), so everything else — including TaskCreate/TaskUpdate — must be
|
|
46
|
+
# silently ignored here.
|
|
47
|
+
case "$tool_name" in
|
|
48
|
+
Agent|Task|spawn_agent) ;;
|
|
49
|
+
*) exit 0 ;;
|
|
50
|
+
esac
|
|
51
|
+
|
|
52
|
+
command -v git >/dev/null 2>&1 || exit 0
|
|
53
|
+
|
|
54
|
+
root=$(detect_project_root)
|
|
55
|
+
[ -n "$root" ] || exit 0
|
|
56
|
+
|
|
57
|
+
call_model=$(jq -r '.tool_input.model // empty' <<<"$_input" 2>/dev/null || true)
|
|
58
|
+
subagent_type=$(jq -r '.tool_input.subagent_type // .tool_input.task_name // .tool_input.agent_type // empty' <<<"$_input" 2>/dev/null || true)
|
|
59
|
+
reasoning_effort=$(jq -r '.tool_input.reasoning_effort // .tool_input.reasoningEffort // empty' <<<"$_input" 2>/dev/null || true)
|
|
60
|
+
|
|
61
|
+
# Ledger join: cheap stat first, so a repo with no active plan pays no extra
|
|
62
|
+
# jq cost. LEDGER_DIR mirrors pre-tools/modules/plan-ledger.sh's override.
|
|
63
|
+
step_id="" step_model=""
|
|
64
|
+
branch=$(git -C "$root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
|
|
65
|
+
if [ -n "$branch" ] && [ "$branch" != "HEAD" ]; then
|
|
66
|
+
slug=$(branch_slug "$branch")
|
|
67
|
+
ledger_dir="${LEDGER_DIR:-$(toolu_project_state_dir plan-ledger "$root")}"
|
|
68
|
+
ledger_file="$ledger_dir/${slug}.json"
|
|
69
|
+
if [ -f "$ledger_file" ]; then
|
|
70
|
+
ledger_json=$(cat "$ledger_file" 2>/dev/null || true)
|
|
71
|
+
if [ -n "$ledger_json" ] && jq -e . >/dev/null 2>&1 <<<"$ledger_json"; then
|
|
72
|
+
# Running step's id if one is running, else the ledger's `next`
|
|
73
|
+
# (already null when every step is fresh-green).
|
|
74
|
+
step_id=$(jq -r '
|
|
75
|
+
(first(.steps[]? | select(.status == "running") | .id)) // (.next // empty)
|
|
76
|
+
' <<<"$ledger_json" 2>/dev/null || true)
|
|
77
|
+
if [ -n "$step_id" ]; then
|
|
78
|
+
step_model=$(jq -r --arg id "$step_id" \
|
|
79
|
+
'(first(.steps[]? | select(.id == $id) | .model)) // empty' \
|
|
80
|
+
<<<"$ledger_json" 2>/dev/null || true)
|
|
81
|
+
fi
|
|
82
|
+
fi
|
|
83
|
+
fi
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
# Always record the delegation — join or not, nudge or not.
|
|
87
|
+
extra=$(jq -n \
|
|
88
|
+
--arg model "$call_model" \
|
|
89
|
+
--arg subagent_type "$subagent_type" \
|
|
90
|
+
--arg reasoning_effort "$reasoning_effort" \
|
|
91
|
+
--arg step_id "$step_id" \
|
|
92
|
+
--arg step_model "$step_model" \
|
|
93
|
+
'{
|
|
94
|
+
model: (if $model == "" then null else $model end),
|
|
95
|
+
subagent_type: (if $subagent_type == "" then null else $subagent_type end),
|
|
96
|
+
reasoning_effort: (if $reasoning_effort == "" then null else $reasoning_effort end),
|
|
97
|
+
step_id: (if $step_id == "" then null else $step_id end),
|
|
98
|
+
step_model: (if $step_model == "" then null else $step_model end)
|
|
99
|
+
}')
|
|
100
|
+
telemetry_append "$root" delegation "$extra"
|
|
101
|
+
|
|
102
|
+
# Nudge/deny: only when the joined step declares a non-null model AND the
|
|
103
|
+
# call supplied one AND they differ. A missing call model is tier-inherit —
|
|
104
|
+
# always legitimate, never nudged or denied.
|
|
105
|
+
if [ -n "$step_model" ] && [ -n "$call_model" ] && [ "$step_model" != "$call_model" ]; then
|
|
106
|
+
mode=$(toolu_gate_mode agentTier)
|
|
107
|
+
reason="plan step \"$step_id\" expects model tier \"$step_model\" but this delegation used \"$call_model\""
|
|
108
|
+
toolu_gate_emit "$mode" "$reason"
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
exit 0
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Pre-tool-use hook dispatcher (entrypoint).
|
|
3
|
+
# Runs all scripts in .claude/hooks/pre-tools/modules/*.sh sequentially.
|
|
4
|
+
# Each script receives $input and $tool_name as env vars plus $input on stdin.
|
|
5
|
+
#
|
|
6
|
+
# Dispatch, decision short-circuit (permissionDecision:"deny"), advisory
|
|
7
|
+
# merging, and module exit-code semantics live in lib/dispatch.sh.
|
|
8
|
+
|
|
9
|
+
HOOK_LIB="$(cd "$(dirname "$0")/../lib" && pwd)"
|
|
10
|
+
# shellcheck source=../lib/config.sh
|
|
11
|
+
. "$HOOK_LIB/config.sh"
|
|
12
|
+
# shellcheck source=../lib/dispatch.sh
|
|
13
|
+
. "$HOOK_LIB/dispatch.sh"
|
|
14
|
+
# shellcheck source=../lib/detect.sh
|
|
15
|
+
. "$HOOK_LIB/detect.sh"
|
|
16
|
+
# shellcheck source=../lib/registry.sh
|
|
17
|
+
. "$HOOK_LIB/registry.sh"
|
|
18
|
+
|
|
19
|
+
if ! toolu_enabled hooks pre-tools; then
|
|
20
|
+
cat > /dev/null 2>&1 || true
|
|
21
|
+
exit 0
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# Exported only once the hook is enabled, so disabled runs keep the env clean.
|
|
25
|
+
export TOOLU_LIB_DIR="$HOOK_LIB"
|
|
26
|
+
TOOLU_CONFIG_DIR="$(toolu_config_root)"
|
|
27
|
+
export TOOLU_CONFIG_DIR
|
|
28
|
+
|
|
29
|
+
input=$(cat)
|
|
30
|
+
tool_name=$(jq -r '.tool_name // ""' <<<"$input" 2>/dev/null || echo "")
|
|
31
|
+
|
|
32
|
+
export input tool_name
|
|
33
|
+
|
|
34
|
+
HOOK_DIR="$(cd "$(dirname "$0")" && pwd)/modules"
|
|
35
|
+
|
|
36
|
+
# Exits 2 if a module hard-blocks via exit code 2 (stderr forwarded).
|
|
37
|
+
toolu_dispatch_hook "$HOOK_DIR" "PreToolUse" "$(toolu_registry_event_dir PreToolUse)"
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Pre-tool check: Bash/Shell command validation
|
|
3
|
+
# Data-driven: loads allow/deny rules from $TOOLU_SETTINGS_DIR/bash-{allow,deny}list.txt.
|
|
4
|
+
# Argv-aware: tokenizes the command and matches "<bin> <flag>" deny rules so
|
|
5
|
+
# substring evasion is harder (e.g. `node -e "evil"` is rejected even if `node`
|
|
6
|
+
# alone is allowed).
|
|
7
|
+
#
|
|
8
|
+
# Inputs (from parent dispatcher pre-tools/mod.sh, via `export`):
|
|
9
|
+
# $tool_name - name of the tool being invoked
|
|
10
|
+
# $input - raw JSON payload on stdin
|
|
11
|
+
|
|
12
|
+
: "${tool_name:=}"
|
|
13
|
+
: "${input:=}"
|
|
14
|
+
|
|
15
|
+
_toolu_lib="${TOOLU_LIB_DIR:-${BASH_SOURCE%/*}/../../lib}"
|
|
16
|
+
# shellcheck source=../../lib/detect.sh
|
|
17
|
+
. "$_toolu_lib/detect.sh"
|
|
18
|
+
# shellcheck source=../../lib/gate-mode.sh
|
|
19
|
+
. "$_toolu_lib/gate-mode.sh"
|
|
20
|
+
|
|
21
|
+
[[ "$tool_name" != "Bash" && "$tool_name" != "Shell" ]] && exit 0
|
|
22
|
+
command -v jq >/dev/null 2>&1 || exit 0
|
|
23
|
+
|
|
24
|
+
TOOLU_SETTINGS_DIR=$(toolu_settings_dir)
|
|
25
|
+
|
|
26
|
+
# read_list + strip_heredocs are sourced from lib/detect.sh.
|
|
27
|
+
|
|
28
|
+
# bash_tokenize <command>
|
|
29
|
+
# Tokenize a shell command into argv tokens, respecting single and double quotes.
|
|
30
|
+
# Echoes one token per line.
|
|
31
|
+
bash_tokenize() {
|
|
32
|
+
local cmd="$1"
|
|
33
|
+
python3 - "$cmd" <<'PY' 2>/dev/null || printf '%s\n' "$cmd"
|
|
34
|
+
import shlex, sys
|
|
35
|
+
try:
|
|
36
|
+
for tok in shlex.split(sys.argv[1], posix=True):
|
|
37
|
+
print(tok)
|
|
38
|
+
except ValueError:
|
|
39
|
+
# Unparseable (unbalanced quote etc) — print whole string so substring rules still fire.
|
|
40
|
+
print(sys.argv[1])
|
|
41
|
+
PY
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# tokens_contain_rule <rule> <token...>
|
|
45
|
+
# Returns 0 if `tokens[0]` matches the first rule token AND every subsequent
|
|
46
|
+
# rule token appears as a distinct later argv token. Substring matching is
|
|
47
|
+
# never used here — that was the source of false-positives on commit messages
|
|
48
|
+
# like `git commit -m "fix cargo test failure"`.
|
|
49
|
+
tokens_contain_rule() {
|
|
50
|
+
local rule="$1"; shift
|
|
51
|
+
local -a tokens=("$@")
|
|
52
|
+
local -a rule_tokens
|
|
53
|
+
# shellcheck disable=SC2206 # intentional word-split on whitespace
|
|
54
|
+
rule_tokens=($rule)
|
|
55
|
+
[ ${#rule_tokens[@]} -eq 0 ] && return 1
|
|
56
|
+
[ "${tokens[0]:-}" = "${rule_tokens[0]}" ] || return 1
|
|
57
|
+
local i j want found
|
|
58
|
+
for ((i=1; i<${#rule_tokens[@]}; i++)); do
|
|
59
|
+
want="${rule_tokens[$i]}"
|
|
60
|
+
found=0
|
|
61
|
+
for ((j=1; j<${#tokens[@]}; j++)); do
|
|
62
|
+
if [ "${tokens[$j]}" = "$want" ]; then
|
|
63
|
+
found=1
|
|
64
|
+
break
|
|
65
|
+
fi
|
|
66
|
+
done
|
|
67
|
+
[ "$found" -eq 0 ] && return 1
|
|
68
|
+
done
|
|
69
|
+
return 0
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# matches_deny <command> <denylist>
|
|
73
|
+
# Returns 0 (match) if any deny rule fires against the command, else 1.
|
|
74
|
+
# Rules containing whitespace (e.g. `node -e`, `cargo test`) use argv-aware
|
|
75
|
+
# multi-token matching only (no substring fallback); single-token rules
|
|
76
|
+
# (bare names like `biome`) substring-match anywhere on the command.
|
|
77
|
+
matches_deny() {
|
|
78
|
+
local cmd="$1"
|
|
79
|
+
local denylist="$2"
|
|
80
|
+
local rule tokens _tok
|
|
81
|
+
tokens=(); while IFS= read -r _tok; do tokens+=("$_tok"); done < <(bash_tokenize "$cmd")
|
|
82
|
+
# Tokenizer yielded nothing (empty/whitespace-only command): fall back to
|
|
83
|
+
# the raw command as a single token — same shape bash_tokenize itself emits
|
|
84
|
+
# on a parse failure. Substring rules below never consult tokens, so this
|
|
85
|
+
# cannot bypass them.
|
|
86
|
+
[ ${#tokens[@]} -eq 0 ] && tokens=("$cmd")
|
|
87
|
+
while IFS= read -r rule; do
|
|
88
|
+
[ -z "$rule" ] && continue
|
|
89
|
+
if [[ "$rule" == *" "* ]]; then
|
|
90
|
+
if tokens_contain_rule "$rule" "${tokens[@]}"; then
|
|
91
|
+
echo "$rule"
|
|
92
|
+
return 0
|
|
93
|
+
fi
|
|
94
|
+
else
|
|
95
|
+
# Single-token rule: substring match against the command. Bare-name
|
|
96
|
+
# rules (`biome`) land here; rules with internal whitespace
|
|
97
|
+
# (`cargo test`) take the multi-token argv branch above.
|
|
98
|
+
if [[ "$cmd" == *"$rule"* ]]; then
|
|
99
|
+
echo "$rule"
|
|
100
|
+
return 0
|
|
101
|
+
fi
|
|
102
|
+
fi
|
|
103
|
+
done <<< "$denylist"
|
|
104
|
+
return 1
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# matches_allow <command> <allowlist>
|
|
108
|
+
# Returns 0 if any allow rule matches. Multi-token rules are argv-aware
|
|
109
|
+
# (same shape as deny); single-token rules substring-match.
|
|
110
|
+
matches_allow() {
|
|
111
|
+
local cmd="$1"
|
|
112
|
+
local allowlist="$2"
|
|
113
|
+
local rule tokens _tok
|
|
114
|
+
tokens=(); while IFS= read -r _tok; do tokens+=("$_tok"); done < <(bash_tokenize "$cmd")
|
|
115
|
+
# Same empty-tokenization fallback as matches_deny.
|
|
116
|
+
[ ${#tokens[@]} -eq 0 ] && tokens=("$cmd")
|
|
117
|
+
while IFS= read -r rule; do
|
|
118
|
+
[ -z "$rule" ] && continue
|
|
119
|
+
if [[ "$rule" == *" "* ]]; then
|
|
120
|
+
if tokens_contain_rule "$rule" "${tokens[@]}"; then
|
|
121
|
+
return 0
|
|
122
|
+
fi
|
|
123
|
+
else
|
|
124
|
+
if [[ "$cmd" == *"$rule"* ]]; then
|
|
125
|
+
return 0
|
|
126
|
+
fi
|
|
127
|
+
fi
|
|
128
|
+
done <<< "$allowlist"
|
|
129
|
+
return 1
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
# Public entry — invokable when the script is sourced (used by tests).
|
|
133
|
+
#
|
|
134
|
+
# Semantics: deny is evaluated FIRST so a deny result still carries the rule
|
|
135
|
+
# that fired. The allowlist is an explicit override on top of deny, intended
|
|
136
|
+
# for project-specific exemptions: deny + matching allow → allow; deny with
|
|
137
|
+
# no allow match → deny; no deny match → allow (default-open).
|
|
138
|
+
bash_commands_decide() {
|
|
139
|
+
local cmd="$1"
|
|
140
|
+
local allowlist denylist hit
|
|
141
|
+
allowlist=$(read_list "$TOOLU_SETTINGS_DIR/bash-allowlist.txt")
|
|
142
|
+
denylist=$(read_list "$TOOLU_SETTINGS_DIR/bash-denylist.txt")
|
|
143
|
+
|
|
144
|
+
# Strip heredoc bodies so we only check actual commands, not prose in commit messages.
|
|
145
|
+
cmd=$(printf '%s\n' "$cmd" | strip_heredocs)
|
|
146
|
+
|
|
147
|
+
if hit=$(matches_deny "$cmd" "$denylist"); then
|
|
148
|
+
if matches_allow "$cmd" "$allowlist"; then
|
|
149
|
+
echo "allow"
|
|
150
|
+
else
|
|
151
|
+
echo "deny:$hit"
|
|
152
|
+
fi
|
|
153
|
+
return 0
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
echo "allow"
|
|
157
|
+
return 0
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
# When sourced (tests), return early.
|
|
161
|
+
(return 0 2>/dev/null) && return 0
|
|
162
|
+
|
|
163
|
+
command=$(echo "$input" | jq -r '.tool_input.command // ""')
|
|
164
|
+
decision=$(bash_commands_decide "$command")
|
|
165
|
+
|
|
166
|
+
case "$decision" in
|
|
167
|
+
deny:*)
|
|
168
|
+
rule="${decision#deny:}"
|
|
169
|
+
mode=$(toolu_gate_mode bashCommands)
|
|
170
|
+
case "$mode" in
|
|
171
|
+
ask)
|
|
172
|
+
reason=$(toolu_gate_guardrail_warning \
|
|
173
|
+
"Claude wants to run a command matching the deny rule \"$rule\"." \
|
|
174
|
+
"Rules in settings/bash-denylist.txt cover commands that execute arbitrary code from a string (node -e, bun -e) or that this project has ruled out. The command runs with your full shell privileges if you approve.")
|
|
175
|
+
;;
|
|
176
|
+
advise)
|
|
177
|
+
reason="Command matches deny rule \"$rule\" (plugins/toolu/settings/bash-denylist.txt). The command was not stopped — gates.bashCommands.mode is 'advise'."
|
|
178
|
+
;;
|
|
179
|
+
*)
|
|
180
|
+
reason="Command blocked by deny rule: $rule"
|
|
181
|
+
;;
|
|
182
|
+
esac
|
|
183
|
+
toolu_gate_emit "$mode" "$reason"
|
|
184
|
+
;;
|
|
185
|
+
esac
|
|
186
|
+
|
|
187
|
+
exit 0
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Pre-tool check: Remind to read rules before editing code files.
|
|
3
|
+
# Data-driven: loads $TOOLU_SETTINGS_DIR/code-edit-rules.json. Empty/missing → no-op.
|
|
4
|
+
#
|
|
5
|
+
# Inputs (from parent dispatcher pre-tools/mod.sh, via `export`):
|
|
6
|
+
# $tool_name - name of the tool being invoked
|
|
7
|
+
# $input - raw JSON payload on stdin
|
|
8
|
+
|
|
9
|
+
: "${tool_name:=}"
|
|
10
|
+
: "${input:=}"
|
|
11
|
+
|
|
12
|
+
_toolu_lib="${TOOLU_LIB_DIR:-${BASH_SOURCE%/*}/../../lib}"
|
|
13
|
+
# shellcheck source=../../lib/detect.sh
|
|
14
|
+
. "$_toolu_lib/detect.sh"
|
|
15
|
+
|
|
16
|
+
# MultiEdit is in the PreToolUse matcher and carries .tool_input.file_path just
|
|
17
|
+
# like Edit/Write — include it so rule reminders are not dropped for MultiEdit.
|
|
18
|
+
[[ "$tool_name" != "Edit" && "$tool_name" != "Write" && "$tool_name" != "MultiEdit" ]] && exit 0
|
|
19
|
+
command -v jq >/dev/null 2>&1 || exit 0
|
|
20
|
+
|
|
21
|
+
TOOLU_SETTINGS_DIR=$(toolu_settings_dir)
|
|
22
|
+
RULES_FILE="$TOOLU_SETTINGS_DIR/code-edit-rules.json"
|
|
23
|
+
|
|
24
|
+
[ -f "$RULES_FILE" ] || exit 0
|
|
25
|
+
|
|
26
|
+
file_path=$(echo "$input" | jq -r '.tool_input.file_path // ""')
|
|
27
|
+
[ -z "$file_path" ] && exit 0
|
|
28
|
+
|
|
29
|
+
# Normalize abs → repo-relative so patterns in code-edit-rules.json match
|
|
30
|
+
# the same way the user wrote them.
|
|
31
|
+
file_path=$(to_relative_path "$file_path")
|
|
32
|
+
|
|
33
|
+
# glob_match <pattern> <path> — bash-native glob check
|
|
34
|
+
glob_match() {
|
|
35
|
+
local pattern="$1"
|
|
36
|
+
local path="$2"
|
|
37
|
+
shopt -s extglob globstar 2>/dev/null || true
|
|
38
|
+
# shellcheck disable=SC2053 # intentional glob match on RHS
|
|
39
|
+
[[ "$path" == $pattern ]]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# Iterate rules and accumulate docs whose match-glob fits.
|
|
43
|
+
docs=""
|
|
44
|
+
rule_count=$(jq '.rules | length' "$RULES_FILE" 2>/dev/null || echo 0)
|
|
45
|
+
[ "$rule_count" = "null" ] && rule_count=0
|
|
46
|
+
|
|
47
|
+
for ((i=0; i<rule_count; i++)); do
|
|
48
|
+
match=$(jq -r ".rules[$i].match // \"\"" "$RULES_FILE")
|
|
49
|
+
[ -z "$match" ] && continue
|
|
50
|
+
if glob_match "$match" "$file_path"; then
|
|
51
|
+
# Base docs
|
|
52
|
+
base=$(jq -r ".rules[$i].docs // [] | join(\" + \")" "$RULES_FILE")
|
|
53
|
+
[ -n "$base" ] && [ "$base" != "null" ] && docs="${docs}${docs:+ + }${base}"
|
|
54
|
+
# Conditional extra docs
|
|
55
|
+
extra_paths_count=$(jq -r ".rules[$i].when_path_matches // [] | length" "$RULES_FILE")
|
|
56
|
+
if [ "$extra_paths_count" -gt 0 ]; then
|
|
57
|
+
for ((j=0; j<extra_paths_count; j++)); do
|
|
58
|
+
cond=$(jq -r ".rules[$i].when_path_matches[$j]" "$RULES_FILE")
|
|
59
|
+
if glob_match "$cond" "$file_path"; then
|
|
60
|
+
extra=$(jq -r ".rules[$i].extra_docs // [] | join(\" + \")" "$RULES_FILE")
|
|
61
|
+
[ -n "$extra" ] && [ "$extra" != "null" ] && docs="${docs}${docs:+ + }${extra}"
|
|
62
|
+
break
|
|
63
|
+
fi
|
|
64
|
+
done
|
|
65
|
+
fi
|
|
66
|
+
break
|
|
67
|
+
fi
|
|
68
|
+
done
|
|
69
|
+
|
|
70
|
+
[ -z "$docs" ] && exit 0
|
|
71
|
+
|
|
72
|
+
jq -n --arg msg "Apply these rules: $docs" --arg file "$file_path" '{
|
|
73
|
+
"hookSpecificOutput": {
|
|
74
|
+
"hookEventName": "PreToolUse",
|
|
75
|
+
"additionalContext": ("File: " + $file + "\n" + $msg)
|
|
76
|
+
}
|
|
77
|
+
}'
|
|
78
|
+
exit 0
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Pre-commit gate — validates Conventional Commits prefix and reminds about
|
|
3
|
+
# scope verification + memory save before commits.
|
|
4
|
+
# Project-agnostic: prefixes come from settings/commit-prefixes.txt; base
|
|
5
|
+
# branch is detected via detect_base_branch.
|
|
6
|
+
|
|
7
|
+
: "${tool_name:=}"
|
|
8
|
+
: "${input:=}"
|
|
9
|
+
|
|
10
|
+
_toolu_lib="${TOOLU_LIB_DIR:-${BASH_SOURCE%/*}/../../lib}"
|
|
11
|
+
# shellcheck source=../../lib/detect.sh
|
|
12
|
+
. "$_toolu_lib/detect.sh"
|
|
13
|
+
# shellcheck source=../../lib/gate-mode.sh
|
|
14
|
+
. "$_toolu_lib/gate-mode.sh"
|
|
15
|
+
|
|
16
|
+
[[ "$tool_name" != "Bash" ]] && exit 0
|
|
17
|
+
command -v jq >/dev/null 2>&1 || exit 0
|
|
18
|
+
|
|
19
|
+
command=$(echo "$input" | jq -r '.tool_input.command // ""' 2>/dev/null || echo "")
|
|
20
|
+
|
|
21
|
+
# Only match git commit commands. is_git_commit (lib/detect.sh) is used rather
|
|
22
|
+
# than a literal prefix match so `git -C <path> commit` and a commit reached
|
|
23
|
+
# through `&&` are seen too, and a heredoc body that merely mentions committing
|
|
24
|
+
# is not.
|
|
25
|
+
is_git_commit "$command" || exit 0
|
|
26
|
+
|
|
27
|
+
mode=$(toolu_gate_mode commitGate)
|
|
28
|
+
[ "$mode" = "off" ] && exit 0
|
|
29
|
+
|
|
30
|
+
TOOLU_SETTINGS_DIR=$(toolu_settings_dir)
|
|
31
|
+
PREFIX_FILE="$TOOLU_SETTINGS_DIR/commit-prefixes.txt"
|
|
32
|
+
BASE_BRANCH=$(detect_base_branch)
|
|
33
|
+
|
|
34
|
+
# read_list is sourced from lib/detect.sh.
|
|
35
|
+
|
|
36
|
+
# Extract the message from -m "..." if present; else allow (it's editor-driven).
|
|
37
|
+
# Double-quoted form honors backslash escapes (`-m "fix \"bug\""`) so an
|
|
38
|
+
# escaped quote no longer truncates the message; single-quoted form cannot
|
|
39
|
+
# contain escapes by shell rules. If neither form parses, msg stays empty and
|
|
40
|
+
# the gate falls through gracefully (no deny, context message only).
|
|
41
|
+
msg=""
|
|
42
|
+
if [[ "$command" == *" -m "* ]]; then
|
|
43
|
+
msg=$(printf '%s' "$command" | sed -nE 's/.* -m[[:space:]]+"((\\.|[^"\\])*)".*/\1/p')
|
|
44
|
+
if [ -z "$msg" ]; then
|
|
45
|
+
msg=$(printf '%s' "$command" | sed -nE "s/.* -m[[:space:]]+'([^']*)'.*/\\1/p")
|
|
46
|
+
else
|
|
47
|
+
# Unescape so the subject parser sees the literal message (\" -> ").
|
|
48
|
+
msg=$(printf '%s' "$msg" | sed -E 's/\\(.)/\1/g')
|
|
49
|
+
fi
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
prefixes=$(read_list "$PREFIX_FILE")
|
|
53
|
+
if [ -n "$msg" ] && [ -n "$prefixes" ]; then
|
|
54
|
+
# Subject = first line up to first colon/space
|
|
55
|
+
subject_prefix=$(printf '%s' "$msg" | head -1 | sed -nE 's/^([a-z]+)(\(.*\))?:.*/\1/p')
|
|
56
|
+
if [ -n "$subject_prefix" ]; then
|
|
57
|
+
if ! echo "$prefixes" | grep -qFx "$subject_prefix"; then
|
|
58
|
+
toolu_gate_emit "$mode" "Unknown Conventional Commits prefix: \"$subject_prefix\". Allowed prefixes are in settings/commit-prefixes.txt. Base branch: $BASE_BRANCH"
|
|
59
|
+
exit 0
|
|
60
|
+
fi
|
|
61
|
+
fi
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
jq -n --arg base "$BASE_BRANCH" '{
|
|
65
|
+
"hookSpecificOutput": {
|
|
66
|
+
"hookEventName": "PreToolUse",
|
|
67
|
+
"additionalContext": ("BEFORE COMMITTING:\n1. Verify diff covers only expected scope (git diff --stat against " + $base + ")\n2. Save memory of significant decisions before committing.\nSkip only if already done this task.")
|
|
68
|
+
}
|
|
69
|
+
}'
|
|
70
|
+
exit 0
|