@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,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "python-quality",
|
|
3
|
+
"version": "6.8.0",
|
|
4
|
+
"description": "Python PostToolUse quality checks registered into the toolu hook engine.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Falconiere Barbosa",
|
|
7
|
+
"email": "hello@falconiere.io"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/falconiere/toolu",
|
|
10
|
+
"repository": "https://github.com/falconiere/toolu.git",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"claude-code",
|
|
14
|
+
"python",
|
|
15
|
+
"quality",
|
|
16
|
+
"hooks"
|
|
17
|
+
],
|
|
18
|
+
"dependencies": [
|
|
19
|
+
{
|
|
20
|
+
"name": "toolu",
|
|
21
|
+
"marketplace": "toolu"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# python-quality
|
|
2
|
+
|
|
3
|
+
Python `PostToolUse` quality checks registered into the toolu hook engine.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
/plugin install python-quality@toolu
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires the `toolu` plugin.
|
|
12
|
+
|
|
13
|
+
## What it provides
|
|
14
|
+
|
|
15
|
+
Every Python file the agent edits is checked on the spot, contributing to toolu's quality gate. The checks (assembled from ordered `hooks/concerns/` fragments into one module at `SessionStart`) are static-only — the gate never invokes `ruff`, `pylint`, or any other linter:
|
|
16
|
+
|
|
17
|
+
- File / function line limits (config-driven).
|
|
18
|
+
- No suppression: bare `except:`, one-line `except ...: pass`, blanket `# noqa`, bare `# type: ignore`.
|
|
19
|
+
- Colocated `test_*.py` next to every module it tests.
|
|
20
|
+
- No mocks in tests (`unittest.mock`/`mock`/`pytest_mock` imports — covering `MagicMock` — plus `mocker`/`monkeypatch` fixtures).
|
|
21
|
+
- Docstring checks on public functions and classes.
|
|
22
|
+
|
|
23
|
+
The fragments register into the core toolu dispatcher and run only while this plugin is installed — uninstall it and the Python rules vanish, fail-closed.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Codex lacks a plugin-dependency manifest field. Warn when this dependent
|
|
3
|
+
# plugin is installed without the toolu core, using Codex's authoritative list.
|
|
4
|
+
set -u
|
|
5
|
+
|
|
6
|
+
[ "${TOOLU_HOST_OVERRIDE:-}" != claude ] || exit 0
|
|
7
|
+
[ -n "${PLUGIN_ROOT:-}" ] || exit 0
|
|
8
|
+
command -v codex >/dev/null 2>&1 || exit 0
|
|
9
|
+
command -v jq >/dev/null 2>&1 || exit 0
|
|
10
|
+
|
|
11
|
+
installed=$(codex plugin list --json 2>/dev/null) || exit 0
|
|
12
|
+
jq -e 'any(.installed[]?; .pluginId == "toolu@toolu" and
|
|
13
|
+
((has("installed") | not) or (.installed == true)) and
|
|
14
|
+
((has("enabled") | not) or (.enabled == true)))' \
|
|
15
|
+
<<<"$installed" >/dev/null 2>&1 && exit 0
|
|
16
|
+
|
|
17
|
+
jq -n '{hookSpecificOutput:{
|
|
18
|
+
hookEventName:"SessionStart",
|
|
19
|
+
additionalContext:"WARN: this plugin requires the toolu core. Install it first with: codex plugin add toolu@toolu"
|
|
20
|
+
}}'
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Post-tool check: Python quality rules.
|
|
3
|
+
# Project-agnostic: no-op outside Python projects or when python3 is missing.
|
|
4
|
+
#
|
|
5
|
+
# Inputs (from parent dispatcher post-tools/mod.sh, via `export`):
|
|
6
|
+
# $tool_name - name of the tool being invoked
|
|
7
|
+
# $input - raw JSON payload on stdin
|
|
8
|
+
# $PROJECT_ROOT - repository root
|
|
9
|
+
|
|
10
|
+
: "${tool_name:=}"
|
|
11
|
+
: "${input:=}"
|
|
12
|
+
: "${PROJECT_ROOT:=$(pwd)}"
|
|
13
|
+
|
|
14
|
+
# Core lib comes from the toolu dispatcher via TOOLU_LIB_DIR (set by
|
|
15
|
+
# plugins/toolu/hooks/post-tools/mod.sh before registry dispatch). Outside
|
|
16
|
+
# that pipeline there is no relative path to it — fail SOFT: a quality check
|
|
17
|
+
# must never break a tool call by erroring.
|
|
18
|
+
[ -n "${TOOLU_LIB_DIR:-}" ] && [ -f "$TOOLU_LIB_DIR/detect.sh" ] || exit 0
|
|
19
|
+
# shellcheck source=../../../toolu/hooks/lib/detect.sh
|
|
20
|
+
. "$TOOLU_LIB_DIR/detect.sh"
|
|
21
|
+
# Threshold resolver (defaults + project/native overrides). Soft if absent.
|
|
22
|
+
# shellcheck source=../../../toolu/hooks/lib/quality-config.sh
|
|
23
|
+
[ -f "$TOOLU_LIB_DIR/quality-config.sh" ] && . "$TOOLU_LIB_DIR/quality-config.sh"
|
|
24
|
+
# Multi-slot gate writer (entries keyed by file — one hook's failure no longer
|
|
25
|
+
# clobbers another's). Soft if absent: fallbacks below keep the legacy
|
|
26
|
+
# single-slot behavior when the toolu lib predates gate-file.sh.
|
|
27
|
+
# shellcheck source=../../../toolu/hooks/lib/gate-file.sh
|
|
28
|
+
[ -f "$TOOLU_LIB_DIR/gate-file.sh" ] && . "$TOOLU_LIB_DIR/gate-file.sh"
|
|
29
|
+
command -v gate_record_failure >/dev/null 2>&1 || gate_record_failure() {
|
|
30
|
+
jq -n --arg reason "$4" --arg source "$3" --arg file "$2" --arg violations "$5" \
|
|
31
|
+
--arg updatedAt "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
32
|
+
'{status: "failing", reason: $reason, source: $source, file: $file,
|
|
33
|
+
violations: $violations, updatedAt: $updatedAt}' > "$1"
|
|
34
|
+
}
|
|
35
|
+
command -v gate_clear_file >/dev/null 2>&1 || gate_clear_file() {
|
|
36
|
+
[ -f "$1" ] || return 0
|
|
37
|
+
local _src _file
|
|
38
|
+
_src=$(jq -r '.source // ""' "$1" 2>/dev/null || echo "")
|
|
39
|
+
_file=$(jq -r '.file // ""' "$1" 2>/dev/null || echo "")
|
|
40
|
+
if [ "$_src" = "$3" ] && [ "$_file" = "$2" ]; then
|
|
41
|
+
jq -n --arg source "$3" --arg updatedAt "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
42
|
+
'{status: "passing", source: $source, updatedAt: $updatedAt}' > "$1"
|
|
43
|
+
fi
|
|
44
|
+
}
|
|
45
|
+
command -v python_max_file_lines >/dev/null 2>&1 || python_max_file_lines() { echo "${DEFAULT_PYTHON_MAX_FILE_LINES:-400}"; }
|
|
46
|
+
command -v python_max_fn_lines >/dev/null 2>&1 || python_max_fn_lines() { echo "${DEFAULT_PYTHON_MAX_FN_LINES:-50}"; }
|
|
47
|
+
# Boolean flag reader (e.g. noMocks) — falls back to the caller's default when
|
|
48
|
+
# quality-config.sh predates this reader.
|
|
49
|
+
command -v quality_flag >/dev/null 2>&1 || quality_flag() { printf '%s' "$3"; }
|
|
50
|
+
# count_python_code_lines comes from detect.sh (sourced above) — no fallback needed.
|
|
51
|
+
|
|
52
|
+
# Load the merged config ONCE so TOOLU_CFG_LOADED sticks for the threshold
|
|
53
|
+
# lookups below — each runs in a $(...) subshell that inherits it and skips
|
|
54
|
+
# re-merging (otherwise every wrapper re-spawns the jq merge).
|
|
55
|
+
command -v toolu_load_config >/dev/null 2>&1 && toolu_load_config 2>/dev/null || true
|
|
56
|
+
|
|
57
|
+
[ "$(detect_python)" = "python" ] || exit 0
|
|
58
|
+
command -v python3 >/dev/null 2>&1 || exit 0
|
|
59
|
+
command -v jq >/dev/null 2>&1 || exit 0
|
|
60
|
+
|
|
61
|
+
fp_from_input=""
|
|
62
|
+
if [[ "$tool_name" == "Write" || "$tool_name" == "Edit" || "$tool_name" == "MultiEdit" ]]; then
|
|
63
|
+
fp_from_input=$(echo "$input" | jq -r '.tool_input.path // .tool_input.file_path // .tool_input.target_file // empty' 2>/dev/null || echo "")
|
|
64
|
+
fi
|
|
65
|
+
FILE_PATH="${CLAUDE_FILE_PATHS:-$fp_from_input}"
|
|
66
|
+
|
|
67
|
+
# Deleted files and move sources cannot be linted after apply_patch completes,
|
|
68
|
+
# but their path-owned quality failure must not remain active forever.
|
|
69
|
+
EDIT_OPERATION="${TOOLU_EDIT_OPERATION:-$(echo "$input" | jq -r '.tool_input.toolu_edit_operation // ""' 2>/dev/null || echo "")}"
|
|
70
|
+
EDIT_MOVED_TO="${TOOLU_EDIT_MOVED_TO:-$(echo "$input" | jq -r '.tool_input.toolu_edit_moved_to // ""' 2>/dev/null || echo "")}"
|
|
71
|
+
if [[ "$EDIT_OPERATION" == delete || -n "$EDIT_MOVED_TO" ]]; then
|
|
72
|
+
if [[ -n "$FILE_PATH" && "$FILE_PATH" =~ \.py$ ]]; then
|
|
73
|
+
GATE_FILE="$(toolu_project_state_root "$PROJECT_ROOT")/quality-gate-status.json"
|
|
74
|
+
gate_clear_file "$GATE_FILE" "$FILE_PATH" "python-quality-hook"
|
|
75
|
+
fi
|
|
76
|
+
exit 0
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
[[ -z "$FILE_PATH" || ! -f "$FILE_PATH" ]] && exit 0
|
|
80
|
+
[[ ! "$FILE_PATH" =~ \.py$ ]] && exit 0
|
|
81
|
+
|
|
82
|
+
MESSAGES=""
|
|
83
|
+
add_error() {
|
|
84
|
+
MESSAGES="${MESSAGES}${1}"$'\n'
|
|
85
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
PY_MAX_FILE=$(python_max_file_lines)
|
|
2
|
+
LINE_COUNT=$(count_python_code_lines "$FILE_PATH")
|
|
3
|
+
if [[ "$LINE_COUNT" -gt "$PY_MAX_FILE" ]]; then
|
|
4
|
+
add_error "File exceeds ${PY_MAX_FILE}-line limit: $FILE_PATH ($LINE_COUNT code lines, blanks/comments excluded) — split into submodules. Override via lang.python.maxFileLines."
|
|
5
|
+
fi
|
|
6
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Test placement: colocated `test_*.py` / `*_test.py` convention (module-sibling,
|
|
2
|
+
# not a separate tests/ tree — the TS __tests__/ colocation principle, applied to
|
|
3
|
+
# Python's own naming idiom). Two rules:
|
|
4
|
+
# (a) a test-bearing file (top-level `def test_...`/`async def test_...`, or a
|
|
5
|
+
# pytest/unittest import) whose basename does not follow the convention.
|
|
6
|
+
# (b) a `test_*.py` sitting in a directory with no non-test .py sibling — it
|
|
7
|
+
# has nothing to be "colocated" with.
|
|
8
|
+
# `conftest.py` (pytest's fixture-sharing file — imports pytest but is not
|
|
9
|
+
# itself a test) and `__init__.py` (package marker, never a test) are exempt
|
|
10
|
+
# from both rules.
|
|
11
|
+
_py_base="$(basename "$FILE_PATH")"
|
|
12
|
+
_is_python_test_file=0
|
|
13
|
+
case "$_py_base" in
|
|
14
|
+
test_*.py|*_test.py) _is_python_test_file=1 ;;
|
|
15
|
+
esac
|
|
16
|
+
|
|
17
|
+
case "$_py_base" in
|
|
18
|
+
conftest.py|__init__.py) ;;
|
|
19
|
+
*)
|
|
20
|
+
_has_test_marker=0
|
|
21
|
+
grep -qE '^(async[[:space:]]+)?def[[:space:]]+test_[A-Za-z0-9_]*[[:space:]]*\(' "$FILE_PATH" 2>/dev/null && _has_test_marker=1
|
|
22
|
+
grep -qE '^(import|from)[[:space:]]+(pytest|unittest)\b' "$FILE_PATH" 2>/dev/null && _has_test_marker=1
|
|
23
|
+
if [[ "$_has_test_marker" -eq 1 && "$_is_python_test_file" -eq 0 ]]; then
|
|
24
|
+
add_error "Test-bearing file not named test_*.py or *_test.py: $FILE_PATH — rename to follow the colocated test convention"
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
if [[ "$_py_base" == test_*.py ]]; then
|
|
28
|
+
_py_dir="$(dirname "$FILE_PATH")"
|
|
29
|
+
if ! find "$_py_dir" -maxdepth 1 -type f -name '*.py' \
|
|
30
|
+
! -name 'test_*.py' ! -name '*_test.py' ! -name 'conftest.py' ! -name '__init__.py' \
|
|
31
|
+
2>/dev/null | grep -q .; then
|
|
32
|
+
add_error "Test not co-located with a module: $FILE_PATH — colocate test_*.py next to the module it tests"
|
|
33
|
+
fi
|
|
34
|
+
fi
|
|
35
|
+
;;
|
|
36
|
+
esac
|
|
37
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
|
|
2
|
+
# Forbidden suppression markers — fix the underlying issue in code, never
|
|
3
|
+
# silence the tool. Four zero-false-positive forms, one awk pass (mirrors
|
|
4
|
+
# 40-unsafe.sh's single-process style). No \b anywhere — BSD awk (macOS) has
|
|
5
|
+
# none; explicit boundary characters are used instead:
|
|
6
|
+
# - bare `except:` (no exception type — swallows EVERYTHING,
|
|
7
|
+
# including KeyboardInterrupt/SystemExit)
|
|
8
|
+
# - one-line `except ...: pass` (catches and silently discards)
|
|
9
|
+
# - blanket `# noqa` (no `:CODE` suffix — suppresses every rule on
|
|
10
|
+
# the line, not a scoped `# noqa: E501`)
|
|
11
|
+
# - blanket `# type: ignore` (no `[code]` suffix — suppresses every mypy
|
|
12
|
+
# error on the line, not a scoped
|
|
13
|
+
# `# type: ignore[arg-type]`)
|
|
14
|
+
# Scoped forms are distinguished from blanket ones by a SECOND regex on the
|
|
15
|
+
# same line rather than a lookahead (POSIX/BSD awk has none).
|
|
16
|
+
SUPPRESSION_HITS=$(awk '
|
|
17
|
+
/^[[:space:]]*except[[:space:]]*:[[:space:]]*(#.*)?$/ {
|
|
18
|
+
print NR": bare except: (swallows everything)"; next
|
|
19
|
+
}
|
|
20
|
+
/^[[:space:]]*except[^:]*:[[:space:]]*pass[[:space:]]*(#.*)?$/ {
|
|
21
|
+
print NR": one-line except ...: pass (silently discarded)"; next
|
|
22
|
+
}
|
|
23
|
+
/#[ \t]*noqa/ && !/#[ \t]*noqa[ \t]*:[ \t]*[A-Za-z0-9]/ {
|
|
24
|
+
print NR": blanket # noqa (no :CODE suffix)"; next
|
|
25
|
+
}
|
|
26
|
+
/#[ \t]*type:[ \t]*ignore/ && !/#[ \t]*type:[ \t]*ignore\[/ {
|
|
27
|
+
print NR": blanket # type: ignore (no [code] suffix)"; next
|
|
28
|
+
}
|
|
29
|
+
' "$FILE_PATH" 2>/dev/null | head -5)
|
|
30
|
+
if [[ -n "$SUPPRESSION_HITS" ]]; then
|
|
31
|
+
add_error "Forbidden suppression in $FILE_PATH — remove it and fix the underlying issue. Scoped forms (except SpecificError:, # noqa: E501, # type: ignore[arg-type]) are fine; blanket ones are not:"$'\n'"${SUPPRESSION_HITS}"
|
|
32
|
+
fi
|
|
33
|
+
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
PY_MAX_FN=$(python_max_fn_lines)
|
|
2
|
+
# Indent-based span (Python has no braces): a `def `/`async def ` line opens a
|
|
3
|
+
# span at its OWN indent; the span closes at the next nonblank line whose
|
|
4
|
+
# indent is <= the def's (decorators above are never counted — they sit at
|
|
5
|
+
# the same indent as the def and simply close the PRIOR span before the def
|
|
6
|
+
# line opens a new one). Only code lines count (blanks and full-line `#`
|
|
7
|
+
# comments are skipped), matching count_python_code_lines. Known limitation:
|
|
8
|
+
# a line continuation or multi-line string containing a dedented look-alike
|
|
9
|
+
# line is not tokenized here — same heuristic class as the rust brace counter.
|
|
10
|
+
LONG_PY_FUNCS=$(awk -v max="$PY_MAX_FN" '
|
|
11
|
+
function lead(s, i,c,n) {
|
|
12
|
+
n = 0
|
|
13
|
+
for (i = 1; i <= length(s); i++) {
|
|
14
|
+
c = substr(s, i, 1)
|
|
15
|
+
if (c == " " || c == "\t") n++
|
|
16
|
+
else break
|
|
17
|
+
}
|
|
18
|
+
return n
|
|
19
|
+
}
|
|
20
|
+
{
|
|
21
|
+
raw = $0
|
|
22
|
+
trimmed = raw
|
|
23
|
+
sub(/^[ \t]+/, "", trimmed)
|
|
24
|
+
blank = (length(trimmed) == 0)
|
|
25
|
+
if (infn && !blank) {
|
|
26
|
+
if (lead(raw) <= def_indent) {
|
|
27
|
+
if (count > max) printf "%s:%d (%d lines)\n", name, start, count
|
|
28
|
+
infn = 0
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (!infn && raw ~ /^[ \t]*(async[ \t]+)?def[ \t]/) {
|
|
32
|
+
infn = 1
|
|
33
|
+
def_indent = lead(raw)
|
|
34
|
+
start = NR
|
|
35
|
+
match(trimmed, /def[ \t]+[A-Za-z_][A-Za-z0-9_]*/)
|
|
36
|
+
name = substr(trimmed, RSTART, RLENGTH)
|
|
37
|
+
sub(/^def[ \t]+/, "", name)
|
|
38
|
+
count = 0
|
|
39
|
+
}
|
|
40
|
+
if (infn && !blank && substr(trimmed, 1, 1) != "#") count++
|
|
41
|
+
}
|
|
42
|
+
END {
|
|
43
|
+
if (infn && count > max) printf "%s:%d (%d lines)\n", name, start, count
|
|
44
|
+
}
|
|
45
|
+
' "$FILE_PATH" 2>/dev/null)
|
|
46
|
+
if [[ -n "$LONG_PY_FUNCS" ]]; then
|
|
47
|
+
add_error "Function too long in $FILE_PATH (>${PY_MAX_FN} lines) — extract helpers."$'\n'"${LONG_PY_FUNCS}"
|
|
48
|
+
fi
|
|
49
|
+
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# --- No-mocks (zero tolerance): forbid mock frameworks — real data/fixtures
|
|
2
|
+
# only. Test files ONLY (test_*.py / *_test.py / conftest.py, per the shared
|
|
3
|
+
# $_is_python_test_file set in 20-tests.sh) — a no-op elsewhere, mirroring
|
|
4
|
+
# rust's test-scope split in 70-no-mocks.sh. Same ast-grep-scan pattern: ONE
|
|
5
|
+
# process, severity warning (a matching rule and a clean run both exit 0 —
|
|
6
|
+
# only a genuine crash is distinguishable from a clean pass), jq flattens
|
|
7
|
+
# every hit to its def/import line, add_error on a match or on tool breakage.
|
|
8
|
+
#
|
|
9
|
+
# Opt-out: lang.python.noMocks (default true) via quality_flag.
|
|
10
|
+
if [[ "${_is_python_test_file:-0}" -eq 1 ]] \
|
|
11
|
+
&& [[ "$(quality_flag python noMocks true)" == "true" ]] \
|
|
12
|
+
&& command -v ast-grep >/dev/null 2>&1; then
|
|
13
|
+
NM_RULES=$(cat <<'PY_NOMOCK_RULES'
|
|
14
|
+
id: mock-import
|
|
15
|
+
language: python
|
|
16
|
+
severity: warning
|
|
17
|
+
message: 'mock import'
|
|
18
|
+
rule:
|
|
19
|
+
any:
|
|
20
|
+
- kind: import_statement
|
|
21
|
+
has: {kind: dotted_name, regex: '^(unittest\.mock|mock|pytest_mock)$'}
|
|
22
|
+
- kind: import_from_statement
|
|
23
|
+
has: {field: module_name, kind: dotted_name, regex: '^(unittest\.mock|mock)$'}
|
|
24
|
+
- kind: import_from_statement
|
|
25
|
+
all:
|
|
26
|
+
- has: {field: module_name, kind: dotted_name, regex: '^unittest$'}
|
|
27
|
+
- has: {field: name, kind: dotted_name, regex: '^mock$'}
|
|
28
|
+
---
|
|
29
|
+
id: mocker-param
|
|
30
|
+
language: python
|
|
31
|
+
severity: warning
|
|
32
|
+
message: 'mocker/monkeypatch fixture parameter'
|
|
33
|
+
rule:
|
|
34
|
+
kind: function_definition
|
|
35
|
+
has:
|
|
36
|
+
field: parameters
|
|
37
|
+
has: {kind: identifier, regex: '^(mocker|monkeypatch)$'}
|
|
38
|
+
PY_NOMOCK_RULES
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
nm_err_file="$(mktemp)"
|
|
42
|
+
nm_json=$(ast-grep scan --inline-rules "$NM_RULES" --json "$FILE_PATH" 2>"$nm_err_file")
|
|
43
|
+
nm_rc=$?
|
|
44
|
+
nm_grep_failed=0
|
|
45
|
+
nm_fail_detail=""
|
|
46
|
+
nm_lines=""
|
|
47
|
+
if [[ "$nm_rc" -ne 0 || -s "$nm_err_file" ]]; then
|
|
48
|
+
nm_grep_failed=1
|
|
49
|
+
nm_stderr_first=$(head -n 1 "$nm_err_file" 2>/dev/null | cut -c1-200)
|
|
50
|
+
nm_fail_detail="ast-grep exit ${nm_rc}${nm_stderr_first:+: $nm_stderr_first}"
|
|
51
|
+
elif [[ -z "$(printf '%s' "$nm_json" | tr -d '[:space:]')" ]]; then
|
|
52
|
+
nm_grep_failed=1
|
|
53
|
+
nm_fail_detail="ast-grep exited 0 with empty output (expected at least the JSON array \"[]\")"
|
|
54
|
+
elif ! nm_lines=$(printf '%s' "$nm_json" | jq -r '
|
|
55
|
+
.[] | "\(.ruleId)\t\(.range.start.line + 1): \(.lines | split("\n")[0])"
|
|
56
|
+
' 2>/dev/null); then
|
|
57
|
+
nm_grep_failed=1
|
|
58
|
+
nm_fail_detail="ast-grep exited 0 but its output did not parse as the documented JSON array"
|
|
59
|
+
nm_lines=""
|
|
60
|
+
fi
|
|
61
|
+
rm -f "$nm_err_file"
|
|
62
|
+
|
|
63
|
+
IMPORT_HITS=$(printf '%s\n' "$nm_lines" | awk -F'\t' '$1=="mock-import"{print $2}' | head -5)
|
|
64
|
+
if [[ -n "$IMPORT_HITS" ]]; then
|
|
65
|
+
add_error "no-mocks: mock import in $FILE_PATH — write against real data/fixtures instead"$'\n'"${IMPORT_HITS}"
|
|
66
|
+
fi
|
|
67
|
+
PARAM_HITS=$(printf '%s\n' "$nm_lines" | awk -F'\t' '$1=="mocker-param"{print $2}' | head -5)
|
|
68
|
+
if [[ -n "$PARAM_HITS" ]]; then
|
|
69
|
+
add_error "no-mocks: mocker/monkeypatch fixture parameter in $FILE_PATH — write against real data/fixtures instead"$'\n'"${PARAM_HITS}"
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
if [[ "$nm_grep_failed" -ne 0 ]]; then
|
|
73
|
+
add_error "ast-grep failed while scanning $FILE_PATH — ${nm_fail_detail:-unknown error}; no-mocks rules could not be verified. Fix the tool/file and re-edit"
|
|
74
|
+
fi
|
|
75
|
+
fi
|
|
76
|
+
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# --- Docs (soft advisory, never blocks) ---
|
|
2
|
+
# A top-level, non-underscore-prefixed `def`/`class` should open with a
|
|
3
|
+
# docstring. Advisory only — collected separately from MESSAGES so it never
|
|
4
|
+
# sets the failing gate. Column-0 (top-level) only: indented defs/methods are
|
|
5
|
+
# exempt (noise control — every private helper method would otherwise need
|
|
6
|
+
# one). The signature may span multiple lines; it is tracked forward to the
|
|
7
|
+
# line that actually ends with `:` before the docstring check runs. Known
|
|
8
|
+
# limitation: a `def`/`class`-shaped line quoted inside a module docstring at
|
|
9
|
+
# column 0 would be mis-read as a real definition — same class of heuristic
|
|
10
|
+
# limit as the other line-based scans in this plugin.
|
|
11
|
+
DOC_ADVISORY=""
|
|
12
|
+
_undoc=$(awk -v sq="'" '
|
|
13
|
+
function isblank(s, t) { t = s; gsub(/^[ \t]+|[ \t]+$/, "", t); return (length(t) == 0) }
|
|
14
|
+
function stripcomment(s, t) { t = s; sub(/[ \t]#.*$/, "", t); return t }
|
|
15
|
+
function endswithcolon(s, t) {
|
|
16
|
+
t = stripcomment(s); gsub(/[ \t]+$/, "", t)
|
|
17
|
+
return (length(t) > 0 && substr(t, length(t), 1) == ":")
|
|
18
|
+
}
|
|
19
|
+
function opens_docstring(s, t, p, body) {
|
|
20
|
+
t = s; gsub(/^[ \t]+/, "", t)
|
|
21
|
+
p = 0
|
|
22
|
+
if (t ~ /^[rRfF]/) p = 1
|
|
23
|
+
body = substr(t, p + 1, 3)
|
|
24
|
+
return (body == "\"\"\"" || body == sq sq sq)
|
|
25
|
+
}
|
|
26
|
+
{
|
|
27
|
+
line = $0
|
|
28
|
+
if (waitbody) {
|
|
29
|
+
if (isblank(line)) next
|
|
30
|
+
if (!opens_docstring(line)) printf "%d: %s\n", defline, defname
|
|
31
|
+
waitbody = 0; next
|
|
32
|
+
}
|
|
33
|
+
if (insig) {
|
|
34
|
+
if (endswithcolon(line)) { insig = 0; waitbody = 1 }
|
|
35
|
+
next
|
|
36
|
+
}
|
|
37
|
+
if (line ~ /^(async[ \t]+def|def|class)[ \t]+[A-Za-z_][A-Za-z0-9_]*/) {
|
|
38
|
+
tmp = line
|
|
39
|
+
sub(/^(async[ \t]+def|def|class)[ \t]+/, "", tmp)
|
|
40
|
+
match(tmp, /^[A-Za-z_][A-Za-z0-9_]*/)
|
|
41
|
+
name = substr(tmp, RSTART, RLENGTH)
|
|
42
|
+
if (substr(name, 1, 1) != "_") {
|
|
43
|
+
defline = NR; defname = name
|
|
44
|
+
if (endswithcolon(line)) { waitbody = 1 } else { insig = 1 }
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
' "$FILE_PATH" 2>/dev/null | head -3)
|
|
49
|
+
if [[ -n "$_undoc" ]]; then
|
|
50
|
+
DOC_ADVISORY="Public def/class missing a docstring in $FILE_PATH — add a concise one:"$'\n'"${_undoc}"
|
|
51
|
+
fi
|
|
52
|
+
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# --- Output ---
|
|
2
|
+
|
|
3
|
+
if [[ -n "$MESSAGES" ]]; then
|
|
4
|
+
# Record this file's violation in the gate status file (entry keyed by file
|
|
5
|
+
# path — does not clobber failures recorded for other files or hooks).
|
|
6
|
+
GATE_DIR="$(toolu_project_state_root "$PROJECT_ROOT")"
|
|
7
|
+
GATE_FILE="$GATE_DIR/quality-gate-status.json"
|
|
8
|
+
mkdir -p "$GATE_DIR"
|
|
9
|
+
gate_record_failure "$GATE_FILE" "$FILE_PATH" "python-quality-hook" \
|
|
10
|
+
"Post-edit Python quality violation(s) detected" "$MESSAGES"
|
|
11
|
+
|
|
12
|
+
jq -n --arg ctx "$MESSAGES" '{
|
|
13
|
+
"hookSpecificOutput": {
|
|
14
|
+
"hookEventName": "PostToolUse",
|
|
15
|
+
"additionalContext": ("QUALITY VIOLATION — fix before proceeding:\n" + $ctx)
|
|
16
|
+
}
|
|
17
|
+
}'
|
|
18
|
+
else
|
|
19
|
+
# Clear this file's entry now that it passes (only if this hook set it).
|
|
20
|
+
# Other files' failures stay recorded; the gate only flips to passing when
|
|
21
|
+
# no entry remains.
|
|
22
|
+
GATE_FILE="$(toolu_project_state_root "$PROJECT_ROOT")/quality-gate-status.json"
|
|
23
|
+
gate_clear_file "$GATE_FILE" "$FILE_PATH" "python-quality-hook"
|
|
24
|
+
|
|
25
|
+
# Docs advisory (non-blocking — no gate write).
|
|
26
|
+
if [[ -n "$DOC_ADVISORY" ]]; then
|
|
27
|
+
jq -n --arg ctx "$DOC_ADVISORY" '{
|
|
28
|
+
"hookSpecificOutput": {
|
|
29
|
+
"hookEventName": "PostToolUse",
|
|
30
|
+
"additionalContext": $ctx
|
|
31
|
+
}
|
|
32
|
+
}'
|
|
33
|
+
fi
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
exit 0
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"SessionStart": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "startup|resume|clear|compact",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/register.sh"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"type": "command",
|
|
13
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/check-toolu.sh"
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# SessionStart registry sync for the python-quality plugin.
|
|
3
|
+
#
|
|
4
|
+
# Assembles hooks/concerns/[0-9][0-9]-*.sh (ordered preamble → concern partials
|
|
5
|
+
# → finalize) into ONE runtime module in the toolu registry
|
|
6
|
+
# (${TOOLU_CONFIG_DIR:-${CLAUDE_CONFIG_DIR:-$HOME/.claude}}/toolu/post-tools.d/) under the
|
|
7
|
+
# namespaced filename python-quality@toolu__python-quality.sh. The fragments are
|
|
8
|
+
# partials of a single script; concatenating them in numeric order yields the
|
|
9
|
+
# one module that runs as a single process doing one gate write (authored
|
|
10
|
+
# fragment-first — unlike rust/ts-quality there was never a pre-split monolith). Prunes any stale entry bearing OUR prefix, and
|
|
11
|
+
# clears our own crashed tmp residue. Other plugins' entries are never touched.
|
|
12
|
+
#
|
|
13
|
+
# Silent on success (SessionStart stdout becomes context); errors are
|
|
14
|
+
# non-fatal — a failed sync means the registry copy is stale, not broken.
|
|
15
|
+
|
|
16
|
+
SPEC="python-quality@toolu"
|
|
17
|
+
OUT="python-quality.sh"
|
|
18
|
+
SELF_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
19
|
+
SRC_DIR="$SELF_DIR/concerns"
|
|
20
|
+
if [ -n "${TOOLU_CONFIG_DIR:-}" ]; then
|
|
21
|
+
CONFIG_ROOT="$TOOLU_CONFIG_DIR"
|
|
22
|
+
elif [ "${TOOLU_HOST_OVERRIDE:-}" = codex ] || { [ -z "${TOOLU_HOST_OVERRIDE:-}" ] && [ -n "${PLUGIN_ROOT:-}" ]; }; then
|
|
23
|
+
CONFIG_ROOT="${CODEX_HOME:-$HOME/.codex}"
|
|
24
|
+
else
|
|
25
|
+
CONFIG_ROOT="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
|
|
26
|
+
fi
|
|
27
|
+
REG_DIR="$CONFIG_ROOT/toolu/post-tools.d"
|
|
28
|
+
|
|
29
|
+
# Consume stdin so Claude Code's hook IPC never stalls.
|
|
30
|
+
cat > /dev/null 2>&1 || true
|
|
31
|
+
|
|
32
|
+
[ -d "$SRC_DIR" ] || exit 0
|
|
33
|
+
mkdir -p "$REG_DIR" 2>/dev/null || exit 0
|
|
34
|
+
|
|
35
|
+
# Clear OUR orphaned atomic-write residue from prior crashed runs. Age-gated
|
|
36
|
+
# so a concurrent SessionStart's in-flight tmp (seconds old) is never clobbered.
|
|
37
|
+
find "$REG_DIR" -maxdepth 1 -name "${SPEC}__*.sh.tmp.*" -mmin +1 -delete 2>/dev/null
|
|
38
|
+
|
|
39
|
+
# Assemble: concatenate the numeric-prefixed fragments in lexical order into one
|
|
40
|
+
# module. A newline guard between fragments keeps a fragment that lacks a
|
|
41
|
+
# trailing newline from running into the next. __tests__/ and any non-prefixed
|
|
42
|
+
# .sh are skipped by the glob.
|
|
43
|
+
dst="$REG_DIR/${SPEC}__${OUT}"
|
|
44
|
+
tmp="${dst}.tmp.$$"
|
|
45
|
+
: > "$tmp"
|
|
46
|
+
for src in "$SRC_DIR"/[0-9][0-9]-*.sh; do
|
|
47
|
+
[ -f "$src" ] || continue
|
|
48
|
+
cat "$src" >> "$tmp" 2>/dev/null || { rm -f "$tmp"; exit 0; }
|
|
49
|
+
printf '\n' >> "$tmp" 2>/dev/null || { rm -f "$tmp"; exit 0; }
|
|
50
|
+
done
|
|
51
|
+
if [ -s "$tmp" ] && { [ ! -f "$dst" ] || ! cmp -s "$tmp" "$dst"; }; then
|
|
52
|
+
mv "$tmp" "$dst" 2>/dev/null || rm -f "$tmp"
|
|
53
|
+
else
|
|
54
|
+
rm -f "$tmp"
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
# Prune: remove any entry bearing OUR prefix that is not the current assembled
|
|
58
|
+
# module (e.g. per-concern files left by an older version). Never glob outside
|
|
59
|
+
# our own spec prefix.
|
|
60
|
+
for old in "$REG_DIR/${SPEC}__"*.sh; do
|
|
61
|
+
[ -f "$old" ] || continue
|
|
62
|
+
[ "$(basename "$old")" = "${SPEC}__${OUT}" ] || rm -f "$old"
|
|
63
|
+
done
|
|
64
|
+
|
|
65
|
+
exit 0
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rust-quality",
|
|
3
|
+
"version": "6.8.0",
|
|
4
|
+
"description": "Rust PostToolUse quality checks registered into the toolu hook engine.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Falconiere Barbosa",
|
|
7
|
+
"email": "hello@falconiere.io"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/falconiere/toolu",
|
|
10
|
+
"repository": "https://github.com/falconiere/toolu.git",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"claude-code",
|
|
14
|
+
"rust",
|
|
15
|
+
"quality",
|
|
16
|
+
"hooks"
|
|
17
|
+
],
|
|
18
|
+
"dependencies": [
|
|
19
|
+
{
|
|
20
|
+
"name": "toolu",
|
|
21
|
+
"marketplace": "toolu"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# rust-quality
|
|
2
|
+
|
|
3
|
+
Rust `PostToolUse` quality checks registered into the toolu hook engine.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
/plugin install rust-quality@toolu
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires the `toolu` plugin.
|
|
12
|
+
|
|
13
|
+
## What it provides
|
|
14
|
+
|
|
15
|
+
Every Rust file the agent edits is checked on the spot, contributing to toolu's quality gate. The checks (assembled from ordered `hooks/concerns/` fragments into one module at `SessionStart`):
|
|
16
|
+
|
|
17
|
+
- File / function / `impl` line limits (config-driven).
|
|
18
|
+
- No `.unwrap()` / `.expect()` — use `?` or `match`.
|
|
19
|
+
- No `unsafe` blocks.
|
|
20
|
+
- No `#[allow]` / `#[expect]` lint suppression — except a `tests/` file's file-level `#![allow(...)]` header, for the lints test code violates on purpose.
|
|
21
|
+
- Unit tests in a module-sibling `tests/` (wired by a bodyless `#[cfg(test)] mod tests;`, `#[path]`-attributed or not); crate-root `tests/` for integration. No inline test bodies in `src/`.
|
|
22
|
+
- Doc-comment checks on public items.
|
|
23
|
+
|
|
24
|
+
The fragments register into the core toolu dispatcher and run only while this plugin is installed — uninstall it and the Rust rules vanish, fail-closed.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Codex lacks a plugin-dependency manifest field. Warn when this dependent
|
|
3
|
+
# plugin is installed without the toolu core, using Codex's authoritative list.
|
|
4
|
+
set -u
|
|
5
|
+
|
|
6
|
+
[ "${TOOLU_HOST_OVERRIDE:-}" != claude ] || exit 0
|
|
7
|
+
[ -n "${PLUGIN_ROOT:-}" ] || exit 0
|
|
8
|
+
command -v codex >/dev/null 2>&1 || exit 0
|
|
9
|
+
command -v jq >/dev/null 2>&1 || exit 0
|
|
10
|
+
|
|
11
|
+
installed=$(codex plugin list --json 2>/dev/null) || exit 0
|
|
12
|
+
jq -e 'any(.installed[]?; .pluginId == "toolu@toolu" and
|
|
13
|
+
((has("installed") | not) or (.installed == true)) and
|
|
14
|
+
((has("enabled") | not) or (.enabled == true)))' \
|
|
15
|
+
<<<"$installed" >/dev/null 2>&1 && exit 0
|
|
16
|
+
|
|
17
|
+
jq -n '{hookSpecificOutput:{
|
|
18
|
+
hookEventName:"SessionStart",
|
|
19
|
+
additionalContext:"WARN: this plugin requires the toolu core. Install it first with: codex plugin add toolu@toolu"
|
|
20
|
+
}}'
|