@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,32 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Content-addressed branch-diff hash: the one formula push-review, plan-ledger
|
|
3
|
+
# (both the checker lib and the push-time gate), and docs-sync each computed as
|
|
4
|
+
# their own inline copy. Survives amend/rebase (content, not commit ids).
|
|
5
|
+
#
|
|
6
|
+
# Public API:
|
|
7
|
+
# toolu_diff_sha REPO_ROOT BASE_REF - print the hash of `git diff
|
|
8
|
+
# BASE_REF...HEAD` run in REPO_ROOT.
|
|
9
|
+
# Non-zero + empty stdout only when git
|
|
10
|
+
# fails or prints nothing; an empty
|
|
11
|
+
# diff returns 0 with the well-known
|
|
12
|
+
# empty-blob sha — callers own the
|
|
13
|
+
# sentinel.
|
|
14
|
+
#
|
|
15
|
+
# NO sentinel mapping here (e.g. push-review's well-known-empty-blob-sha ->
|
|
16
|
+
# "empty-diff" substitution) — an empty diff prints the real (empty-blob) sha
|
|
17
|
+
# like any other; each caller decides what an empty diff means to it.
|
|
18
|
+
#
|
|
19
|
+
# Source via: . "${TOOLU_LIB_DIR:-${BASH_SOURCE%/*}}/diff-sha.sh"
|
|
20
|
+
|
|
21
|
+
# pipefail so a failing `git diff` isn't masked by `git hash-object` succeeding
|
|
22
|
+
# on the resulting empty stream (which hashes to the well-known empty-blob sha).
|
|
23
|
+
set -o pipefail
|
|
24
|
+
|
|
25
|
+
# toolu_diff_sha REPO_ROOT BASE_REF
|
|
26
|
+
toolu_diff_sha() {
|
|
27
|
+
local repo_root="$1" base_ref="$2" sha
|
|
28
|
+
sha=$(git -C "$repo_root" diff --no-color "${base_ref}...HEAD" 2>/dev/null \
|
|
29
|
+
| git hash-object --stdin 2>/dev/null) || return 1
|
|
30
|
+
[ -n "$sha" ] || return 1
|
|
31
|
+
printf '%s\n' "$sha"
|
|
32
|
+
}
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Shared module dispatcher for the pre-tools / post-tools hook entrypoints.
|
|
3
|
+
# Source via: . "${BASH_SOURCE%/*}/../lib/dispatch.sh"
|
|
4
|
+
#
|
|
5
|
+
# Public API:
|
|
6
|
+
# toolu_dispatch_modules MODULES_DIR EVENT_NAME [REGISTRY_DIR...]
|
|
7
|
+
# Runs every MODULES_DIR/*.sh in lexical order, then every *.sh in each
|
|
8
|
+
# REGISTRY_DIR (built-in modules first), feeding each the exported `input`
|
|
9
|
+
# variable (raw hook JSON) on stdin. EVENT_NAME is "PreToolUse" or
|
|
10
|
+
# "PostToolUse" and selects the decision semantics.
|
|
11
|
+
# Registry modules (anything outside MODULES_DIR) MUST be namespaced
|
|
12
|
+
# "<plugin-spec>__<name>.sh" (specs must not contain "__"; "__" instead of
|
|
13
|
+
# "." so specs like "name@git.example.com" parse unambiguously) and run
|
|
14
|
+
# only when `toolu_plugin_active <plugin-spec>` succeeds. A registry
|
|
15
|
+
# file that is not namespaced, or dispatched without that helper sourced,
|
|
16
|
+
# is SKIPPED — fail closed, so a partial-sourcing bug degrades to "do
|
|
17
|
+
# less" instead of running ungated modules. Built-in MODULES_DIR scripts
|
|
18
|
+
# are never gated. The plugin-active lookup is memoized per spec within
|
|
19
|
+
# one dispatch call.
|
|
20
|
+
#
|
|
21
|
+
# Output discipline:
|
|
22
|
+
# - PreToolUse: a module emitting `hookSpecificOutput.permissionDecision ==
|
|
23
|
+
# "deny"` is authoritative — its output is emitted immediately and
|
|
24
|
+
# dispatch stops (security wins; a deny must not be suppressed by an
|
|
25
|
+
# advisory).
|
|
26
|
+
# - PreToolUse: a module emitting `permissionDecision == "ask"` is HELD, not
|
|
27
|
+
# emitted immediately, and dispatch continues — a later module must still
|
|
28
|
+
# be able to deny, and a prompt the user could answer "yes" to must never
|
|
29
|
+
# outrank a hard block. The first ask wins if several fire; it is emitted
|
|
30
|
+
# at the end with any collected advisories appended to its reason, so an
|
|
31
|
+
# advisory is never lost behind a prompt.
|
|
32
|
+
# - PostToolUse: a module emitting top-level `decision == "block"` is
|
|
33
|
+
# authoritative — its output is emitted immediately and dispatch stops.
|
|
34
|
+
# (PostToolUse has no permissionDecision; that is PreToolUse-only.)
|
|
35
|
+
# - Otherwise every module's advisory `hookSpecificOutput.additionalContext`
|
|
36
|
+
# (and top-level `systemMessage`) is collected and merged into ONE final
|
|
37
|
+
# JSON object emitted at the end.
|
|
38
|
+
#
|
|
39
|
+
# Module exit-code semantics (deliberate — the old `echo "$input" | bash`
|
|
40
|
+
# pipeline masked these entirely):
|
|
41
|
+
# - 0: stdout handled per the discipline above; stderr discarded.
|
|
42
|
+
# - 2: the Claude Code "block via exit code" convention — the module's
|
|
43
|
+
# stderr is forwarded and the dispatcher returns 2, so the entrypoint
|
|
44
|
+
# exits 2 and Claude Code sees the block plus the stderr feedback.
|
|
45
|
+
# Remaining modules are skipped.
|
|
46
|
+
# - any other non-zero: treated as a module failure — one warning line on
|
|
47
|
+
# stderr (visible in claude --debug), the module's possibly-partial
|
|
48
|
+
# stdout is DISCARDED, and dispatch continues with the next module.
|
|
49
|
+
|
|
50
|
+
_TOOLU_DISPATCH_LIB_DIR="$(cd "${BASH_SOURCE%/*}" && pwd)"
|
|
51
|
+
# shellcheck source=edit-records.sh
|
|
52
|
+
. "$_TOOLU_DISPATCH_LIB_DIR/edit-records.sh"
|
|
53
|
+
|
|
54
|
+
toolu_dispatch_modules() {
|
|
55
|
+
# With <2 args `shift 2` would not shift at all, leaving $1 to be re-globbed
|
|
56
|
+
# as a registry dir and every built-in module run twice.
|
|
57
|
+
[[ $# -lt 2 ]] && return 0
|
|
58
|
+
local modules_dir="$1" event="$2"; shift 2
|
|
59
|
+
local registry_dirs=("$@")
|
|
60
|
+
local script result rc err_file decision ctx msg c base plugin
|
|
61
|
+
local contexts=() messages=()
|
|
62
|
+
# First `ask` seen this dispatch, held until every module has run.
|
|
63
|
+
local ask_result=""
|
|
64
|
+
# Per-dispatch memo of plugin-active lookups (space-delimited spec lists;
|
|
65
|
+
# plain strings, not associative arrays, for bash 3.2 compatibility).
|
|
66
|
+
local active_specs=" " inactive_specs=" "
|
|
67
|
+
|
|
68
|
+
# Constant for the whole dispatch; resolved once instead of per module.
|
|
69
|
+
local plugin_helper_ok=0
|
|
70
|
+
declare -F toolu_plugin_active >/dev/null 2>&1 && plugin_helper_ok=1
|
|
71
|
+
|
|
72
|
+
err_file=$(mktemp "${TMPDIR:-/tmp}/toolu-dispatch.XXXXXX") || return 0
|
|
73
|
+
|
|
74
|
+
# Ordered module list: built-in dir first, then each registry dir.
|
|
75
|
+
local scripts=()
|
|
76
|
+
for script in "$modules_dir"/*.sh; do
|
|
77
|
+
[[ -f "$script" ]] && scripts+=("$script")
|
|
78
|
+
done
|
|
79
|
+
local rdir
|
|
80
|
+
for rdir in "${registry_dirs[@]}"; do
|
|
81
|
+
[[ -d "$rdir" ]] || continue
|
|
82
|
+
for script in "$rdir"/*.sh; do
|
|
83
|
+
[[ -f "$script" ]] && scripts+=("$script")
|
|
84
|
+
done
|
|
85
|
+
done
|
|
86
|
+
|
|
87
|
+
for script in "${scripts[@]}"; do
|
|
88
|
+
[[ ! -f "$script" ]] && continue
|
|
89
|
+
|
|
90
|
+
# Anything outside MODULES_DIR is a registry module and MUST satisfy the
|
|
91
|
+
# gating contract; violations are skipped, never run. Built-ins are
|
|
92
|
+
# globbed directly from $modules_dir, so "registry" means the script's
|
|
93
|
+
# dirname is not EXACTLY $modules_dir — a prefix match would let a
|
|
94
|
+
# registry dir nested under the modules tree masquerade as built-in (and
|
|
95
|
+
# an empty modules_dir prefix would match every absolute path).
|
|
96
|
+
base=$(basename "$script")
|
|
97
|
+
if [[ -z "$modules_dir" || "${script%/*}" != "$modules_dir" ]]; then
|
|
98
|
+
# The spec must be non-empty ("__foo.sh") and whitespace-free: the
|
|
99
|
+
# memo lists are space-delimited, so a spec containing whitespace
|
|
100
|
+
# could substring-collide with another spec's memo entry.
|
|
101
|
+
plugin="${base%%__*}"
|
|
102
|
+
if [[ "$base" != *__*.sh || -z "$plugin" || "$plugin" == *[[:space:]]* ]]; then
|
|
103
|
+
printf 'toolu-dispatch: registry module %s lacks <plugin-spec>__<name>.sh namespace; skipped\n' \
|
|
104
|
+
"$base" >&2
|
|
105
|
+
continue
|
|
106
|
+
fi
|
|
107
|
+
if [[ $plugin_helper_ok -ne 1 ]]; then
|
|
108
|
+
printf 'toolu-dispatch: toolu_plugin_active not sourced; registry module %s skipped\n' \
|
|
109
|
+
"$base" >&2
|
|
110
|
+
continue
|
|
111
|
+
fi
|
|
112
|
+
# Resolve each distinct spec at most once per dispatch (hot path: one
|
|
113
|
+
# jq per plugin instead of one per module).
|
|
114
|
+
if [[ "$active_specs" != *" $plugin "* ]]; then
|
|
115
|
+
[[ "$inactive_specs" == *" $plugin "* ]] && continue
|
|
116
|
+
if ! toolu_plugin_active "$plugin"; then
|
|
117
|
+
inactive_specs="${inactive_specs}${plugin} "
|
|
118
|
+
continue
|
|
119
|
+
fi
|
|
120
|
+
active_specs="${active_specs}${plugin} "
|
|
121
|
+
fi
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
rc=0
|
|
125
|
+
# Modules are always executed with `bash` regardless of their shebang. This
|
|
126
|
+
# is fine today because only *.sh files are globbed above; a future non-bash
|
|
127
|
+
# module would require this invocation to honor the script's interpreter.
|
|
128
|
+
# shellcheck disable=SC2154 # $input is exported by the sourcing entrypoint.
|
|
129
|
+
result=$(bash "$script" <<<"$input" 2>"$err_file") || rc=$?
|
|
130
|
+
|
|
131
|
+
if [[ $rc -eq 2 ]]; then
|
|
132
|
+
# Deliberate hard block: propagate stderr + exit code 2.
|
|
133
|
+
cat "$err_file" >&2
|
|
134
|
+
rm -f "$err_file"
|
|
135
|
+
return 2
|
|
136
|
+
fi
|
|
137
|
+
if [[ $rc -ne 0 ]]; then
|
|
138
|
+
# Module failure: make it visible, drop its partial output, keep going.
|
|
139
|
+
printf 'toolu-dispatch: module %s exited %d; output skipped\n' \
|
|
140
|
+
"$(basename "$script")" "$rc" >&2
|
|
141
|
+
continue
|
|
142
|
+
fi
|
|
143
|
+
[[ -z "$result" ]] && continue
|
|
144
|
+
|
|
145
|
+
case "$event" in
|
|
146
|
+
PreToolUse)
|
|
147
|
+
decision=$(jq -r '.hookSpecificOutput.permissionDecision // empty' <<<"$result" 2>/dev/null)
|
|
148
|
+
if [[ "$decision" == "deny" ]]; then
|
|
149
|
+
printf '%s\n' "$result"
|
|
150
|
+
rm -f "$err_file"
|
|
151
|
+
return 0
|
|
152
|
+
fi
|
|
153
|
+
if [[ "$decision" == "ask" ]]; then
|
|
154
|
+
# Hold the first ask and keep going: a later deny outranks it. The
|
|
155
|
+
# ask payload carries its text in permissionDecisionReason, not in
|
|
156
|
+
# additionalContext, so there is nothing to harvest below.
|
|
157
|
+
[[ -z "$ask_result" ]] && ask_result="$result"
|
|
158
|
+
continue
|
|
159
|
+
fi
|
|
160
|
+
;;
|
|
161
|
+
PostToolUse)
|
|
162
|
+
decision=$(jq -r '.decision // empty' <<<"$result" 2>/dev/null)
|
|
163
|
+
if [[ "$decision" == "block" ]]; then
|
|
164
|
+
printf '%s\n' "$result"
|
|
165
|
+
rm -f "$err_file"
|
|
166
|
+
return 0
|
|
167
|
+
fi
|
|
168
|
+
;;
|
|
169
|
+
esac
|
|
170
|
+
|
|
171
|
+
ctx=$(jq -r '.hookSpecificOutput.additionalContext // empty' <<<"$result" 2>/dev/null)
|
|
172
|
+
if [[ -n "$ctx" ]]; then
|
|
173
|
+
local seen=0 existing
|
|
174
|
+
for existing in "${contexts[@]}"; do [[ "$existing" == "$ctx" ]] && seen=1; done
|
|
175
|
+
[[ $seen -eq 0 ]] && contexts+=("$ctx")
|
|
176
|
+
fi
|
|
177
|
+
msg=$(jq -r '.systemMessage // empty' <<<"$result" 2>/dev/null)
|
|
178
|
+
if [[ -n "$msg" ]]; then
|
|
179
|
+
local seen_msg=0 existing_msg
|
|
180
|
+
for existing_msg in "${messages[@]}"; do [[ "$existing_msg" == "$msg" ]] && seen_msg=1; done
|
|
181
|
+
[[ $seen_msg -eq 0 ]] && messages+=("$msg")
|
|
182
|
+
fi
|
|
183
|
+
done
|
|
184
|
+
rm -f "$err_file"
|
|
185
|
+
|
|
186
|
+
local merged_ctx="" merged_msg=""
|
|
187
|
+
if [[ ${#contexts[@]} -gt 0 ]]; then
|
|
188
|
+
for c in "${contexts[@]}"; do
|
|
189
|
+
if [[ -z "$merged_ctx" ]]; then
|
|
190
|
+
merged_ctx="$c"
|
|
191
|
+
else
|
|
192
|
+
merged_ctx="${merged_ctx}"$'\n\n'"${c}"
|
|
193
|
+
fi
|
|
194
|
+
done
|
|
195
|
+
fi
|
|
196
|
+
if [[ ${#messages[@]} -gt 0 ]]; then
|
|
197
|
+
for c in "${messages[@]}"; do
|
|
198
|
+
if [[ -z "$merged_msg" ]]; then
|
|
199
|
+
merged_msg="$c"
|
|
200
|
+
else
|
|
201
|
+
merged_msg="${merged_msg}"$'\n\n'"${c}"
|
|
202
|
+
fi
|
|
203
|
+
done
|
|
204
|
+
fi
|
|
205
|
+
|
|
206
|
+
# A held ask is the decision, and it absorbs every advisory collected after
|
|
207
|
+
# it so nothing a later module wanted to say is dropped.
|
|
208
|
+
if [[ -n "$ask_result" ]]; then
|
|
209
|
+
# If the enrichment fails for any reason, emit the ask EXACTLY as the module
|
|
210
|
+
# wrote it. Losing an advisory is a cosmetic loss; losing the decision would
|
|
211
|
+
# turn a prompt into a silent allow.
|
|
212
|
+
jq -n --argjson ask "$ask_result" --arg ctx "$merged_ctx" --arg msg "$merged_msg" '
|
|
213
|
+
($ask.hookSpecificOutput.permissionDecisionReason // "") as $reason
|
|
214
|
+
| $ask
|
|
215
|
+
| .hookSpecificOutput.permissionDecisionReason =
|
|
216
|
+
(if $ctx != "" then ($reason + "\n\n" + $ctx) else $reason end)
|
|
217
|
+
| (if $msg != "" then .systemMessage = ((.systemMessage // "") | if . == "" then $msg else . + "\n\n" + $msg end) else . end)
|
|
218
|
+
' 2>/dev/null || printf '%s\n' "$ask_result"
|
|
219
|
+
return 0
|
|
220
|
+
fi
|
|
221
|
+
|
|
222
|
+
if [[ -n "$merged_ctx" || -n "$merged_msg" ]]; then
|
|
223
|
+
jq -n --arg ctx "$merged_ctx" --arg msg "$merged_msg" --arg ev "$event" '
|
|
224
|
+
{}
|
|
225
|
+
| (if $ctx != "" then .hookSpecificOutput = { hookEventName: $ev, additionalContext: $ctx } else . end)
|
|
226
|
+
| (if $msg != "" then .systemMessage = $msg else . end)
|
|
227
|
+
'
|
|
228
|
+
fi
|
|
229
|
+
|
|
230
|
+
return 0
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
# Host-aware dispatch wrapper. Non-edit tools preserve the single-dispatch
|
|
234
|
+
# path. Edit tools are normalized to one synthetic Edit payload per path; any
|
|
235
|
+
# per-path denial/block wins for the entire original patch, while exact
|
|
236
|
+
# duplicate advisories are emitted once.
|
|
237
|
+
toolu_dispatch_hook() {
|
|
238
|
+
[[ $# -lt 2 ]] && return 0
|
|
239
|
+
local modules_dir="$1" event="$2"; shift 2
|
|
240
|
+
local registry_dirs=("$@")
|
|
241
|
+
local original_input="${input:-}" original_tool="${tool_name:-}"
|
|
242
|
+
local records normalize_rc=0 record path operation from moved_to synthetic
|
|
243
|
+
local result rc decision ctx msg c existing seen
|
|
244
|
+
local contexts=() messages=()
|
|
245
|
+
# Same hold-the-ask discipline as toolu_dispatch_modules, across the
|
|
246
|
+
# synthetic per-path payloads of one apply_patch.
|
|
247
|
+
local ask_result=""
|
|
248
|
+
|
|
249
|
+
records=$(toolu_normalize_edit_records "$original_input" "$original_tool") || normalize_rc=$?
|
|
250
|
+
if [ "$normalize_rc" -eq 1 ] || [ "$normalize_rc" -eq 3 ]; then
|
|
251
|
+
toolu_dispatch_modules "$modules_dir" "$event" "${registry_dirs[@]}"
|
|
252
|
+
return $?
|
|
253
|
+
fi
|
|
254
|
+
if [ "$normalize_rc" -eq 2 ] || [ -z "$records" ]; then
|
|
255
|
+
case "$event" in
|
|
256
|
+
PreToolUse)
|
|
257
|
+
printf '%s\n' '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Unable to parse apply_patch file headers; patch blocked so protected-file and quality gates cannot be bypassed."}}'
|
|
258
|
+
;;
|
|
259
|
+
PostToolUse)
|
|
260
|
+
printf '%s\n' '{"decision":"block","reason":"Unable to parse apply_patch file headers; per-file post-edit quality checks could not run."}'
|
|
261
|
+
;;
|
|
262
|
+
esac
|
|
263
|
+
return 0
|
|
264
|
+
fi
|
|
265
|
+
|
|
266
|
+
while IFS= read -r record; do
|
|
267
|
+
[ -n "$record" ] || continue
|
|
268
|
+
path=$(jq -er '.path | strings' <<<"$record" 2>/dev/null) || continue
|
|
269
|
+
operation=$(jq -r '.operation // "update"' <<<"$record" 2>/dev/null)
|
|
270
|
+
from=$(jq -r '.from // ""' <<<"$record" 2>/dev/null)
|
|
271
|
+
moved_to=$(jq -r '.moved_to // ""' <<<"$record" 2>/dev/null)
|
|
272
|
+
synthetic=$(jq -c --arg path "$path" --arg operation "$operation" \
|
|
273
|
+
--arg from "$from" --arg moved_to "$moved_to" '
|
|
274
|
+
.tool_name = "Edit"
|
|
275
|
+
| .tool_input = ((.tool_input // {}) + {
|
|
276
|
+
file_path: $path,
|
|
277
|
+
path: $path,
|
|
278
|
+
toolu_edit_operation: $operation,
|
|
279
|
+
toolu_edit_from: $from,
|
|
280
|
+
toolu_edit_moved_to: $moved_to
|
|
281
|
+
})
|
|
282
|
+
' <<<"$original_input" 2>/dev/null) || continue
|
|
283
|
+
|
|
284
|
+
input="$synthetic"
|
|
285
|
+
tool_name=Edit
|
|
286
|
+
TOOLU_EDIT_OPERATION="$operation"
|
|
287
|
+
TOOLU_EDIT_FROM="$from"
|
|
288
|
+
TOOLU_EDIT_MOVED_TO="$moved_to"
|
|
289
|
+
export input tool_name TOOLU_EDIT_OPERATION TOOLU_EDIT_FROM TOOLU_EDIT_MOVED_TO
|
|
290
|
+
|
|
291
|
+
rc=0
|
|
292
|
+
result=$(toolu_dispatch_modules "$modules_dir" "$event" "${registry_dirs[@]}") || rc=$?
|
|
293
|
+
if [ "$rc" -eq 2 ]; then
|
|
294
|
+
input="$original_input"; tool_name="$original_tool"; export input tool_name
|
|
295
|
+
unset TOOLU_EDIT_OPERATION TOOLU_EDIT_FROM TOOLU_EDIT_MOVED_TO
|
|
296
|
+
return 2
|
|
297
|
+
fi
|
|
298
|
+
[ -n "$result" ] || continue
|
|
299
|
+
|
|
300
|
+
case "$event" in
|
|
301
|
+
PreToolUse)
|
|
302
|
+
decision=$(jq -r '.hookSpecificOutput.permissionDecision // empty' <<<"$result" 2>/dev/null)
|
|
303
|
+
;;
|
|
304
|
+
PostToolUse)
|
|
305
|
+
decision=$(jq -r '.decision // empty' <<<"$result" 2>/dev/null)
|
|
306
|
+
;;
|
|
307
|
+
*) decision="" ;;
|
|
308
|
+
esac
|
|
309
|
+
if [ "$decision" = deny ] || [ "$decision" = block ]; then
|
|
310
|
+
input="$original_input"; tool_name="$original_tool"; export input tool_name
|
|
311
|
+
unset TOOLU_EDIT_OPERATION TOOLU_EDIT_FROM TOOLU_EDIT_MOVED_TO
|
|
312
|
+
printf '%s\n' "$result"
|
|
313
|
+
return 0
|
|
314
|
+
fi
|
|
315
|
+
if [ "$decision" = ask ]; then
|
|
316
|
+
# One path asking is enough to prompt for the whole patch, but a later
|
|
317
|
+
# path may still deny it outright — so hold and keep walking.
|
|
318
|
+
[ -z "$ask_result" ] && ask_result="$result"
|
|
319
|
+
continue
|
|
320
|
+
fi
|
|
321
|
+
|
|
322
|
+
ctx=$(jq -r '.hookSpecificOutput.additionalContext // empty' <<<"$result" 2>/dev/null)
|
|
323
|
+
if [ -n "$ctx" ]; then
|
|
324
|
+
seen=0
|
|
325
|
+
for existing in "${contexts[@]}"; do [ "$existing" = "$ctx" ] && seen=1; done
|
|
326
|
+
[ "$seen" -eq 0 ] && contexts+=("$ctx")
|
|
327
|
+
fi
|
|
328
|
+
msg=$(jq -r '.systemMessage // empty' <<<"$result" 2>/dev/null)
|
|
329
|
+
if [ -n "$msg" ]; then
|
|
330
|
+
seen=0
|
|
331
|
+
for existing in "${messages[@]}"; do [ "$existing" = "$msg" ] && seen=1; done
|
|
332
|
+
[ "$seen" -eq 0 ] && messages+=("$msg")
|
|
333
|
+
fi
|
|
334
|
+
done <<<"$records"
|
|
335
|
+
|
|
336
|
+
input="$original_input"; tool_name="$original_tool"; export input tool_name
|
|
337
|
+
unset TOOLU_EDIT_OPERATION TOOLU_EDIT_FROM TOOLU_EDIT_MOVED_TO
|
|
338
|
+
|
|
339
|
+
local merged_ctx="" merged_msg=""
|
|
340
|
+
for c in "${contexts[@]}"; do
|
|
341
|
+
if [ -z "$merged_ctx" ]; then merged_ctx="$c"; else merged_ctx="${merged_ctx}"$'\n\n'"${c}"; fi
|
|
342
|
+
done
|
|
343
|
+
for c in "${messages[@]}"; do
|
|
344
|
+
if [ -z "$merged_msg" ]; then merged_msg="$c"; else merged_msg="${merged_msg}"$'\n\n'"${c}"; fi
|
|
345
|
+
done
|
|
346
|
+
if [ -n "$ask_result" ]; then
|
|
347
|
+
# Same fail-closed fallback as toolu_dispatch_modules above.
|
|
348
|
+
jq -n --argjson ask "$ask_result" --arg ctx "$merged_ctx" --arg msg "$merged_msg" '
|
|
349
|
+
($ask.hookSpecificOutput.permissionDecisionReason // "") as $reason
|
|
350
|
+
| $ask
|
|
351
|
+
| .hookSpecificOutput.permissionDecisionReason =
|
|
352
|
+
(if $ctx != "" then ($reason + "\n\n" + $ctx) else $reason end)
|
|
353
|
+
| (if $msg != "" then .systemMessage = ((.systemMessage // "") | if . == "" then $msg else . + "\n\n" + $msg end) else . end)
|
|
354
|
+
' 2>/dev/null || printf '%s\n' "$ask_result"
|
|
355
|
+
return 0
|
|
356
|
+
fi
|
|
357
|
+
if [ -n "$merged_ctx" ] || [ -n "$merged_msg" ]; then
|
|
358
|
+
jq -n --arg ctx "$merged_ctx" --arg msg "$merged_msg" --arg ev "$event" '
|
|
359
|
+
{}
|
|
360
|
+
| (if $ctx != "" then .hookSpecificOutput = {hookEventName:$ev,additionalContext:$ctx} else . end)
|
|
361
|
+
| (if $msg != "" then .systemMessage = $msg else . end)
|
|
362
|
+
'
|
|
363
|
+
fi
|
|
364
|
+
return 0
|
|
365
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Resolve the docs-sync glob sets for the docs-sync pre-tools module. Mirrors
|
|
3
|
+
# quality-config.sh: a project/user override from the merged toolu config
|
|
4
|
+
# (.claude/toolu.config.json `docsSync.*`) wins, else a built-in default.
|
|
5
|
+
# Never errors — missing jq or config falls through to the defaults.
|
|
6
|
+
#
|
|
7
|
+
# Public API (each echoes a newline-separated glob list):
|
|
8
|
+
# docs_sync_surfaces - doc files whose change SATISFIES the check
|
|
9
|
+
# docs_sync_surface_excludes - doc paths that DO NOT satisfy it (carved back
|
|
10
|
+
# out of `surfaces`; releases are per-release,
|
|
11
|
+
# not per-task, and `*` crosses `/` so the
|
|
12
|
+
# `docs/*.md` surface would otherwise swallow them)
|
|
13
|
+
# docs_sync_code_surfaces - code files whose change DEMANDS a doc touch
|
|
14
|
+
#
|
|
15
|
+
# Globs are matched by the module with bash `case` fnmatch, where a single `*`
|
|
16
|
+
# crosses `/` — so `docs/*.md` already covers nested paths and `*.ts` matches
|
|
17
|
+
# any depth. Author overrides with that semantics in mind.
|
|
18
|
+
#
|
|
19
|
+
# Source via: . "$TOOLU_LIB_DIR/docs-sync-config.sh"
|
|
20
|
+
|
|
21
|
+
_DS_LIB_DIR="${TOOLU_LIB_DIR:-${BASH_SOURCE%/*}}"
|
|
22
|
+
# shellcheck source=./config.sh
|
|
23
|
+
[ -f "$_DS_LIB_DIR/config.sh" ] && . "$_DS_LIB_DIR/config.sh"
|
|
24
|
+
|
|
25
|
+
# Built-in defaults — single source of truth, overridable from the environment
|
|
26
|
+
# (tests set these to assert fall-through without touching call sites).
|
|
27
|
+
: "${DEFAULT_DOCS_SYNC_SURFACES:=README.md
|
|
28
|
+
*/README.md
|
|
29
|
+
docs/*.md
|
|
30
|
+
*/SKILL.md
|
|
31
|
+
AGENTS.md
|
|
32
|
+
*/AGENTS.md
|
|
33
|
+
CLAUDE.md
|
|
34
|
+
*/CLAUDE.md
|
|
35
|
+
*/workflows/*.md}"
|
|
36
|
+
: "${DEFAULT_DOCS_SYNC_SURFACE_EXCLUDES:=docs/releases/*
|
|
37
|
+
*/docs/releases/*}"
|
|
38
|
+
: "${DEFAULT_DOCS_SYNC_CODE_SURFACES:=*.ts
|
|
39
|
+
*.rs
|
|
40
|
+
*.sh
|
|
41
|
+
*/commands/*
|
|
42
|
+
*plugin.json
|
|
43
|
+
*.config.json}"
|
|
44
|
+
|
|
45
|
+
# _ds_override KEY -> newline-joined override globs, or "" when unset/empty.
|
|
46
|
+
# Reads docsSync.<key> as a JSON array of strings from the merged config.
|
|
47
|
+
_ds_override() {
|
|
48
|
+
local key="$1"
|
|
49
|
+
command -v toolu_load_config >/dev/null 2>&1 || return 0
|
|
50
|
+
toolu_load_config
|
|
51
|
+
[ "${_TOOLU_HAS_JQ:-0}" = "1" ] || return 0
|
|
52
|
+
[ -n "${TOOLU_CFG_JSON:-}" ] || return 0
|
|
53
|
+
jq -r --arg k "$key" '
|
|
54
|
+
(.docsSync? // {})[$k]?
|
|
55
|
+
| if type=="array" then .[] else empty end
|
|
56
|
+
' <<< "$TOOLU_CFG_JSON" 2>/dev/null
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# docs_sync_surfaces -> doc-surface globs (override -> default).
|
|
60
|
+
docs_sync_surfaces() {
|
|
61
|
+
local v
|
|
62
|
+
v=$(_ds_override surfaces)
|
|
63
|
+
[ -n "$v" ] && { printf '%s\n' "$v"; return 0; }
|
|
64
|
+
printf '%s\n' "$DEFAULT_DOCS_SYNC_SURFACES"
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# docs_sync_surface_excludes -> doc paths carved back out (override -> default).
|
|
68
|
+
docs_sync_surface_excludes() {
|
|
69
|
+
local v
|
|
70
|
+
v=$(_ds_override surfaceExcludes)
|
|
71
|
+
[ -n "$v" ] && { printf '%s\n' "$v"; return 0; }
|
|
72
|
+
printf '%s\n' "$DEFAULT_DOCS_SYNC_SURFACE_EXCLUDES"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# docs_sync_code_surfaces -> code-surface globs (override -> default).
|
|
76
|
+
docs_sync_code_surfaces() {
|
|
77
|
+
local v
|
|
78
|
+
v=$(_ds_override codeSurfaces)
|
|
79
|
+
[ -n "$v" ] && { printf '%s\n' "$v"; return 0; }
|
|
80
|
+
printf '%s\n' "$DEFAULT_DOCS_SYNC_CODE_SURFACES"
|
|
81
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Normalize host-specific edit payloads into one JSON record per affected path.
|
|
3
|
+
# Records are newline-delimited JSON objects with at least `path` and
|
|
4
|
+
# `operation`. A move produces one source record and one destination record so
|
|
5
|
+
# protected-file and post-edit concerns inspect both sides.
|
|
6
|
+
|
|
7
|
+
toolu_is_edit_tool() {
|
|
8
|
+
case "${1:-}" in
|
|
9
|
+
Edit|Write|MultiEdit|apply_patch) return 0 ;;
|
|
10
|
+
*) return 1 ;;
|
|
11
|
+
esac
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
_toolu_patch_path_valid() {
|
|
15
|
+
local path="${1:-}"
|
|
16
|
+
[ -n "$path" ] || return 1
|
|
17
|
+
case "$path" in
|
|
18
|
+
*$'\n'*|*$'\r'*|*$'\t'*) return 1 ;;
|
|
19
|
+
esac
|
|
20
|
+
return 0
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
_toolu_edit_record() {
|
|
24
|
+
local path="$1" operation="$2" relation_key="${3:-}" relation_value="${4:-}"
|
|
25
|
+
if [ -n "$relation_key" ]; then
|
|
26
|
+
jq -cn --arg path "$path" --arg operation "$operation" \
|
|
27
|
+
--arg key "$relation_key" --arg value "$relation_value" \
|
|
28
|
+
'{path:$path,operation:$operation} + {($key):$value}'
|
|
29
|
+
else
|
|
30
|
+
jq -cn --arg path "$path" --arg operation "$operation" \
|
|
31
|
+
'{path:$path,operation:$operation}'
|
|
32
|
+
fi
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# Parse the apply_patch command carried by Codex. Return 2 with no output when
|
|
36
|
+
# framing or any file header is malformed; callers use that as a fail-closed
|
|
37
|
+
# PreToolUse signal.
|
|
38
|
+
_toolu_apply_patch_records() {
|
|
39
|
+
local patch="$1" line raw path pending_update="" target
|
|
40
|
+
local begun=0 ended=0 header_count=0 invalid=0
|
|
41
|
+
local records=()
|
|
42
|
+
|
|
43
|
+
while IFS= read -r line || [ -n "$line" ]; do
|
|
44
|
+
line=${line%$'\r'}
|
|
45
|
+
if [ "$ended" -eq 1 ]; then
|
|
46
|
+
[ -z "$line" ] || invalid=1
|
|
47
|
+
continue
|
|
48
|
+
fi
|
|
49
|
+
case "$line" in
|
|
50
|
+
'*** Begin Patch')
|
|
51
|
+
if [ "$begun" -ne 0 ]; then invalid=1; else begun=1; fi
|
|
52
|
+
;;
|
|
53
|
+
'*** End Patch')
|
|
54
|
+
if [ "$begun" -ne 1 ]; then
|
|
55
|
+
invalid=1
|
|
56
|
+
else
|
|
57
|
+
if [ -n "$pending_update" ]; then
|
|
58
|
+
records+=("$(_toolu_edit_record "$pending_update" update)")
|
|
59
|
+
pending_update=""
|
|
60
|
+
fi
|
|
61
|
+
ended=1
|
|
62
|
+
fi
|
|
63
|
+
;;
|
|
64
|
+
'*** Add File:'*)
|
|
65
|
+
[ "$begun" -eq 1 ] || { invalid=1; continue; }
|
|
66
|
+
if [ -n "$pending_update" ]; then
|
|
67
|
+
records+=("$(_toolu_edit_record "$pending_update" update)")
|
|
68
|
+
pending_update=""
|
|
69
|
+
fi
|
|
70
|
+
raw=${line#'*** Add File:'}
|
|
71
|
+
[[ "$raw" == ' '* ]] || { invalid=1; continue; }
|
|
72
|
+
path=${raw# }
|
|
73
|
+
_toolu_patch_path_valid "$path" || { invalid=1; continue; }
|
|
74
|
+
records+=("$(_toolu_edit_record "$path" add)")
|
|
75
|
+
header_count=$((header_count + 1))
|
|
76
|
+
;;
|
|
77
|
+
'*** Update File:'*)
|
|
78
|
+
[ "$begun" -eq 1 ] || { invalid=1; continue; }
|
|
79
|
+
if [ -n "$pending_update" ]; then
|
|
80
|
+
records+=("$(_toolu_edit_record "$pending_update" update)")
|
|
81
|
+
fi
|
|
82
|
+
raw=${line#'*** Update File:'}
|
|
83
|
+
[[ "$raw" == ' '* ]] || { invalid=1; pending_update=""; continue; }
|
|
84
|
+
path=${raw# }
|
|
85
|
+
_toolu_patch_path_valid "$path" || { invalid=1; pending_update=""; continue; }
|
|
86
|
+
pending_update="$path"
|
|
87
|
+
header_count=$((header_count + 1))
|
|
88
|
+
;;
|
|
89
|
+
'*** Delete File:'*)
|
|
90
|
+
[ "$begun" -eq 1 ] || { invalid=1; continue; }
|
|
91
|
+
if [ -n "$pending_update" ]; then
|
|
92
|
+
records+=("$(_toolu_edit_record "$pending_update" update)")
|
|
93
|
+
pending_update=""
|
|
94
|
+
fi
|
|
95
|
+
raw=${line#'*** Delete File:'}
|
|
96
|
+
[[ "$raw" == ' '* ]] || { invalid=1; continue; }
|
|
97
|
+
path=${raw# }
|
|
98
|
+
_toolu_patch_path_valid "$path" || { invalid=1; continue; }
|
|
99
|
+
records+=("$(_toolu_edit_record "$path" delete)")
|
|
100
|
+
header_count=$((header_count + 1))
|
|
101
|
+
;;
|
|
102
|
+
'*** Move to:'*)
|
|
103
|
+
[ "$begun" -eq 1 ] && [ -n "$pending_update" ] || { invalid=1; continue; }
|
|
104
|
+
raw=${line#'*** Move to:'}
|
|
105
|
+
[[ "$raw" == ' '* ]] || { invalid=1; continue; }
|
|
106
|
+
target=${raw# }
|
|
107
|
+
_toolu_patch_path_valid "$target" || { invalid=1; continue; }
|
|
108
|
+
records+=("$(_toolu_edit_record "$pending_update" update moved_to "$target")")
|
|
109
|
+
records+=("$(_toolu_edit_record "$target" move from "$pending_update")")
|
|
110
|
+
pending_update=""
|
|
111
|
+
header_count=$((header_count + 1))
|
|
112
|
+
;;
|
|
113
|
+
'*** End of File')
|
|
114
|
+
# Optional Codex hunk marker: it describes EOF placement inside the
|
|
115
|
+
# current file operation and does not introduce another affected path.
|
|
116
|
+
[ "$begun" -eq 1 ] && [ "$header_count" -gt 0 ] || invalid=1
|
|
117
|
+
;;
|
|
118
|
+
'*** '*)
|
|
119
|
+
# Unknown apply_patch control header: never silently skip a path-shaped
|
|
120
|
+
# operation that a newer patch grammar may introduce.
|
|
121
|
+
invalid=1
|
|
122
|
+
;;
|
|
123
|
+
*) ;;
|
|
124
|
+
esac
|
|
125
|
+
done <<<"$patch"
|
|
126
|
+
|
|
127
|
+
if [ "$invalid" -ne 0 ] || [ "$begun" -ne 1 ] || [ "$ended" -ne 1 ] || [ "$header_count" -eq 0 ]; then
|
|
128
|
+
return 2
|
|
129
|
+
fi
|
|
130
|
+
printf '%s\n' "${records[@]}"
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
# toolu_normalize_edit_records INPUT TOOL_NAME
|
|
134
|
+
# 0: emitted one or more records
|
|
135
|
+
# 1: TOOL_NAME is not an edit tool
|
|
136
|
+
# 2: edit payload is malformed
|
|
137
|
+
# 3: normalization dependency is unavailable
|
|
138
|
+
toolu_normalize_edit_records() {
|
|
139
|
+
local raw_input="$1" edit_tool="$2" path operation patch
|
|
140
|
+
toolu_is_edit_tool "$edit_tool" || return 1
|
|
141
|
+
command -v jq >/dev/null 2>&1 || return 3
|
|
142
|
+
|
|
143
|
+
if [ "$edit_tool" = apply_patch ]; then
|
|
144
|
+
patch=$(jq -er '.tool_input.command | strings' <<<"$raw_input" 2>/dev/null) || return 2
|
|
145
|
+
_toolu_apply_patch_records "$patch"
|
|
146
|
+
return $?
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
path=$(jq -er '.tool_input.file_path // .tool_input.path // .tool_input.target_file | strings' \
|
|
150
|
+
<<<"$raw_input" 2>/dev/null) || return 2
|
|
151
|
+
_toolu_patch_path_valid "$path" || return 2
|
|
152
|
+
case "$edit_tool" in
|
|
153
|
+
Write) operation="write" ;;
|
|
154
|
+
*) operation="update" ;;
|
|
155
|
+
esac
|
|
156
|
+
_toolu_edit_record "$path" "$operation"
|
|
157
|
+
}
|