@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.
Files changed (260) hide show
  1. package/README.md +239 -0
  2. package/candidates/README.md +30 -0
  3. package/candidates/r10-as-cast.yml +24 -0
  4. package/candidates/r1d-scattered-numeric-const-in-component.yml +13 -0
  5. package/candidates/r2-helper-fn-in-tsx.yml +22 -0
  6. package/candidates/r3-hooks-outside-layout.yml +21 -0
  7. package/candidates/r6-jsx-comment-smell.yml +14 -0
  8. package/candidates/r7-raw-hsl-outside-tokens.yml +17 -0
  9. package/candidates/r8-inline-style-prop.yml +13 -0
  10. package/candidates/r9-optional-store-arg.yml +24 -0
  11. package/configs/biome.json +96 -0
  12. package/configs/tsconfig.base.json +21 -0
  13. package/dist/astGrep.d.ts +16 -0
  14. package/dist/astGrep.js +48 -0
  15. package/dist/astGrep.js.map +1 -0
  16. package/dist/cli.d.ts +2 -0
  17. package/dist/cli.js +138 -0
  18. package/dist/cli.js.map +1 -0
  19. package/dist/deploy.d.ts +1 -0
  20. package/dist/deploy.js +34 -0
  21. package/dist/deploy.js.map +1 -0
  22. package/dist/doctor.d.ts +6 -0
  23. package/dist/doctor.js +102 -0
  24. package/dist/doctor.js.map +1 -0
  25. package/dist/fileWalk.d.ts +2 -0
  26. package/dist/fileWalk.js +45 -0
  27. package/dist/fileWalk.js.map +1 -0
  28. package/dist/findings.d.ts +12 -0
  29. package/dist/findings.js +8 -0
  30. package/dist/findings.js.map +1 -0
  31. package/dist/gateRed.d.ts +1 -0
  32. package/dist/gateRed.js +93 -0
  33. package/dist/gateRed.js.map +1 -0
  34. package/dist/gitDiff.d.ts +18 -0
  35. package/dist/gitDiff.js +90 -0
  36. package/dist/gitDiff.js.map +1 -0
  37. package/dist/hookPostedit.d.ts +1 -0
  38. package/dist/hookPostedit.js +117 -0
  39. package/dist/hookPostedit.js.map +1 -0
  40. package/dist/hookStop.d.ts +1 -0
  41. package/dist/hookStop.js +56 -0
  42. package/dist/hookStop.js.map +1 -0
  43. package/dist/hookStopTest.d.ts +1 -0
  44. package/dist/hookStopTest.js +71 -0
  45. package/dist/hookStopTest.js.map +1 -0
  46. package/dist/init.d.ts +24 -0
  47. package/dist/init.js +172 -0
  48. package/dist/init.js.map +1 -0
  49. package/dist/initTest.d.ts +1 -0
  50. package/dist/initTest.js +88 -0
  51. package/dist/initTest.js.map +1 -0
  52. package/dist/iterate.d.ts +9 -0
  53. package/dist/iterate.js +79 -0
  54. package/dist/iterate.js.map +1 -0
  55. package/dist/iterateTest.d.ts +1 -0
  56. package/dist/iterateTest.js +91 -0
  57. package/dist/iterateTest.js.map +1 -0
  58. package/dist/leakPatterns.d.ts +5 -0
  59. package/dist/leakPatterns.js +22 -0
  60. package/dist/leakPatterns.js.map +1 -0
  61. package/dist/leaks.d.ts +8 -0
  62. package/dist/leaks.js +211 -0
  63. package/dist/leaks.js.map +1 -0
  64. package/dist/leaksRed.d.ts +1 -0
  65. package/dist/leaksRed.js +62 -0
  66. package/dist/leaksRed.js.map +1 -0
  67. package/dist/metrics.d.ts +10 -0
  68. package/dist/metrics.js +113 -0
  69. package/dist/metrics.js.map +1 -0
  70. package/dist/metricsAggregate.d.ts +5 -0
  71. package/dist/metricsAggregate.js +78 -0
  72. package/dist/metricsAggregate.js.map +1 -0
  73. package/dist/metricsBaseline.d.ts +21 -0
  74. package/dist/metricsBaseline.js +88 -0
  75. package/dist/metricsBaseline.js.map +1 -0
  76. package/dist/metricsCollect.d.ts +8 -0
  77. package/dist/metricsCollect.js +295 -0
  78. package/dist/metricsCollect.js.map +1 -0
  79. package/dist/metricsCycle.d.ts +1 -0
  80. package/dist/metricsCycle.js +79 -0
  81. package/dist/metricsCycle.js.map +1 -0
  82. package/dist/metricsTypes.d.ts +102 -0
  83. package/dist/metricsTypes.js +2 -0
  84. package/dist/metricsTypes.js.map +1 -0
  85. package/dist/packagePaths.d.ts +7 -0
  86. package/dist/packagePaths.js +11 -0
  87. package/dist/packagePaths.js.map +1 -0
  88. package/dist/pluginApi.d.ts +24 -0
  89. package/dist/pluginApi.js +12 -0
  90. package/dist/pluginApi.js.map +1 -0
  91. package/dist/pluginResolve.d.ts +6 -0
  92. package/dist/pluginResolve.js +48 -0
  93. package/dist/pluginResolve.js.map +1 -0
  94. package/dist/screenScope.d.ts +21 -0
  95. package/dist/screenScope.js +81 -0
  96. package/dist/screenScope.js.map +1 -0
  97. package/dist/screenScopeTest.d.ts +1 -0
  98. package/dist/screenScopeTest.js +116 -0
  99. package/dist/screenScopeTest.js.map +1 -0
  100. package/dist/severity.d.ts +9 -0
  101. package/dist/severity.js +62 -0
  102. package/dist/severity.js.map +1 -0
  103. package/dist/severityTest.d.ts +1 -0
  104. package/dist/severityTest.js +63 -0
  105. package/dist/severityTest.js.map +1 -0
  106. package/dist/sourceLines.d.ts +9 -0
  107. package/dist/sourceLines.js +52 -0
  108. package/dist/sourceLines.js.map +1 -0
  109. package/dist/structure.d.ts +2 -0
  110. package/dist/structure.js +98 -0
  111. package/dist/structure.js.map +1 -0
  112. package/dist/testRules.d.ts +1 -0
  113. package/dist/testRules.js +285 -0
  114. package/dist/testRules.js.map +1 -0
  115. package/dist/textGrep.d.ts +2 -0
  116. package/dist/textGrep.js +52 -0
  117. package/dist/textGrep.js.map +1 -0
  118. package/dist/tomlTable.d.ts +2 -0
  119. package/dist/tomlTable.js +26 -0
  120. package/dist/tomlTable.js.map +1 -0
  121. package/dist/toolResolve.d.ts +18 -0
  122. package/dist/toolResolve.js +50 -0
  123. package/dist/toolResolve.js.map +1 -0
  124. package/dist/verify.d.ts +3 -0
  125. package/dist/verify.js +49 -0
  126. package/dist/verify.js.map +1 -0
  127. package/dist/verifyDiff.d.ts +18 -0
  128. package/dist/verifyDiff.js +126 -0
  129. package/dist/verifyDiff.js.map +1 -0
  130. package/dist/workspaces.d.ts +6 -0
  131. package/dist/workspaces.js +84 -0
  132. package/dist/workspaces.js.map +1 -0
  133. package/mise.toml +9 -0
  134. package/package.json +62 -0
  135. package/rules/__fixtures__/as-any-escape/bad.ts +8 -0
  136. package/rules/__fixtures__/as-any-escape/good.ts +14 -0
  137. package/rules/__fixtures__/catch-empty/bad.ts +7 -0
  138. package/rules/__fixtures__/catch-empty/good.ts +14 -0
  139. package/rules/__fixtures__/direct-store-import/bad.tsx +23 -0
  140. package/rules/__fixtures__/direct-store-import/good.tsx +34 -0
  141. package/rules/__fixtures__/direct-store-import/showcase/ignored.tsx +9 -0
  142. package/rules/__fixtures__/discriminator-ternary/bad.tsx +10 -0
  143. package/rules/__fixtures__/discriminator-ternary/good.tsx +23 -0
  144. package/rules/__fixtures__/hardcoded-url-in-component/components/bad.tsx +8 -0
  145. package/rules/__fixtures__/hardcoded-url-in-component/components/good.tsx +14 -0
  146. package/rules/__fixtures__/inline-map-row/bad.tsx +72 -0
  147. package/rules/__fixtures__/inline-map-row/good.tsx +51 -0
  148. package/rules/__fixtures__/instanceof-map-set/bad.ts +7 -0
  149. package/rules/__fixtures__/instanceof-map-set/good.ts +8 -0
  150. package/rules/__fixtures__/literal-union-in-component/components/bad.ts +8 -0
  151. package/rules/__fixtures__/literal-union-in-component/components/good.ts +23 -0
  152. package/rules/__fixtures__/missing-observer/bad.tsx +66 -0
  153. package/rules/__fixtures__/missing-observer/good.tsx +69 -0
  154. package/rules/__fixtures__/no-local-kit-clone/bad.ts +30 -0
  155. package/rules/__fixtures__/no-local-kit-clone/bad.tsx +30 -0
  156. package/rules/__fixtures__/no-local-kit-clone/good.ts +31 -0
  157. package/rules/__fixtures__/no-local-kit-clone/good.tsx +31 -0
  158. package/rules/__fixtures__/non-exhaustive-match/bad.ts +8 -0
  159. package/rules/__fixtures__/non-exhaustive-match/good.ts +14 -0
  160. package/rules/__fixtures__/page-store-bare-usememo/bad.ts +15 -0
  161. package/rules/__fixtures__/page-store-bare-usememo/bad.tsx +13 -0
  162. package/rules/__fixtures__/page-store-bare-usememo/good.ts +17 -0
  163. package/rules/__fixtures__/page-store-bare-usememo/good.tsx +14 -0
  164. package/rules/__fixtures__/screen-file-styling/bad.screen.tsx +10 -0
  165. package/rules/__fixtures__/screen-file-styling/good.screen.tsx +8 -0
  166. package/rules/__fixtures__/state-loading-boolean-shape/bad.ts +36 -0
  167. package/rules/__fixtures__/state-loading-boolean-shape/bad.tsx +36 -0
  168. package/rules/__fixtures__/state-loading-boolean-shape/good.ts +44 -0
  169. package/rules/__fixtures__/state-loading-boolean-shape/good.tsx +44 -0
  170. package/rules/__fixtures__/store-async-method/bad.ts +37 -0
  171. package/rules/__fixtures__/store-async-method/bad.tsx +37 -0
  172. package/rules/__fixtures__/store-async-method/good.ts +73 -0
  173. package/rules/__fixtures__/store-async-method/good.tsx +73 -0
  174. package/rules/__fixtures__/store-context-provider/bad.ts +16 -0
  175. package/rules/__fixtures__/store-context-provider/bad.tsx +24 -0
  176. package/rules/__fixtures__/store-context-provider/good.ts +13 -0
  177. package/rules/__fixtures__/store-context-provider/good.tsx +11 -0
  178. package/rules/__fixtures__/store-delegation-getter/bad.ts +30 -0
  179. package/rules/__fixtures__/store-delegation-getter/bad.tsx +30 -0
  180. package/rules/__fixtures__/store-delegation-getter/good.ts +65 -0
  181. package/rules/__fixtures__/store-delegation-getter/good.tsx +65 -0
  182. package/rules/__fixtures__/store-new-map/bad.ts +22 -0
  183. package/rules/__fixtures__/store-new-map/bad.tsx +22 -0
  184. package/rules/__fixtures__/store-new-map/good.ts +41 -0
  185. package/rules/__fixtures__/store-new-map/good.tsx +41 -0
  186. package/rules/__fixtures__/store-no-runinaction/bad.ts +67 -0
  187. package/rules/__fixtures__/store-no-runinaction/bad.tsx +67 -0
  188. package/rules/__fixtures__/store-no-runinaction/good.ts +39 -0
  189. package/rules/__fixtures__/store-no-runinaction/good.tsx +39 -0
  190. package/rules/__fixtures__/store-no-setinterval-poll/bad.ts +19 -0
  191. package/rules/__fixtures__/store-no-setinterval-poll/bad.tsx +19 -0
  192. package/rules/__fixtures__/store-no-setinterval-poll/good.ts +29 -0
  193. package/rules/__fixtures__/store-no-setinterval-poll/good.tsx +29 -0
  194. package/rules/__fixtures__/ts-pattern-dangling-match/bad.ts +20 -0
  195. package/rules/__fixtures__/ts-pattern-dangling-match/good.ts +35 -0
  196. package/rules/__fixtures__/ui-imports-app-store/src/ui/bad.tsx +13 -0
  197. package/rules/__fixtures__/ui-imports-app-store/src/ui/good.tsx +23 -0
  198. package/rules/__fixtures__/view-file-logic/bad.view.tsx +16 -0
  199. package/rules/__fixtures__/view-file-logic/good.view.tsx +7 -0
  200. package/rules/__fixtures__/view-imports-store/bad.view.tsx +13 -0
  201. package/rules/__fixtures__/view-imports-store/good.view.tsx +23 -0
  202. package/rules/__fixtures__/zod-optional-nullable/bad.ts +10 -0
  203. package/rules/__fixtures__/zod-optional-nullable/bad.tsx +17 -0
  204. package/rules/__fixtures__/zod-optional-nullable/good.ts +16 -0
  205. package/rules/__fixtures__/zod-optional-nullable/good.tsx +20 -0
  206. package/rules/as-any-escape-tsx.yml +53 -0
  207. package/rules/as-any-escape.yml +29 -0
  208. package/rules/catch-empty-tsx.yml +55 -0
  209. package/rules/catch-empty.yml +31 -0
  210. package/rules/classname-not-composed.yml +28 -0
  211. package/rules/classname-visual-identity.yml +34 -0
  212. package/rules/demo-mode-by-default-ts.yml +76 -0
  213. package/rules/demo-mode-by-default.yml +54 -0
  214. package/rules/direct-store-import.yml +118 -0
  215. package/rules/discriminator-ternary.yml +70 -0
  216. package/rules/hardcoded-url-in-component.yml +44 -0
  217. package/rules/inline-map-row.yml +107 -0
  218. package/rules/instanceof-map-set-tsx.yml +46 -0
  219. package/rules/instanceof-map-set.yml +22 -0
  220. package/rules/json-roundtrip.yml +9 -0
  221. package/rules/jsx-cond-and.yml +16 -0
  222. package/rules/jsx-ternary.yml +35 -0
  223. package/rules/kind-if-without-match.yml +13 -0
  224. package/rules/literal-union-in-component.yml +71 -0
  225. package/rules/match-bool-to-null.yml +15 -0
  226. package/rules/missing-observer.yml +189 -0
  227. package/rules/mobx-effect-observable-dep.yml +23 -0
  228. package/rules/mobx-effect-store-write.yml +21 -0
  229. package/rules/mobx-usestate-from-store.yml +20 -0
  230. package/rules/no-local-kit-clone-tsx.yml +104 -0
  231. package/rules/no-local-kit-clone.yml +93 -0
  232. package/rules/non-exhaustive-match.yml +34 -0
  233. package/rules/page-store-bare-usememo-tsx.yml +47 -0
  234. package/rules/page-store-bare-usememo.yml +43 -0
  235. package/rules/screen-file-styling.yml +29 -0
  236. package/rules/silent-default-return.yml +17 -0
  237. package/rules/state-loading-boolean-shape-tsx.yml +102 -0
  238. package/rules/state-loading-boolean-shape.yml +91 -0
  239. package/rules/store-async-method-tsx.yml +108 -0
  240. package/rules/store-async-method.yml +98 -0
  241. package/rules/store-context-provider-tsx.yml +68 -0
  242. package/rules/store-context-provider.yml +78 -0
  243. package/rules/store-delegation-getter-tsx.yml +104 -0
  244. package/rules/store-delegation-getter.yml +93 -0
  245. package/rules/store-new-map-tsx.yml +84 -0
  246. package/rules/store-new-map.yml +73 -0
  247. package/rules/store-no-runinaction-tsx.yml +71 -0
  248. package/rules/store-no-runinaction.yml +60 -0
  249. package/rules/store-no-setinterval-poll-tsx.yml +62 -0
  250. package/rules/store-no-setinterval-poll.yml +51 -0
  251. package/rules/ts-pattern-dangling-match-tsx.yml +41 -0
  252. package/rules/ts-pattern-dangling-match.yml +63 -0
  253. package/rules/ui-imports-app-store.yml +49 -0
  254. package/rules/view-file-logic.yml +53 -0
  255. package/rules/view-imports-store.yml +64 -0
  256. package/rules/zod-optional-nullable-tsx.yml +56 -0
  257. package/rules/zod-optional-nullable.yml +29 -0
  258. package/sgconfig.yml +8 -0
  259. package/structure/rules/component-decl.yml +48 -0
  260. package/structure/sgconfig.yml +7 -0
@@ -0,0 +1,52 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { collectFiles } from "./fileWalk.js";
3
+ // Ported from defensive-errors scan.sh — patterns that don't express cleanly
4
+ // in ast-grep. Warning tier always: text greps never block a gate.
5
+ const TEXT_RULES = [
6
+ {
7
+ id: "ts-suppression",
8
+ pattern: /@ts-nocheck|@ts-ignore|@ts-expect-error/,
9
+ exts: [".ts", ".tsx"],
10
+ excludePath: /__tests__/,
11
+ message: "TypeScript suppression comment hides a real type error. Fix the type at the source instead.",
12
+ },
13
+ {
14
+ id: "jsx-cond-and-textgrep",
15
+ pattern: /\{[A-Za-z_!][A-Za-z0-9_.!?]* && </,
16
+ exts: [".tsx", ".jsx"],
17
+ excludePath: /__tests__|\/ai-elements\//,
18
+ message: "JSX `{cond && <...>}` silently drops the !cond branch. Use match(cond).with(true, ...).with(false, () => null).exhaustive() so both arms are visible.",
19
+ },
20
+ ];
21
+ export function textGrepFindings(targets) {
22
+ const findings = [];
23
+ for (const rule of TEXT_RULES) {
24
+ for (const file of collectFiles(targets, rule.exts)) {
25
+ if (rule.excludePath.test(file))
26
+ continue;
27
+ let text;
28
+ try {
29
+ text = readFileSync(file, "utf8");
30
+ }
31
+ catch {
32
+ continue;
33
+ }
34
+ const lines = text.split("\n");
35
+ for (let i = 0; i < lines.length; i++) {
36
+ const line = lines[i];
37
+ if (line !== undefined && rule.pattern.test(line)) {
38
+ findings.push({
39
+ rule: rule.id,
40
+ severity: "warning",
41
+ file,
42
+ line: i + 1,
43
+ message: rule.message,
44
+ source: "text-grep",
45
+ });
46
+ }
47
+ }
48
+ }
49
+ }
50
+ return findings;
51
+ }
52
+ //# sourceMappingURL=textGrep.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"textGrep.js","sourceRoot":"","sources":["../src/textGrep.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAW7C,6EAA6E;AAC7E,mEAAmE;AACnE,MAAM,UAAU,GAAe;IAC7B;QACE,EAAE,EAAE,gBAAgB;QACpB,OAAO,EAAE,yCAAyC;QAClD,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC;QACrB,WAAW,EAAE,WAAW;QACxB,OAAO,EACL,6FAA6F;KAChG;IACD;QACE,EAAE,EAAE,uBAAuB;QAC3B,OAAO,EAAE,mCAAmC;QAC5C,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,2BAA2B;QACxC,OAAO,EACL,uJAAuJ;KAC1J;CACF,CAAC;AAEF,MAAM,UAAU,gBAAgB,CAAC,OAAiB;IAChD,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC9B,KAAK,MAAM,IAAI,IAAI,YAAY,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACpD,IAAI,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC;gBAAE,SAAS;YAC1C,IAAI,IAAY,CAAC;YACjB,IAAI,CAAC;gBACH,IAAI,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YACpC,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;YACD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC/B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;gBACtB,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;oBAClD,QAAQ,CAAC,IAAI,CAAC;wBACZ,IAAI,EAAE,IAAI,CAAC,EAAE;wBACb,QAAQ,EAAE,SAAS;wBACnB,IAAI;wBACJ,IAAI,EAAE,CAAC,GAAG,CAAC;wBACX,OAAO,EAAE,IAAI,CAAC,OAAO;wBACrB,MAAM,EAAE,WAAW;qBACpB,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,2 @@
1
+ /** Raw key = "value" pairs of one TOML table; enough for [deploy]/[leaks] lookup. */
2
+ export declare function readTomlTable(path: string, table: string): Record<string, string>;
@@ -0,0 +1,26 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ /** Raw key = "value" pairs of one TOML table; enough for [deploy]/[leaks] lookup. */
3
+ export function readTomlTable(path, table) {
4
+ if (!existsSync(path))
5
+ return {};
6
+ const out = {};
7
+ let inTable = false;
8
+ for (const raw of readFileSync(path, "utf8").split("\n")) {
9
+ const line = raw.trim();
10
+ if (line.startsWith("[")) {
11
+ inTable = line === `[${table}]`;
12
+ continue;
13
+ }
14
+ if (!inTable || line === "" || line.startsWith("#"))
15
+ continue;
16
+ // bare or quoted key ('zod-optional-nullable' is a valid bare key, but
17
+ // quoted spellings must read identically)
18
+ const m = line.match(/^(?:"([^"]+)"|([A-Za-z0-9_-]+))\s*=\s*"([^"]*)"/);
19
+ const key = m?.[1] ?? m?.[2];
20
+ const value = m?.[3];
21
+ if (key !== undefined && value !== undefined)
22
+ out[key] = value;
23
+ }
24
+ return out;
25
+ }
26
+ //# sourceMappingURL=tomlTable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tomlTable.js","sourceRoot":"","sources":["../src/tomlTable.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEnD,qFAAqF;AACrF,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,KAAa;IACvD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IACjC,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,KAAK,MAAM,GAAG,IAAI,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACzD,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QACxB,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACzB,OAAO,GAAG,IAAI,KAAK,IAAI,KAAK,GAAG,CAAC;YAChC,SAAS;QACX,CAAC;QACD,IAAI,CAAC,OAAO,IAAI,IAAI,KAAK,EAAE,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QAC9D,uEAAuE;QACvE,0CAA0C;QAC1C,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,iDAAiD,CAAC,CAAC;QACxE,MAAM,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC7B,MAAM,KAAK,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACrB,IAAI,GAAG,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS;YAAE,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACjE,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,18 @@
1
+ export interface ResolvedTool {
2
+ /** argv prefix to run the tool, e.g. ["ast-grep"] or ["mise", "x", "ast-grep@0.45.2", "--", "ast-grep"]. */
3
+ argv: string[];
4
+ via: "path" | "mise";
5
+ version: string;
6
+ }
7
+ export declare class ToolMissingError extends Error {
8
+ constructor(tool: string, hint: string);
9
+ }
10
+ /** Version pins live in mise.toml only — one source of truth for CLI, CI, and hosts. */
11
+ export declare function pinnedVersion(tool: string): string | undefined;
12
+ /**
13
+ * PATH first, `mise x <tool>@<pin>` as the fallback, one-line install hint as
14
+ * the failure — the opt-hook.sh philosophy: never hard-require a specific
15
+ * install route, never silently continue without the tool either.
16
+ */
17
+ export declare function resolveTool(tool: string): ResolvedTool;
18
+ export declare function tryResolveTool(tool: string): ResolvedTool | undefined;
@@ -0,0 +1,50 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { readFileSync } from "node:fs";
3
+ import { miseToml } from "./packagePaths.js";
4
+ export class ToolMissingError extends Error {
5
+ constructor(tool, hint) {
6
+ super(`${tool} not found on PATH and mise is unavailable. Install it: ${hint}`);
7
+ this.name = "ToolMissingError";
8
+ }
9
+ }
10
+ /** Version pins live in mise.toml only — one source of truth for CLI, CI, and hosts. */
11
+ export function pinnedVersion(tool) {
12
+ const text = readFileSync(miseToml, "utf8");
13
+ const m = text.match(new RegExp(`^${tool.replace(/[-]/g, "\\-")}\\s*=\\s*"([^"]+)"`, "m"));
14
+ return m?.[1];
15
+ }
16
+ function probe(argv) {
17
+ const [cmd, ...args] = argv;
18
+ if (cmd === undefined)
19
+ return undefined;
20
+ const res = spawnSync(cmd, [...args, "--version"], { encoding: "utf8" });
21
+ if (res.status !== 0 || res.error)
22
+ return undefined;
23
+ return res.stdout.trim().split("\n")[0];
24
+ }
25
+ /**
26
+ * PATH first, `mise x <tool>@<pin>` as the fallback, one-line install hint as
27
+ * the failure — the opt-hook.sh philosophy: never hard-require a specific
28
+ * install route, never silently continue without the tool either.
29
+ */
30
+ export function resolveTool(tool) {
31
+ const direct = probe([tool]);
32
+ if (direct !== undefined)
33
+ return { argv: [tool], via: "path", version: direct };
34
+ const pin = pinnedVersion(tool);
35
+ const spec = pin === undefined ? tool : `${tool}@${pin}`;
36
+ const viaMise = probe(["mise", "x", spec, "--", tool]);
37
+ if (viaMise !== undefined) {
38
+ return { argv: ["mise", "x", spec, "--", tool], via: "mise", version: viaMise };
39
+ }
40
+ throw new ToolMissingError(tool, `mise use -g ${tool}@${pin ?? "latest"}`);
41
+ }
42
+ export function tryResolveTool(tool) {
43
+ try {
44
+ return resolveTool(tool);
45
+ }
46
+ catch {
47
+ return undefined;
48
+ }
49
+ }
50
+ //# sourceMappingURL=toolResolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolResolve.js","sourceRoot":"","sources":["../src/toolResolve.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAS7C,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IACzC,YAAY,IAAY,EAAE,IAAY;QACpC,KAAK,CAAC,GAAG,IAAI,2DAA2D,IAAI,EAAE,CAAC,CAAC;QAChF,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CACF;AAED,wFAAwF;AACxF,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,MAAM,IAAI,GAAG,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC5C,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,oBAAoB,EAAE,GAAG,CAAC,CAAC,CAAC;IAC3F,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AAChB,CAAC;AAED,SAAS,KAAK,CAAC,IAAc;IAC3B,MAAM,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IAC5B,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACxC,MAAM,GAAG,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,WAAW,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;IACzE,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,IAAI,GAAG,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IACpD,OAAO,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAC1C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAC7B,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAEhF,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;IAChC,MAAM,IAAI,GAAG,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,GAAG,EAAE,CAAC;IACzD,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IACvD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,OAAO,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;IAClF,CAAC;IAED,MAAM,IAAI,gBAAgB,CAAC,IAAI,EAAE,eAAe,IAAI,IAAI,GAAG,IAAI,QAAQ,EAAE,CAAC,CAAC;AAC7E,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,IAAI,CAAC;QACH,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { type Finding } from "./findings.js";
2
+ export declare function collectVerifyFindings(targets: string[], severityArgs?: string[]): Finding[];
3
+ export declare function runVerify(targets: string[], json: boolean): number;
package/dist/verify.js ADDED
@@ -0,0 +1,49 @@
1
+ import { scanFindings } from "./astGrep.js";
2
+ import { formatFinding, hasErrors } from "./findings.js";
3
+ import { rulesConfig } from "./packagePaths.js";
4
+ import { dropScreenExempt, ScreenScopeError, screenExemptRuleIds, screensPattern, } from "./screenScope.js";
5
+ import { SeverityConfigError, severityRaiseArgs } from "./severity.js";
6
+ import { structureFindings } from "./structure.js";
7
+ import { textGrepFindings } from "./textGrep.js";
8
+ export function collectVerifyFindings(targets, severityArgs = []) {
9
+ const findings = [
10
+ ...scanFindings(rulesConfig, targets, severityArgs),
11
+ ...textGrepFindings(targets),
12
+ ...structureFindings(targets),
13
+ ];
14
+ // Applied HERE rather than in `runVerify`, so verify-diff and the hooks get
15
+ // the same scoping: a screen that is legal under `verify` must not be gated
16
+ // by `verify-diff` on the same line.
17
+ return dropScreenExempt(findings, screensPattern(process.cwd()), screenExemptRuleIds());
18
+ }
19
+ export function runVerify(targets, json) {
20
+ const paths = targets.length > 0 ? targets : ["."];
21
+ // Both manifest sections are resolved here so a config the gate cannot honour
22
+ // exits 2 with the reason — never a stack trace, and never silently dropped.
23
+ let findings;
24
+ try {
25
+ findings = collectVerifyFindings(paths, severityRaiseArgs(process.cwd()));
26
+ }
27
+ catch (err) {
28
+ if (err instanceof SeverityConfigError || err instanceof ScreenScopeError) {
29
+ console.error(`guardrails verify: ${err.message}`);
30
+ return 2;
31
+ }
32
+ throw err;
33
+ }
34
+ const errors = findings.filter((f) => f.severity === "error").length;
35
+ const warnings = findings.filter((f) => f.severity === "warning").length;
36
+ if (json) {
37
+ console.log(JSON.stringify({ command: "verify", targets: paths, errors, warnings, findings }, null, 2));
38
+ }
39
+ else {
40
+ for (const f of findings)
41
+ console.log(formatFinding(f));
42
+ console.log(`guardrails verify: ${errors} error(s), ${warnings} warning(s) in ${paths.join(" ")}`);
43
+ if (errors > 0) {
44
+ console.log("Fix these, or suppress a genuine false positive with a justified comment on the line above:\n // ast-grep-ignore: <rule-id> -- <why this is legitimate>");
45
+ }
46
+ }
47
+ return hasErrors(findings) ? 1 : 0;
48
+ }
49
+ //# sourceMappingURL=verify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import { type Finding } from "./findings.js";
2
+ /**
3
+ * `[verify] exclude = "<regex>"` in .agentvibes/project.toml — a VENDORED-tree
4
+ * carve-out, ported from merkle's check-diff.sh: a verbatim copy of somebody
5
+ * else's package must not be held to this repo's rules (editing it to satisfy
6
+ * them would turn the next upstream re-sync into a merge conflict and falsify
7
+ * the byte-identical provenance that is the reason to vendor). The regex tests
8
+ * the file path relative to the repo root.
9
+ */
10
+ export declare function verifyExcludeRe(cwd: string): RegExp | undefined;
11
+ export interface VerifyDiffResult {
12
+ rungLabel: string;
13
+ filesScanned: number;
14
+ violations: Finding[];
15
+ }
16
+ /** The verify-diff computation without reporting — shared by the CLI command and hook-stop. */
17
+ export declare function runVerifyDiffCollect(cwd: string, base: string | undefined): VerifyDiffResult;
18
+ export declare function runVerifyDiff(base: string | undefined, json: boolean): number;
@@ -0,0 +1,126 @@
1
+ import { join, relative } from "node:path";
2
+ import { match } from "ts-pattern";
3
+ import { scanFindings } from "./astGrep.js";
4
+ import { formatFinding } from "./findings.js";
5
+ import { addedLines, changedFiles, gitRoot, resolveMergeBase } from "./gitDiff.js";
6
+ import { rulesConfig } from "./packagePaths.js";
7
+ import { SeverityConfigError, severityRaiseArgs } from "./severity.js";
8
+ import { structureFindings } from "./structure.js";
9
+ import { readTomlTable } from "./tomlTable.js";
10
+ /**
11
+ * `[verify] exclude = "<regex>"` in .agentvibes/project.toml — a VENDORED-tree
12
+ * carve-out, ported from merkle's check-diff.sh: a verbatim copy of somebody
13
+ * else's package must not be held to this repo's rules (editing it to satisfy
14
+ * them would turn the next upstream re-sync into a merge conflict and falsify
15
+ * the byte-identical provenance that is the reason to vendor). The regex tests
16
+ * the file path relative to the repo root.
17
+ */
18
+ export function verifyExcludeRe(cwd) {
19
+ const raw = readTomlTable(join(cwd, ".agentvibes", "project.toml"), "verify").exclude;
20
+ if (raw === undefined || raw === "")
21
+ return undefined;
22
+ return new RegExp(raw);
23
+ }
24
+ function resolveRung(cwd, base) {
25
+ const root = gitRoot(cwd);
26
+ if (root === undefined)
27
+ return { kind: "whole-tree" };
28
+ const mb = resolveMergeBase(root, base);
29
+ if (mb === undefined)
30
+ return { kind: "whole-file", root };
31
+ return { kind: "added-lines", root, mergeBase: mb };
32
+ }
33
+ function planFor(rung) {
34
+ return match(rung)
35
+ .with({ kind: "whole-tree" }, () => ({
36
+ rungLabel: "whole-tree (not a git repository — nothing to diff against, gating everything)",
37
+ files: undefined,
38
+ keep: () => true,
39
+ }))
40
+ .with({ kind: "whole-file" }, ({ root }) => {
41
+ const { tracked, untracked } = changedFiles(root, "HEAD");
42
+ return {
43
+ rungLabel: "whole-file (no resolvable base ref — gating every line of changed/untracked files)",
44
+ files: [...new Set([...tracked, ...untracked])],
45
+ keep: () => true,
46
+ };
47
+ })
48
+ .with({ kind: "added-lines" }, ({ root, mergeBase }) => {
49
+ const { tracked, untracked } = changedFiles(root, mergeBase);
50
+ const untrackedSet = new Set(untracked);
51
+ const addedByFile = new Map();
52
+ return {
53
+ rungLabel: `added-lines vs merge-base ${mergeBase.slice(0, 12)}`,
54
+ files: [...new Set([...tracked, ...untracked])],
55
+ keep: (f) => {
56
+ if (untrackedSet.has(f.file))
57
+ return true;
58
+ let added = addedByFile.get(f.file);
59
+ if (added === undefined) {
60
+ added = addedLines(root, mergeBase, f.file);
61
+ addedByFile.set(f.file, added);
62
+ }
63
+ return added.has(f.line);
64
+ },
65
+ };
66
+ })
67
+ .exhaustive();
68
+ }
69
+ /** The verify-diff computation without reporting — shared by the CLI command and hook-stop. */
70
+ export function runVerifyDiffCollect(cwd, base) {
71
+ const severityArgs = severityRaiseArgs(cwd);
72
+ const plan = planFor(resolveRung(cwd, base));
73
+ const exclude = verifyExcludeRe(cwd);
74
+ if (exclude !== undefined && plan.files !== undefined) {
75
+ // changedFiles yields absolute paths; the exclude regex (donor precedent:
76
+ // '^packages/site-chrome/') is written against repo-root-relative ones.
77
+ plan.files = plan.files.filter((f) => !exclude.test(relative(cwd, f)));
78
+ }
79
+ if (plan.files !== undefined && plan.files.length === 0) {
80
+ return { rungLabel: plan.rungLabel, filesScanned: 0, violations: [] };
81
+ }
82
+ const targets = plan.files ?? ["."];
83
+ const violations = [
84
+ ...scanFindings(rulesConfig, targets, severityArgs),
85
+ ...structureFindings(targets),
86
+ ]
87
+ .filter((f) => f.severity === "error")
88
+ .filter(plan.keep);
89
+ return { rungLabel: plan.rungLabel, filesScanned: plan.files?.length ?? -1, violations };
90
+ }
91
+ export function runVerifyDiff(base, json) {
92
+ let result;
93
+ try {
94
+ result = runVerifyDiffCollect(process.cwd(), base);
95
+ }
96
+ catch (err) {
97
+ if (err instanceof SeverityConfigError) {
98
+ console.error(`guardrails verify-diff: ${err.message}`);
99
+ return 2;
100
+ }
101
+ throw err;
102
+ }
103
+ const { rungLabel, filesScanned, violations: gated } = result;
104
+ if (filesScanned === 0) {
105
+ report(json, rungLabel, 0, [], "no changed .ts/.tsx files — nothing to gate");
106
+ return 0;
107
+ }
108
+ report(json, rungLabel, filesScanned, gated, gated.length === 0
109
+ ? "OK — no new error-tier findings"
110
+ : `${gated.length} new ERROR-tier finding(s)`);
111
+ return gated.length === 0 ? 0 : 1;
112
+ }
113
+ function report(json, rung, files, gated, summary) {
114
+ if (json) {
115
+ console.log(JSON.stringify({ command: "verify-diff", rung, filesScanned: files, violations: gated, summary }, null, 2));
116
+ return;
117
+ }
118
+ console.log(`guardrails verify-diff — ${rung}`);
119
+ for (const f of gated)
120
+ console.log(formatFinding(f));
121
+ console.log(`guardrails verify-diff: ${summary}`);
122
+ if (gated.length > 0) {
123
+ console.log("Fix these, or suppress a genuine false positive with a justified comment on the line above:\n // ast-grep-ignore: <rule-id> -- <why this is legitimate>");
124
+ }
125
+ }
126
+ //# sourceMappingURL=verifyDiff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verifyDiff.js","sourceRoot":"","sources":["../src/verifyDiff.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export interface WorkspacePackage {
2
+ name: string;
3
+ dir: string;
4
+ deps: Record<string, string>;
5
+ }
6
+ export declare function listWorkspacePackages(cwd: string): WorkspacePackage[];
@@ -0,0 +1,84 @@
1
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
2
+ import { basename, join } from "node:path";
3
+ function workspaceGlobs(cwd) {
4
+ const pnpmPath = join(cwd, "pnpm-workspace.yaml");
5
+ if (existsSync(pnpmPath)) {
6
+ const globs = [];
7
+ let inPackages = false;
8
+ for (const raw of readFileSync(pnpmPath, "utf8").split("\n")) {
9
+ const line = raw.replace(/#.*$/, "").trimEnd();
10
+ if (/^packages:\s*$/.test(line)) {
11
+ inPackages = true;
12
+ continue;
13
+ }
14
+ if (inPackages) {
15
+ const m = line.match(/^\s+-\s+["']?([^"']+)["']?\s*$/);
16
+ if (m?.[1] !== undefined)
17
+ globs.push(m[1]);
18
+ else if (!/^\s/.test(line) && line !== "")
19
+ inPackages = false;
20
+ }
21
+ }
22
+ if (globs.length > 0)
23
+ return globs;
24
+ }
25
+ const pkgPath = join(cwd, "package.json");
26
+ // ast-grep-ignore: silent-default-return -- no package.json means no workspaces to enumerate; the caller falls back to root-only detection
27
+ if (!existsSync(pkgPath))
28
+ return [];
29
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
30
+ if (Array.isArray(pkg.workspaces))
31
+ return pkg.workspaces;
32
+ return pkg.workspaces?.packages ?? [];
33
+ }
34
+ /** Expand a workspace glob. Supports literal dirs and a trailing `*` segment (`packages/*`). */
35
+ function expandGlob(cwd, glob) {
36
+ const clean = glob.replace(/\/$/, "");
37
+ if (clean.startsWith("!"))
38
+ return [];
39
+ if (!clean.includes("*"))
40
+ return [join(cwd, clean)];
41
+ const starAt = clean.indexOf("*");
42
+ const parent = join(cwd, clean.slice(0, starAt).replace(/\/$/, ""));
43
+ let entries;
44
+ try {
45
+ entries = readdirSync(parent);
46
+ }
47
+ catch {
48
+ return [];
49
+ }
50
+ return entries
51
+ .map((e) => join(parent, e))
52
+ .filter((p) => {
53
+ try {
54
+ return statSync(p).isDirectory();
55
+ }
56
+ catch {
57
+ return false;
58
+ }
59
+ });
60
+ }
61
+ export function listWorkspacePackages(cwd) {
62
+ const out = [];
63
+ for (const glob of workspaceGlobs(cwd)) {
64
+ for (const dir of expandGlob(cwd, glob)) {
65
+ const pkgPath = join(dir, "package.json");
66
+ if (!existsSync(pkgPath))
67
+ continue;
68
+ let pkg;
69
+ try {
70
+ pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
71
+ }
72
+ catch {
73
+ continue;
74
+ }
75
+ out.push({
76
+ name: pkg.name ?? basename(dir),
77
+ dir,
78
+ deps: { ...pkg.dependencies, ...pkg.devDependencies },
79
+ });
80
+ }
81
+ }
82
+ return out;
83
+ }
84
+ //# sourceMappingURL=workspaces.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspaces.js","sourceRoot":"","sources":["../src/workspaces.ts"],"names":[],"mappings":"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"}
package/mise.toml ADDED
@@ -0,0 +1,9 @@
1
+ # Pinned toolchain for @agentvibes/guardrails. The CLI resolves ast-grep and
2
+ # biome from PATH first and falls back to `mise x <tool>@<pin>` using these
3
+ # pins, so every host (and CI via jdx/mise-action) runs the same versions.
4
+ [tools]
5
+ node = "25.9.0"
6
+ pnpm = "10.12.1"
7
+ ast-grep = "0.45.2"
8
+ biome = "2.5.10"
9
+ gitleaks = "8.30.1"
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@agentvibes/guardrails",
3
+ "version": "0.1.0",
4
+ "description": "Verification toolkit that makes agents write simple code: ast-grep rule canon, diff ratchet, biome/tsconfig presets, one CLI.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/AgentVibes/guardrails.git"
9
+ },
10
+ "type": "module",
11
+ "bin": {
12
+ "guardrails": "dist/cli.js"
13
+ },
14
+ "exports": {
15
+ "./biome": "./configs/biome.json",
16
+ "./tsconfig": "./configs/tsconfig.base.json",
17
+ "./sgconfig": "./sgconfig.yml",
18
+ "./package.json": "./package.json",
19
+ "./plugin": "./dist/pluginApi.js"
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "rules",
24
+ "structure",
25
+ "candidates",
26
+ "configs",
27
+ "sgconfig.yml",
28
+ "mise.toml"
29
+ ],
30
+ "scripts": {
31
+ "build": "tsc -p tsconfig.json",
32
+ "prepare": "tsc -p tsconfig.json",
33
+ "typecheck": "tsc -p tsconfig.json --noEmit",
34
+ "lint": "biome check .",
35
+ "format": "biome check --write .",
36
+ "test:rules": "node dist/testRules.js",
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",
39
+ "test:metrics": "node dist/metricsCycle.js",
40
+ "leaks": "node dist/cli.js leaks .",
41
+ "test:leaks": "node dist/leaksRed.js",
42
+ "test:init": "node dist/initTest.js",
43
+ "test:severity": "node dist/severityTest.js",
44
+ "test:hooks": "node dist/hookStopTest.js",
45
+ "test:iterate": "node dist/iterateTest.js",
46
+ "test:screens": "node dist/screenScopeTest.js"
47
+ },
48
+ "dependencies": {
49
+ "ts-pattern": "^5.6.2",
50
+ "typescript": "^5.7.3"
51
+ },
52
+ "devDependencies": {
53
+ "@types/node": "^22.13.0"
54
+ },
55
+ "engines": {
56
+ "node": ">=22"
57
+ },
58
+ "packageManager": "pnpm@10.12.1",
59
+ "publishConfig": {
60
+ "access": "public"
61
+ }
62
+ }
@@ -0,0 +1,8 @@
1
+ // Every cast here MUST be reported by `as-any-escape`.
2
+ export const a = payload as any
3
+ export const b = payload as unknown as User
4
+ // ADDED BY THE WAVE-2 UPGRADE: a surface `pattern:` is compared at `smart`
5
+ // strictness, which also compares interleaved comment nodes, so this slipped
6
+ // straight through the old body.
7
+ export const c = payload as /* legacy shim */ any
8
+ export const d = payload as /* boundary */ unknown as User
@@ -0,0 +1,14 @@
1
+ // NOTHING in this file may be reported by `as-any-escape`.
2
+ import { UserSchema } from "./schema"
3
+
4
+ // The fix: validate at the boundary.
5
+ export const a = UserSchema.parse(payload)
6
+
7
+ // Widening to `unknown` alone is fine — it FORCES the caller to narrow, which
8
+ // is the behaviour we want. Only the double-cast that lands back on a concrete
9
+ // type is an escape hatch.
10
+ export const b = payload as unknown
11
+
12
+ // Ordinary, checked casts.
13
+ export const c = "idle" as const
14
+ export const d = value as never
@@ -0,0 +1,7 @@
1
+ // Every catch here MUST be reported by `catch-empty`.
2
+ export function a() { try { work() } catch { } }
3
+ export function b() { try { work() } catch (e) { } }
4
+ // ADDED BY THE WAVE-2 UPGRADE: the type annotation is an extra named node, so
5
+ // the old `catch ($_) { }` pattern did not cover either of these.
6
+ export function c() { try { work() } catch (e: unknown) { } }
7
+ export function d() { try { work() } catch (e: any) { } }
@@ -0,0 +1,14 @@
1
+ // NOTHING in this file may be reported by `catch-empty`.
2
+ export function a() {
3
+ try { work() } catch (e) { logger.error("work failed", e); throw e }
4
+ }
5
+ export function b(): Result {
6
+ try { return { status: "ok", value: work() } }
7
+ catch (e: unknown) { return { status: "error", message: String(e) } }
8
+ }
9
+ // A catch that deliberately does nothing must SAY so — the comment is the
10
+ // difference between a decision and an oversight, and ast-grep-ignore records it.
11
+ export function c() {
12
+ // ast-grep-ignore: catch-empty -- idempotent mkdir, "already exists" is expected
13
+ try { mkdir(p) } catch (e) { }
14
+ }
@@ -0,0 +1,23 @@
1
+ // Every import in this file MUST be reported by `direct-store-import`.
2
+
3
+ // A store CLASS pulled into a component file — the component now constructs or
4
+ // type-switches on the implementation instead of receiving a model.
5
+ import { GalleryStore } from "../../stores/galleryStore"
6
+
7
+ // A slice-store singleton. `rootStore` at a screen is legal (decision D1);
8
+ // reaching past it for one slice is not.
9
+ import { galleryStore } from "../../stores/galleryStore"
10
+
11
+ // Same, from a barrel.
12
+ import { authStore } from "../../stores"
13
+
14
+ // Aliased — the local binding is what the file actually uses.
15
+ import { galleryStore as gallery } from "../../store/gallery"
16
+
17
+ // Default-exported store instance.
18
+ import photoStore from "../../stores/photoStore"
19
+
20
+ // Mixed: `rootStore` alone would be fine, the slice next to it is not.
21
+ import { rootStore, uiStore } from "../../stores/rootStore"
22
+
23
+ export const X = () => <div>{GalleryStore.name}{galleryStore.count}{authStore.id}{gallery.n}{photoStore.n}{rootStore.n}{uiStore.tab}</div>