@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,7 @@
|
|
|
1
|
+
if [[ "$FILE_PATH" =~ use-.*\.ts$ || "$FILE_PATH" =~ use[A-Z].*\.ts$ ]]; then
|
|
2
|
+
HOOK_COUNT=$(grep -cE '^[[:space:]]*(const \[|useRef\(|useEffect\()' "$FILE_PATH" 2>/dev/null || true)
|
|
3
|
+
if [[ "$HOOK_COUNT" -gt 3 ]]; then
|
|
4
|
+
add_error "Hook does too many things in $FILE_PATH ($HOOK_COUNT useState/useRef/useEffect) — split into focused hooks"
|
|
5
|
+
fi
|
|
6
|
+
fi
|
|
7
|
+
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
if grep -qE 'function is[A-Z].*\): .* is [A-Z]' "$FILE_PATH" 2>/dev/null; then
|
|
2
|
+
if [[ -f "$PROJECT_ROOT/package.json" ]] && grep -q '"zod"' "$PROJECT_ROOT/package.json" 2>/dev/null; then
|
|
3
|
+
add_error "Manual type guard in $FILE_PATH — use Zod schema instead"
|
|
4
|
+
fi
|
|
5
|
+
fi
|
|
6
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
NEW_TYPES=$(grep -oE '^export (interface|type) [A-Z][a-zA-Z]+' "$FILE_PATH" 2>/dev/null | awk '{print $NF}')
|
|
2
|
+
if [[ -n "$NEW_TYPES" ]]; then
|
|
3
|
+
# git grep instead of grep -r: uses the index, skips .gitignore'd trees
|
|
4
|
+
# (node_modules, dist) — the recursive walk was a per-edit hot-path cost on
|
|
5
|
+
# large monorepos. --untracked keeps brand-new files visible.
|
|
6
|
+
_ts_rel_fp="${FILE_PATH#"$PROJECT_ROOT"/}"
|
|
7
|
+
for TYPE_NAME in $NEW_TYPES; do
|
|
8
|
+
DUPE_FILE=$(git -C "$PROJECT_ROOT" grep -l --untracked -E \
|
|
9
|
+
"^export (interface|type) ${TYPE_NAME}[ <{]" -- 'packages/*.ts' 'packages/*.tsx' 'apps/*.ts' 'apps/*.tsx' 2>/dev/null \
|
|
10
|
+
| grep -vFx "$_ts_rel_fp" | head -1)
|
|
11
|
+
if [[ -n "$DUPE_FILE" ]]; then
|
|
12
|
+
add_error "Type '$TYPE_NAME' in $FILE_PATH already defined in $DUPE_FILE — import instead of redefining"
|
|
13
|
+
fi
|
|
14
|
+
done
|
|
15
|
+
fi
|
|
16
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Check: forbidden sub-component file names (must match the exported function)
|
|
2
|
+
_basename="$(basename "$FILE_PATH" .tsx)"
|
|
3
|
+
_basename="${_basename%.ts}"
|
|
4
|
+
if [[ "$FILE_PATH" =~ \.(tsx)$ ]]; then
|
|
5
|
+
if echo "$_basename" | grep -qE '^(parts|components|helpers|items|sections|elements)$|-(parts|sections|items|elements)$'; then
|
|
6
|
+
add_error "Forbidden component filename '$_basename.tsx' in $FILE_PATH — name file after its exported function (e.g. api-key-create-button.tsx)"
|
|
7
|
+
fi
|
|
8
|
+
fi
|
|
9
|
+
|
|
10
|
+
# --- New checks ---
|
|
11
|
+
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Check: console.log — capture once, then test (not grep-twice).
|
|
2
|
+
CONSOLE_LINES=$(grep -nE '^[[:space:]]*console\.log\(' "$FILE_PATH" 2>/dev/null | grep -vE '^[0-9]+:[[:space:]]*//' | head -3)
|
|
3
|
+
if [[ -n "$CONSOLE_LINES" ]]; then
|
|
4
|
+
add_error "Forbidden console.log in $FILE_PATH — use console.error/warn/info"$'\n'"${CONSOLE_LINES}"
|
|
5
|
+
fi
|
|
6
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Check: suppression comments — fix the issue in code, never silence the tool.
|
|
2
|
+
# @ts-expect-error is exempt in test/spec files, where asserting a compile error
|
|
3
|
+
# is a legitimate test; banned everywhere else like the rest.
|
|
4
|
+
SUPPRESS_TOKENS='@ts-ignore|@ts-nocheck|eslint-disable|biome-ignore'
|
|
5
|
+
if [[ ! "$FILE_PATH" =~ \.(test|spec)\.(ts|tsx)$ ]]; then
|
|
6
|
+
SUPPRESS_TOKENS="${SUPPRESS_TOKENS}|@ts-expect-error"
|
|
7
|
+
fi
|
|
8
|
+
DISABLE_LINES=$(grep -nE "(//|/\*+)[[:space:]]*(${SUPPRESS_TOKENS})" "$FILE_PATH" 2>/dev/null | head -3)
|
|
9
|
+
if [[ -n "$DISABLE_LINES" ]]; then
|
|
10
|
+
add_error "Forbidden suppression comment in $FILE_PATH — fix the underlying issue in code, never silence it"$'\n'"${DISABLE_LINES}"
|
|
11
|
+
fi
|
|
12
|
+
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Check: confirm()/alert() in frontend files
|
|
2
|
+
if [[ "$FILE_PATH" == */components/* || "$FILE_PATH" == */routes/* ]]; then
|
|
3
|
+
CONFIRM_LINES=$(grep -nE '\b(confirm|alert)[[:space:]]*\(' "$FILE_PATH" 2>/dev/null | grep -vE '^[0-9]+:[[:space:]]*//' | grep -vE '(ConfirmDeleteAlert|AlertDialog|customAlert|customConfirm)' | head -3)
|
|
4
|
+
if [[ -n "$CONFIRM_LINES" ]]; then
|
|
5
|
+
add_error "Forbidden confirm()/alert() in $FILE_PATH — use AlertDialog component"$'\n'"${CONFIRM_LINES}"
|
|
6
|
+
fi
|
|
7
|
+
fi
|
|
8
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Check: raw radix AlertDialog/Dialog imports. Drop leading-`//` comment lines
|
|
2
|
+
# first (same filter as the AS_LINES check above) so a commented-out import does
|
|
3
|
+
# not false-positive. Like AS_LINES, only whole-line `//` comments are dropped —
|
|
4
|
+
# a radix path inside a trailing comment after real code still matches (rare:
|
|
5
|
+
# imports are standalone). Kept consistent with the sibling rules on purpose.
|
|
6
|
+
if grep -nE "from ['\"]@radix-ui/react-(alert-dialog|dialog)['\"]" "$FILE_PATH" 2>/dev/null \
|
|
7
|
+
| grep -qvE '^[0-9]+:[[:space:]]*//'; then
|
|
8
|
+
if [[ "$FILE_PATH" != */packages/ui/* ]]; then
|
|
9
|
+
add_error "Raw radix import in $FILE_PATH — use shared components from @/components/ui/"
|
|
10
|
+
fi
|
|
11
|
+
fi
|
|
12
|
+
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Check: mutable props (Props type param without Readonly)
|
|
2
|
+
PROPS_LINES=$(grep -nE '\((props|[a-z]+Props):[[:space:]]+[A-Z][a-zA-Z]+Props\)' "$FILE_PATH" 2>/dev/null | grep -v 'Readonly' | head -3)
|
|
3
|
+
if [[ -n "$PROPS_LINES" ]]; then
|
|
4
|
+
add_error "Mutable props in $FILE_PATH — wrap in Readonly<Props>"$'\n'"${PROPS_LINES}"
|
|
5
|
+
fi
|
|
6
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Check: manual try/catch+toast pattern in components
|
|
2
|
+
if [[ "$FILE_PATH" == */components/* || "$FILE_PATH" == */routes/* ]]; then
|
|
3
|
+
# [[:space:]] not \s: BSD awk (macOS) has no \s and reads it as a literal `s`,
|
|
4
|
+
# which silently killed this rule for the universal `catch (` spelling.
|
|
5
|
+
CATCH_TOAST=$(awk '/catch[[:space:]]*\(/{found=1} found && /toast\(/{print NR": "$0; found=0}' "$FILE_PATH" 2>/dev/null | head -3)
|
|
6
|
+
if [[ -n "$CATCH_TOAST" ]]; then
|
|
7
|
+
add_error "Manual try/catch+toast in $FILE_PATH — use shared error handling"$'\n'"${CATCH_TOAST}"
|
|
8
|
+
fi
|
|
9
|
+
fi
|
|
10
|
+
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# shellcheck shell=bash
|
|
2
|
+
# --- Error-handling rules (zero tolerance) ---
|
|
3
|
+
|
|
4
|
+
if command -v ast-grep >/dev/null 2>&1; then
|
|
5
|
+
# ONE ast-grep process for ALL the rules below, not one per rule. The previous shape ran
|
|
6
|
+
# `ast-grep run -p <pattern>` once per pattern — 14 spawns, each re-parsing the
|
|
7
|
+
# same file — and measured ~170ms of the TS gate's ~300ms on every edit.
|
|
8
|
+
# `scan --inline-rules` parses once and returns every rule's hits as JSON
|
|
9
|
+
# (~13ms). The excerpt lines rebuilt from .file/.range/.lines below are
|
|
10
|
+
# byte-identical to what `run -p` printed for every pattern rule. The one
|
|
11
|
+
# exception is swallow-bare, which had to become a catch_clause match (see its
|
|
12
|
+
# comment): its excerpt is the catch clause rather than the whole try
|
|
13
|
+
# statement, which is narrower, not malformed.
|
|
14
|
+
#
|
|
15
|
+
# severity is `warning` on purpose: `scan` exits non-zero when an ERROR-level
|
|
16
|
+
# rule matches, which would be indistinguishable from a crashed tool. At
|
|
17
|
+
# warning level both a clean and a matching run exit 0, so a non-zero exit (or
|
|
18
|
+
# any stderr) still means the tool genuinely broke — preserving the fail-loud
|
|
19
|
+
# contract the old per-pattern ast_scan had.
|
|
20
|
+
ts_ast_err_file="$(mktemp)"
|
|
21
|
+
ts_ast_rules=$(cat <<'TS_AST_RULES'
|
|
22
|
+
id: empty-catch
|
|
23
|
+
language: ts
|
|
24
|
+
severity: warning
|
|
25
|
+
message: empty catch
|
|
26
|
+
rule:
|
|
27
|
+
pattern: 'try { $$$ } catch ($_) { }'
|
|
28
|
+
---
|
|
29
|
+
id: empty-catch-noarg
|
|
30
|
+
language: ts
|
|
31
|
+
severity: warning
|
|
32
|
+
message: empty catch (no binding)
|
|
33
|
+
rule:
|
|
34
|
+
pattern: 'try { $$$ } catch { }'
|
|
35
|
+
---
|
|
36
|
+
id: empty-catch-handler
|
|
37
|
+
language: ts
|
|
38
|
+
severity: warning
|
|
39
|
+
message: empty promise catch handler
|
|
40
|
+
rule:
|
|
41
|
+
pattern: '$_.catch(() => { })'
|
|
42
|
+
---
|
|
43
|
+
id: null-catch-handler
|
|
44
|
+
language: ts
|
|
45
|
+
severity: warning
|
|
46
|
+
message: promise catch handler returning null
|
|
47
|
+
rule:
|
|
48
|
+
pattern: '$_.catch(() => null)'
|
|
49
|
+
---
|
|
50
|
+
id: undef-catch-handler
|
|
51
|
+
language: ts
|
|
52
|
+
severity: warning
|
|
53
|
+
message: promise catch handler returning undefined
|
|
54
|
+
rule:
|
|
55
|
+
pattern: '$_.catch(() => undefined)'
|
|
56
|
+
---
|
|
57
|
+
id: swallow-null-arg
|
|
58
|
+
language: ts
|
|
59
|
+
severity: warning
|
|
60
|
+
message: catch returns null
|
|
61
|
+
rule:
|
|
62
|
+
pattern: 'try { $$$ } catch ($_) { return null }'
|
|
63
|
+
---
|
|
64
|
+
id: swallow-undef-arg
|
|
65
|
+
language: ts
|
|
66
|
+
severity: warning
|
|
67
|
+
message: catch returns undefined
|
|
68
|
+
rule:
|
|
69
|
+
pattern: 'try { $$$ } catch ($_) { return undefined }'
|
|
70
|
+
---
|
|
71
|
+
id: swallow-null
|
|
72
|
+
language: ts
|
|
73
|
+
severity: warning
|
|
74
|
+
message: catch returns null (no binding)
|
|
75
|
+
rule:
|
|
76
|
+
pattern: 'try { $$$ } catch { return null }'
|
|
77
|
+
---
|
|
78
|
+
id: swallow-undef
|
|
79
|
+
language: ts
|
|
80
|
+
severity: warning
|
|
81
|
+
message: catch returns undefined (no binding)
|
|
82
|
+
rule:
|
|
83
|
+
pattern: 'try { $$$ } catch { return undefined }'
|
|
84
|
+
---
|
|
85
|
+
# A bare `return` in an ast-grep pattern acts as a WILDCARD over the optional
|
|
86
|
+
# argument: `catch { return }` also matched `return []`, `return 42` and
|
|
87
|
+
# `return null`, i.e. every non-nullish fallback got reported as "returns a
|
|
88
|
+
# nullish value". Match the catch clause itself, then require that its single
|
|
89
|
+
# return statement carries no argument at all.
|
|
90
|
+
id: swallow-bare
|
|
91
|
+
language: ts
|
|
92
|
+
severity: warning
|
|
93
|
+
message: catch returns nothing
|
|
94
|
+
rule:
|
|
95
|
+
all:
|
|
96
|
+
- any:
|
|
97
|
+
- pattern:
|
|
98
|
+
context: 'try {} catch { return }'
|
|
99
|
+
selector: catch_clause
|
|
100
|
+
- pattern:
|
|
101
|
+
context: 'try {} catch ($_) { return }'
|
|
102
|
+
selector: catch_clause
|
|
103
|
+
- not:
|
|
104
|
+
has:
|
|
105
|
+
stopBy: end
|
|
106
|
+
kind: return_statement
|
|
107
|
+
has:
|
|
108
|
+
stopBy: neighbor
|
|
109
|
+
pattern: $X
|
|
110
|
+
---
|
|
111
|
+
id: throw-empty-error
|
|
112
|
+
language: ts
|
|
113
|
+
severity: warning
|
|
114
|
+
message: throw new Error() with no message
|
|
115
|
+
rule:
|
|
116
|
+
pattern: 'throw new Error()'
|
|
117
|
+
---
|
|
118
|
+
id: throw-string
|
|
119
|
+
language: ts
|
|
120
|
+
severity: warning
|
|
121
|
+
message: throw of a string literal
|
|
122
|
+
rule:
|
|
123
|
+
pattern: 'throw "$S"'
|
|
124
|
+
---
|
|
125
|
+
id: throw-template
|
|
126
|
+
language: ts
|
|
127
|
+
severity: warning
|
|
128
|
+
message: throw of a template literal
|
|
129
|
+
rule:
|
|
130
|
+
pattern: 'throw `$S`'
|
|
131
|
+
TS_AST_RULES
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
ts_ast_failed=0
|
|
135
|
+
ts_ast_fail_detail=""
|
|
136
|
+
ts_ast_lines=""
|
|
137
|
+
ts_ast_json=$(ast-grep scan --inline-rules "$ts_ast_rules" --json "$FILE_PATH" 2>"$ts_ast_err_file")
|
|
138
|
+
ts_ast_rc=$?
|
|
139
|
+
# Flatten every hit to "<ruleId>\t<file>:<line>:<source line>", one output line
|
|
140
|
+
# per matched source line — the exact shape `run -p` emitted, now tagged with
|
|
141
|
+
# the rule that produced it.
|
|
142
|
+
#
|
|
143
|
+
# Two distinct failure modes, reported distinctly: ast-grep itself broke, or it
|
|
144
|
+
# exited 0 and returned something that is not the documented JSON array. Both
|
|
145
|
+
# mean the rules could not be verified, but collapsing them into one message
|
|
146
|
+
# sends the reader to the wrong tool.
|
|
147
|
+
ts_ast_fail_stage=""
|
|
148
|
+
if [[ "$ts_ast_rc" -ne 0 || -s "$ts_ast_err_file" ]]; then
|
|
149
|
+
ts_ast_failed=1
|
|
150
|
+
ts_ast_fail_stage="ast-grep"
|
|
151
|
+
elif ! ts_ast_lines=$(printf '%s' "$ts_ast_json" | jq -r '
|
|
152
|
+
.[] | . as $m
|
|
153
|
+
| ((($m.lines // "") | split("\n")) | to_entries[])
|
|
154
|
+
| "\($m.ruleId)\t\($m.file):\($m.range.start.line + 1 + .key):\(.value)"
|
|
155
|
+
' 2>/dev/null); then
|
|
156
|
+
ts_ast_failed=1
|
|
157
|
+
ts_ast_fail_stage="jq"
|
|
158
|
+
ts_ast_lines=""
|
|
159
|
+
fi
|
|
160
|
+
if [[ "$ts_ast_failed" -ne 0 ]]; then
|
|
161
|
+
# Exit code plus the first stderr line (capped) so the agent learns WHAT
|
|
162
|
+
# broke, not just THAT it did.
|
|
163
|
+
ts_ast_stderr_first=$(head -n 1 "$ts_ast_err_file" 2>/dev/null | cut -c1-200)
|
|
164
|
+
if [[ "$ts_ast_fail_stage" == "jq" ]]; then
|
|
165
|
+
ts_ast_fail_detail="ast-grep exited 0 but its output did not parse as the documented JSON array"
|
|
166
|
+
else
|
|
167
|
+
ts_ast_fail_detail="ast-grep exit ${ts_ast_rc}${ts_ast_stderr_first:+: $ts_ast_stderr_first}"
|
|
168
|
+
fi
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
# ts_join_hits BLOCK... — concatenate non-empty hit blocks into $TS_JOINED with
|
|
172
|
+
# one newline between them. Each block comes from a $(...) capture, which strips
|
|
173
|
+
# the trailing newline, so plain concatenation glued one block's last line onto
|
|
174
|
+
# the next block's first line. Sets a variable instead of printing to avoid a
|
|
175
|
+
# fork on a path that runs after every edit.
|
|
176
|
+
ts_join_hits() {
|
|
177
|
+
local _part
|
|
178
|
+
TS_JOINED=""
|
|
179
|
+
for _part in "$@"; do
|
|
180
|
+
[ -z "$_part" ] && continue
|
|
181
|
+
[ -n "$TS_JOINED" ] && TS_JOINED="${TS_JOINED}"$'\n'
|
|
182
|
+
TS_JOINED="${TS_JOINED}${_part}"
|
|
183
|
+
done
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
# ts_ast_hits RULE_ID LIMIT — this rule's excerpt lines, capped like before.
|
|
187
|
+
ts_ast_hits() {
|
|
188
|
+
local _id="$1" _limit="$2" _row _rest
|
|
189
|
+
[ -n "$ts_ast_lines" ] || return 0
|
|
190
|
+
while IFS=$'\t' read -r _row _rest; do
|
|
191
|
+
[ "$_row" = "$_id" ] && printf '%s\n' "$_rest"
|
|
192
|
+
done <<< "$ts_ast_lines" | head -n "$_limit"
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
# Empty catch — swallowed error
|
|
196
|
+
EMPTY_CATCH=$(ts_ast_hits empty-catch 3)
|
|
197
|
+
EMPTY_CATCH_NOARG=$(ts_ast_hits empty-catch-noarg 3)
|
|
198
|
+
if [[ -n "$EMPTY_CATCH" || -n "$EMPTY_CATCH_NOARG" ]]; then
|
|
199
|
+
ts_join_hits "$EMPTY_CATCH" "$EMPTY_CATCH_NOARG"
|
|
200
|
+
add_error "Empty catch block in $FILE_PATH — handle the error or rethrow; do not swallow"$'\n'"${TS_JOINED}"
|
|
201
|
+
fi
|
|
202
|
+
|
|
203
|
+
# Silent promise rejection — .catch(() => {}) / .catch(() => null)
|
|
204
|
+
EMPTY_CATCH_HANDLER=$(ts_ast_hits empty-catch-handler 3)
|
|
205
|
+
NULL_CATCH_HANDLER=$(ts_ast_hits null-catch-handler 3)
|
|
206
|
+
UNDEF_CATCH_HANDLER=$(ts_ast_hits undef-catch-handler 3)
|
|
207
|
+
if [[ -n "$EMPTY_CATCH_HANDLER" || -n "$NULL_CATCH_HANDLER" || -n "$UNDEF_CATCH_HANDLER" ]]; then
|
|
208
|
+
ts_join_hits "$EMPTY_CATCH_HANDLER" "$NULL_CATCH_HANDLER" "$UNDEF_CATCH_HANDLER"
|
|
209
|
+
add_error "Silent promise rejection in $FILE_PATH — log or rethrow the error"$'\n'"${TS_JOINED}"
|
|
210
|
+
fi
|
|
211
|
+
|
|
212
|
+
# Swallow via a catch that just returns a nullish value — error vanishes.
|
|
213
|
+
SWALLOW_CATCH=""
|
|
214
|
+
for _rule in swallow-null-arg swallow-undef-arg \
|
|
215
|
+
swallow-null swallow-undef swallow-bare; do
|
|
216
|
+
_h=$(ts_ast_hits "$_rule" 2)
|
|
217
|
+
ts_join_hits "$SWALLOW_CATCH" "$_h"
|
|
218
|
+
SWALLOW_CATCH="$TS_JOINED"
|
|
219
|
+
done
|
|
220
|
+
if [[ -n "$SWALLOW_CATCH" ]]; then
|
|
221
|
+
add_error "Catch swallows the error by returning a nullish value in $FILE_PATH — handle, log, or rethrow it"$'\n'"${SWALLOW_CATCH}"
|
|
222
|
+
fi
|
|
223
|
+
|
|
224
|
+
# throw new Error() with no message
|
|
225
|
+
THROW_EMPTY_ERROR=$(ts_ast_hits throw-empty-error 3)
|
|
226
|
+
if [[ -n "$THROW_EMPTY_ERROR" ]]; then
|
|
227
|
+
add_error "throw new Error() with no message in $FILE_PATH — include a descriptive message"$'\n'"${THROW_EMPTY_ERROR}"
|
|
228
|
+
fi
|
|
229
|
+
|
|
230
|
+
# throw of string literal — breaks instanceof Error
|
|
231
|
+
THROW_STRING=$(ts_ast_hits throw-string 3)
|
|
232
|
+
THROW_TSTR=$(ts_ast_hits throw-template 3)
|
|
233
|
+
if [[ -n "$THROW_STRING" || -n "$THROW_TSTR" ]]; then
|
|
234
|
+
ts_join_hits "$THROW_STRING" "$THROW_TSTR"
|
|
235
|
+
add_error "throw of string literal in $FILE_PATH — throw an Error (or subclass) instead"$'\n'"${TS_JOINED}"
|
|
236
|
+
fi
|
|
237
|
+
|
|
238
|
+
rm -f "$ts_ast_err_file"
|
|
239
|
+
if [[ "$ts_ast_failed" -ne 0 ]]; then
|
|
240
|
+
add_error "ast-grep failed while scanning $FILE_PATH — ${ts_ast_fail_detail:-unknown error}; error-handling rules could not be verified. Fix the tool/file and re-edit"
|
|
241
|
+
fi
|
|
242
|
+
fi
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# throw of numeric/boolean/null/undefined literal — match anywhere on line,
|
|
2
|
+
# strip line comments and inline /* */ blocks first so `// throw 5` style
|
|
3
|
+
# annotations and example code in comments don't trigger.
|
|
4
|
+
THROW_LITERAL=$(awk '
|
|
5
|
+
{
|
|
6
|
+
line = $0
|
|
7
|
+
# Strip /* ... */ blocks on a single line (heuristic; multi-line not handled).
|
|
8
|
+
# Greedy on purpose: a line with TWO inline blocks sandwiching real code
|
|
9
|
+
# (`/* a */ throw 5; /* b */`) erases the `throw 5` and misses it. Vanishingly
|
|
10
|
+
# rare; accepted over a count_code_lines-style state machine here.
|
|
11
|
+
gsub(/\/\*.*\*\//, "", line)
|
|
12
|
+
# Strip // line comments (heuristic — does not preserve `//` inside a string,
|
|
13
|
+
# which is rare in real code).
|
|
14
|
+
sub(/\/\/.*$/, "", line)
|
|
15
|
+
if (match(line, /(^|[^a-zA-Z_$])throw[ \t]+(-?[0-9]+(\.[0-9]+)?|null|undefined|true|false)([ \t]|;|}|$)/)) {
|
|
16
|
+
print NR ": " $0
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
' "$FILE_PATH" 2>/dev/null | head -3)
|
|
20
|
+
if [[ -n "$THROW_LITERAL" ]]; then
|
|
21
|
+
add_error "throw of non-Error literal in $FILE_PATH — throw an Error (or subclass) instead"$'\n'"${THROW_LITERAL}"
|
|
22
|
+
fi
|
|
23
|
+
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Check: no-mocks — tests must exercise real data/services, not mocked stand-ins.
|
|
2
|
+
# Scope: test files only — *.test.ts(x) / *.spec.ts(x), or any file under a
|
|
3
|
+
# __tests__/ directory — excluding e2e specs, which follow the platform's own
|
|
4
|
+
# fixture conventions (same carve-out as 20-tests.sh / 65-suppression.sh).
|
|
5
|
+
# Opt-out: lang.ts.noMocks=false in the merged config (default true — blocking).
|
|
6
|
+
if [[ "$FILE_PATH" != */e2e/* ]] && { [[ "$FILE_PATH" =~ \.(test|spec)\.(ts|tsx)$ ]] || [[ "$FILE_PATH" == */__tests__/* ]]; } \
|
|
7
|
+
&& [ "$(quality_flag ts noMocks true)" = "true" ]; then
|
|
8
|
+
|
|
9
|
+
NOMOCKS_LINES=""
|
|
10
|
+
if command -v ast-grep >/dev/null 2>&1; then
|
|
11
|
+
# ONE ast-grep process for all the mock-call rules below, same shape as
|
|
12
|
+
# 78-error-ast.sh: `scan --inline-rules` parses once and returns every
|
|
13
|
+
# rule's hits as JSON. severity is `warning` on purpose — `scan` exits
|
|
14
|
+
# non-zero on an ERROR-level match, which would be indistinguishable from a
|
|
15
|
+
# crashed tool; at `warning` a non-zero exit (or any stderr) always means
|
|
16
|
+
# the tool itself broke.
|
|
17
|
+
ts_nomocks_err_file="$(mktemp)"
|
|
18
|
+
ts_nomocks_rules=$(cat <<'TS_NOMOCKS_RULES'
|
|
19
|
+
id: jest-mock
|
|
20
|
+
language: ts
|
|
21
|
+
severity: warning
|
|
22
|
+
message: jest.mock() call
|
|
23
|
+
rule:
|
|
24
|
+
pattern: jest.mock($$$)
|
|
25
|
+
---
|
|
26
|
+
id: vi-mock
|
|
27
|
+
language: ts
|
|
28
|
+
severity: warning
|
|
29
|
+
message: vi.mock() call
|
|
30
|
+
rule:
|
|
31
|
+
pattern: vi.mock($$$)
|
|
32
|
+
---
|
|
33
|
+
id: jest-fn
|
|
34
|
+
language: ts
|
|
35
|
+
severity: warning
|
|
36
|
+
message: jest.fn() call
|
|
37
|
+
rule:
|
|
38
|
+
pattern: jest.fn($$$)
|
|
39
|
+
---
|
|
40
|
+
id: vi-fn
|
|
41
|
+
language: ts
|
|
42
|
+
severity: warning
|
|
43
|
+
message: vi.fn() call
|
|
44
|
+
rule:
|
|
45
|
+
pattern: vi.fn($$$)
|
|
46
|
+
---
|
|
47
|
+
id: sinon-method
|
|
48
|
+
language: ts
|
|
49
|
+
severity: warning
|
|
50
|
+
message: sinon mock method call
|
|
51
|
+
rule:
|
|
52
|
+
pattern: sinon.$M($$$)
|
|
53
|
+
TS_NOMOCKS_RULES
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
ts_nomocks_failed=0
|
|
57
|
+
ts_nomocks_fail_stage=""
|
|
58
|
+
ts_nomocks_json=$(ast-grep scan --inline-rules "$ts_nomocks_rules" --json "$FILE_PATH" 2>"$ts_nomocks_err_file")
|
|
59
|
+
ts_nomocks_rc=$?
|
|
60
|
+
if [[ "$ts_nomocks_rc" -ne 0 || -s "$ts_nomocks_err_file" ]]; then
|
|
61
|
+
ts_nomocks_failed=1
|
|
62
|
+
ts_nomocks_fail_stage="ast-grep"
|
|
63
|
+
elif [[ -z "$(printf '%s' "$ts_nomocks_json" | tr -d '[:space:]')" ]]; then
|
|
64
|
+
# ast-grep exited 0 with genuinely empty stdout — jq also exits 0 on
|
|
65
|
+
# empty input with empty output, so left unchecked this would be
|
|
66
|
+
# indistinguishable from a clean "[]" scan and silently swallow a real
|
|
67
|
+
# breakage as "no hits". A well-behaved scan always emits at least "[]".
|
|
68
|
+
ts_nomocks_failed=1
|
|
69
|
+
ts_nomocks_fail_stage="empty-output"
|
|
70
|
+
elif ! NOMOCKS_LINES=$(printf '%s' "$ts_nomocks_json" | jq -r '
|
|
71
|
+
.[] | . as $m
|
|
72
|
+
| ((($m.lines // "") | split("\n")) | to_entries[])
|
|
73
|
+
| "\($m.ruleId)\t\($m.file):\($m.range.start.line + 1 + .key):\(.value)"
|
|
74
|
+
' 2>/dev/null); then
|
|
75
|
+
ts_nomocks_failed=1
|
|
76
|
+
ts_nomocks_fail_stage="jq"
|
|
77
|
+
NOMOCKS_LINES=""
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
if [[ "$ts_nomocks_failed" -ne 0 ]]; then
|
|
81
|
+
ts_nomocks_stderr_first=$(head -n 1 "$ts_nomocks_err_file" 2>/dev/null | cut -c1-200)
|
|
82
|
+
if [[ "$ts_nomocks_fail_stage" == "jq" ]]; then
|
|
83
|
+
add_error "ast-grep failed while scanning $FILE_PATH for mocks — its output did not parse as the documented JSON array; no-mocks rule could not be verified. Fix the tool/file and re-edit"
|
|
84
|
+
elif [[ "$ts_nomocks_fail_stage" == "empty-output" ]]; then
|
|
85
|
+
add_error "ast-grep failed while scanning $FILE_PATH for mocks — exited 0 with empty output (expected at least the JSON array \"[]\"); no-mocks rule could not be verified. Fix the tool/file and re-edit"
|
|
86
|
+
else
|
|
87
|
+
add_error "ast-grep failed while scanning $FILE_PATH for mocks — exit ${ts_nomocks_rc}${ts_nomocks_stderr_first:+: $ts_nomocks_stderr_first}; no-mocks rule could not be verified. Fix the tool/file and re-edit"
|
|
88
|
+
fi
|
|
89
|
+
fi
|
|
90
|
+
rm -f "$ts_nomocks_err_file"
|
|
91
|
+
|
|
92
|
+
if [[ -n "$NOMOCKS_LINES" ]]; then
|
|
93
|
+
# Strip the leading "<ruleId>\t" — only the excerpt (file:line:source) is shown.
|
|
94
|
+
NOMOCKS_EXCERPT=$(printf '%s\n' "$NOMOCKS_LINES" | cut -f2- | head -5)
|
|
95
|
+
add_error "Mocked test double in $FILE_PATH — tests must exercise real data/services, not mocks/stubs (jest.mock/vi.mock/jest.fn/vi.fn/sinon)"$'\n'"${NOMOCKS_EXCERPT}"
|
|
96
|
+
fi
|
|
97
|
+
fi
|
|
98
|
+
# ast-grep absent: the mock-call rules above are skipped, same fail-soft
|
|
99
|
+
# posture as 78-error-ast.sh — but the ts-mockito import check below needs no
|
|
100
|
+
# external tool, so it still runs.
|
|
101
|
+
|
|
102
|
+
# ts-mockito is a whole mocking LIBRARY imported by name — a plain grep for
|
|
103
|
+
# the import specifier, same idiom as the `../` import check in 10-imports.sh
|
|
104
|
+
# (quote-agnostic via a bracket class).
|
|
105
|
+
if grep -qE "from[[:space:]]+[\"']ts-mockito[\"']" "$FILE_PATH" 2>/dev/null; then
|
|
106
|
+
add_error "Import from ts-mockito in $FILE_PATH — tests must exercise real data/services, not mocks/stubs"
|
|
107
|
+
fi
|
|
108
|
+
fi
|
|
109
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Check: code duplication (jscpd, scoped to package directory)
|
|
2
|
+
# Advisory only — warns but does NOT block (pre-existing duplication is not the editor's fault).
|
|
3
|
+
DUPLICATION_WARNING=""
|
|
4
|
+
if [[ -z "$MESSAGES" && ! "$FILE_PATH" =~ \.(test|spec)\. ]]; then
|
|
5
|
+
_pkg_rel="${FILE_PATH#"$PROJECT_ROOT"/}"
|
|
6
|
+
_pkg_dir=""
|
|
7
|
+
if [[ "$_pkg_rel" == apps/* || "$_pkg_rel" == packages/* ]]; then
|
|
8
|
+
_pkg_dir="$PROJECT_ROOT/$(echo "$_pkg_rel" | cut -d/ -f1-2)"
|
|
9
|
+
fi
|
|
10
|
+
# jscpd runner — use whatever package manager runner is available.
|
|
11
|
+
jscpd_runner=""
|
|
12
|
+
case "$pm" in
|
|
13
|
+
bun) command -v bunx >/dev/null 2>&1 && jscpd_runner="bunx jscpd" ;;
|
|
14
|
+
pnpm) command -v pnpm >/dev/null 2>&1 && jscpd_runner="pnpm dlx jscpd" ;;
|
|
15
|
+
yarn) command -v yarn >/dev/null 2>&1 && jscpd_runner="yarn dlx jscpd" ;;
|
|
16
|
+
npm) command -v npx >/dev/null 2>&1 && jscpd_runner="npx jscpd" ;;
|
|
17
|
+
esac
|
|
18
|
+
if [[ -n "$_pkg_dir" && -d "$_pkg_dir" && -f "$PROJECT_ROOT/.jscpd.json" && -n "$jscpd_runner" ]]; then
|
|
19
|
+
# shellcheck disable=SC2086 # $jscpd_runner is a multi-word command (e.g. "pnpm dlx jscpd") — intentional word split
|
|
20
|
+
_jscpd_out=$(timeout 10 $jscpd_runner "$_pkg_dir" \
|
|
21
|
+
--config "$PROJECT_ROOT/.jscpd.json" \
|
|
22
|
+
2>&1 || true)
|
|
23
|
+
_file_base=$(basename "$FILE_PATH")
|
|
24
|
+
if echo "$_jscpd_out" | grep -qi "found.*clone\|duplicat" && \
|
|
25
|
+
echo "$_jscpd_out" | grep -q "$_file_base"; then
|
|
26
|
+
DUPLICATION_WARNING="Code duplication detected involving $FILE_PATH — deduplicate or run '$TYPECHECK_CMD' before commit"
|
|
27
|
+
fi
|
|
28
|
+
fi
|
|
29
|
+
fi
|
|
30
|
+
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# --- Docs (soft advisory, never blocks) ---
|
|
2
|
+
# Exported API should carry a concise JSDoc. Advisory only — kept out of
|
|
3
|
+
# MESSAGES so it never sets the failing gate. Skips tests, barrels, declarations.
|
|
4
|
+
DOC_ADVISORY=""
|
|
5
|
+
_ts_base="$(basename "$FILE_PATH")"
|
|
6
|
+
if [[ ! "$FILE_PATH" =~ \.(test|spec)\.(ts|tsx)$ && ! "$FILE_PATH" =~ \.d\.ts$ \
|
|
7
|
+
&& "$_ts_base" != "index.ts" && "$_ts_base" != "index.tsx" ]]; then
|
|
8
|
+
_undoc=$(awk '
|
|
9
|
+
/^[[:space:]]*$/ { next } # blanks do not reset the doc context
|
|
10
|
+
/^[[:space:]]*\/\// { next } # line comments / pragmas sit between doc and export
|
|
11
|
+
{
|
|
12
|
+
if ($0 ~ /^export (async )?function / || $0 ~ /^export (abstract )?class / \
|
|
13
|
+
|| $0 ~ /^export default / || $0 ~ /^export (const|interface|type|enum) [A-Z]/ \
|
|
14
|
+
|| $0 ~ /^export const [a-z_][A-Za-z0-9_]* = (async )?(\(|function)/) {
|
|
15
|
+
if (prev !~ /\*\/[[:space:]]*$/ && prev !~ /^[[:space:]]*\/\*\*/) printf "%d: %s\n", NR, $0
|
|
16
|
+
}
|
|
17
|
+
prev=$0
|
|
18
|
+
}
|
|
19
|
+
' "$FILE_PATH" 2>/dev/null | head -3)
|
|
20
|
+
if [[ -n "$_undoc" ]]; then
|
|
21
|
+
DOC_ADVISORY="Exported API missing a JSDoc in $FILE_PATH — add a concise /** */ doc:"$'\n'"${_undoc}"
|
|
22
|
+
fi
|
|
23
|
+
_verbose_doc=$(awk '
|
|
24
|
+
!inb && /\/\*\*/ { inb=1; start=NR; cnt=0 } # !inb: a /** in prose must not reset the count mid-block
|
|
25
|
+
inb { cnt++ }
|
|
26
|
+
inb && /\*\// { if (cnt>12) printf "%d: JSDoc block is %d lines — trim to the essentials\n", start, cnt; inb=0 }
|
|
27
|
+
' "$FILE_PATH" 2>/dev/null | head -2)
|
|
28
|
+
if [[ -n "$_verbose_doc" ]]; then
|
|
29
|
+
# Separator appended on its own line: ANSI-C quoting is NOT honored inside a
|
|
30
|
+
# ${var:+word} expansion, so the old one-liner would have emitted a literal
|
|
31
|
+
# $'\n' there.
|
|
32
|
+
[[ -n "$DOC_ADVISORY" ]] && DOC_ADVISORY="${DOC_ADVISORY}"$'\n'
|
|
33
|
+
DOC_ADVISORY="${DOC_ADVISORY}Verbose JSDoc in $FILE_PATH — docs must be present but concise:"$'\n'"${_verbose_doc}"
|
|
34
|
+
fi
|
|
35
|
+
fi
|
|
36
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Handler-presence advisory (non-blocking): the file awaits but has no try/catch
|
|
2
|
+
# and no .catch anywhere — rejections may be unhandled. Advisory, not a block,
|
|
3
|
+
# because the handler can legitimately live in every caller.
|
|
4
|
+
ERR_ADVISORY=""
|
|
5
|
+
# Comment-only lines are stripped first: a commented-out `await` must not raise
|
|
6
|
+
# the advisory, and a `try`/`.catch` that only appears in a comment must not
|
|
7
|
+
# suppress it. (`*`-prefixed lines are JSDoc/block-comment continuations; a
|
|
8
|
+
# CODE line starting with `*` — a multiplication continuation holding the only
|
|
9
|
+
# await — is stripped too, accepted: advisory-only, fails open.)
|
|
10
|
+
_ts_noncomment=$(grep -vE '^[[:space:]]*(//|/\*|\*)' "$FILE_PATH" 2>/dev/null)
|
|
11
|
+
if printf '%s\n' "$_ts_noncomment" | grep -qE '\bawait[[:space:]]' \
|
|
12
|
+
&& ! printf '%s\n' "$_ts_noncomment" | grep -qE '\btry\b|\.catch\('; then
|
|
13
|
+
ERR_ADVISORY="Async code in $FILE_PATH uses await with no try/catch or .catch in the file — ensure rejections are handled here or by every caller."
|
|
14
|
+
fi
|
|
15
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
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" "ts-quality-hook" \
|
|
10
|
+
"Post-edit 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" "ts-quality-hook"
|
|
24
|
+
|
|
25
|
+
# Advisories (non-blocking — no gate write). Duplication + docs combined into
|
|
26
|
+
# a single additionalContext so the hook emits exactly one JSON object.
|
|
27
|
+
# Joined with REAL newlines. `${ADVISORY:+$ADVISORY\n}` appended a literal
|
|
28
|
+
# backslash-n (and ANSI-C quoting is not honored inside a :+ expansion either),
|
|
29
|
+
# so stacked advisories reached the agent as one run-on line.
|
|
30
|
+
ADVISORY="$DUPLICATION_WARNING"
|
|
31
|
+
for _adv in "$DOC_ADVISORY" "$ERR_ADVISORY"; do
|
|
32
|
+
[[ -z "$_adv" ]] && continue
|
|
33
|
+
[[ -n "$ADVISORY" ]] && ADVISORY="${ADVISORY}"$'\n'
|
|
34
|
+
ADVISORY="${ADVISORY}${_adv}"
|
|
35
|
+
done
|
|
36
|
+
if [[ -n "$ADVISORY" ]]; then
|
|
37
|
+
jq -n --arg ctx "$ADVISORY" '{
|
|
38
|
+
"hookSpecificOutput": {
|
|
39
|
+
"hookEventName": "PostToolUse",
|
|
40
|
+
"additionalContext": $ctx
|
|
41
|
+
}
|
|
42
|
+
}'
|
|
43
|
+
fi
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
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
|
+
}
|