@agentvibes/guardrails 0.1.0 → 0.2.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 +131 -5
- package/dist/astGrep.d.ts +3 -0
- package/dist/astGrep.js +15 -2
- package/dist/astGrep.js.map +1 -1
- package/dist/doctor.js +38 -2
- package/dist/doctor.js.map +1 -1
- package/dist/hookPostedit.js +18 -1
- package/dist/hookPostedit.js.map +1 -1
- package/dist/presetDrift.d.ts +31 -0
- package/dist/presetDrift.js +226 -0
- package/dist/presetDrift.js.map +1 -0
- package/dist/presetDriftTest.d.ts +1 -0
- package/dist/presetDriftTest.js +136 -0
- package/dist/presetDriftTest.js.map +1 -0
- package/dist/repoRules.d.ts +27 -0
- package/dist/repoRules.js +86 -0
- package/dist/repoRules.js.map +1 -0
- package/dist/repoRulesTest.d.ts +1 -0
- package/dist/repoRulesTest.js +122 -0
- package/dist/repoRulesTest.js.map +1 -0
- package/dist/screenScope.d.ts +16 -5
- package/dist/screenScope.js +66 -21
- package/dist/screenScope.js.map +1 -1
- package/dist/screenScopeTest.js +67 -7
- package/dist/screenScopeTest.js.map +1 -1
- package/dist/siblingCloneTest.d.ts +1 -0
- package/dist/siblingCloneTest.js +201 -0
- package/dist/siblingCloneTest.js.map +1 -0
- package/dist/testRules.js +77 -0
- package/dist/testRules.js.map +1 -1
- package/dist/twinCoverageTest.d.ts +1 -0
- package/dist/twinCoverageTest.js +262 -0
- package/dist/twinCoverageTest.js.map +1 -0
- package/dist/verify.d.ts +1 -1
- package/dist/verify.js +33 -8
- package/dist/verify.js.map +1 -1
- package/dist/verifyDiff.js +6 -5
- package/dist/verifyDiff.js.map +1 -1
- package/package.json +7 -3
- package/rules/__fixtures__/demo-mode-by-default/bad.ts +22 -0
- package/rules/__fixtures__/demo-mode-by-default/bad.tsx +22 -0
- package/rules/__fixtures__/demo-mode-by-default/good.ts +21 -0
- package/rules/__fixtures__/demo-mode-by-default/good.tsx +21 -0
- package/rules/__fixtures__/json-roundtrip/bad.ts +6 -0
- package/rules/__fixtures__/json-roundtrip/bad.tsx +8 -0
- package/rules/__fixtures__/json-roundtrip/good.ts +5 -0
- package/rules/__fixtures__/json-roundtrip/good.tsx +7 -0
- package/rules/__fixtures__/kind-if-without-match/bad.ts +17 -0
- package/rules/__fixtures__/kind-if-without-match/bad.tsx +18 -0
- package/rules/__fixtures__/kind-if-without-match/good.ts +14 -0
- package/rules/__fixtures__/kind-if-without-match/good.tsx +13 -0
- package/rules/__fixtures__/literal-union-in-component/components/bad.tsx +16 -0
- package/rules/__fixtures__/literal-union-in-component/components/good.tsx +25 -0
- package/rules/__fixtures__/non-exhaustive-match/bad.tsx +12 -0
- package/rules/__fixtures__/non-exhaustive-match/good.tsx +14 -0
- package/rules/__fixtures__/silent-default-return/bad.ts +9 -0
- package/rules/__fixtures__/silent-default-return/bad.tsx +10 -0
- package/rules/__fixtures__/silent-default-return/good.ts +17 -0
- package/rules/__fixtures__/silent-default-return/good.tsx +11 -0
- package/rules/demo-mode-by-default-ts.yml +17 -1
- package/rules/demo-mode-by-default.yml +17 -1
- package/rules/json-roundtrip-tsx.yml +26 -0
- package/rules/kind-if-without-match-tsx.yml +31 -0
- package/rules/literal-union-in-component-tsx.yml +81 -0
- package/rules/no-local-kit-clone-tsx.yml +16 -0
- package/rules/no-local-kit-clone.yml +16 -0
- package/rules/non-exhaustive-match-tsx.yml +48 -0
- package/rules/silent-default-return-tsx.yml +33 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { match } from "ts-pattern";
|
|
4
|
+
import { rulesDir } from "./packagePaths.js";
|
|
5
|
+
const EXCEPTIONS = {
|
|
6
|
+
// ── JSX-node rules: the pattern cannot be spelled in a .ts grammar ──────
|
|
7
|
+
"classname-not-composed": {
|
|
8
|
+
only: "tsx",
|
|
9
|
+
kind: "impossible",
|
|
10
|
+
why: "matches a `jsx_attribute`; there are none in .ts",
|
|
11
|
+
},
|
|
12
|
+
"classname-visual-identity": {
|
|
13
|
+
only: "tsx",
|
|
14
|
+
kind: "impossible",
|
|
15
|
+
why: "matches a `jsx_attribute`; there are none in .ts",
|
|
16
|
+
},
|
|
17
|
+
"inline-map-row": {
|
|
18
|
+
only: "tsx",
|
|
19
|
+
kind: "impossible",
|
|
20
|
+
why: "matches `jsx_element` / `jsx_expression` inside a `.map()`; there are none in .ts",
|
|
21
|
+
},
|
|
22
|
+
"jsx-cond-and": {
|
|
23
|
+
only: "tsx",
|
|
24
|
+
kind: "impossible",
|
|
25
|
+
why: "the whole rule is `$COND && <JSX/>` patterns",
|
|
26
|
+
},
|
|
27
|
+
"jsx-ternary": {
|
|
28
|
+
only: "tsx",
|
|
29
|
+
kind: "impossible",
|
|
30
|
+
why: "requires a JSX consequence or alternative, by its `is-jsx` util",
|
|
31
|
+
},
|
|
32
|
+
// ── `files:`-scoped rules: the missing arm would have no file to read ───
|
|
33
|
+
"direct-store-import": {
|
|
34
|
+
only: "tsx",
|
|
35
|
+
kind: "impossible",
|
|
36
|
+
why: "`files:` is `**/*.tsx` only",
|
|
37
|
+
},
|
|
38
|
+
"screen-file-styling": {
|
|
39
|
+
only: "tsx",
|
|
40
|
+
kind: "impossible",
|
|
41
|
+
why: "`files:` is `**/*.screen.tsx` only (and it matches a `jsx_attribute`)",
|
|
42
|
+
},
|
|
43
|
+
"view-file-logic": {
|
|
44
|
+
only: "tsx",
|
|
45
|
+
kind: "impossible",
|
|
46
|
+
why: "`files:` is `**/*.view.tsx` only",
|
|
47
|
+
},
|
|
48
|
+
"view-imports-store": {
|
|
49
|
+
only: "tsx",
|
|
50
|
+
kind: "impossible",
|
|
51
|
+
why: "`files:` is `**/*.view.tsx` only",
|
|
52
|
+
},
|
|
53
|
+
// ── React-runtime rules: the construct only exists in a component ───────
|
|
54
|
+
"missing-observer": {
|
|
55
|
+
only: "tsx",
|
|
56
|
+
kind: "impossible",
|
|
57
|
+
why: "asks whether a COMPONENT reading a store is wrapped in `observer()`; a .ts module is not a component",
|
|
58
|
+
},
|
|
59
|
+
"mobx-effect-observable-dep": {
|
|
60
|
+
only: "tsx",
|
|
61
|
+
kind: "impossible",
|
|
62
|
+
why: "matches `useEffect(cb, [deps])` in a component; a .ts custom hook has no store to depend on in this shape",
|
|
63
|
+
},
|
|
64
|
+
"mobx-effect-store-write": {
|
|
65
|
+
only: "tsx",
|
|
66
|
+
kind: "impossible",
|
|
67
|
+
why: "matches a store write inside a component's `useEffect`",
|
|
68
|
+
},
|
|
69
|
+
"mobx-usestate-from-store": {
|
|
70
|
+
only: "tsx",
|
|
71
|
+
kind: "impossible",
|
|
72
|
+
why: "matches `useState(store.field)` — seeding component state from a store",
|
|
73
|
+
},
|
|
74
|
+
// ── Real gaps. Measured, tracked, not silently green. ───────────────────
|
|
75
|
+
"discriminator-ternary": {
|
|
76
|
+
only: "tsx",
|
|
77
|
+
kind: "gap",
|
|
78
|
+
why: "stands down when a branch is JSX, so what it matches is a plain ternary that .ts can hold too; measured 0 in .ts across the five consumer repos 2026-09-06 (is-760b0b4b)",
|
|
79
|
+
},
|
|
80
|
+
"match-bool-to-null": {
|
|
81
|
+
only: "tsx",
|
|
82
|
+
kind: "gap",
|
|
83
|
+
why: "the `match(cond).with(true, …).with(false, () => null)` pattern does not require JSX; measured 0 in .ts across the five consumer repos 2026-09-06 (is-760b0b4b)",
|
|
84
|
+
},
|
|
85
|
+
"hardcoded-url-in-component": {
|
|
86
|
+
only: "tsx",
|
|
87
|
+
kind: "gap",
|
|
88
|
+
why: "its `files:` globs already name `**/components/**/*.ts` and `**/screens/**/*.ts`, which `language: tsx` makes dead lines; measured 0 in .ts across the five consumer repos 2026-09-06 (is-760b0b4b)",
|
|
89
|
+
},
|
|
90
|
+
"ui-imports-app-store": {
|
|
91
|
+
only: "tsx",
|
|
92
|
+
kind: "gap",
|
|
93
|
+
why: "`files:` names whole directories, so the .ts files in them are unscanned; measured 0 in .ts across the five consumer repos 2026-09-06, but this rule is error-tier and its .ts arm can turn a consumer's CI red — that is a decision, not a chore (is-760b0b4b)",
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Keys compared between the two arms of a family.
|
|
98
|
+
*
|
|
99
|
+
* `severity` is the one that must be byte-identical: the same code reported as
|
|
100
|
+
* a warning in `.ts` and an error in `.tsx` is a gate that depends on a file
|
|
101
|
+
* extension, and nothing else in this package works that way.
|
|
102
|
+
*
|
|
103
|
+
* `files` and `ignores` are compared with `.tsx` folded to `.ts`, because they
|
|
104
|
+
* SHOULD differ — the `.ts` arm ignores `**\/*.test.ts` and the tsx arm
|
|
105
|
+
* `**\/*.test.tsx`, naming the same intent. Folded, a real divergence (one arm
|
|
106
|
+
* scoped to a directory the other is not) still shows.
|
|
107
|
+
*
|
|
108
|
+
* `rule` and `utils` are deliberately NOT compared. The twin comments say "keep
|
|
109
|
+
* the rule block IDENTICAL" and that is the right default, but it is a rule of
|
|
110
|
+
* thumb rather than an invariant: `store-context-provider-tsx` carries an extra
|
|
111
|
+
* `jsx_opening_element` arm for `<XxxStoreContext.Provider>`, which cannot be
|
|
112
|
+
* spelled in the `.ts` grammar at all. Asserting identity here would force that
|
|
113
|
+
* correct rule to be wrong.
|
|
114
|
+
*/
|
|
115
|
+
const MUST_MATCH = ["severity"];
|
|
116
|
+
const MUST_MATCH_FOLDED = ["files", "ignores"];
|
|
117
|
+
/** Erase the one difference two arms are SUPPOSED to have: the extension. */
|
|
118
|
+
function foldExtensions(block) {
|
|
119
|
+
return block.replace(/\.tsx\b/g, ".ts");
|
|
120
|
+
}
|
|
121
|
+
/** Split a rule file into its column-0 `key:` blocks, dropping column-0 comments. */
|
|
122
|
+
function topLevelBlocks(text) {
|
|
123
|
+
const blocks = new Map();
|
|
124
|
+
let key;
|
|
125
|
+
let buf = [];
|
|
126
|
+
const flush = () => {
|
|
127
|
+
if (key !== undefined)
|
|
128
|
+
blocks.set(key, buf.join("\n").trimEnd());
|
|
129
|
+
key = undefined;
|
|
130
|
+
buf = [];
|
|
131
|
+
};
|
|
132
|
+
for (const line of text.split("\n")) {
|
|
133
|
+
const head = /^([A-Za-z_][A-Za-z0-9_-]*):(.*)$/.exec(line);
|
|
134
|
+
if (head?.[1] !== undefined) {
|
|
135
|
+
flush();
|
|
136
|
+
key = head[1];
|
|
137
|
+
buf = [head[2] ?? ""];
|
|
138
|
+
}
|
|
139
|
+
else if (line.startsWith("#")) {
|
|
140
|
+
// A column-0 comment is provenance prose. It ENDS the block above it —
|
|
141
|
+
// without this, the header comments between `id:` and `language:` were
|
|
142
|
+
// swallowed into the id and every family name came out as an essay.
|
|
143
|
+
flush();
|
|
144
|
+
}
|
|
145
|
+
else if (key !== undefined) {
|
|
146
|
+
buf.push(line);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
flush();
|
|
150
|
+
return blocks;
|
|
151
|
+
}
|
|
152
|
+
function loadRules() {
|
|
153
|
+
const errors = [];
|
|
154
|
+
const rules = [];
|
|
155
|
+
for (const file of readdirSync(rulesDir)
|
|
156
|
+
.filter((f) => f.endsWith(".yml"))
|
|
157
|
+
.sort()) {
|
|
158
|
+
const blocks = topLevelBlocks(readFileSync(join(rulesDir, file), "utf8"));
|
|
159
|
+
const id = blocks.get("id")?.trim();
|
|
160
|
+
const language = blocks.get("language")?.trim();
|
|
161
|
+
if (id === undefined || id === "") {
|
|
162
|
+
errors.push(`${file}: no top-level \`id:\``);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (language !== "typescript" && language !== "tsx") {
|
|
166
|
+
errors.push(`${file}: \`language:\` is ${language ?? "(absent)"}, expected typescript or tsx`);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (id !== file.replace(/\.yml$/, "")) {
|
|
170
|
+
errors.push(`${file}: id \`${id}\` does not match the filename — ast-grep reports the id, so the two must agree`);
|
|
171
|
+
}
|
|
172
|
+
rules.push({ file, id, language, blocks });
|
|
173
|
+
}
|
|
174
|
+
return { rules, errors };
|
|
175
|
+
}
|
|
176
|
+
/** The id with a trailing `-ts` / `-tsx` stripped. */
|
|
177
|
+
function familyOf(id) {
|
|
178
|
+
return id.replace(/-tsx$/, "").replace(/-ts$/, "");
|
|
179
|
+
}
|
|
180
|
+
function main() {
|
|
181
|
+
console.log("== rule twin coverage ==");
|
|
182
|
+
const { rules, errors } = loadRules();
|
|
183
|
+
let fail = errors.length > 0;
|
|
184
|
+
for (const e of errors)
|
|
185
|
+
console.log(`FAIL ${e}`);
|
|
186
|
+
const families = new Map();
|
|
187
|
+
for (const r of rules) {
|
|
188
|
+
const fam = families.get(familyOf(r.id)) ?? {};
|
|
189
|
+
const seen = fam[r.language];
|
|
190
|
+
if (seen !== undefined) {
|
|
191
|
+
console.log(`FAIL ${familyOf(r.id)}: two \`language: ${r.language}\` arms — ${seen.file} and ${r.file}`);
|
|
192
|
+
fail = true;
|
|
193
|
+
}
|
|
194
|
+
fam[r.language] = r;
|
|
195
|
+
families.set(familyOf(r.id), fam);
|
|
196
|
+
}
|
|
197
|
+
const gaps = [];
|
|
198
|
+
for (const [name, arms] of [...families].sort()) {
|
|
199
|
+
const exception = EXCEPTIONS[name];
|
|
200
|
+
const missing = arms.typescript === undefined ? "typescript" : arms.tsx === undefined ? "tsx" : undefined;
|
|
201
|
+
if (missing === undefined) {
|
|
202
|
+
if (exception !== undefined) {
|
|
203
|
+
console.log(`FAIL ${name}: both arms exist, but EXCEPTIONS still lists it as ${exception.only}-only — delete the stale entry`);
|
|
204
|
+
fail = true;
|
|
205
|
+
}
|
|
206
|
+
// Both arms present: they must agree on what they report.
|
|
207
|
+
const a = arms.typescript;
|
|
208
|
+
const b = arms.tsx;
|
|
209
|
+
if (a !== undefined && b !== undefined) {
|
|
210
|
+
const drifted = [
|
|
211
|
+
...MUST_MATCH.filter((k) => (a.blocks.get(k) ?? "") !== (b.blocks.get(k) ?? "")),
|
|
212
|
+
...MUST_MATCH_FOLDED.filter((k) => foldExtensions(a.blocks.get(k) ?? "") !== foldExtensions(b.blocks.get(k) ?? "")),
|
|
213
|
+
];
|
|
214
|
+
if (drifted.length > 0) {
|
|
215
|
+
console.log(`FAIL ${name}: ${a.file} and ${b.file} disagree on ${drifted.join(", ")} — twins are one rule wearing two ids`);
|
|
216
|
+
fail = true;
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
console.log(` ok ${name}: ${a.id} + ${b.id}, same severity and same scope`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
const covered = missing === "tsx" ? "typescript" : "tsx";
|
|
225
|
+
if (exception === undefined) {
|
|
226
|
+
console.log(`FAIL ${name}: \`language: ${covered}\` only — no ${missing} arm and no EXCEPTIONS entry. ` +
|
|
227
|
+
`ast-grep's languages are disjoint, so this rule is blind to every .${missing === "tsx" ? "tsx" : "ts"} file. ` +
|
|
228
|
+
`Write ${name}${missing === "tsx" ? "-tsx" : "-ts"}.yml, or add an entry saying why it cannot fire there.`);
|
|
229
|
+
fail = true;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (exception.only !== covered) {
|
|
233
|
+
console.log(`FAIL ${name}: EXCEPTIONS says ${exception.only}-only but the rule on disk is ${covered}-only`);
|
|
234
|
+
fail = true;
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
match(exception.kind)
|
|
238
|
+
.with("gap", () => {
|
|
239
|
+
gaps.push(`${name} (no .${missing === "tsx" ? "tsx" : "ts"} arm): ${exception.why}`);
|
|
240
|
+
console.log(` ok ${name}: ${covered}-only, KNOWN GAP — ${exception.why}`);
|
|
241
|
+
})
|
|
242
|
+
.with("impossible", () => {
|
|
243
|
+
console.log(` ok ${name}: ${covered}-only — ${exception.why}`);
|
|
244
|
+
})
|
|
245
|
+
.exhaustive();
|
|
246
|
+
}
|
|
247
|
+
if (gaps.length > 0) {
|
|
248
|
+
console.log("");
|
|
249
|
+
console.log(`known language gaps (${gaps.length}) — tracked, not fixed:`);
|
|
250
|
+
for (const g of gaps)
|
|
251
|
+
console.log(` · ${g}`);
|
|
252
|
+
}
|
|
253
|
+
console.log("");
|
|
254
|
+
if (fail) {
|
|
255
|
+
console.error("twin coverage assertions FAILED");
|
|
256
|
+
return 1;
|
|
257
|
+
}
|
|
258
|
+
console.log(`all ${families.size} rule families accounted for`);
|
|
259
|
+
return 0;
|
|
260
|
+
}
|
|
261
|
+
process.exit(main());
|
|
262
|
+
//# sourceMappingURL=twinCoverageTest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"twinCoverageTest.js","sourceRoot":"","sources":["../src/twinCoverageTest.ts"],"names":[],"mappings":"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"}
|
package/dist/verify.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { type Finding } from "./findings.js";
|
|
2
|
-
export declare function collectVerifyFindings(targets: string[], severityArgs?: string[]): Finding[];
|
|
2
|
+
export declare function collectVerifyFindings(targets: string[], severityArgs?: string[], cwd?: string): Finding[];
|
|
3
3
|
export declare function runVerify(targets: string[], json: boolean): number;
|
package/dist/verify.js
CHANGED
|
@@ -1,23 +1,46 @@
|
|
|
1
|
-
import { scanFindings } from "./astGrep.js";
|
|
1
|
+
import { AstGrepConfigError, scanFindings } from "./astGrep.js";
|
|
2
2
|
import { formatFinding, hasErrors } from "./findings.js";
|
|
3
3
|
import { rulesConfig } from "./packagePaths.js";
|
|
4
|
-
import {
|
|
4
|
+
import { driftMessage, presetDrift, presetEnforced } from "./presetDrift.js";
|
|
5
|
+
import { dedupeFindings, ruleConfigs } from "./repoRules.js";
|
|
6
|
+
import { dropScopeExempt, ScreenScopeError, scopeExemptRuleIds, scopePatterns, } from "./screenScope.js";
|
|
5
7
|
import { SeverityConfigError, severityRaiseArgs } from "./severity.js";
|
|
6
8
|
import { structureFindings } from "./structure.js";
|
|
7
9
|
import { textGrepFindings } from "./textGrep.js";
|
|
8
|
-
export function collectVerifyFindings(targets, severityArgs = []) {
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
export function collectVerifyFindings(targets, severityArgs = [], cwd = process.cwd()) {
|
|
11
|
+
// Canon first, then the repo's own sgconfig.yml when it has one — see
|
|
12
|
+
// repoRules.ts for why both and not either alone. The repo config normally
|
|
13
|
+
// lists the canon ruleDir too, so the overlap is deduped rather than
|
|
14
|
+
// reported twice.
|
|
15
|
+
const findings = dedupeFindings([
|
|
16
|
+
...ruleConfigs(cwd, rulesConfig).flatMap((c) => scanFindings(c, targets, severityArgs)),
|
|
11
17
|
...textGrepFindings(targets),
|
|
12
18
|
...structureFindings(targets),
|
|
13
|
-
];
|
|
19
|
+
]);
|
|
14
20
|
// Applied HERE rather than in `runVerify`, so verify-diff and the hooks get
|
|
15
21
|
// the same scoping: a screen that is legal under `verify` must not be gated
|
|
16
22
|
// by `verify-diff` on the same line.
|
|
17
|
-
return
|
|
23
|
+
return dropScopeExempt(findings, scopePatterns(cwd), scopeExemptRuleIds());
|
|
18
24
|
}
|
|
19
25
|
export function runVerify(targets, json) {
|
|
20
26
|
const paths = targets.length > 0 ? targets : ["."];
|
|
27
|
+
// Preset drift is checked FIRST (is-9c7a78d7): a repo running its own biome
|
|
28
|
+
// config is not being linted by the thing this gate reports on, so a finding
|
|
29
|
+
// count from it means less than it looks. Enforcement is opt-in per repo
|
|
30
|
+
// (`[biome] preset = "enforced"`) while epic is-a70a5963 migrates the 37
|
|
31
|
+
// configs; unenforced, the drift is still SAID — silence is what let 37
|
|
32
|
+
// configs exist.
|
|
33
|
+
const drift = presetDrift(process.cwd());
|
|
34
|
+
if (drift.length > 0) {
|
|
35
|
+
const message = driftMessage(drift);
|
|
36
|
+
if (presetEnforced(process.cwd())) {
|
|
37
|
+
console.error(`guardrails verify: ${message}`);
|
|
38
|
+
return 2;
|
|
39
|
+
}
|
|
40
|
+
if (!json) {
|
|
41
|
+
console.log(`note: ${message}\n Not gated here yet — add [biome] preset = "enforced" to .agentvibes/project.toml once this repo is migrated.`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
21
44
|
// Both manifest sections are resolved here so a config the gate cannot honour
|
|
22
45
|
// exits 2 with the reason — never a stack trace, and never silently dropped.
|
|
23
46
|
let findings;
|
|
@@ -25,7 +48,9 @@ export function runVerify(targets, json) {
|
|
|
25
48
|
findings = collectVerifyFindings(paths, severityRaiseArgs(process.cwd()));
|
|
26
49
|
}
|
|
27
50
|
catch (err) {
|
|
28
|
-
if (err instanceof SeverityConfigError ||
|
|
51
|
+
if (err instanceof SeverityConfigError ||
|
|
52
|
+
err instanceof ScreenScopeError ||
|
|
53
|
+
err instanceof AstGrepConfigError) {
|
|
29
54
|
console.error(`guardrails verify: ${err.message}`);
|
|
30
55
|
return 2;
|
|
31
56
|
}
|
package/dist/verify.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"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"}
|
package/dist/verifyDiff.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { join, relative } from "node:path";
|
|
2
2
|
import { match } from "ts-pattern";
|
|
3
|
-
import { scanFindings } from "./astGrep.js";
|
|
3
|
+
import { AstGrepConfigError, scanFindings } from "./astGrep.js";
|
|
4
4
|
import { formatFinding } from "./findings.js";
|
|
5
5
|
import { addedLines, changedFiles, gitRoot, resolveMergeBase } from "./gitDiff.js";
|
|
6
6
|
import { rulesConfig } from "./packagePaths.js";
|
|
7
|
+
import { dedupeFindings, ruleConfigs } from "./repoRules.js";
|
|
7
8
|
import { SeverityConfigError, severityRaiseArgs } from "./severity.js";
|
|
8
9
|
import { structureFindings } from "./structure.js";
|
|
9
10
|
import { readTomlTable } from "./tomlTable.js";
|
|
@@ -80,10 +81,10 @@ export function runVerifyDiffCollect(cwd, base) {
|
|
|
80
81
|
return { rungLabel: plan.rungLabel, filesScanned: 0, violations: [] };
|
|
81
82
|
}
|
|
82
83
|
const targets = plan.files ?? ["."];
|
|
83
|
-
const violations = [
|
|
84
|
-
...scanFindings(
|
|
84
|
+
const violations = dedupeFindings([
|
|
85
|
+
...ruleConfigs(cwd, rulesConfig).flatMap((c) => scanFindings(c, targets, severityArgs)),
|
|
85
86
|
...structureFindings(targets),
|
|
86
|
-
]
|
|
87
|
+
])
|
|
87
88
|
.filter((f) => f.severity === "error")
|
|
88
89
|
.filter(plan.keep);
|
|
89
90
|
return { rungLabel: plan.rungLabel, filesScanned: plan.files?.length ?? -1, violations };
|
|
@@ -94,7 +95,7 @@ export function runVerifyDiff(base, json) {
|
|
|
94
95
|
result = runVerifyDiffCollect(process.cwd(), base);
|
|
95
96
|
}
|
|
96
97
|
catch (err) {
|
|
97
|
-
if (err instanceof SeverityConfigError) {
|
|
98
|
+
if (err instanceof SeverityConfigError || err instanceof AstGrepConfigError) {
|
|
98
99
|
console.error(`guardrails verify-diff: ${err.message}`);
|
|
99
100
|
return 2;
|
|
100
101
|
}
|
package/dist/verifyDiff.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verifyDiff.js","sourceRoot":"","sources":["../src/verifyDiff.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"verifyDiff.js","sourceRoot":"","sources":["../src/verifyDiff.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentvibes/guardrails",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Verification toolkit that makes agents write simple code: ast-grep rule canon, diff ratchet, biome/tsconfig presets, one CLI.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"format": "biome check --write .",
|
|
36
36
|
"test:rules": "node dist/testRules.js",
|
|
37
37
|
"test:gate": "node dist/gateRed.js",
|
|
38
|
-
"check": "pnpm build && pnpm lint && pnpm test:rules && pnpm test:gate && pnpm test:metrics && pnpm test:init && pnpm test:severity && pnpm test:screens && pnpm test:hooks && pnpm test:iterate && pnpm test:leaks && pnpm leaks",
|
|
38
|
+
"check": "pnpm build && pnpm lint && pnpm test:rules && pnpm test:twins && pnpm test:repo-rules && pnpm test:preset && pnpm test:gate && pnpm test:sibling-clone && pnpm test:metrics && pnpm test:init && pnpm test:severity && pnpm test:screens && pnpm test:hooks && pnpm test:iterate && pnpm test:leaks && pnpm leaks",
|
|
39
39
|
"test:metrics": "node dist/metricsCycle.js",
|
|
40
40
|
"leaks": "node dist/cli.js leaks .",
|
|
41
41
|
"test:leaks": "node dist/leaksRed.js",
|
|
@@ -43,7 +43,11 @@
|
|
|
43
43
|
"test:severity": "node dist/severityTest.js",
|
|
44
44
|
"test:hooks": "node dist/hookStopTest.js",
|
|
45
45
|
"test:iterate": "node dist/iterateTest.js",
|
|
46
|
-
"test:screens": "node dist/screenScopeTest.js"
|
|
46
|
+
"test:screens": "node dist/screenScopeTest.js",
|
|
47
|
+
"test:twins": "node dist/twinCoverageTest.js",
|
|
48
|
+
"test:repo-rules": "node dist/repoRulesTest.js",
|
|
49
|
+
"test:preset": "node dist/presetDriftTest.js",
|
|
50
|
+
"test:sibling-clone": "node dist/siblingCloneTest.js"
|
|
47
51
|
},
|
|
48
52
|
"dependencies": {
|
|
49
53
|
"ts-pattern": "^5.6.2",
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Every pair here MUST be reported by `demo-mode-by-default-ts`.
|
|
2
|
+
// The rule is scoped to the positions that decide what the app STARTS on:
|
|
3
|
+
// an initialState-family function, a DEFAULT_/INITIAL_ binding, or the initial
|
|
4
|
+
// argument of useState/useReducer.
|
|
5
|
+
|
|
6
|
+
// The annotated form, which the rule caught from the start.
|
|
7
|
+
export const DEFAULT_MODE: Mode = { kind: "demo" }
|
|
8
|
+
|
|
9
|
+
// The `as const` form (is-9e66ec5a). This is the spelling people reach for
|
|
10
|
+
// when the object carries no type annotation to hold the literal type, and it
|
|
11
|
+
// was silently missed — DataSpool's retired local `no-fake-defaults` caught it,
|
|
12
|
+
// so adopting the canon had been a coverage regression.
|
|
13
|
+
export const DEFAULT_STATE = { mode: "demo" as const }
|
|
14
|
+
|
|
15
|
+
// `as <Type>` and `satisfies <Type>`, the same wrapper wearing other names.
|
|
16
|
+
export const DEFAULT_BACKEND = { source: "fixture" as Backend }
|
|
17
|
+
export const INITIAL_MODE = { kind: "mock" satisfies Mode }
|
|
18
|
+
|
|
19
|
+
// Inside an initialState-family function rather than a binding.
|
|
20
|
+
export function initialState() {
|
|
21
|
+
return { mode: "demo" as const }
|
|
22
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Every pair here MUST be reported by `demo-mode-by-default`.
|
|
2
|
+
// The rule is scoped to the positions that decide what the app STARTS on:
|
|
3
|
+
// an initialState-family function, a DEFAULT_/INITIAL_ binding, or the initial
|
|
4
|
+
// argument of useState/useReducer.
|
|
5
|
+
|
|
6
|
+
// The annotated form, which the rule caught from the start.
|
|
7
|
+
export const DEFAULT_MODE: Mode = { kind: "demo" }
|
|
8
|
+
|
|
9
|
+
// The `as const` form (is-9e66ec5a). This is the spelling people reach for
|
|
10
|
+
// when the object carries no type annotation to hold the literal type, and it
|
|
11
|
+
// was silently missed — DataSpool's retired local `no-fake-defaults` caught it,
|
|
12
|
+
// so adopting the canon had been a coverage regression.
|
|
13
|
+
export const DEFAULT_STATE = { mode: "demo" as const }
|
|
14
|
+
|
|
15
|
+
// `as <Type>` and `satisfies <Type>`, the same wrapper wearing other names.
|
|
16
|
+
export const DEFAULT_BACKEND = { source: "fixture" as Backend }
|
|
17
|
+
export const INITIAL_MODE = { kind: "mock" satisfies Mode }
|
|
18
|
+
|
|
19
|
+
// Inside an initialState-family function rather than a binding.
|
|
20
|
+
export function initialState() {
|
|
21
|
+
return { mode: "demo" as const }
|
|
22
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// NOTHING in this file may be reported by `demo-mode-by-default-ts`.
|
|
2
|
+
|
|
3
|
+
// The real source as the default is the whole point of the rule.
|
|
4
|
+
export const DEFAULT_MODE = { kind: "live" as const }
|
|
5
|
+
export const DEFAULT_BACKEND: Backend = { source: "api" }
|
|
6
|
+
|
|
7
|
+
// A demo literal in a `.with(...)` match arm is the CORRECT pattern — an
|
|
8
|
+
// explicit flag selecting the sandbox — and is excluded by design.
|
|
9
|
+
export const pick = (flag: Flag) =>
|
|
10
|
+
match(flag)
|
|
11
|
+
.with("sandbox", () => ({ kind: "demo" as const }))
|
|
12
|
+
.with("real", () => ({ kind: "live" as const }))
|
|
13
|
+
.exhaustive()
|
|
14
|
+
|
|
15
|
+
// Not a start-up position: an ordinary binding the rule deliberately ignores,
|
|
16
|
+
// so the scope stays narrow rather than flagging every mention of a fixture.
|
|
17
|
+
export const sampleRow = { kind: "demo" as const }
|
|
18
|
+
|
|
19
|
+
// The quote characters are part of the match, so a longer string that merely
|
|
20
|
+
// STARTS with a banned word is not a finding.
|
|
21
|
+
export const DEFAULT_STATE = { kind: "demo-recording" as const }
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// NOTHING in this file may be reported by `demo-mode-by-default`.
|
|
2
|
+
|
|
3
|
+
// The real source as the default is the whole point of the rule.
|
|
4
|
+
export const DEFAULT_MODE = { kind: "live" as const }
|
|
5
|
+
export const DEFAULT_BACKEND: Backend = { source: "api" }
|
|
6
|
+
|
|
7
|
+
// A demo literal in a `.with(...)` match arm is the CORRECT pattern — an
|
|
8
|
+
// explicit flag selecting the sandbox — and is excluded by design.
|
|
9
|
+
export const pick = (flag: Flag) =>
|
|
10
|
+
match(flag)
|
|
11
|
+
.with("sandbox", () => ({ kind: "demo" as const }))
|
|
12
|
+
.with("real", () => ({ kind: "live" as const }))
|
|
13
|
+
.exhaustive()
|
|
14
|
+
|
|
15
|
+
// Not a start-up position: an ordinary binding the rule deliberately ignores,
|
|
16
|
+
// so the scope stays narrow rather than flagging every mention of a fixture.
|
|
17
|
+
export const sampleRow = { kind: "demo" as const }
|
|
18
|
+
|
|
19
|
+
// The quote characters are part of the match, so a longer string that merely
|
|
20
|
+
// STARTS with a banned word is not a finding.
|
|
21
|
+
export const DEFAULT_STATE = { kind: "demo-recording" as const }
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Every clone here MUST be reported by `json-roundtrip-tsx`.
|
|
2
|
+
// The .ts arm (`json-roundtrip`) does NOT see this file.
|
|
3
|
+
export const Card = ({ draft }: Props) => {
|
|
4
|
+
const snapshot = JSON.parse(JSON.stringify(draft))
|
|
5
|
+
return <pre>{snapshot.title}</pre>
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const clone = (x: Draft) => JSON.parse(JSON.stringify(x))
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// NOTHING in this file may be reported by `json-roundtrip`.
|
|
2
|
+
// The rule is the composed round trip, not either half on its own.
|
|
3
|
+
export const a = (raw: string) => DraftSchema.parse(JSON.parse(raw))
|
|
4
|
+
export const b = (x: Draft) => JSON.stringify(x)
|
|
5
|
+
export const c = (x: Draft) => structuredClone(x)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Every `if` here MUST be reported by `kind-if-without-match`.
|
|
2
|
+
export const a = (r: R) => {
|
|
3
|
+
if (r.kind === "idle") return 0
|
|
4
|
+
return 1
|
|
5
|
+
}
|
|
6
|
+
export const b = (r: R) => {
|
|
7
|
+
if (r.type === "photo") return 0
|
|
8
|
+
return 1
|
|
9
|
+
}
|
|
10
|
+
export const c = (r: R) => {
|
|
11
|
+
if (r.status === "ready") return 0
|
|
12
|
+
return 1
|
|
13
|
+
}
|
|
14
|
+
export const d = (r: R) => {
|
|
15
|
+
if (r._obj === "Task") return 0
|
|
16
|
+
return 1
|
|
17
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Every `if` here MUST be reported by `kind-if-without-match-tsx`.
|
|
2
|
+
// The .ts arm (`kind-if-without-match`) does NOT see this file.
|
|
3
|
+
export const A = (r: R) => {
|
|
4
|
+
if (r.kind === "idle") return <Spinner />
|
|
5
|
+
return <List />
|
|
6
|
+
}
|
|
7
|
+
export const B = (r: R) => {
|
|
8
|
+
if (r.type === "photo") return <Photo />
|
|
9
|
+
return <Video />
|
|
10
|
+
}
|
|
11
|
+
export const C = (r: R) => {
|
|
12
|
+
if (r.status === "ready") return <List />
|
|
13
|
+
return <Spinner />
|
|
14
|
+
}
|
|
15
|
+
export const D = (r: R) => {
|
|
16
|
+
if (r._obj === "Task") return <Task />
|
|
17
|
+
return <Habit />
|
|
18
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// NOTHING in this file may be reported by `kind-if-without-match`.
|
|
2
|
+
import { match } from "ts-pattern"
|
|
3
|
+
|
|
4
|
+
export const a = (r: R) =>
|
|
5
|
+
match(r)
|
|
6
|
+
.with({ kind: "idle" }, () => 0)
|
|
7
|
+
.with({ kind: "ready" }, () => 1)
|
|
8
|
+
.exhaustive()
|
|
9
|
+
|
|
10
|
+
// Not a discriminant field, so not this rule's business.
|
|
11
|
+
export const b = (r: R) => {
|
|
12
|
+
if (r.count === 0) return 0
|
|
13
|
+
return 1
|
|
14
|
+
}
|