@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,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: review
|
|
3
|
+
description: Pre-push review mirroring CI code-review@v8 (+ Jev) so first push stays clean. Reviews correctness, security, performance, test coverage, doc accuracy, and tight assertions, then records push-review state. Use before pushing a feature branch, when asked to review before push, or when pr-babysit needs a reviewer. Explicit — does NOT auto-fire on edits.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# toolu-review:review
|
|
7
|
+
|
|
8
|
+
A pre-push reviewer tuned to what this repo's CI Toolu Code Review action
|
|
9
|
+
(`falconiere/toolu-ghactions/code-review@v8`, Jev-enabled; posts as
|
|
10
|
+
`github-actions[bot]`) flags — run it locally so the verdict is clean on the
|
|
11
|
+
first push instead of bouncing low/nit findings back as rework.
|
|
12
|
+
|
|
13
|
+
## What it reviews
|
|
14
|
+
|
|
15
|
+
Review `git diff <base>...HEAD` against these dimensions. Every finding blocks
|
|
16
|
+
(the gate requires zero) — fix in code, do not suppress:
|
|
17
|
+
|
|
18
|
+
1. **Correctness** — logic, edge cases, error handling (no swallowed errors, no
|
|
19
|
+
`@ts-ignore`/`eslint-disable`/`#[allow]` papering over a real problem).
|
|
20
|
+
2. **Security** — input validation, injection, secrets, unsafe file/symlink ops.
|
|
21
|
+
3. **Performance** — hot paths (e.g. per-render/per-hook work), needless spawns.
|
|
22
|
+
4. **Test coverage for every NEW behavior** — a new code path without a colocated
|
|
23
|
+
real-data test is a finding. (The bot flagged a missing bats for an orphan
|
|
24
|
+
sweep on a prior PR — catch that class here.)
|
|
25
|
+
5. **Doc/comment accuracy** — comments must match behavior; e.g. no "one-time" on
|
|
26
|
+
a block that runs every invocation; no stale paths after a move.
|
|
27
|
+
6. **Tight test assertions** — assert the full identity, not a loose suffix
|
|
28
|
+
(`*/statusline/statusline.sh`, not `*/statusline.sh`).
|
|
29
|
+
7. **In-session migration WARNs** — a breaking change (moved path, removed symlink)
|
|
30
|
+
must surface an actionable in-session hint, not a silent failure later.
|
|
31
|
+
8. **Convention adherence** — follow `AGENTS.md` / related convention files the CI
|
|
32
|
+
action reads from the base ref (same bar as `code-review@v8`).
|
|
33
|
+
|
|
34
|
+
## How to run
|
|
35
|
+
|
|
36
|
+
**Commit the fix first, then review.** The gate binds `git diff <base>...HEAD` —
|
|
37
|
+
the *committed* diff. State recorded while a fix is still uncommitted describes
|
|
38
|
+
the pre-fix tree, so committing staleifies it and the push denies.
|
|
39
|
+
|
|
40
|
+
1. Resolve the diff: `git diff --no-color <base>...HEAD` (base = the push-review
|
|
41
|
+
gate's base; the helper below resolves it the same way).
|
|
42
|
+
2. Review every changed hunk against the checklist. Read surrounding code and
|
|
43
|
+
grep for usage before claiming a finding — no speculative nits.
|
|
44
|
+
3. Fix accepted findings in code, commit them, re-review until none remain.
|
|
45
|
+
4. Record the clean state for the push-review gate:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Codex
|
|
49
|
+
TOOLU_HOST_OVERRIDE=codex bash \
|
|
50
|
+
"${TOOLU_CONFIG_DIR:-${CODEX_HOME:-$HOME/.codex}}/toolu-review/write-state.sh" \
|
|
51
|
+
--findings-count 0 --reviewers '["toolu-review:review"]'
|
|
52
|
+
|
|
53
|
+
# Claude Code
|
|
54
|
+
TOOLU_HOST_OVERRIDE=claude bash \
|
|
55
|
+
"${TOOLU_CONFIG_DIR:-${CLAUDE_CONFIG_DIR:-$HOME/.claude}}/toolu-review/write-state.sh" \
|
|
56
|
+
--findings-count 0 --reviewers '["toolu-review:review"]'
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Pass `--branch <name>` on a detached checkout — pr-babysit's
|
|
60
|
+
`git worktree add --detach` + `git push origin HEAD:<name>` contract — so
|
|
61
|
+
the state file is keyed to the branch the push targets; the push-review gate
|
|
62
|
+
resolves that same branch from the refspec. Omitting it there fails with
|
|
63
|
+
"not on a branch (detached HEAD?)".
|
|
64
|
+
Pass `--repo <path>` when the reviewed checkout is not the session's cwd —
|
|
65
|
+
a worktree, say. The gate reads the state file under the **pushed repo's own
|
|
66
|
+
root**, so a file written anywhere else is invisible to it. `--repo` defaults
|
|
67
|
+
to the cwd's repo root and the script fails with "not inside a git repo" when
|
|
68
|
+
the path given is not one. `$STATE_DIR`, when set, overrides the directory for
|
|
69
|
+
the writer and the gate alike.
|
|
70
|
+
|
|
71
|
+
`write-state.sh` is published below the active host's explicit config root,
|
|
72
|
+
as shown above, by the plugin's SessionStart hook. Always pass the matching
|
|
73
|
+
host override in the same command: plugin-root variables are lifecycle
|
|
74
|
+
context and are not reliable in ordinary shell calls.
|
|
75
|
+
|
|
76
|
+
It computes the gate's exact `diff_sha`/`base`/`slug`, sets `review_round`
|
|
77
|
+
(1 for a new `diff_sha`, +1 only when rewriting at the same one — the gate
|
|
78
|
+
caps at 5 rounds on an unchanged diff), and writes
|
|
79
|
+
the active host's `<repo root>/.claude/tmp/push-review/` or
|
|
80
|
+
`<repo root>/.codex/tmp/push-review/` path atomically as
|
|
81
|
+
schema `version: 2`, including `reviewed_files` — auto-computed from
|
|
82
|
+
`git diff --name-only <base>...HEAD` (sorted, unique); pass
|
|
83
|
+
`--reviewed-files a.ts,b.rs` only if the review genuinely covered a
|
|
84
|
+
different path set than the one auto-detected. The gate denies unless
|
|
85
|
+
`reviewed_files` matches the diff's changed paths exactly. Harmless no-op
|
|
86
|
+
when the toolu push-review gate is not installed (the file goes unread).
|
|
87
|
+
|
|
88
|
+
If findings remain that you cannot fix (e.g. needs a human decision), record them
|
|
89
|
+
with `--findings-count <n> --findings '<json>'` instead of 0 — the gate will then
|
|
90
|
+
keep blocking the push, which is correct: open findings are not done.
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# write-state.sh — write the push-review state file the toolu push-review
|
|
3
|
+
# gate validates (<host-dir>/tmp/push-review/<branch-slug>.json).
|
|
4
|
+
#
|
|
5
|
+
# The review JUDGMENT (which findings exist) belongs to the caller — the
|
|
6
|
+
# `toolu-review:review` skill. This script does only the deterministic
|
|
7
|
+
# bookkeeping: compute the gate's diff_sha/base/slug, bump review_round, and
|
|
8
|
+
# write the JSON atomically.
|
|
9
|
+
#
|
|
10
|
+
# The base / diff_sha / slug / reviewed_files recipes are MIRRORS of the gate
|
|
11
|
+
# in plugins/toolu/hooks/pre-tools/modules/push-review.sh — the cross-check in
|
|
12
|
+
# scripts/__tests__/state-writer.bats asserts they produce identical SHAs (and,
|
|
13
|
+
# for reviewed_files, an identical file list), so a drift in either recipe
|
|
14
|
+
# fails CI. This mirror is deliberate (cross-plugin sourcing is barred here —
|
|
15
|
+
# see spec Non-Goal 7): keep it local rather than sourcing toolu's diff-sha.sh.
|
|
16
|
+
# Harmless no-op when the toolu gate is not installed (the file is simply
|
|
17
|
+
# never read).
|
|
18
|
+
#
|
|
19
|
+
# Usage: write-state.sh --findings-count N [--reviewers JSON] [--findings JSON]
|
|
20
|
+
# [--repo PATH] [--reviewed-files a.ts,b.rs]
|
|
21
|
+
# --findings-count (required) integer; the gate allows push only when 0.
|
|
22
|
+
# --reviewers (default ["toolu-review:review"]) JSON array.
|
|
23
|
+
# --findings (default []) JSON array of {path,severity,text}.
|
|
24
|
+
# --repo (default: the cwd's repo root) the checkout being reviewed.
|
|
25
|
+
# Pass the worktree path when reviewing a worktree from a
|
|
26
|
+
# session rooted elsewhere — the gate reads the state file
|
|
27
|
+
# under the pushed repo's own root, so writing it anywhere
|
|
28
|
+
# else is invisible to the gate.
|
|
29
|
+
# --branch (default: the checked-out branch) the branch the push
|
|
30
|
+
# targets. Required on a detached checkout — pr-babysit's
|
|
31
|
+
# `git worktree add --detach` + `push origin HEAD:<branch>`
|
|
32
|
+
# contract — and must name an existing refs/heads or
|
|
33
|
+
# refs/remotes/origin ref. On an attached checkout it must
|
|
34
|
+
# equal the checked-out branch: the gate keys the state
|
|
35
|
+
# file by that branch and would never read a re-keyed one.
|
|
36
|
+
# --reviewed-files (default: auto-computed) comma-separated list of paths,
|
|
37
|
+
# overriding the auto-computed `git diff --name-only
|
|
38
|
+
# <base>...HEAD` file list. The gate (schema v2) requires
|
|
39
|
+
# `reviewed_files` (sorted, unique) to equal the diff's
|
|
40
|
+
# changed paths exactly, so only override this when the
|
|
41
|
+
# review genuinely covered a different path set than the
|
|
42
|
+
# one auto-detected here.
|
|
43
|
+
# Prints the state file path on success.
|
|
44
|
+
set -o pipefail
|
|
45
|
+
|
|
46
|
+
findings_count=""
|
|
47
|
+
reviewers='["toolu-review:review"]'
|
|
48
|
+
findings='[]'
|
|
49
|
+
repo=""
|
|
50
|
+
branch_arg=""
|
|
51
|
+
reviewed_files_arg=""
|
|
52
|
+
reviewed_files_set=0
|
|
53
|
+
while [ $# -gt 0 ]; do
|
|
54
|
+
case "$1" in
|
|
55
|
+
--findings-count) findings_count="$2"; shift 2 ;;
|
|
56
|
+
--reviewers) reviewers="$2"; shift 2 ;;
|
|
57
|
+
--findings) findings="$2"; shift 2 ;;
|
|
58
|
+
--repo) repo="$2"; shift 2 ;;
|
|
59
|
+
--branch) branch_arg="$2"; shift 2 ;;
|
|
60
|
+
--reviewed-files) reviewed_files_arg="$2"; reviewed_files_set=1; shift 2 ;;
|
|
61
|
+
*) echo "write-state.sh: unknown arg: $1" >&2; exit 2 ;;
|
|
62
|
+
esac
|
|
63
|
+
done
|
|
64
|
+
|
|
65
|
+
[ -n "$findings_count" ] || { echo "write-state.sh: --findings-count required" >&2; exit 2; }
|
|
66
|
+
[[ "$findings_count" =~ ^[0-9]+$ ]] || { echo "write-state.sh: --findings-count must be an integer" >&2; exit 2; }
|
|
67
|
+
command -v jq >/dev/null 2>&1 || { echo "write-state.sh: jq required" >&2; exit 2; }
|
|
68
|
+
command -v git >/dev/null 2>&1 || { echo "write-state.sh: git required" >&2; exit 2; }
|
|
69
|
+
|
|
70
|
+
# Repo root — MIRROR of push_target_root's fallback chain. The gate resolves
|
|
71
|
+
# the root from the push command (`git -C <path> push`), so the writer must be
|
|
72
|
+
# able to name the same checkout or the two disagree in a worktree.
|
|
73
|
+
repo_root=$(git -C "${repo:-.}" rev-parse --show-toplevel 2>/dev/null || echo "")
|
|
74
|
+
[ -n "$repo_root" ] || { echo "write-state.sh: ${repo:-$(pwd)} is not inside a git repo" >&2; exit 1; }
|
|
75
|
+
|
|
76
|
+
# Branch — MIRROR of push_target_branch (lib/detect.sh): the checked-out
|
|
77
|
+
# branch when attached; on a detached checkout the branch the push targets,
|
|
78
|
+
# which the caller names with --branch because the writer never sees the push
|
|
79
|
+
# command. Validated against real refs so a typo cannot key a state file the
|
|
80
|
+
# gate will never read.
|
|
81
|
+
branch=$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
|
|
82
|
+
if [ -z "$branch" ] || [ "$branch" = "HEAD" ]; then
|
|
83
|
+
[ -n "$branch_arg" ] || { echo "write-state.sh: not on a branch (detached HEAD?) — pass --branch <name> naming the branch the push targets (git push origin HEAD:<name>)" >&2; exit 1; }
|
|
84
|
+
git check-ref-format --branch "$branch_arg" >/dev/null 2>&1 \
|
|
85
|
+
|| { echo "write-state.sh: --branch '$branch_arg' is not a valid branch name" >&2; exit 1; }
|
|
86
|
+
if ! git -C "$repo_root" show-ref --verify --quiet "refs/heads/$branch_arg" \
|
|
87
|
+
&& ! git -C "$repo_root" show-ref --verify --quiet "refs/remotes/origin/$branch_arg"; then
|
|
88
|
+
echo "write-state.sh: unknown branch '$branch_arg' (no refs/heads/$branch_arg or refs/remotes/origin/$branch_arg)" >&2; exit 1
|
|
89
|
+
fi
|
|
90
|
+
branch="$branch_arg"
|
|
91
|
+
elif [ -n "$branch_arg" ] && [ "$branch_arg" != "$branch" ]; then
|
|
92
|
+
echo "write-state.sh: --branch '$branch_arg' does not match the checked-out branch '$branch'; the gate keys the state file by the checked-out branch" >&2; exit 1
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
# Base branch — mirror of detect_base_branch's core; $PUSH_REVIEW_BASE matches
|
|
96
|
+
# the gate's own override.
|
|
97
|
+
base="${PUSH_REVIEW_BASE:-}"
|
|
98
|
+
if [ -z "$base" ]; then
|
|
99
|
+
base=$(git -C "$repo_root" symbolic-ref --quiet refs/remotes/origin/HEAD 2>/dev/null | sed 's#refs/remotes/origin/##')
|
|
100
|
+
[ -n "$base" ] || base="main"
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
# diff_sha — MIRROR of push-review.sh's current_diff_sha (cross-checked by
|
|
104
|
+
# state-writer.bats).
|
|
105
|
+
diff_sha=$(git -C "$repo_root" diff --no-color "${base}...HEAD" 2>/dev/null | git -C "$repo_root" hash-object --stdin 2>/dev/null || echo "")
|
|
106
|
+
[ -n "$diff_sha" ] || { echo "write-state.sh: git diff ${base}...HEAD failed" >&2; exit 1; }
|
|
107
|
+
|
|
108
|
+
# Refuse the empty-blob SHA: an empty diff against base means nothing diverged
|
|
109
|
+
# (or a force-reset). The push-review gate treats this sentinel as never-matching
|
|
110
|
+
# and denies, so writing a state file with it just yields a confusing
|
|
111
|
+
# "review recorded" → "diff is empty" deny. Fail early with an actionable message.
|
|
112
|
+
EMPTY_BLOB_SHA="e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"
|
|
113
|
+
if [ "$diff_sha" = "$EMPTY_BLOB_SHA" ]; then
|
|
114
|
+
echo "write-state.sh: diff against ${base} is empty; nothing to review yet" >&2
|
|
115
|
+
exit 1
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
# reviewed_files — MIRROR of the gate's `changed_sorted` (push-review.sh):
|
|
119
|
+
# auto-computed from the real diff's changed paths (sorted, unique) unless
|
|
120
|
+
# --reviewed-files overrides it. The gate denies unless this list equals its
|
|
121
|
+
# own `git diff --name-only` computation exactly, so both branches emit a
|
|
122
|
+
# sorted-unique JSON array of strings.
|
|
123
|
+
if [ "$reviewed_files_set" -eq 1 ]; then
|
|
124
|
+
reviewed_files_json=$(printf '%s' "$reviewed_files_arg" \
|
|
125
|
+
| jq -R 'split(",") | map(select(length > 0)) | unique')
|
|
126
|
+
else
|
|
127
|
+
reviewed_files_json=$(git -C "$repo_root" diff --no-color "${base}...HEAD" --name-only 2>/dev/null \
|
|
128
|
+
| sort -u | jq -R -s 'split("\n") | map(select(length > 0))')
|
|
129
|
+
fi
|
|
130
|
+
# jq always prints at least "[]" on success, so empty stdout here only
|
|
131
|
+
# happens on a real git/jq failure — surface it rather than writing a state
|
|
132
|
+
# file with a silently-wrong (and gate-denying) reviewed_files.
|
|
133
|
+
[ -n "$reviewed_files_json" ] || { echo "write-state.sh: failed to compute reviewed_files" >&2; exit 1; }
|
|
134
|
+
|
|
135
|
+
# slug — MIRROR of push-review.sh:_branch_slug.
|
|
136
|
+
slug=$(echo "$branch" | tr '/' '_' | tr -cd 'a-zA-Z0-9_-')
|
|
137
|
+
[ -n "$slug" ] || slug="_default"
|
|
138
|
+
|
|
139
|
+
# state_dir — MIRROR of the gate's resolution, including its $STATE_DIR override.
|
|
140
|
+
# The plugin is self-contained, so mirror the core adapter's project-dir choice.
|
|
141
|
+
project_dir="${TOOLU_PROJECT_CONFIG_DIRNAME:-}"
|
|
142
|
+
if [ -z "$project_dir" ]; then
|
|
143
|
+
if [ "${TOOLU_HOST_OVERRIDE:-}" = codex ] || { [ -z "${TOOLU_HOST_OVERRIDE:-}" ] && [ -n "${PLUGIN_ROOT:-}" ]; }; then
|
|
144
|
+
project_dir=.codex
|
|
145
|
+
else
|
|
146
|
+
project_dir=.claude
|
|
147
|
+
fi
|
|
148
|
+
fi
|
|
149
|
+
state_dir="${STATE_DIR:-$repo_root/$project_dir/tmp/push-review}"
|
|
150
|
+
state_file="$state_dir/${slug}.json"
|
|
151
|
+
mkdir -p "$state_dir" || { echo "write-state.sh: cannot create $state_dir" >&2; exit 1; }
|
|
152
|
+
|
|
153
|
+
# review_round counts rewrites AT THE SAME diff_sha, so the gate's MAX_ROUNDS
|
|
154
|
+
# cap means "reviewers keep finding issues in code that never changed". A
|
|
155
|
+
# changed diff restarts at 1: the previous rounds judged different code, and
|
|
156
|
+
# nothing ever resets the counter after a push, so carrying it forward made the
|
|
157
|
+
# cap terminal for any branch that lived long enough.
|
|
158
|
+
prev_round=0
|
|
159
|
+
if [ -f "$state_file" ]; then
|
|
160
|
+
prev_sha=$(jq -r '.diff_sha // ""' "$state_file" 2>/dev/null || echo "")
|
|
161
|
+
if [ "$prev_sha" = "$diff_sha" ]; then
|
|
162
|
+
prev_round=$(jq -r '.review_round // 0' "$state_file" 2>/dev/null || echo 0)
|
|
163
|
+
[[ "$prev_round" =~ ^[0-9]+$ ]] || prev_round=0
|
|
164
|
+
fi
|
|
165
|
+
fi
|
|
166
|
+
review_round=$((prev_round + 1))
|
|
167
|
+
|
|
168
|
+
reviewed_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
169
|
+
|
|
170
|
+
tmp="$state_file.tmp.$$"
|
|
171
|
+
if ! jq -n \
|
|
172
|
+
--arg branch "$branch" \
|
|
173
|
+
--arg diff_sha "$diff_sha" \
|
|
174
|
+
--arg base "$base" \
|
|
175
|
+
--arg reviewed_at "$reviewed_at" \
|
|
176
|
+
--argjson reviewers "$reviewers" \
|
|
177
|
+
--argjson findings_count "$findings_count" \
|
|
178
|
+
--argjson findings "$findings" \
|
|
179
|
+
--argjson review_round "$review_round" \
|
|
180
|
+
--argjson reviewed_files "$reviewed_files_json" \
|
|
181
|
+
'{version:2, branch:$branch, diff_sha:$diff_sha, base_branch:$base,
|
|
182
|
+
reviewed_at:$reviewed_at, reviewers:$reviewers,
|
|
183
|
+
findings_count:$findings_count, findings:$findings, review_round:$review_round,
|
|
184
|
+
reviewed_files:$reviewed_files}' \
|
|
185
|
+
> "$tmp"; then
|
|
186
|
+
rm -f "$tmp"; echo "write-state.sh: jq failed (bad --reviewers/--findings/--reviewed-files JSON?)" >&2; exit 1
|
|
187
|
+
fi
|
|
188
|
+
mv "$tmp" "$state_file" || { rm -f "$tmp"; echo "write-state.sh: atomic mv failed" >&2; exit 1; }
|
|
189
|
+
echo "$state_file"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ts-quality",
|
|
3
|
+
"version": "6.8.0",
|
|
4
|
+
"description": "TypeScript 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
|
+
"typescript",
|
|
15
|
+
"quality",
|
|
16
|
+
"hooks"
|
|
17
|
+
],
|
|
18
|
+
"dependencies": [
|
|
19
|
+
{
|
|
20
|
+
"name": "toolu",
|
|
21
|
+
"marketplace": "toolu"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# ts-quality
|
|
2
|
+
|
|
3
|
+
TypeScript `PostToolUse` quality checks registered into the toolu hook engine.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
/plugin install ts-quality@toolu
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires the `toolu` plugin.
|
|
12
|
+
|
|
13
|
+
## What it provides
|
|
14
|
+
|
|
15
|
+
Every TypeScript 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 line limits (config-driven).
|
|
18
|
+
- No `../` relative imports — use the `@/` alias (only enforced when the project defines a `@/*` path alias in its tsconfig).
|
|
19
|
+
- No `as` type assertions and no hand-rolled type guards — use a type guard / Zod schema.
|
|
20
|
+
- Tests colocated in a flat `__tests__/`.
|
|
21
|
+
- Duplicate-type detection across the tree, plus "does too much" / too-many-factories heuristics.
|
|
22
|
+
- No `console` left in, no lint suppression, no thrown literals; React hooks/props, toast, and error-handling AST checks.
|
|
23
|
+
|
|
24
|
+
The fragments register into the core toolu dispatcher and run only while this plugin is installed — uninstall it and the TypeScript 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,123 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Post-tool check: TypeScript / TSX quality rules
|
|
3
|
+
# Lightweight file-level checks (fast, no external tool invocations).
|
|
4
|
+
# Project-agnostic: no-op outside TS projects; package-manager driven.
|
|
5
|
+
#
|
|
6
|
+
# Inputs (from parent dispatcher post-tools/mod.sh, via `export`):
|
|
7
|
+
# $tool_name - name of the tool being invoked
|
|
8
|
+
# $input - raw JSON payload on stdin
|
|
9
|
+
# $PROJECT_ROOT - repository root
|
|
10
|
+
|
|
11
|
+
: "${tool_name:=}"
|
|
12
|
+
: "${input:=}"
|
|
13
|
+
: "${PROJECT_ROOT:=$(pwd)}"
|
|
14
|
+
|
|
15
|
+
# Core lib comes from the toolu dispatcher via TOOLU_LIB_DIR (set by
|
|
16
|
+
# plugins/toolu/hooks/post-tools/mod.sh before registry dispatch). Outside
|
|
17
|
+
# that pipeline there is no relative path to it — fail SOFT: a quality check
|
|
18
|
+
# must never break a tool call by erroring.
|
|
19
|
+
[ -n "${TOOLU_LIB_DIR:-}" ] && [ -f "$TOOLU_LIB_DIR/detect.sh" ] || exit 0
|
|
20
|
+
# shellcheck source=../../../toolu/hooks/lib/detect.sh
|
|
21
|
+
. "$TOOLU_LIB_DIR/detect.sh"
|
|
22
|
+
# Threshold resolver (defaults + project/native overrides). Soft if absent.
|
|
23
|
+
# shellcheck source=../../../toolu/hooks/lib/quality-config.sh
|
|
24
|
+
[ -f "$TOOLU_LIB_DIR/quality-config.sh" ] && . "$TOOLU_LIB_DIR/quality-config.sh"
|
|
25
|
+
# Multi-slot gate writer (entries keyed by file — one hook's failure no longer
|
|
26
|
+
# clobbers another's). Soft if absent: fallbacks below keep the legacy
|
|
27
|
+
# single-slot behavior when the toolu lib predates gate-file.sh.
|
|
28
|
+
# shellcheck source=../../../toolu/hooks/lib/gate-file.sh
|
|
29
|
+
[ -f "$TOOLU_LIB_DIR/gate-file.sh" ] && . "$TOOLU_LIB_DIR/gate-file.sh"
|
|
30
|
+
command -v gate_record_failure >/dev/null 2>&1 || gate_record_failure() {
|
|
31
|
+
jq -n --arg reason "$4" --arg source "$3" --arg file "$2" --arg violations "$5" \
|
|
32
|
+
--arg updatedAt "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
33
|
+
'{status: "failing", reason: $reason, source: $source, file: $file,
|
|
34
|
+
violations: $violations, updatedAt: $updatedAt}' > "$1"
|
|
35
|
+
}
|
|
36
|
+
command -v gate_clear_file >/dev/null 2>&1 || gate_clear_file() {
|
|
37
|
+
[ -f "$1" ] || return 0
|
|
38
|
+
local _src _file
|
|
39
|
+
_src=$(jq -r '.source // ""' "$1" 2>/dev/null || echo "")
|
|
40
|
+
_file=$(jq -r '.file // ""' "$1" 2>/dev/null || echo "")
|
|
41
|
+
if [ "$_src" = "$3" ] && [ "$_file" = "$2" ]; then
|
|
42
|
+
jq -n --arg source "$3" --arg updatedAt "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
43
|
+
'{status: "passing", source: $source, updatedAt: $updatedAt}' > "$1"
|
|
44
|
+
fi
|
|
45
|
+
}
|
|
46
|
+
command -v ts_max_file_lines >/dev/null 2>&1 || ts_max_file_lines() { echo "${DEFAULT_TS_MAX_FILE_LINES:-300}"; }
|
|
47
|
+
command -v ts_max_fn_lines >/dev/null 2>&1 || ts_max_fn_lines() { echo "${DEFAULT_TS_MAX_FN_LINES:-60}"; }
|
|
48
|
+
command -v ts_max_file_lines_source >/dev/null 2>&1 || ts_max_file_lines_source() { printf 'default'; }
|
|
49
|
+
# Boolean flag reader (e.g. noMocks) — falls back to the caller's default when
|
|
50
|
+
# quality-config.sh predates this reader.
|
|
51
|
+
command -v quality_flag >/dev/null 2>&1 || quality_flag() { printf '%s' "$3"; }
|
|
52
|
+
# count_code_lines comes from detect.sh (sourced above) — no fallback needed.
|
|
53
|
+
|
|
54
|
+
# Load the merged config ONCE in this shell so TOOLU_CFG_LOADED sticks for
|
|
55
|
+
# the threshold lookups below — each runs in a $(...) subshell that inherits it
|
|
56
|
+
# and skips re-merging (otherwise every wrapper re-spawns the jq merge).
|
|
57
|
+
command -v toolu_load_config >/dev/null 2>&1 && toolu_load_config 2>/dev/null || true
|
|
58
|
+
|
|
59
|
+
# Exit early if this isn't a TypeScript project.
|
|
60
|
+
[ "$(detect_ts)" = "ts" ] || exit 0
|
|
61
|
+
|
|
62
|
+
# Exit if no package manager is detected — we cannot recommend a typecheck command.
|
|
63
|
+
pm="$(detect_node_pm)"
|
|
64
|
+
[ -n "$pm" ] || exit 0
|
|
65
|
+
command -v "$pm" >/dev/null 2>&1 || exit 0
|
|
66
|
+
|
|
67
|
+
# Resolve the project's typecheck command per package manager.
|
|
68
|
+
typecheck_cmd() {
|
|
69
|
+
case "$1" in
|
|
70
|
+
bun) echo "bun run typecheck" ;;
|
|
71
|
+
pnpm) echo "pnpm -w typecheck" ;;
|
|
72
|
+
yarn) echo "yarn typecheck" ;;
|
|
73
|
+
npm) echo "npm run typecheck" ;;
|
|
74
|
+
*) echo "$1 run typecheck" ;;
|
|
75
|
+
esac
|
|
76
|
+
}
|
|
77
|
+
TYPECHECK_CMD="$(typecheck_cmd "$pm")"
|
|
78
|
+
|
|
79
|
+
command -v jq >/dev/null 2>&1 || exit 0
|
|
80
|
+
|
|
81
|
+
fp_from_input=""
|
|
82
|
+
if [[ "$tool_name" == "Write" || "$tool_name" == "Edit" || "$tool_name" == "MultiEdit" ]]; then
|
|
83
|
+
fp_from_input=$(echo "$input" | jq -r '.tool_input.path // .tool_input.file_path // .tool_input.target_file // empty' 2>/dev/null || echo "")
|
|
84
|
+
fi
|
|
85
|
+
FILE_PATH="${CLAUDE_FILE_PATHS:-$fp_from_input}"
|
|
86
|
+
|
|
87
|
+
# A deletion (or the source side of a move) no longer exists to lint, but any
|
|
88
|
+
# gate entry previously owned by this path must be removed. The normalized
|
|
89
|
+
# dispatcher exports these fields and also embeds them in tool_input so the
|
|
90
|
+
# assembled module remains independently testable.
|
|
91
|
+
EDIT_OPERATION="${TOOLU_EDIT_OPERATION:-$(echo "$input" | jq -r '.tool_input.toolu_edit_operation // ""' 2>/dev/null || echo "")}"
|
|
92
|
+
EDIT_MOVED_TO="${TOOLU_EDIT_MOVED_TO:-$(echo "$input" | jq -r '.tool_input.toolu_edit_moved_to // ""' 2>/dev/null || echo "")}"
|
|
93
|
+
if [[ "$EDIT_OPERATION" == delete || -n "$EDIT_MOVED_TO" ]]; then
|
|
94
|
+
if [[ -n "$FILE_PATH" && "$FILE_PATH" =~ \.(ts|tsx)$ ]]; then
|
|
95
|
+
GATE_FILE="$(toolu_project_state_root "$PROJECT_ROOT")/quality-gate-status.json"
|
|
96
|
+
gate_clear_file "$GATE_FILE" "$FILE_PATH" "ts-quality-hook"
|
|
97
|
+
fi
|
|
98
|
+
exit 0
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
[[ -z "$FILE_PATH" || ! -f "$FILE_PATH" ]] && exit 0
|
|
102
|
+
[[ ! "$FILE_PATH" =~ \.(ts|tsx)$ ]] && exit 0
|
|
103
|
+
|
|
104
|
+
# Skip files inside git linked worktrees — quality state is for the main checkout only.
|
|
105
|
+
# One git call, one dirname: --git-dir + --git-common-dir come back on two lines.
|
|
106
|
+
_file_dir="$(dirname "$FILE_PATH")"
|
|
107
|
+
_file_git_dir=""; _file_common_dir=""
|
|
108
|
+
{ IFS= read -r _file_git_dir; IFS= read -r _file_common_dir; } < <(
|
|
109
|
+
git -C "$_file_dir" rev-parse --path-format=absolute --git-dir --git-common-dir 2>/dev/null
|
|
110
|
+
)
|
|
111
|
+
_file_git_dir="${_file_git_dir%/}"
|
|
112
|
+
_file_common_dir="${_file_common_dir%/}"
|
|
113
|
+
if [[ -n "$_file_git_dir" && -n "$_file_common_dir" && "$_file_git_dir" != "$_file_common_dir" ]]; then
|
|
114
|
+
exit 0
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
MESSAGES=""
|
|
118
|
+
# Separator is a REAL newline. It used to be a literal backslash-n inside double
|
|
119
|
+
# quotes, which jq then encoded as "\\n" — so every multi-line violation reached
|
|
120
|
+
# the agent as one run-on line with a visible \n in it.
|
|
121
|
+
add_error() {
|
|
122
|
+
MESSAGES="${MESSAGES}${1}"$'\n'
|
|
123
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# --- Existing checks ---
|
|
2
|
+
|
|
3
|
+
# The "../ import → @/ alias" guidance is only actionable when the project
|
|
4
|
+
# actually defines a `@/*` path alias. In a repo that uses plain NodeNext
|
|
5
|
+
# relative imports with no alias configured, the recommendation cannot be
|
|
6
|
+
# followed, so flagging `../` there is a false positive that blocks every edit
|
|
7
|
+
# to such a file. Gate the check on the alias existing: walk up from the edited
|
|
8
|
+
# file to PROJECT_ROOT for a tsconfig whose compilerOptions.paths declares a
|
|
9
|
+
# `@/`-prefixed key. Tolerant of a missing / JSONC / oddly-shaped tsconfig (jq
|
|
10
|
+
# failure → treat as "no alias", so we never emit advice the project can't act
|
|
11
|
+
# on).
|
|
12
|
+
_ts_has_at_alias() {
|
|
13
|
+
local dir root cfg
|
|
14
|
+
dir="$(cd "$(dirname "$FILE_PATH")" 2>/dev/null && pwd)" || return 1
|
|
15
|
+
root="$(cd "${PROJECT_ROOT:-/}" 2>/dev/null && pwd)" || root=""
|
|
16
|
+
while [ -n "$dir" ]; do
|
|
17
|
+
for cfg in "$dir/tsconfig.json" "$dir/tsconfig.base.json"; do
|
|
18
|
+
[ -f "$cfg" ] || continue
|
|
19
|
+
if jq -e '[(.compilerOptions.paths // {}) | keys[] | select(startswith("@/"))] | length > 0' "$cfg" >/dev/null 2>&1; then
|
|
20
|
+
return 0
|
|
21
|
+
fi
|
|
22
|
+
done
|
|
23
|
+
[ "$dir" = "$root" ] && break
|
|
24
|
+
[ "$dir" = "/" ] && break
|
|
25
|
+
dir="$(dirname "$dir")"
|
|
26
|
+
done
|
|
27
|
+
return 1
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if grep -q 'from ["'"'"']\.\./' "$FILE_PATH" 2>/dev/null && _ts_has_at_alias; then
|
|
31
|
+
add_error "Forbidden ../ import in $FILE_PATH — use @/ alias"
|
|
32
|
+
fi
|
|
33
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
AS_LINES=$(grep -nE '\)[[:space:]]+as[[:space:]]+[a-zA-Z]|\bas[[:space:]]+any\b|\bas[[:space:]]+unknown\b|[a-zA-Z>][[:space:]]+as[[:space:]]+[A-Z]|[a-zA-Z>][[:space:]]+as[[:space:]]+(string|number|boolean|object|symbol|bigint|never|undefined|null|void)\b' "$FILE_PATH" 2>/dev/null \
|
|
2
|
+
| grep -vE '^[0-9]+:[[:space:]]*//' \
|
|
3
|
+
| grep -vE '\bas[[:space:]]+const\b' \
|
|
4
|
+
| grep -vE '\bimport\b|^[0-9]+:[[:space:]]*export[[:space:]]*(type[[:space:]]+)?\{' \
|
|
5
|
+
| head -5)
|
|
6
|
+
if [[ -n "$AS_LINES" ]]; then
|
|
7
|
+
add_error "Forbidden 'as' type assertion in $FILE_PATH — use type guards or Zod"$'\n'"${AS_LINES}"
|
|
8
|
+
fi
|
|
9
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# E2E specs follow a framework layout (e2e/specs/<feature>/*.spec.ts), not the
|
|
2
|
+
# unit-test __tests__/ co-location model — exempt them from these rules.
|
|
3
|
+
if [[ "$FILE_PATH" =~ \.(test|spec)\.(ts|tsx)$ && "$FILE_PATH" != */e2e/* ]]; then
|
|
4
|
+
if [[ "$FILE_PATH" != */__tests__/* ]]; then
|
|
5
|
+
add_error "Test file outside __tests__/: $FILE_PATH — move to sibling __tests__/ directory"
|
|
6
|
+
else
|
|
7
|
+
_after_tests="${FILE_PATH##*__tests__/}"
|
|
8
|
+
if [[ "$_after_tests" == */* ]]; then
|
|
9
|
+
_subdir="${_after_tests%%/*}"
|
|
10
|
+
if [[ "$_subdir" != "fixtures" && "$_subdir" != "helpers" && "$_subdir" != "utils" ]]; then
|
|
11
|
+
add_error "Test nested in __tests__/ subdirectory: $FILE_PATH — keep __tests__/ flat (only fixtures/helpers/utils subdirs allowed; no mocks/ — tests must exercise real data)"
|
|
12
|
+
fi
|
|
13
|
+
fi
|
|
14
|
+
# `%` (shortest suffix) resolves to the NEAREST __tests__, matching the
|
|
15
|
+
# `##` (last __tests__) used for the nested-subdir check above; `%%` would
|
|
16
|
+
# pick the OUTERMOST __tests__ and mis-validate a doubly-nested path.
|
|
17
|
+
_tests_dir="${FILE_PATH%__tests__/*}__tests__"
|
|
18
|
+
_parent_dir=$(dirname "$_tests_dir")
|
|
19
|
+
if ! find "$_parent_dir" -maxdepth 1 -type f \( -name "*.ts" -o -name "*.tsx" \) \
|
|
20
|
+
! -name "*.test.*" ! -name "*.spec.*" ! -name "*.d.ts" 2>/dev/null | grep -q . \
|
|
21
|
+
&& ! find "$_parent_dir" -maxdepth 1 -type d ! -name "__tests__" ! -name "." 2>/dev/null | grep -q .; then
|
|
22
|
+
add_error "Test not co-located with source: $FILE_PATH — __tests__/ must be at the same level as the code it tests"
|
|
23
|
+
fi
|
|
24
|
+
fi
|
|
25
|
+
fi
|
|
26
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Resolve the limit AND where it came from in one pass (avoids running the
|
|
2
|
+
# override/native lookups twice). Contract: ts_max_file_lines_resolved prints
|
|
3
|
+
# exactly "<int> <source>" where <source> is ONE token (override|native|default)
|
|
4
|
+
# — the two-field `read` below relies on it; a multi-word source would spill
|
|
5
|
+
# into TS_MAX_SRC and break the case arms.
|
|
6
|
+
TS_MAX_FILE=""; TS_MAX_SRC="default"
|
|
7
|
+
if command -v ts_max_file_lines_resolved >/dev/null 2>&1; then
|
|
8
|
+
read -r TS_MAX_FILE TS_MAX_SRC <<<"$(ts_max_file_lines_resolved)"
|
|
9
|
+
else
|
|
10
|
+
TS_MAX_FILE=$(ts_max_file_lines)
|
|
11
|
+
fi
|
|
12
|
+
[ -n "$TS_MAX_FILE" ] || TS_MAX_FILE="${DEFAULT_TS_MAX_FILE_LINES:-300}"
|
|
13
|
+
[ -n "$TS_MAX_SRC" ] || TS_MAX_SRC="default" # empty read -> case must still pick a branch
|
|
14
|
+
TS_LINE_COUNT=$(count_code_lines "$FILE_PATH")
|
|
15
|
+
if [[ "$TS_LINE_COUNT" -gt "$TS_MAX_FILE" ]]; then
|
|
16
|
+
_split_hint="split into smaller modules"
|
|
17
|
+
_linter="$(detect_ts_linter)"
|
|
18
|
+
if [ -n "$_linter" ]; then
|
|
19
|
+
# Only claim the linter owns the limit when the limit truly came from its
|
|
20
|
+
# config; if a linter is present but its config isn't machine-readable
|
|
21
|
+
# (e.g. .eslintrc.cjs / eslint.config.js), say so instead of contradicting.
|
|
22
|
+
case "$TS_MAX_SRC" in
|
|
23
|
+
native) _split_hint="$_split_hint ($_linter enforces this max-lines limit)" ;;
|
|
24
|
+
default)
|
|
25
|
+
# Biome has no max-lines rule at all, so "unparsed config form" would be
|
|
26
|
+
# misleading for it — say so plainly. ESLint/oxc do have max-lines, so
|
|
27
|
+
# there the limit genuinely could have come from an unreadable config.
|
|
28
|
+
if [ "$_linter" = "biome" ]; then
|
|
29
|
+
_split_hint="$_split_hint (biome has no max-lines equivalent — gate uses the ${TS_MAX_FILE}-line default)"
|
|
30
|
+
else
|
|
31
|
+
_split_hint="$_split_hint ($_linter is present but the gate's limit didn't come from its config (unparsed config form or a per-glob override) — gate uses the ${TS_MAX_FILE}-line default; align them)"
|
|
32
|
+
fi
|
|
33
|
+
;;
|
|
34
|
+
esac
|
|
35
|
+
fi
|
|
36
|
+
_approx=""
|
|
37
|
+
has_unterminated_block "$FILE_PATH" && _approx=" (size approximated — an unterminated /* or a string containing /* may be affecting the count)"
|
|
38
|
+
add_error "TS file exceeds ${TS_MAX_FILE}-line limit: $FILE_PATH ($TS_LINE_COUNT code lines, blanks/comments excluded)${_approx} — $_split_hint"
|
|
39
|
+
fi
|
|
40
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
TS_MAX_FN=$(ts_max_fn_lines)
|
|
2
|
+
# Brace-depth fn-length counter — mirrors rust-quality.sh so TS and Rust measure
|
|
3
|
+
# the same way. The end of a function is found by counting brace depth from the
|
|
4
|
+
# fn's own line (single-line "..."/'...'/`...` string and quote char-literal
|
|
5
|
+
# contents stripped first), so a method inside a class/object is measured to ITS
|
|
6
|
+
# own indented close, an inner if/for/switch close can't end the range early, and
|
|
7
|
+
# a column-0 `}` inside a template literal no longer cuts the range short — the
|
|
8
|
+
# bugs the old `^}` col-0 marker had. Three start forms: `function` declarations,
|
|
9
|
+
# `const NAME = (…) =>` / `= function` expressions (params may span lines), and
|
|
10
|
+
# class/object methods `name(…) {` (control-flow keywords excluded so their
|
|
11
|
+
# blocks aren't mistaken for methods). Known limitation (same as Rust): a brace
|
|
12
|
+
# inside a MULTI-LINE template/string still leaks into the count — an unbalanced
|
|
13
|
+
# one leaves the fn unmeasured (fail-open), never falsely flagged short.
|
|
14
|
+
LONG_FUNCS=$(awk -v max="$TS_MAX_FN" -v q="'" '
|
|
15
|
+
function strip(l) {
|
|
16
|
+
gsub(/\\"/, "", l) # escaped double quotes
|
|
17
|
+
gsub(/"[^"]*"/, "", l) # double-quoted string contents
|
|
18
|
+
gsub(q "[^" q "]*" q, "", l) # single-quoted string / char-literal contents
|
|
19
|
+
gsub(/`[^`]*`/, "", l) # single-line template-literal contents
|
|
20
|
+
return l
|
|
21
|
+
}
|
|
22
|
+
function fnstart() { infn=1; start=NR; name=$0; depth=0; opened=0 }
|
|
23
|
+
!infn {
|
|
24
|
+
s=strip($0)
|
|
25
|
+
if (s ~ /^[[:space:]]*(export[[:space:]]+)?(default[[:space:]]+)?(async[[:space:]]+)?function[ \t*]/) { fnstart() }
|
|
26
|
+
else if (s ~ /^[[:space:]]*(export[[:space:]]+)?(default[[:space:]]+)?const[[:space:]]+[A-Za-z_$][A-Za-z0-9_$]*[[:space:]]*=[[:space:]]*(async[[:space:]]+)?(\(|function[ \t(*])/) { fnstart() }
|
|
27
|
+
else if (s ~ /^[[:space:]]+(public[[:space:]]+|private[[:space:]]+|protected[[:space:]]+|static[[:space:]]+|async[[:space:]]+|override[[:space:]]+|readonly[[:space:]]+|get[[:space:]]+|set[[:space:]]+|\*[[:space:]]*)*[A-Za-z_$][A-Za-z0-9_$]*[[:space:]]*(<[^(){}]*>)?[[:space:]]*\(/ \
|
|
28
|
+
&& s !~ /^[[:space:]]*(if|for|while|switch|catch|return|do|else|function|await|with|yield|throw|new|typeof|delete|void|in|of|case)[^A-Za-z0-9_$]/ \
|
|
29
|
+
&& s !~ /=>/ \
|
|
30
|
+
&& s !~ /;[[:space:]]*$/ \
|
|
31
|
+
&& s ~ /\)([[:space:]]*:[^={]*)?[[:space:]]*\{[[:space:]]*$/) { fnstart() }
|
|
32
|
+
}
|
|
33
|
+
infn {
|
|
34
|
+
line=strip($0)
|
|
35
|
+
no=gsub(/\{/, "{", line); nc=gsub(/\}/, "}", line)
|
|
36
|
+
depth += no - nc
|
|
37
|
+
if (no > 0) opened=1
|
|
38
|
+
if (opened && depth <= 0) {
|
|
39
|
+
len=NR-start
|
|
40
|
+
if (len > max) printf "%s:%d (%d lines)\n", name, start, len
|
|
41
|
+
infn=0
|
|
42
|
+
}
|
|
43
|
+
# Release a brace-less single-line form (an expression-bodied arrow like
|
|
44
|
+
# `const sq = (x) => x * x;` or a parenthesized `const x = (a, b);`) on its
|
|
45
|
+
# terminating `;`. Mirrors rust-quality.sh: without this the start sticks and
|
|
46
|
+
# the NEXT real function is misattributed to this line — a bogus over-length.
|
|
47
|
+
if (!opened && $0 ~ /;[[:space:]]*$/) infn=0
|
|
48
|
+
}
|
|
49
|
+
' "$FILE_PATH" 2>/dev/null)
|
|
50
|
+
if [[ -n "$LONG_FUNCS" ]]; then
|
|
51
|
+
add_error "Function too long in $FILE_PATH (>${TS_MAX_FN} lines) — simplify or split"
|
|
52
|
+
fi
|
|
53
|
+
|