@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,117 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { dirname, relative } from "node:path";
4
+ import { formatFinding } from "./findings.js";
5
+ import { addedLines, gitRoot, resolveMergeBase } from "./gitDiff.js";
6
+ import { severityRaiseArgs } from "./severity.js";
7
+ import { collectVerifyFindings } from "./verify.js";
8
+ import { verifyExcludeRe } from "./verifyDiff.js";
9
+ const SUPPRESS = "False positive? Suppress a specific finding with a justified comment on the line above it:\n// ast-grep-ignore: <rule-id> -- <why this is legitimate>\nThe scan honors it and will not re-flag that line.";
10
+ function readStdin() {
11
+ try {
12
+ return readFileSync(0, "utf8");
13
+ }
14
+ catch {
15
+ return "";
16
+ }
17
+ }
18
+ function isTracked(root, file) {
19
+ const res = spawnSync("git", ["-C", root, "ls-files", "--error-unmatch", "--", file], {
20
+ encoding: "utf8",
21
+ });
22
+ return res.status === 0;
23
+ }
24
+ /** undefined = whole file (ladder rungs 1/2/4); a Set = rung 3's changed lines. */
25
+ function changedLines(tool, file) {
26
+ if (tool === "Write")
27
+ return undefined;
28
+ const root = gitRoot(dirname(file));
29
+ if (root === undefined)
30
+ return undefined;
31
+ if (!isTracked(root, file))
32
+ return undefined;
33
+ const mb = resolveMergeBase(root);
34
+ if (mb === undefined)
35
+ return undefined;
36
+ return addedLines(root, mb, file);
37
+ }
38
+ /** Lines this working tree added vs HEAD — the newness test for structure tiering. */
39
+ function newLines(file) {
40
+ const root = gitRoot(dirname(file));
41
+ if (root === undefined)
42
+ return undefined;
43
+ if (!isTracked(root, file))
44
+ return undefined;
45
+ return addedLines(root, "HEAD", file);
46
+ }
47
+ export function runHookPostedit() {
48
+ let input;
49
+ try {
50
+ input = JSON.parse(readStdin());
51
+ }
52
+ catch {
53
+ return 0;
54
+ }
55
+ const tool = input.tool_name ?? "";
56
+ const file = input.tool_input?.file_path ?? "";
57
+ if (tool !== "Edit" && tool !== "Write")
58
+ return 0;
59
+ const configDir = process.env.AGENT_CONFIG_DIR;
60
+ if (configDir !== undefined && configDir !== "" && file.startsWith(`${configDir}/`))
61
+ return 0;
62
+ if (!file.endsWith(".ts") && !file.endsWith(".tsx"))
63
+ return 0;
64
+ // ast-grep-ignore: silent-default-return -- a hook fires on deletes/renames too; a vanished file has nothing to scan and hooks must never fail the edit (donor hook-postedit.sh line `[ -f "$FILE" ] || exit 0`)
65
+ if (!existsSync(file))
66
+ return 0;
67
+ // Honour the repo's `[verify] exclude` carve-out (vendored trees) the same
68
+ // way verify-diff does — an edit inside a vendored copy is not gated.
69
+ const repoRoot = gitRoot(dirname(file));
70
+ if (repoRoot !== undefined &&
71
+ verifyExcludeRe(repoRoot)?.test(relative(repoRoot, file)) === true) {
72
+ return 0;
73
+ }
74
+ // [severity] raises apply here too; a MISCONFIGURED table must not fail the
75
+ // edit (hooks never block on their own defects) — the raise is skipped and
76
+ // verify/verify-diff will fail loudly on the same config in CI.
77
+ let severityArgs = [];
78
+ try {
79
+ severityArgs = severityRaiseArgs(repoRoot ?? dirname(file));
80
+ }
81
+ catch {
82
+ severityArgs = [];
83
+ }
84
+ let findings = collectVerifyFindings([file], severityArgs);
85
+ const lines = changedLines(tool, file);
86
+ if (lines !== undefined) {
87
+ // An EMPTY set is a real answer (a revert git already collapsed) — there
88
+ // is nothing of the agent's to gate. Only failure to COMPUTE degrades.
89
+ findings = findings.filter((f) => lines.has(f.line));
90
+ }
91
+ else {
92
+ const fresh = newLines(file);
93
+ findings = findings.map((f) => f.source === "structure" && f.severity === "error" && !(fresh?.has(f.line) ?? true)
94
+ ? { ...f, severity: "warning" }
95
+ : f);
96
+ }
97
+ if (findings.length === 0)
98
+ return 0;
99
+ const rendered = findings.map(formatFinding).join("\n").split("\n").slice(0, 60).join("\n");
100
+ const base = file.split("/").pop() ?? file;
101
+ if (findings.some((f) => f.severity === "error")) {
102
+ console.log(JSON.stringify({
103
+ decision: "block",
104
+ reason: `guardrails scan found ERROR-tier violations in ${base}:\n\n${rendered}\n\nFix these before continuing. ${SUPPRESS}\nRule rationale: the defensive-errors and mobx-models skills; \`guardrails verify --help\`.`,
105
+ }));
106
+ }
107
+ else {
108
+ console.log(JSON.stringify({
109
+ hookSpecificOutput: {
110
+ hookEventName: "PostToolUse",
111
+ additionalContext: `guardrails scan flagged warnings in ${base}:\n\n${rendered}\n\nAddress these or, if a finding is a false positive, suppress it. ${SUPPRESS}`,
112
+ },
113
+ }));
114
+ }
115
+ return 0;
116
+ }
117
+ //# sourceMappingURL=hookPostedit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hookPostedit.js","sourceRoot":"","sources":["../src/hookPostedit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare function runHookStop(): number;
@@ -0,0 +1,56 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { formatFinding } from "./findings.js";
5
+ import { runVerifyDiffCollect } from "./verifyDiff.js";
6
+ const MAX_BLOCKS = Number(process.env.GUARDRAILS_STOP_MAX_BLOCKS ?? 3);
7
+ function counterPath(sessionId) {
8
+ const dir = join(tmpdir(), "guardrails-stop");
9
+ mkdirSync(dir, { recursive: true });
10
+ return join(dir, `${sessionId.replace(/[^A-Za-z0-9_-]/g, "_")}.count`);
11
+ }
12
+ function readCount(path) {
13
+ // ast-grep-ignore: silent-default-return -- a missing counter file IS the zero-blocks state, not a masked failure
14
+ if (!existsSync(path))
15
+ return 0;
16
+ const n = Number(readFileSync(path, "utf8").trim());
17
+ return Number.isFinite(n) ? n : 0;
18
+ }
19
+ export function runHookStop() {
20
+ try {
21
+ let input;
22
+ try {
23
+ input = JSON.parse(readFileSync(0, "utf8"));
24
+ }
25
+ catch {
26
+ return 0;
27
+ }
28
+ const sessionId = input.session_id ?? "unknown-session";
29
+ const { violations } = runVerifyDiffCollect(process.cwd(), undefined);
30
+ if (violations.length === 0)
31
+ return 0;
32
+ const path = counterPath(sessionId);
33
+ const blocks = readCount(path);
34
+ const rendered = violations.map(formatFinding).join("\n").split("\n").slice(0, 40).join("\n");
35
+ if (blocks >= MAX_BLOCKS) {
36
+ // Loud skip, never a fourth block: the warning goes to stderr (visible
37
+ // in the transcript) and names what was left red.
38
+ console.error(`guardrails hook-stop: SKIPPING the block — this session was already stopped ${blocks}x by this gate (max ${MAX_BLOCKS}). ${violations.length} error-tier finding(s) REMAIN on the session's changed lines:\n${rendered}\nRun \`guardrails verify-diff\` and fix them; CI will hold the same line.`);
39
+ return 0;
40
+ }
41
+ writeFileSync(path, String(blocks + 1));
42
+ const finalWarning = blocks + 1 === MAX_BLOCKS
43
+ ? `\n(This is block ${blocks + 1}/${MAX_BLOCKS} — the next Stop will NOT be blocked; CI still will.)`
44
+ : "";
45
+ console.log(JSON.stringify({
46
+ decision: "block",
47
+ reason: `guardrails verify-diff: ${violations.length} error-tier finding(s) on lines this session changed — finish the cleanup before stopping:\n\n${rendered}\n\nFix 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>${finalWarning}`,
48
+ }));
49
+ return 0;
50
+ }
51
+ catch {
52
+ // Self-defect rule: a broken hook must not wedge the session.
53
+ return 0;
54
+ }
55
+ }
56
+ //# sourceMappingURL=hookStop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hookStop.js","sourceRoot":"","sources":["../src/hookStop.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,71 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { packageRoot } from "./packagePaths.js";
6
+ // Fixtures for the Stop hook: block on red, silent on green, and the loop
7
+ // breaker — after max blocks the hook SKIPS loudly instead of wedging the
8
+ // session forever behind its own gate.
9
+ function stop(cwd, sessionId) {
10
+ const res = spawnSync("node", [join(packageRoot, "dist", "cli.js"), "hook-stop"], {
11
+ cwd,
12
+ input: JSON.stringify({ session_id: sessionId }),
13
+ encoding: "utf8",
14
+ env: { ...process.env, GUARDRAILS_STOP_MAX_BLOCKS: "2" },
15
+ });
16
+ if (res.error)
17
+ throw res.error;
18
+ return { status: res.status ?? -1, out: res.stdout, err: res.stderr };
19
+ }
20
+ function expectStep(step, ok, detail) {
21
+ if (!ok) {
22
+ console.error(`FAIL hook-stop: ${step}\n${detail}`);
23
+ return false;
24
+ }
25
+ console.log(` ok hook-stop: ${step}`);
26
+ return true;
27
+ }
28
+ function main() {
29
+ const dir = mkdtempSync(join(tmpdir(), "guardrails-hookstop-"));
30
+ const session = `hookstop-test-${Date.now()}`;
31
+ try {
32
+ const git = (...args) => {
33
+ const r = spawnSync("git", ["-C", dir, ...args], { encoding: "utf8" });
34
+ if (r.status !== 0)
35
+ throw new Error(`git ${args.join(" ")}: ${r.stderr}`);
36
+ };
37
+ git("init", "-q");
38
+ writeFileSync(join(dir, "clean.ts"), "export const ok = 1;\n");
39
+ git("add", "-A");
40
+ git("-c", "user.email=t@t", "-c", "user.name=t", "commit", "-qm", "init");
41
+ const green = stop(dir, session);
42
+ if (!expectStep("clean session → silent allow", green.status === 0 && green.out.trim() === "", `${green.out}${green.err}`)) {
43
+ return 1;
44
+ }
45
+ writeFileSync(join(dir, "sessionWork.ts"), "export const boom = (x: unknown) => x as any;\n");
46
+ const b1 = stop(dir, session);
47
+ if (!expectStep("red session → block 1 with findings", b1.status === 0 &&
48
+ b1.out.includes('"decision":"block"') &&
49
+ b1.out.includes("as-any-escape"), b1.out)) {
50
+ return 1;
51
+ }
52
+ const b2 = stop(dir, session);
53
+ if (!expectStep("block 2/2 carries the final warning", b2.out.includes('"decision":"block"') && b2.out.includes("will NOT be blocked"), b2.out)) {
54
+ return 1;
55
+ }
56
+ const b3 = stop(dir, session);
57
+ if (!expectStep("block 3 → loop breaker: loud SKIP on stderr, no block", b3.status === 0 &&
58
+ !b3.out.includes("block") &&
59
+ b3.err.includes("SKIPPING") &&
60
+ b3.err.includes("as-any-escape"), `${b3.out}\n${b3.err}`)) {
61
+ return 1;
62
+ }
63
+ console.log("hook-stop fixtures passed");
64
+ return 0;
65
+ }
66
+ finally {
67
+ rmSync(dir, { recursive: true, force: true });
68
+ }
69
+ }
70
+ process.exit(main());
71
+ //# sourceMappingURL=hookStopTest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hookStopTest.js","sourceRoot":"","sources":["../src/hookStopTest.ts"],"names":[],"mappings":"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"}
package/dist/init.d.ts ADDED
@@ -0,0 +1,24 @@
1
+ interface Stack {
2
+ platform: string;
3
+ state: string;
4
+ storeRuntime: string;
5
+ styling: string;
6
+ componentModel: string;
7
+ }
8
+ /** Per-package detection signals; a package with none contributes nothing. */
9
+ interface AppSignals {
10
+ name: string;
11
+ platform?: string;
12
+ state?: "mobx";
13
+ styling?: string;
14
+ }
15
+ export interface DetectedStack {
16
+ stack: Stack;
17
+ /** workspace packages that carried at least one signal */
18
+ apps: AppSignals[];
19
+ /** fields where signal-bearing apps disagree — per-app blocks get emitted */
20
+ conflicts: (keyof Stack)[];
21
+ hadPackageJson: boolean;
22
+ }
23
+ export declare function runInit(json: boolean): number;
24
+ export {};
package/dist/init.js ADDED
@@ -0,0 +1,172 @@
1
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { listWorkspacePackages } from "./workspaces.js";
4
+ function signalsOf(name, deps) {
5
+ const has = (dep) => dep in deps;
6
+ const platform = has("expo")
7
+ ? "expo"
8
+ : has("next")
9
+ ? "next-client"
10
+ : has("vite")
11
+ ? "vite-spa"
12
+ : undefined;
13
+ const state = has("mobx") ? "mobx" : undefined;
14
+ const stylingDep = has("nativewind") ? "nativewind" : has("tailwindcss") ? "tailwind" : undefined;
15
+ if (platform === undefined && state === undefined && stylingDep === undefined)
16
+ return undefined;
17
+ // An app (something with a platform) that installs no styling system is
18
+ // theme-inline by house convention; a library without a platform makes no
19
+ // styling claim at all.
20
+ const styling = stylingDep ?? (platform !== undefined ? "theme-inline" : undefined);
21
+ return {
22
+ name,
23
+ ...(platform !== undefined ? { platform } : {}),
24
+ ...(state !== undefined ? { state } : {}),
25
+ ...(styling !== undefined ? { styling } : {}),
26
+ };
27
+ }
28
+ // Monorepo-aware: the root package.json of a workspace repo is dependency-
29
+ // empty, so signals are gathered from every workspace package too (the
30
+ // observatory adoption hit exactly this — root-only detection said state=none
31
+ // for a mobx repo). When signal-bearing apps DISAGREE on a field, the
32
+ // aggregate takes the first app's value and a per-app block is materialized
33
+ // for each, so the disagreement is visible in the TOML instead of averaged
34
+ // away. Manual editing of the materialized file remains the designed fallback.
35
+ function detectStack(cwd) {
36
+ const pkgPath = join(cwd, "package.json");
37
+ let rootDeps = {};
38
+ const hadPackageJson = existsSync(pkgPath);
39
+ if (hadPackageJson) {
40
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
41
+ rootDeps = { ...pkg.dependencies, ...pkg.devDependencies };
42
+ }
43
+ const apps = [];
44
+ const rootSignals = signalsOf("(root)", rootDeps);
45
+ if (rootSignals !== undefined)
46
+ apps.push(rootSignals);
47
+ for (const ws of listWorkspacePackages(cwd)) {
48
+ const s = signalsOf(ws.name, ws.deps);
49
+ if (s !== undefined)
50
+ apps.push(s);
51
+ }
52
+ const distinct = (field) => [
53
+ ...new Set(apps.map((a) => a[field]).filter((v) => v !== undefined)),
54
+ ];
55
+ const platforms = distinct("platform");
56
+ const stylings = distinct("styling");
57
+ const state = apps.some((a) => a.state === "mobx") ? "mobx" : "none";
58
+ const conflicts = [];
59
+ if (platforms.length > 1)
60
+ conflicts.push("platform");
61
+ if (stylings.length > 1)
62
+ conflicts.push("styling");
63
+ const stack = {
64
+ platform: platforms[0] ?? "unknown",
65
+ state,
66
+ storeRuntime: state === "mobx" ? "client-singleton" : "none",
67
+ styling: stylings[0] ?? "theme-inline",
68
+ componentModel: "conventional",
69
+ };
70
+ return { stack, apps, conflicts, hadPackageJson };
71
+ }
72
+ function stackToml(detected) {
73
+ const { stack, apps, conflicts } = detected;
74
+ const provenance = apps.length > 0 ? `# detected from: ${apps.map((a) => a.name).join(", ")}` : undefined;
75
+ const lines = [
76
+ "[stack]",
77
+ ...(provenance !== undefined ? [provenance] : []),
78
+ ...(conflicts.length > 0
79
+ ? [`# apps disagree on: ${conflicts.join(", ")} — per-app values below; edit as needed`]
80
+ : []),
81
+ `platform = "${stack.platform}" # vite-spa | expo | next-client | next-ssr`,
82
+ `state = "${stack.state}" # mobx | none`,
83
+ `storeRuntime = "${stack.storeRuntime}" # client-singleton | request-scoped | none`,
84
+ `styling = "${stack.styling}" # theme-inline | tailwind | nativewind`,
85
+ `componentModel = "${stack.componentModel}" # two-tier | conventional`,
86
+ ];
87
+ if (conflicts.length > 0) {
88
+ for (const app of apps) {
89
+ lines.push("", `[stack.app."${app.name}"]`);
90
+ if (app.platform !== undefined)
91
+ lines.push(`platform = "${app.platform}"`);
92
+ if (app.state !== undefined)
93
+ lines.push(`state = "${app.state}"`);
94
+ if (app.styling !== undefined)
95
+ lines.push(`styling = "${app.styling}"`);
96
+ }
97
+ }
98
+ lines.push("");
99
+ return lines.join("\n");
100
+ }
101
+ const SGCONFIG_STUB = `# ast-grep config — rules come from the @agentvibes/guardrails package.
102
+ # Add repo-local extra rules by creating .ast-grep/rules/ and uncommenting the
103
+ # second ruleDir. Run: guardrails verify [paths]
104
+ ruleDirs:
105
+ - node_modules/@agentvibes/guardrails/rules
106
+ # - .ast-grep/rules
107
+ `;
108
+ const BIOME_STUB = `{
109
+ "$schema": "https://biomejs.dev/schemas/2.5.10/schema.json",
110
+ "extends": ["@agentvibes/guardrails/biome"]
111
+ }
112
+ `;
113
+ export function runInit(json) {
114
+ const cwd = process.cwd();
115
+ const actions = [];
116
+ const detected = detectStack(cwd);
117
+ const { stack, apps, conflicts, hadPackageJson } = detected;
118
+ const sgPath = join(cwd, "sgconfig.yml");
119
+ if (existsSync(sgPath)) {
120
+ actions.push({ file: "sgconfig.yml", action: "skipped-exists" });
121
+ }
122
+ else {
123
+ writeFileSync(sgPath, SGCONFIG_STUB);
124
+ actions.push({ file: "sgconfig.yml", action: "created" });
125
+ }
126
+ const biomePath = join(cwd, "biome.json");
127
+ if (existsSync(biomePath)) {
128
+ actions.push({ file: "biome.json", action: "skipped-exists" });
129
+ }
130
+ else {
131
+ writeFileSync(biomePath, BIOME_STUB);
132
+ actions.push({ file: "biome.json", action: "created" });
133
+ }
134
+ const tomlDir = join(cwd, ".agentvibes");
135
+ const tomlPath = join(tomlDir, "project.toml");
136
+ if (!existsSync(tomlPath)) {
137
+ mkdirSync(tomlDir, { recursive: true });
138
+ writeFileSync(tomlPath, `${stackToml(detected)}`);
139
+ actions.push({ file: ".agentvibes/project.toml", action: "created" });
140
+ }
141
+ else if (/^\[stack\]/m.test(readFileSync(tomlPath, "utf8"))) {
142
+ actions.push({ file: ".agentvibes/project.toml", action: "skipped-exists" });
143
+ }
144
+ else {
145
+ appendFileSync(tomlPath, `\n${stackToml(detected)}`);
146
+ actions.push({ file: ".agentvibes/project.toml", action: "appended" });
147
+ }
148
+ if (json) {
149
+ console.log(JSON.stringify({ command: "init", detected: stack, apps, conflicts, hadPackageJson, actions }, null, 2));
150
+ return 0;
151
+ }
152
+ console.log(`guardrails init — detected stack${hadPackageJson ? "" : " (no package.json found — all defaults)"}:`);
153
+ console.log(` platform=${stack.platform} state=${stack.state} storeRuntime=${stack.storeRuntime} styling=${stack.styling} componentModel=${stack.componentModel}`);
154
+ if (apps.length > 0) {
155
+ console.log(` signal packages: ${apps.map((a) => a.name).join(", ")}`);
156
+ }
157
+ if (conflicts.length > 0) {
158
+ console.log(` apps DISAGREE on ${conflicts.join(", ")} — per-app [stack.app] blocks materialized; edit them by hand`);
159
+ }
160
+ for (const a of actions)
161
+ console.log(` ${a.action.padEnd(14)} ${a.file}`);
162
+ const skippedStack = actions.some((a) => a.file === ".agentvibes/project.toml" && a.action === "skipped-exists");
163
+ if (skippedStack) {
164
+ console.log(" [stack] already declared — the existing config wins over detection; edit it by hand.");
165
+ }
166
+ else {
167
+ console.log(" Detected values are now MATERIALIZED in .agentvibes/project.toml — edit them there;");
168
+ console.log(" nothing re-infers them silently.");
169
+ }
170
+ return 0;
171
+ }
172
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,88 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { packageRoot } from "./packagePaths.js";
6
+ // Fixture tests for `guardrails init` stack detection. Three mini-repos:
7
+ // 1. single-package app — the original root-only path still works
8
+ // 2. pnpm monorepo, one signal app — signals aggregate from the workspace
9
+ // (the observatory case: root package.json is dependency-empty)
10
+ // 3. pnpm monorepo, two apps that DISAGREE — the aggregate takes the first
11
+ // app's value and per-app [stack.app] blocks are materialized
12
+ function init(cwd) {
13
+ const res = spawnSync("node", [join(packageRoot, "dist", "cli.js"), "init"], {
14
+ cwd,
15
+ encoding: "utf8",
16
+ });
17
+ if (res.error)
18
+ throw res.error;
19
+ return { status: res.status ?? -1, out: `${res.stdout}${res.stderr}` };
20
+ }
21
+ function pkg(dir, name, deps) {
22
+ mkdirSync(dir, { recursive: true });
23
+ writeFileSync(join(dir, "package.json"), JSON.stringify({ name, dependencies: deps }));
24
+ }
25
+ function expectToml(step, dir, wants, rejects) {
26
+ const toml = readFileSync(join(dir, ".agentvibes", "project.toml"), "utf8");
27
+ for (const w of wants) {
28
+ if (!toml.includes(w)) {
29
+ console.error(`FAIL init: ${step} — materialized TOML lacks '${w}'\n${toml}`);
30
+ return false;
31
+ }
32
+ }
33
+ for (const r of rejects) {
34
+ if (toml.includes(r)) {
35
+ console.error(`FAIL init: ${step} — materialized TOML unexpectedly contains '${r}'\n${toml}`);
36
+ return false;
37
+ }
38
+ }
39
+ console.log(` ok init: ${step}`);
40
+ return true;
41
+ }
42
+ function main() {
43
+ const base = mkdtempSync(join(tmpdir(), "guardrails-init-test-"));
44
+ try {
45
+ // 1. single-package app
46
+ const single = join(base, "single");
47
+ pkg(single, "single-app", { mobx: "^6", vite: "^6" });
48
+ if (init(single).status !== 0)
49
+ return 1;
50
+ if (!expectToml("single-package app detects from root", single, ['platform = "vite-spa"', 'state = "mobx"', 'storeRuntime = "client-singleton"'], ["[stack.app"])) {
51
+ return 1;
52
+ }
53
+ // 2. monorepo, dependency-empty root, one signal app among libraries
54
+ const mono = join(base, "mono");
55
+ pkg(mono, "mono-root", {});
56
+ writeFileSync(join(mono, "pnpm-workspace.yaml"), 'packages:\n - "packages/*"\n');
57
+ pkg(join(mono, "packages", "web-ui"), "@mono/web-ui", { mobx: "^6", vite: "^6" });
58
+ pkg(join(mono, "packages", "lib"), "@mono/lib", { "ts-pattern": "^5" });
59
+ if (init(mono).status !== 0)
60
+ return 1;
61
+ if (!expectToml("monorepo aggregates workspace signals", mono, ['state = "mobx"', 'platform = "vite-spa"', "detected from: @mono/web-ui"], ["[stack.app", "@mono/lib"])) {
62
+ return 1;
63
+ }
64
+ // 3. monorepo with two disagreeing apps
65
+ const dual = join(base, "dual");
66
+ pkg(dual, "dual-root", {});
67
+ writeFileSync(join(dual, "pnpm-workspace.yaml"), 'packages:\n - "apps/*"\n');
68
+ pkg(join(dual, "apps", "mobile"), "@dual/mobile", { expo: "^52", nativewind: "^4" });
69
+ pkg(join(dual, "apps", "web"), "@dual/web", { vite: "^6", mobx: "^6" });
70
+ if (init(dual).status !== 0)
71
+ return 1;
72
+ if (!expectToml("disagreeing apps materialize per-app blocks", dual, [
73
+ "apps disagree on: platform, styling",
74
+ '[stack.app."@dual/mobile"]',
75
+ '[stack.app."@dual/web"]',
76
+ 'state = "mobx"',
77
+ ], [])) {
78
+ return 1;
79
+ }
80
+ console.log("init stack-detection fixtures passed");
81
+ return 0;
82
+ }
83
+ finally {
84
+ rmSync(base, { recursive: true, force: true });
85
+ }
86
+ }
87
+ process.exit(main());
88
+ //# sourceMappingURL=initTest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"initTest.js","sourceRoot":"","sources":["../src/initTest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export interface IterateOptions {
2
+ task: string | undefined;
3
+ cmd: string | undefined;
4
+ gate: string | undefined;
5
+ max: number;
6
+ json: boolean;
7
+ cliPath: string;
8
+ }
9
+ export declare function runIterate(opts: IterateOptions): number;