@agentvibes/guardrails 0.1.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/README.md +239 -0
- package/candidates/README.md +30 -0
- package/candidates/r10-as-cast.yml +24 -0
- package/candidates/r1d-scattered-numeric-const-in-component.yml +13 -0
- package/candidates/r2-helper-fn-in-tsx.yml +22 -0
- package/candidates/r3-hooks-outside-layout.yml +21 -0
- package/candidates/r6-jsx-comment-smell.yml +14 -0
- package/candidates/r7-raw-hsl-outside-tokens.yml +17 -0
- package/candidates/r8-inline-style-prop.yml +13 -0
- package/candidates/r9-optional-store-arg.yml +24 -0
- package/configs/biome.json +96 -0
- package/configs/tsconfig.base.json +21 -0
- package/dist/astGrep.d.ts +16 -0
- package/dist/astGrep.js +48 -0
- package/dist/astGrep.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +138 -0
- package/dist/cli.js.map +1 -0
- package/dist/deploy.d.ts +1 -0
- package/dist/deploy.js +34 -0
- package/dist/deploy.js.map +1 -0
- package/dist/doctor.d.ts +6 -0
- package/dist/doctor.js +102 -0
- package/dist/doctor.js.map +1 -0
- package/dist/fileWalk.d.ts +2 -0
- package/dist/fileWalk.js +45 -0
- package/dist/fileWalk.js.map +1 -0
- package/dist/findings.d.ts +12 -0
- package/dist/findings.js +8 -0
- package/dist/findings.js.map +1 -0
- package/dist/gateRed.d.ts +1 -0
- package/dist/gateRed.js +93 -0
- package/dist/gateRed.js.map +1 -0
- package/dist/gitDiff.d.ts +18 -0
- package/dist/gitDiff.js +90 -0
- package/dist/gitDiff.js.map +1 -0
- package/dist/hookPostedit.d.ts +1 -0
- package/dist/hookPostedit.js +117 -0
- package/dist/hookPostedit.js.map +1 -0
- package/dist/hookStop.d.ts +1 -0
- package/dist/hookStop.js +56 -0
- package/dist/hookStop.js.map +1 -0
- package/dist/hookStopTest.d.ts +1 -0
- package/dist/hookStopTest.js +71 -0
- package/dist/hookStopTest.js.map +1 -0
- package/dist/init.d.ts +24 -0
- package/dist/init.js +172 -0
- package/dist/init.js.map +1 -0
- package/dist/initTest.d.ts +1 -0
- package/dist/initTest.js +88 -0
- package/dist/initTest.js.map +1 -0
- package/dist/iterate.d.ts +9 -0
- package/dist/iterate.js +79 -0
- package/dist/iterate.js.map +1 -0
- package/dist/iterateTest.d.ts +1 -0
- package/dist/iterateTest.js +91 -0
- package/dist/iterateTest.js.map +1 -0
- package/dist/leakPatterns.d.ts +5 -0
- package/dist/leakPatterns.js +22 -0
- package/dist/leakPatterns.js.map +1 -0
- package/dist/leaks.d.ts +8 -0
- package/dist/leaks.js +211 -0
- package/dist/leaks.js.map +1 -0
- package/dist/leaksRed.d.ts +1 -0
- package/dist/leaksRed.js +62 -0
- package/dist/leaksRed.js.map +1 -0
- package/dist/metrics.d.ts +10 -0
- package/dist/metrics.js +113 -0
- package/dist/metrics.js.map +1 -0
- package/dist/metricsAggregate.d.ts +5 -0
- package/dist/metricsAggregate.js +78 -0
- package/dist/metricsAggregate.js.map +1 -0
- package/dist/metricsBaseline.d.ts +21 -0
- package/dist/metricsBaseline.js +88 -0
- package/dist/metricsBaseline.js.map +1 -0
- package/dist/metricsCollect.d.ts +8 -0
- package/dist/metricsCollect.js +295 -0
- package/dist/metricsCollect.js.map +1 -0
- package/dist/metricsCycle.d.ts +1 -0
- package/dist/metricsCycle.js +79 -0
- package/dist/metricsCycle.js.map +1 -0
- package/dist/metricsTypes.d.ts +102 -0
- package/dist/metricsTypes.js +2 -0
- package/dist/metricsTypes.js.map +1 -0
- package/dist/packagePaths.d.ts +7 -0
- package/dist/packagePaths.js +11 -0
- package/dist/packagePaths.js.map +1 -0
- package/dist/pluginApi.d.ts +24 -0
- package/dist/pluginApi.js +12 -0
- package/dist/pluginApi.js.map +1 -0
- package/dist/pluginResolve.d.ts +6 -0
- package/dist/pluginResolve.js +48 -0
- package/dist/pluginResolve.js.map +1 -0
- package/dist/screenScope.d.ts +21 -0
- package/dist/screenScope.js +81 -0
- package/dist/screenScope.js.map +1 -0
- package/dist/screenScopeTest.d.ts +1 -0
- package/dist/screenScopeTest.js +116 -0
- package/dist/screenScopeTest.js.map +1 -0
- package/dist/severity.d.ts +9 -0
- package/dist/severity.js +62 -0
- package/dist/severity.js.map +1 -0
- package/dist/severityTest.d.ts +1 -0
- package/dist/severityTest.js +63 -0
- package/dist/severityTest.js.map +1 -0
- package/dist/sourceLines.d.ts +9 -0
- package/dist/sourceLines.js +52 -0
- package/dist/sourceLines.js.map +1 -0
- package/dist/structure.d.ts +2 -0
- package/dist/structure.js +98 -0
- package/dist/structure.js.map +1 -0
- package/dist/testRules.d.ts +1 -0
- package/dist/testRules.js +285 -0
- package/dist/testRules.js.map +1 -0
- package/dist/textGrep.d.ts +2 -0
- package/dist/textGrep.js +52 -0
- package/dist/textGrep.js.map +1 -0
- package/dist/tomlTable.d.ts +2 -0
- package/dist/tomlTable.js +26 -0
- package/dist/tomlTable.js.map +1 -0
- package/dist/toolResolve.d.ts +18 -0
- package/dist/toolResolve.js +50 -0
- package/dist/toolResolve.js.map +1 -0
- package/dist/verify.d.ts +3 -0
- package/dist/verify.js +49 -0
- package/dist/verify.js.map +1 -0
- package/dist/verifyDiff.d.ts +18 -0
- package/dist/verifyDiff.js +126 -0
- package/dist/verifyDiff.js.map +1 -0
- package/dist/workspaces.d.ts +6 -0
- package/dist/workspaces.js +84 -0
- package/dist/workspaces.js.map +1 -0
- package/mise.toml +9 -0
- package/package.json +62 -0
- package/rules/__fixtures__/as-any-escape/bad.ts +8 -0
- package/rules/__fixtures__/as-any-escape/good.ts +14 -0
- package/rules/__fixtures__/catch-empty/bad.ts +7 -0
- package/rules/__fixtures__/catch-empty/good.ts +14 -0
- package/rules/__fixtures__/direct-store-import/bad.tsx +23 -0
- package/rules/__fixtures__/direct-store-import/good.tsx +34 -0
- package/rules/__fixtures__/direct-store-import/showcase/ignored.tsx +9 -0
- package/rules/__fixtures__/discriminator-ternary/bad.tsx +10 -0
- package/rules/__fixtures__/discriminator-ternary/good.tsx +23 -0
- package/rules/__fixtures__/hardcoded-url-in-component/components/bad.tsx +8 -0
- package/rules/__fixtures__/hardcoded-url-in-component/components/good.tsx +14 -0
- package/rules/__fixtures__/inline-map-row/bad.tsx +72 -0
- package/rules/__fixtures__/inline-map-row/good.tsx +51 -0
- package/rules/__fixtures__/instanceof-map-set/bad.ts +7 -0
- package/rules/__fixtures__/instanceof-map-set/good.ts +8 -0
- package/rules/__fixtures__/literal-union-in-component/components/bad.ts +8 -0
- package/rules/__fixtures__/literal-union-in-component/components/good.ts +23 -0
- package/rules/__fixtures__/missing-observer/bad.tsx +66 -0
- package/rules/__fixtures__/missing-observer/good.tsx +69 -0
- package/rules/__fixtures__/no-local-kit-clone/bad.ts +30 -0
- package/rules/__fixtures__/no-local-kit-clone/bad.tsx +30 -0
- package/rules/__fixtures__/no-local-kit-clone/good.ts +31 -0
- package/rules/__fixtures__/no-local-kit-clone/good.tsx +31 -0
- package/rules/__fixtures__/non-exhaustive-match/bad.ts +8 -0
- package/rules/__fixtures__/non-exhaustive-match/good.ts +14 -0
- package/rules/__fixtures__/page-store-bare-usememo/bad.ts +15 -0
- package/rules/__fixtures__/page-store-bare-usememo/bad.tsx +13 -0
- package/rules/__fixtures__/page-store-bare-usememo/good.ts +17 -0
- package/rules/__fixtures__/page-store-bare-usememo/good.tsx +14 -0
- package/rules/__fixtures__/screen-file-styling/bad.screen.tsx +10 -0
- package/rules/__fixtures__/screen-file-styling/good.screen.tsx +8 -0
- package/rules/__fixtures__/state-loading-boolean-shape/bad.ts +36 -0
- package/rules/__fixtures__/state-loading-boolean-shape/bad.tsx +36 -0
- package/rules/__fixtures__/state-loading-boolean-shape/good.ts +44 -0
- package/rules/__fixtures__/state-loading-boolean-shape/good.tsx +44 -0
- package/rules/__fixtures__/store-async-method/bad.ts +37 -0
- package/rules/__fixtures__/store-async-method/bad.tsx +37 -0
- package/rules/__fixtures__/store-async-method/good.ts +73 -0
- package/rules/__fixtures__/store-async-method/good.tsx +73 -0
- package/rules/__fixtures__/store-context-provider/bad.ts +16 -0
- package/rules/__fixtures__/store-context-provider/bad.tsx +24 -0
- package/rules/__fixtures__/store-context-provider/good.ts +13 -0
- package/rules/__fixtures__/store-context-provider/good.tsx +11 -0
- package/rules/__fixtures__/store-delegation-getter/bad.ts +30 -0
- package/rules/__fixtures__/store-delegation-getter/bad.tsx +30 -0
- package/rules/__fixtures__/store-delegation-getter/good.ts +65 -0
- package/rules/__fixtures__/store-delegation-getter/good.tsx +65 -0
- package/rules/__fixtures__/store-new-map/bad.ts +22 -0
- package/rules/__fixtures__/store-new-map/bad.tsx +22 -0
- package/rules/__fixtures__/store-new-map/good.ts +41 -0
- package/rules/__fixtures__/store-new-map/good.tsx +41 -0
- package/rules/__fixtures__/store-no-runinaction/bad.ts +67 -0
- package/rules/__fixtures__/store-no-runinaction/bad.tsx +67 -0
- package/rules/__fixtures__/store-no-runinaction/good.ts +39 -0
- package/rules/__fixtures__/store-no-runinaction/good.tsx +39 -0
- package/rules/__fixtures__/store-no-setinterval-poll/bad.ts +19 -0
- package/rules/__fixtures__/store-no-setinterval-poll/bad.tsx +19 -0
- package/rules/__fixtures__/store-no-setinterval-poll/good.ts +29 -0
- package/rules/__fixtures__/store-no-setinterval-poll/good.tsx +29 -0
- package/rules/__fixtures__/ts-pattern-dangling-match/bad.ts +20 -0
- package/rules/__fixtures__/ts-pattern-dangling-match/good.ts +35 -0
- package/rules/__fixtures__/ui-imports-app-store/src/ui/bad.tsx +13 -0
- package/rules/__fixtures__/ui-imports-app-store/src/ui/good.tsx +23 -0
- package/rules/__fixtures__/view-file-logic/bad.view.tsx +16 -0
- package/rules/__fixtures__/view-file-logic/good.view.tsx +7 -0
- package/rules/__fixtures__/view-imports-store/bad.view.tsx +13 -0
- package/rules/__fixtures__/view-imports-store/good.view.tsx +23 -0
- package/rules/__fixtures__/zod-optional-nullable/bad.ts +10 -0
- package/rules/__fixtures__/zod-optional-nullable/bad.tsx +17 -0
- package/rules/__fixtures__/zod-optional-nullable/good.ts +16 -0
- package/rules/__fixtures__/zod-optional-nullable/good.tsx +20 -0
- package/rules/as-any-escape-tsx.yml +53 -0
- package/rules/as-any-escape.yml +29 -0
- package/rules/catch-empty-tsx.yml +55 -0
- package/rules/catch-empty.yml +31 -0
- package/rules/classname-not-composed.yml +28 -0
- package/rules/classname-visual-identity.yml +34 -0
- package/rules/demo-mode-by-default-ts.yml +76 -0
- package/rules/demo-mode-by-default.yml +54 -0
- package/rules/direct-store-import.yml +118 -0
- package/rules/discriminator-ternary.yml +70 -0
- package/rules/hardcoded-url-in-component.yml +44 -0
- package/rules/inline-map-row.yml +107 -0
- package/rules/instanceof-map-set-tsx.yml +46 -0
- package/rules/instanceof-map-set.yml +22 -0
- package/rules/json-roundtrip.yml +9 -0
- package/rules/jsx-cond-and.yml +16 -0
- package/rules/jsx-ternary.yml +35 -0
- package/rules/kind-if-without-match.yml +13 -0
- package/rules/literal-union-in-component.yml +71 -0
- package/rules/match-bool-to-null.yml +15 -0
- package/rules/missing-observer.yml +189 -0
- package/rules/mobx-effect-observable-dep.yml +23 -0
- package/rules/mobx-effect-store-write.yml +21 -0
- package/rules/mobx-usestate-from-store.yml +20 -0
- package/rules/no-local-kit-clone-tsx.yml +104 -0
- package/rules/no-local-kit-clone.yml +93 -0
- package/rules/non-exhaustive-match.yml +34 -0
- package/rules/page-store-bare-usememo-tsx.yml +47 -0
- package/rules/page-store-bare-usememo.yml +43 -0
- package/rules/screen-file-styling.yml +29 -0
- package/rules/silent-default-return.yml +17 -0
- package/rules/state-loading-boolean-shape-tsx.yml +102 -0
- package/rules/state-loading-boolean-shape.yml +91 -0
- package/rules/store-async-method-tsx.yml +108 -0
- package/rules/store-async-method.yml +98 -0
- package/rules/store-context-provider-tsx.yml +68 -0
- package/rules/store-context-provider.yml +78 -0
- package/rules/store-delegation-getter-tsx.yml +104 -0
- package/rules/store-delegation-getter.yml +93 -0
- package/rules/store-new-map-tsx.yml +84 -0
- package/rules/store-new-map.yml +73 -0
- package/rules/store-no-runinaction-tsx.yml +71 -0
- package/rules/store-no-runinaction.yml +60 -0
- package/rules/store-no-setinterval-poll-tsx.yml +62 -0
- package/rules/store-no-setinterval-poll.yml +51 -0
- package/rules/ts-pattern-dangling-match-tsx.yml +41 -0
- package/rules/ts-pattern-dangling-match.yml +63 -0
- package/rules/ui-imports-app-store.yml +49 -0
- package/rules/view-file-logic.yml +53 -0
- package/rules/view-imports-store.yml +64 -0
- package/rules/zod-optional-nullable-tsx.yml +56 -0
- package/rules/zod-optional-nullable.yml +29 -0
- package/sgconfig.yml +8 -0
- package/structure/rules/component-decl.yml +48 -0
- package/structure/sgconfig.yml +7 -0
package/README.md
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# @agentvibes/guardrails
|
|
2
|
+
|
|
3
|
+
Verification toolkit that makes agents write simple code. One package holds the
|
|
4
|
+
canonical ast-grep rule set (moved here from the agent-skills `defensive-errors`
|
|
5
|
+
skill — this repo is now the source of truth; the skill and hooks consume
|
|
6
|
+
builds of this package), the react structure check, an added-lines diff
|
|
7
|
+
ratchet, and the shared biome/tsconfig presets.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
pnpm add -D @agentvibes/guardrails
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Tools (ast-grep, biome) are not npm dependencies: the CLI resolves them from
|
|
16
|
+
PATH, falls back to `mise x <tool>@<pin>` using the pins in this package's
|
|
17
|
+
`mise.toml`, and prints a one-line install hint when neither works.
|
|
18
|
+
|
|
19
|
+
Installing straight from git (`github:AgentVibes/guardrails#<sha>`) works —
|
|
20
|
+
the `prepare` script builds `dist/` at install time — but pnpm blocks
|
|
21
|
+
lifecycle scripts of git-hosted deps by default, and the unblock knob moved
|
|
22
|
+
between pnpm versions:
|
|
23
|
+
|
|
24
|
+
| pnpm | where to allow the build |
|
|
25
|
+
|---|---|
|
|
26
|
+
| 10.0–10.4 | `package.json` → `pnpm.onlyBuiltDependencies: ["@agentvibes/guardrails"]` |
|
|
27
|
+
| 10.5+ | `pnpm-workspace.yaml` → `onlyBuiltDependencies: ["@agentvibes/guardrails"]` |
|
|
28
|
+
| 11+ | `pnpm-workspace.yaml` → `allowBuilds` keyed by the exact resolved spec |
|
|
29
|
+
|
|
30
|
+
(or install from the registry once the package is published).
|
|
31
|
+
|
|
32
|
+
## Commands
|
|
33
|
+
|
|
34
|
+
All subcommands accept `--json`.
|
|
35
|
+
|
|
36
|
+
| command | what | exit |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `guardrails verify [paths]` | full scan: rule canon + text-greps + structure (one component per file, 120-line error / 90-line warning) | 1 on any error-tier finding |
|
|
39
|
+
| `guardrails verify-diff [--base R]` | ratchet: error-tier findings on lines your diff ADDED vs merge-base; falls back to whole-file, then whole-tree — never to silence | 1 on new findings only |
|
|
40
|
+
| `guardrails doctor` | tool versions + resolution route, ruleset SHA, config discovery | 1 if a tool is missing |
|
|
41
|
+
| `guardrails init` | writes `sgconfig.yml`, `biome.json` (extends the preset), and a detected-and-materialized `[stack]` in `.agentvibes/project.toml` | 0 |
|
|
42
|
+
| `guardrails metrics [paths]` | per-component (loc, hooks, props, observer, JSX depth, branching), per-file (sloc, context-cost) and per-project metrics; `--check` compares the GATED set against the committed `.guardrails/metrics.json` baseline (recomputes facts, never trusts the file); `--update-baseline` tightens it (2% hysteresis, never loosens without `--force`); `--snapshot` appends a JSONL trend row | 1 on ratchet regression, 2 when `--check` finds no baseline |
|
|
43
|
+
|
|
44
|
+
Gated (ratchet, lower = better): p90 component_loc, useState density,
|
|
45
|
+
inline-map-row count, p90 context-cost, runInAction count, async-in-store,
|
|
46
|
+
new-Map-in-store, reactions total, loading-boolean shapes. Everything else is
|
|
47
|
+
observe-only.
|
|
48
|
+
|
|
49
|
+
Counter provenance: runInAction / async-in-store / new-Map-in-store /
|
|
50
|
+
inline-map-row are finding counts of the canon rules (`store-no-runinaction`,
|
|
51
|
+
`store-async-method`, `store-new-map`, `inline-map-row` + tsx twins) — the
|
|
52
|
+
metric counts exactly what `verify` gates. Note the rules' scoping:
|
|
53
|
+
"store" means a class calling `make(Auto)Observable` (not a name/path
|
|
54
|
+
heuristic), and `async_in_store` counts only async methods that mutate `this`
|
|
55
|
+
WITHOUT a `runInAction` patch — the patched ones are already in
|
|
56
|
+
`runInAction_count`, so the two counters partition the should-be-`flow()`
|
|
57
|
+
population without double counting. `reactions_total` is a direct AST
|
|
58
|
+
count (no rule yet). `loading_boolean_shapes` is deliberately NOT the
|
|
59
|
+
`state-loading-boolean-shape` rule: the rule fires only on type-level shapes
|
|
60
|
+
where the outcome is recorded twice (loading flag + second outcome/payload
|
|
61
|
+
field), while the metric also counts lone boolean progress-flag declarations
|
|
62
|
+
(`loading = false` class fields) — the wider Resource<T> migration target.
|
|
63
|
+
|
|
64
|
+
## CI gate (reusable workflow)
|
|
65
|
+
|
|
66
|
+
Add the whole gate as one job:
|
|
67
|
+
|
|
68
|
+
```yaml
|
|
69
|
+
jobs:
|
|
70
|
+
guardrails:
|
|
71
|
+
uses: AgentVibes/guardrails/.github/workflows/guardrails-gate.yml@v0
|
|
72
|
+
# with:
|
|
73
|
+
# leaks: true # recommended for public repos
|
|
74
|
+
# base: origin/main # override the merge-base ladder
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
It checks out with full history, installs the pinned toolchain via mise, runs
|
|
78
|
+
`pnpm install --frozen-lockfile`, then BLOCKING `guardrails verify-diff` and
|
|
79
|
+
`guardrails metrics --check` (skipped with a loud `::notice::` when the repo
|
|
80
|
+
has no `.guardrails/metrics.json` baseline — never silently).
|
|
81
|
+
|
|
82
|
+
Workspaces that reach outside their repo (`link:../<repo>/...` overrides,
|
|
83
|
+
`../../<org>/<repo>` workspace globs) pass
|
|
84
|
+
`sibling_repos: "owner/repo owner2/repo2"`: the gate then checks the main
|
|
85
|
+
repo out at `repos/<org>/<repo>` depth and shallow-clones each sibling at its
|
|
86
|
+
own `repos/<owner>/<name>`, reproducing a `gits/<org>/<repo>` disk layout so
|
|
87
|
+
the relative escapes resolve. Private siblings additionally need
|
|
88
|
+
`secrets: { sibling_token: <PAT with read on them> }` — the default job token
|
|
89
|
+
cannot reach other repos. `@v0` is a
|
|
90
|
+
moving tag that follows validated releases, actions-style; pin `@<commit-sha>`
|
|
91
|
+
if your repo wants immutable supply-chain refs.
|
|
92
|
+
|
|
93
|
+
**Mandatory adoption step — prove the gate can fail.** After wiring the job,
|
|
94
|
+
open a throwaway PR containing an error-tier violation and watch it go red:
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
git checkout -b gate-red-team
|
|
98
|
+
printf 'export const boom = (x: unknown) => x as any;\n' > gateRedTeam.ts
|
|
99
|
+
git add gateRedTeam.ts && git commit -m "red-team the guardrails gate" && git push -u origin gate-red-team
|
|
100
|
+
# open the PR → the guardrails job MUST fail on as-any-escape.
|
|
101
|
+
# Then close the PR and delete the branch.
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
A gate that has never been seen red proves nothing — do not skip this.
|
|
105
|
+
|
|
106
|
+
## Stop hook
|
|
107
|
+
|
|
108
|
+
`guardrails hook-stop` is the "cannot say done over red findings" echelon: on
|
|
109
|
+
Claude Code's Stop event it runs the verify-diff ratchet over the session's
|
|
110
|
+
changed files and blocks the stop while NEW error-tier findings remain. Loop
|
|
111
|
+
breaker built in: after 3 blocks in one session (env
|
|
112
|
+
`GUARDRAILS_STOP_MAX_BLOCKS`) it stops blocking and prints a loud warning
|
|
113
|
+
instead — a wedged agent is worse than undercleaned code, and CI holds the
|
|
114
|
+
same line anyway. Like hook-postedit, it never fails the session on its own
|
|
115
|
+
defects.
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{ "hooks": { "Stop": [ { "hooks": [{ "type": "command",
|
|
119
|
+
"command": "pnpm exec guardrails hook-stop" }] } ] } }
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
For a USER-level install (synced settings.json, per-machine tools), route
|
|
123
|
+
both hooks through an opt-hook.sh-style wrapper that no-ops when the tool is
|
|
124
|
+
absent on a host — a bare command errors on every event on machines without
|
|
125
|
+
the package.
|
|
126
|
+
|
|
127
|
+
## Iterate harness
|
|
128
|
+
|
|
129
|
+
`guardrails iterate --task <file|text> --cmd '<runner>'` is the
|
|
130
|
+
iterate-until-pass loop for cheap-model campaigns: run the agent command, run
|
|
131
|
+
the gate ITSELF (never trusting the agent's report), feed the findings back as
|
|
132
|
+
the next prompt's compact feedback, repeat to green or `--max` (default 6;
|
|
133
|
+
`--gate` overrides the default `verify-diff`). The runner gets the prompt on
|
|
134
|
+
stdin and via `$GUARDRAILS_PROMPT_FILE`, plus `$GUARDRAILS_ATTEMPT`:
|
|
135
|
+
|
|
136
|
+
```sh
|
|
137
|
+
guardrails iterate --task task.md \
|
|
138
|
+
--cmd 'claude -p "$(cat "$GUARDRAILS_PROMPT_FILE")" --model sonnet' \
|
|
139
|
+
--gate 'pnpm exec guardrails verify-diff && pnpm exec guardrails metrics --check'
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Every attempt's tuple (agent exit, gate exit, gate tail, duration) is recorded
|
|
143
|
+
and printed (`--json` for harness pipelines).
|
|
144
|
+
|
|
145
|
+
## Post-edit hook
|
|
146
|
+
|
|
147
|
+
`guardrails hook-postedit` is the Claude Code PostToolUse hook: it reads the
|
|
148
|
+
hook JSON on stdin, and for an Edit/Write of a .ts/.tsx file scans just that
|
|
149
|
+
file, gating the lines the edit actually changed (merge-base ladder; Write,
|
|
150
|
+
untracked files, and unresolvable bases degrade to whole-file — never to
|
|
151
|
+
silence). Error-tier findings emit a blocking decision, warnings attach as
|
|
152
|
+
context. Wire it in settings.json:
|
|
153
|
+
|
|
154
|
+
```json
|
|
155
|
+
{ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write",
|
|
156
|
+
"hooks": [{ "type": "command", "command": "pnpm exec guardrails hook-postedit" }] } ] } }
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Leak gate
|
|
160
|
+
|
|
161
|
+
`guardrails leaks [paths]` is the public/private boundary gate: it scans every
|
|
162
|
+
text file for credential patterns, runs gitleaks when available, and exits 1
|
|
163
|
+
on any hit. The package ships only generic credential shapes; house marker
|
|
164
|
+
lists ship via private plugins/config — `.guardrails/leaks.txt`, a
|
|
165
|
+
`[leaks] patterns_file =` manifest entry, or the plugin contract's
|
|
166
|
+
`leakPatterns()` hook (see `@agentvibes/guardrails/plugin`). Pattern files are
|
|
167
|
+
one regex per line (`<id> <regex>`, `#` comments) and are themselves exempt
|
|
168
|
+
from the scan. This repo runs the gate against itself in `pnpm check` and CI.
|
|
169
|
+
|
|
170
|
+
## Deploy plugins
|
|
171
|
+
|
|
172
|
+
`guardrails deploy [args…]` is an extension point, not a deployer: the public
|
|
173
|
+
CLI resolves a plugin — `plugin = "<npm name>"` under `[deploy]` in
|
|
174
|
+
`.agentvibes/project.toml`, or a single `guardrails-plugin-*` dependency — and
|
|
175
|
+
hands it the args plus the `[deploy]` table. A plugin exports (default or
|
|
176
|
+
named `plugin`) an object `{ name, deploy(args, context) }`; the contract type
|
|
177
|
+
ships as `@agentvibes/guardrails/plugin`. All topology facts (hosts, orgs,
|
|
178
|
+
registries, SSO) live in private plugin packages; `guardrails leaks` enforces
|
|
179
|
+
that this package contains none.
|
|
180
|
+
|
|
181
|
+
## Presets
|
|
182
|
+
|
|
183
|
+
```jsonc
|
|
184
|
+
// biome.json
|
|
185
|
+
{ "extends": ["@agentvibes/guardrails/biome"] }
|
|
186
|
+
// tsconfig.json
|
|
187
|
+
{ "extends": "@agentvibes/guardrails/tsconfig" }
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Rules and fixtures
|
|
191
|
+
|
|
192
|
+
- `rules/` — the canon. Stable ids, each message is a mini-manual (why + fix).
|
|
193
|
+
Suppress a genuine false positive with
|
|
194
|
+
`// ast-grep-ignore: <rule-id> -- <why>` on the line above.
|
|
195
|
+
- `rules/__fixtures__/` — bad/good fixture pairs; `pnpm test:rules` asserts
|
|
196
|
+
exact hit counts in both directions (a rule that fails to load fails the
|
|
197
|
+
test — silence is not a pass).
|
|
198
|
+
- `structure/` — the `component-decl` marker rule the CLI turns into
|
|
199
|
+
`react-multi-component` / `react-component-too-long` /
|
|
200
|
+
`react-component-needs-folder` findings.
|
|
201
|
+
- `candidates/` — triaged in wave 2. Three PPA R-rules were promoted into
|
|
202
|
+
`rules/`; the eight that remain are repo-local by decision, with the reason
|
|
203
|
+
for each recorded in `candidates/README.md`. Still loaded nowhere.
|
|
204
|
+
|
|
205
|
+
Per-repo severity RAISE: a repo that held a rule stricter than the canon keeps
|
|
206
|
+
its gate via `[severity]` in `.agentvibes/project.toml`
|
|
207
|
+
(`zod-optional-nullable = "error"`) — applied through ast-grep's native
|
|
208
|
+
`--error=<rule-id>` in verify, verify-diff and the post-edit hook, so no
|
|
209
|
+
same-id rule fork is ever needed. Raise-only: downgrades and unknown rule ids
|
|
210
|
+
are refused (exit 2); weakening has its own sanctioned homes (warn-tier biome
|
|
211
|
+
deviation, `[verify] exclude` for vendored trees, justified per-line
|
|
212
|
+
`ast-grep-ignore`).
|
|
213
|
+
|
|
214
|
+
## Repo-local extra rules
|
|
215
|
+
|
|
216
|
+
A rule that encodes ONE repo's convention does not belong in the canon — but it
|
|
217
|
+
should still run. `guardrails init` already writes the second `ruleDir`
|
|
218
|
+
commented out; uncomment it and drop the rule in:
|
|
219
|
+
|
|
220
|
+
```yaml
|
|
221
|
+
# sgconfig.yml
|
|
222
|
+
ruleDirs:
|
|
223
|
+
- node_modules/@agentvibes/guardrails/rules
|
|
224
|
+
- .ast-grep/rules # repo-local extras
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Both directories load together and findings print their own rule id, so a local
|
|
228
|
+
rule suppresses under its own name. Verified: a canon rule and a repo-local rule
|
|
229
|
+
firing side by side in one `ast-grep scan -c sgconfig.yml` run.
|
|
230
|
+
|
|
231
|
+
Keep local ids distinct from canon ids — ast-grep refuses to load two rules with
|
|
232
|
+
the same id, and a repo-local override of a canon rule is a silent fork rather
|
|
233
|
+
than a fix. If a local rule turns out to be generally useful, promote it here
|
|
234
|
+
with fixtures instead of copying it into a second repo.
|
|
235
|
+
|
|
236
|
+
## Development
|
|
237
|
+
|
|
238
|
+
`pnpm check` = build (tsc) + biome + fixture harness + gate-can-go-red test.
|
|
239
|
+
CI runs exactly that, blocking.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# candidates/ — triaged, wave 2
|
|
2
|
+
|
|
3
|
+
The 11 PPA R-rules that came verbatim from `merkle-substrate/apps/ppa/.ast-grep/rules/`
|
|
4
|
+
have been triaged. **Three were promoted into `rules/` and deleted from here.**
|
|
5
|
+
The eight below stay **repo-local**: they encode a PPA convention rather than a
|
|
6
|
+
house rule, so they keep living in the PPA repo and load through the repo-local
|
|
7
|
+
`ruleDirs` entry (see the package README, "Repo-local extra rules").
|
|
8
|
+
|
|
9
|
+
They are still NOT in the package `sgconfig.yml` and load nowhere from here.
|
|
10
|
+
|
|
11
|
+
## Promoted to canon
|
|
12
|
+
|
|
13
|
+
| R-rule | canon id | why |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| r4-ternary-on-discriminator | `discriminator-ternary` | 18 hits outside PPA, and measured **disjoint** from both rules it looked like a duplicate of: `∩ jsx-ternary = 0`, `∩ kind-if-without-match = 0`. The three partition the space — `if` on a discriminator, ternary yielding JSX, ternary yielding values. A `not: is-jsx` clause makes that disjointness structural rather than lucky. |
|
|
16
|
+
| r14-literal-union-in-component | `literal-union-in-component` | Already a house rule — report §8 bans string-literal unions from component files in the `ui/` tier. Globs generalised from `apps/ppa/**` to `**/components/**`, `**/screens/**`, `**/app/**`. |
|
|
17
|
+
| r1a-hardcoded-url-in-component | `hardcoded-url-in-component` | Enforces CLAUDE.md "No localhost": every service URL comes from `dev url`, never a literal. Same reusability boundary `ui-imports-app-store` guards from the other side. |
|
|
18
|
+
|
|
19
|
+
## Staying repo-local
|
|
20
|
+
|
|
21
|
+
| R-rule | why it is not canon |
|
|
22
|
+
|---|---|
|
|
23
|
+
| r10-as-cast | 121 hits in tg-gallery + observatory alone. `as Type` is legitimate at boundaries this codebase does not yet route through Zod, and `as-any-escape` already covers the escape-hatch half. Canon-promoting it buys a suppression campaign. |
|
|
24
|
+
| r1d-scattered-numeric-const-in-component | 25 hits, but "module-level ALL_CAPS numeric belongs in tokens.ts" is a PPA layout convention, not a house rule. |
|
|
25
|
+
| r2-helper-fn-in-tsx | Its own message admits "unavoidable false positives — it can't distinguish PascalCase observer-wrapped components from helpers". A rule its author documents as broken does not become canon. (The stated cause, "metavariable regex unsupported in ast-grep 0.42", no longer holds on 0.45 — so a corrected version is a real candidate for a later wave, but it would be a new rule, not this one.) |
|
|
26
|
+
| r3-hooks-outside-layout | Bans useState/useEffect/useMemo outside two named layout files. That is PPA's architectural stance; the house rule (C24) permits a hook that owns DOM or lifecycle, and `usePageStore` is sanctioned. |
|
|
27
|
+
| r6-jsx-comment-smell | "A JSX comment is a smell" at severity `hint`. House code uses JSX comments for real explanation (tg-gallery, observatory); this is a PPA style preference. |
|
|
28
|
+
| r7-raw-hsl-outside-tokens | Points at `apps/ppa/lib/tokens.ts`. Tailwind remains a legal manifest value for web apps, so a raw-colour ban cannot be unconditional canon. |
|
|
29
|
+
| r8-inline-style-prop | **Stale as well as local.** It tells you to prefer NativeWind `className` over `style={{}}` — but decision #10 (2026-08-28) converges PPA to *theme-inline*, which makes inline style from a theme hook the intended idiom. The rule now argues against the decision it lives under; PPA should retire or invert it. |
|
|
30
|
+
| r9-optional-store-arg | Flags every `?:` parameter and property in store source. The principle is house-wide (make invalid states unrepresentable), but the rule as written has no way to tell a genuine optional from a modelled one, which is why PPA scoped it to two directories with three ignores. |
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
id: r10-as-cast
|
|
2
|
+
language: typescript
|
|
3
|
+
severity: warning
|
|
4
|
+
message: |
|
|
5
|
+
R10 — `as Type` cast. Use Zod .safeParse() at boundaries, type guards,
|
|
6
|
+
or fix the source type. Allowed: `as const`, `as never` (test stubs),
|
|
7
|
+
`as any` (still flagged separately by defensive-errors as-any-escape).
|
|
8
|
+
See <vault>/_agent/Notes/2026-05-03-code-quality-rules.md R10.
|
|
9
|
+
files:
|
|
10
|
+
- "apps/ppa-libs/store/src/**/*.ts"
|
|
11
|
+
- "apps/ppa/**/*.ts"
|
|
12
|
+
- "apps/ppa/**/*.tsx"
|
|
13
|
+
ignores:
|
|
14
|
+
- "**/__tests__/**"
|
|
15
|
+
- "**/*.test.ts"
|
|
16
|
+
- "**/*.test.tsx"
|
|
17
|
+
rule:
|
|
18
|
+
pattern: '$EXPR as $TYPE'
|
|
19
|
+
not:
|
|
20
|
+
any:
|
|
21
|
+
- pattern: '$EXPR as const'
|
|
22
|
+
- pattern: '$EXPR as never'
|
|
23
|
+
- pattern: '$EXPR as any'
|
|
24
|
+
- pattern: '$EXPR as unknown'
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
id: r1d-scattered-numeric-const-in-component
|
|
2
|
+
language: tsx
|
|
3
|
+
severity: warning
|
|
4
|
+
message: |
|
|
5
|
+
R1d — Module-level numeric ALL_CAPS constant in a component file.
|
|
6
|
+
Move to apps/ppa/lib/tokens.ts (layout dimensions) or a per-module
|
|
7
|
+
constants.ts (timeouts, thresholds, limits).
|
|
8
|
+
See <vault>/_agent/Notes/2026-05-03-code-quality-rules.md R1.
|
|
9
|
+
files:
|
|
10
|
+
- "apps/ppa/components/**/*.tsx"
|
|
11
|
+
rule:
|
|
12
|
+
kind: variable_declarator
|
|
13
|
+
regex: '^[A-Z][A-Z0-9_]+ = -?[0-9]'
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
id: r2-helper-fn-in-tsx
|
|
2
|
+
language: tsx
|
|
3
|
+
severity: warning
|
|
4
|
+
message: |
|
|
5
|
+
R2 — Helper function declared in a component file. Move to either:
|
|
6
|
+
- the appropriate MobX store as a `get` (computed) or method (action), or
|
|
7
|
+
- apps/ppa/lib/<scope>/ as a stateless utility.
|
|
8
|
+
Component files should be thin views; logic lives in stores.
|
|
9
|
+
Note: this rule has unavoidable false positives — it can't distinguish
|
|
10
|
+
PascalCase observer-wrapped components from helpers without metavariable
|
|
11
|
+
regex (unsupported in ast-grep 0.42). Filter visually or use the
|
|
12
|
+
scripts/lint/helpers-in-tsx.sh wrapper which adds a camelCase guard.
|
|
13
|
+
See <vault>/_agent/Notes/2026-05-03-code-quality-rules.md R2.
|
|
14
|
+
files:
|
|
15
|
+
- "apps/ppa/components/**/*.tsx"
|
|
16
|
+
rule:
|
|
17
|
+
any:
|
|
18
|
+
# Arrow functions WITH explicit return type — strongest signal of "helper"
|
|
19
|
+
- kind: variable_declarator
|
|
20
|
+
regex: '^[a-z][a-zA-Z0-9]* = .*\): [A-Za-z<>|& ]+ =>'
|
|
21
|
+
# function declarations
|
|
22
|
+
- pattern: 'function $NAME($$$): $RT { $$$ }'
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
id: r3-hooks-outside-layout
|
|
2
|
+
language: tsx
|
|
3
|
+
severity: warning
|
|
4
|
+
message: |
|
|
5
|
+
R3 — React hook (useState/useCallback/useMemo/useEffect) outside a
|
|
6
|
+
layout/page root. Promote local state to a ui-tier MobX store and
|
|
7
|
+
bind via plain props. The only legitimate hook callsites are:
|
|
8
|
+
- apps/ppa/app/_layout.tsx
|
|
9
|
+
- apps/ppa/app/(app)/_layout.tsx
|
|
10
|
+
- PageStoreManager.getOrCreate(...) wrapping at a route root
|
|
11
|
+
- Animation primitives (mark with `// hooks-ok: animation` comment)
|
|
12
|
+
See <vault>/_agent/Notes/2026-05-03-code-quality-rules.md R3.
|
|
13
|
+
files:
|
|
14
|
+
- "apps/ppa/components/**/*.tsx"
|
|
15
|
+
rule:
|
|
16
|
+
any:
|
|
17
|
+
- pattern: 'useState($$$)'
|
|
18
|
+
- pattern: 'useCallback($$$)'
|
|
19
|
+
- pattern: 'useMemo($$$)'
|
|
20
|
+
- pattern: 'useEffect($$$)'
|
|
21
|
+
- pattern: 'useReducer($$$)'
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
id: r6-jsx-comment-smell
|
|
2
|
+
language: tsx
|
|
3
|
+
severity: hint
|
|
4
|
+
message: |
|
|
5
|
+
R6 — JSX comment ({/* ... */}) is a smell. The element it labels
|
|
6
|
+
deserves a name — extract a named observer subcomponent.
|
|
7
|
+
Use the /refactor-jsx-comment slash command to extract.
|
|
8
|
+
See <vault>/_agent/Notes/2026-05-03-code-quality-rules.md R6.
|
|
9
|
+
files:
|
|
10
|
+
- "apps/ppa/components/**/*.tsx"
|
|
11
|
+
rule:
|
|
12
|
+
kind: jsx_expression
|
|
13
|
+
has:
|
|
14
|
+
kind: comment
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
id: r7-raw-hsl-outside-tokens
|
|
2
|
+
language: tsx
|
|
3
|
+
severity: warning
|
|
4
|
+
message: |
|
|
5
|
+
R7 — Raw hsl(...) string in a component. Use useTokens() and the
|
|
6
|
+
semantic tokens defined in apps/ppa/lib/tokens.ts. If the token
|
|
7
|
+
you need doesn't exist, ADD it to tokens.ts/themed-tokens.ts first.
|
|
8
|
+
See <vault>/_agent/Notes/2026-05-03-code-quality-rules.md R7
|
|
9
|
+
and AGENTS.md (HSL/tokens rule).
|
|
10
|
+
files:
|
|
11
|
+
- "apps/ppa/components/**/*.tsx"
|
|
12
|
+
ignores:
|
|
13
|
+
- "**/_fixture.ts"
|
|
14
|
+
- "**/__tests__/**"
|
|
15
|
+
rule:
|
|
16
|
+
kind: string
|
|
17
|
+
regex: 'hsl\('
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
id: r8-inline-style-prop
|
|
2
|
+
language: tsx
|
|
3
|
+
severity: hint
|
|
4
|
+
message: |
|
|
5
|
+
R8 — Inline style={{}} prop. Prefer NativeWind className for static
|
|
6
|
+
values (color, spacing, layout, radius). Reserve style={{}} for
|
|
7
|
+
genuinely dynamic values: animated transforms, computed widths/positions.
|
|
8
|
+
See <vault>/_agent/Notes/2026-05-03-code-quality-rules.md R8.
|
|
9
|
+
files:
|
|
10
|
+
- "apps/ppa/components/**/*.tsx"
|
|
11
|
+
rule:
|
|
12
|
+
kind: jsx_attribute
|
|
13
|
+
regex: '^style=\{\{'
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
id: r9-optional-store-arg
|
|
2
|
+
language: typescript
|
|
3
|
+
severity: warning
|
|
4
|
+
message: |
|
|
5
|
+
R9 — Optional `?:` parameter or property in store API. Either every
|
|
6
|
+
caller passes it (required) or none do (don't define it). For "patch
|
|
7
|
+
any subset" shapes, model as a discriminated union of intents OR
|
|
8
|
+
split into N narrow methods.
|
|
9
|
+
Boundary cases (wrapping isomorphic-git, etc.) are exempt via the
|
|
10
|
+
ignores list below.
|
|
11
|
+
See <vault>/_agent/Notes/2026-05-03-code-quality-rules.md R9.
|
|
12
|
+
files:
|
|
13
|
+
- "apps/ppa-libs/store/src/**/*.ts"
|
|
14
|
+
- "packages/gitapp-store/src/**/*.ts"
|
|
15
|
+
ignores:
|
|
16
|
+
- "**/__tests__/**"
|
|
17
|
+
- "**/contents/iso-git/**"
|
|
18
|
+
- "**/operations.ts"
|
|
19
|
+
rule:
|
|
20
|
+
any:
|
|
21
|
+
- kind: property_signature
|
|
22
|
+
regex: '\?:'
|
|
23
|
+
- kind: required_parameter
|
|
24
|
+
regex: '\?:'
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/2.5.10/schema.json",
|
|
3
|
+
"root": false,
|
|
4
|
+
"vcs": {
|
|
5
|
+
"enabled": true,
|
|
6
|
+
"clientKind": "git",
|
|
7
|
+
"useIgnoreFile": true
|
|
8
|
+
},
|
|
9
|
+
"files": {
|
|
10
|
+
"ignoreUnknown": false,
|
|
11
|
+
"includes": [
|
|
12
|
+
"**/*.ts",
|
|
13
|
+
"**/*.tsx",
|
|
14
|
+
"**/*.json",
|
|
15
|
+
"!**/dist",
|
|
16
|
+
"!**/node_modules",
|
|
17
|
+
"!**/*.tsbuildinfo",
|
|
18
|
+
"!pnpm-lock.yaml"
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
"formatter": {
|
|
22
|
+
"enabled": true,
|
|
23
|
+
"indentStyle": "space",
|
|
24
|
+
"indentWidth": 2,
|
|
25
|
+
"lineWidth": 100,
|
|
26
|
+
"lineEnding": "lf"
|
|
27
|
+
},
|
|
28
|
+
"javascript": {
|
|
29
|
+
"formatter": {
|
|
30
|
+
"quoteStyle": "double",
|
|
31
|
+
"trailingCommas": "all",
|
|
32
|
+
"semicolons": "always",
|
|
33
|
+
"arrowParentheses": "always",
|
|
34
|
+
"bracketSpacing": true
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"json": {
|
|
38
|
+
"formatter": {
|
|
39
|
+
"indentWidth": 2,
|
|
40
|
+
"trailingCommas": "none"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"linter": {
|
|
44
|
+
"enabled": true,
|
|
45
|
+
"rules": {
|
|
46
|
+
"preset": "recommended",
|
|
47
|
+
"complexity": {
|
|
48
|
+
"useArrowFunction": "warn",
|
|
49
|
+
"useLiteralKeys": "error",
|
|
50
|
+
"useOptionalChain": "warn"
|
|
51
|
+
},
|
|
52
|
+
"correctness": {
|
|
53
|
+
"noUnusedImports": "error",
|
|
54
|
+
"noUnusedVariables": "error",
|
|
55
|
+
"useExhaustiveDependencies": "warn"
|
|
56
|
+
},
|
|
57
|
+
"style": {
|
|
58
|
+
"noNonNullAssertion": "off",
|
|
59
|
+
"useAsConstAssertion": "error",
|
|
60
|
+
"useConst": "error",
|
|
61
|
+
"useFilenamingConvention": {
|
|
62
|
+
"level": "error",
|
|
63
|
+
"options": {
|
|
64
|
+
"requireAscii": true,
|
|
65
|
+
"filenameCases": ["camelCase", "PascalCase"]
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"useImportType": "error",
|
|
69
|
+
"useNodejsImportProtocol": "error",
|
|
70
|
+
"useTemplate": "warn",
|
|
71
|
+
"useThrowOnlyError": "error"
|
|
72
|
+
},
|
|
73
|
+
"suspicious": {
|
|
74
|
+
"noConsole": {
|
|
75
|
+
"level": "warn",
|
|
76
|
+
"options": {
|
|
77
|
+
"allow": ["error", "warn"]
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"noEmptyBlockStatements": "error",
|
|
81
|
+
"noExplicitAny": "error"
|
|
82
|
+
},
|
|
83
|
+
"performance": {
|
|
84
|
+
"noBarrelFile": "off"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"assist": {
|
|
89
|
+
"enabled": true,
|
|
90
|
+
"actions": {
|
|
91
|
+
"source": {
|
|
92
|
+
"organizeImports": "on"
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"lib": ["ES2022"],
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"resolveJsonModule": true,
|
|
8
|
+
"strict": true,
|
|
9
|
+
"noUncheckedIndexedAccess": true,
|
|
10
|
+
"exactOptionalPropertyTypes": true,
|
|
11
|
+
"noImplicitOverride": true,
|
|
12
|
+
"noImplicitReturns": true,
|
|
13
|
+
"noFallthroughCasesInSwitch": true,
|
|
14
|
+
"forceConsistentCasingInFileNames": true,
|
|
15
|
+
"skipLibCheck": true,
|
|
16
|
+
"esModuleInterop": true,
|
|
17
|
+
"allowSyntheticDefaultImports": true,
|
|
18
|
+
"isolatedModules": true,
|
|
19
|
+
"verbatimModuleSyntax": true
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Finding, Severity } from "./findings.js";
|
|
2
|
+
export interface AstGrepRow {
|
|
3
|
+
ruleId: string;
|
|
4
|
+
file: string;
|
|
5
|
+
/** 1-based */
|
|
6
|
+
startLine: number;
|
|
7
|
+
/** inclusive line span of the matched node */
|
|
8
|
+
spanLines: number;
|
|
9
|
+
severity: Severity;
|
|
10
|
+
message: string;
|
|
11
|
+
metaText: (name: string) => string | undefined;
|
|
12
|
+
}
|
|
13
|
+
/** Run `ast-grep scan -c <config> --json` and return structured matches. */
|
|
14
|
+
export declare function scan(configPath: string, targets: string[], extraArgs?: string[]): AstGrepRow[];
|
|
15
|
+
/** Reportable (error/warning) rule findings, dropping marker/hint tiers. */
|
|
16
|
+
export declare function scanFindings(configPath: string, targets: string[], extraArgs?: string[]): Finding[];
|
package/dist/astGrep.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { resolveTool } from "./toolResolve.js";
|
|
3
|
+
/** Run `ast-grep scan -c <config> --json` and return structured matches. */
|
|
4
|
+
export function scan(configPath, targets, extraArgs = []) {
|
|
5
|
+
const tool = resolveTool("ast-grep");
|
|
6
|
+
const [cmd, ...prefix] = tool.argv;
|
|
7
|
+
if (cmd === undefined)
|
|
8
|
+
throw new Error("empty ast-grep argv");
|
|
9
|
+
const res = spawnSync(cmd, [...prefix, "scan", "-c", configPath, ...extraArgs, "--json", ...targets], {
|
|
10
|
+
encoding: "utf8",
|
|
11
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
12
|
+
});
|
|
13
|
+
if (res.error)
|
|
14
|
+
throw res.error;
|
|
15
|
+
const out = res.stdout.trim();
|
|
16
|
+
if (out === "")
|
|
17
|
+
return [];
|
|
18
|
+
let parsed;
|
|
19
|
+
try {
|
|
20
|
+
parsed = JSON.parse(out);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
throw new Error(`ast-grep produced unparsable JSON (exit ${res.status}). stderr:\n${res.stderr.slice(0, 2000)}`);
|
|
24
|
+
}
|
|
25
|
+
return parsed.map((m) => ({
|
|
26
|
+
ruleId: m.ruleId,
|
|
27
|
+
file: m.file,
|
|
28
|
+
startLine: m.range.start.line + 1,
|
|
29
|
+
spanLines: m.range.end.line - m.range.start.line + 1,
|
|
30
|
+
severity: m.severity,
|
|
31
|
+
message: m.message,
|
|
32
|
+
metaText: (name) => m.metaVariables?.single?.[name]?.text,
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
/** Reportable (error/warning) rule findings, dropping marker/hint tiers. */
|
|
36
|
+
export function scanFindings(configPath, targets, extraArgs = []) {
|
|
37
|
+
return scan(configPath, targets, extraArgs)
|
|
38
|
+
.filter((r) => r.severity === "error" || r.severity === "warning")
|
|
39
|
+
.map((r) => ({
|
|
40
|
+
rule: r.ruleId,
|
|
41
|
+
severity: r.severity,
|
|
42
|
+
file: r.file,
|
|
43
|
+
line: r.startLine,
|
|
44
|
+
message: r.message,
|
|
45
|
+
source: "ast-grep",
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=astGrep.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"astGrep.js","sourceRoot":"","sources":["../src/astGrep.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAuB/C,4EAA4E;AAC5E,MAAM,UAAU,IAAI,CAClB,UAAkB,EAClB,OAAiB,EACjB,YAAsB,EAAE;IAExB,MAAM,IAAI,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC;IACrC,MAAM,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC;IACnC,IAAI,GAAG,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;IAC9D,MAAM,GAAG,GAAG,SAAS,CACnB,GAAG,EACH,CAAC,GAAG,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,SAAS,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,EACzE;QACE,QAAQ,EAAE,MAAM;QAChB,SAAS,EAAE,GAAG,GAAG,IAAI,GAAG,IAAI;KAC7B,CACF,CAAC;IACF,IAAI,GAAG,CAAC,KAAK;QAAE,MAAM,GAAG,CAAC,KAAK,CAAC;IAC/B,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IAC9B,IAAI,GAAG,KAAK,EAAE;QAAE,OAAO,EAAE,CAAC;IAC1B,IAAI,MAAsB,CAAC;IAC3B,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAmB,CAAC;IAC7C,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CACb,2CAA2C,GAAG,CAAC,MAAM,eAAe,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,CAChG,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACxB,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC;QACjC,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC;QACpD,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,QAAQ,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,EAAE,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI;KAClE,CAAC,CAAC,CAAC;AACN,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,YAAY,CAC1B,UAAkB,EAClB,OAAiB,EACjB,YAAsB,EAAE;IAExB,OAAO,IAAI,CAAC,UAAU,EAAE,OAAO,EAAE,SAAS,CAAC;SACxC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC;SACjE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACX,IAAI,EAAE,CAAC,CAAC,MAAM;QACd,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,IAAI,EAAE,CAAC,CAAC,SAAS;QACjB,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,MAAM,EAAE,UAAmB;KAC5B,CAAC,CAAC,CAAC;AACR,CAAC"}
|
package/dist/cli.d.ts
ADDED