@agentvibes/guardrails 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +239 -0
- package/candidates/README.md +30 -0
- package/candidates/r10-as-cast.yml +24 -0
- package/candidates/r1d-scattered-numeric-const-in-component.yml +13 -0
- package/candidates/r2-helper-fn-in-tsx.yml +22 -0
- package/candidates/r3-hooks-outside-layout.yml +21 -0
- package/candidates/r6-jsx-comment-smell.yml +14 -0
- package/candidates/r7-raw-hsl-outside-tokens.yml +17 -0
- package/candidates/r8-inline-style-prop.yml +13 -0
- package/candidates/r9-optional-store-arg.yml +24 -0
- package/configs/biome.json +96 -0
- package/configs/tsconfig.base.json +21 -0
- package/dist/astGrep.d.ts +16 -0
- package/dist/astGrep.js +48 -0
- package/dist/astGrep.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +138 -0
- package/dist/cli.js.map +1 -0
- package/dist/deploy.d.ts +1 -0
- package/dist/deploy.js +34 -0
- package/dist/deploy.js.map +1 -0
- package/dist/doctor.d.ts +6 -0
- package/dist/doctor.js +102 -0
- package/dist/doctor.js.map +1 -0
- package/dist/fileWalk.d.ts +2 -0
- package/dist/fileWalk.js +45 -0
- package/dist/fileWalk.js.map +1 -0
- package/dist/findings.d.ts +12 -0
- package/dist/findings.js +8 -0
- package/dist/findings.js.map +1 -0
- package/dist/gateRed.d.ts +1 -0
- package/dist/gateRed.js +93 -0
- package/dist/gateRed.js.map +1 -0
- package/dist/gitDiff.d.ts +18 -0
- package/dist/gitDiff.js +90 -0
- package/dist/gitDiff.js.map +1 -0
- package/dist/hookPostedit.d.ts +1 -0
- package/dist/hookPostedit.js +117 -0
- package/dist/hookPostedit.js.map +1 -0
- package/dist/hookStop.d.ts +1 -0
- package/dist/hookStop.js +56 -0
- package/dist/hookStop.js.map +1 -0
- package/dist/hookStopTest.d.ts +1 -0
- package/dist/hookStopTest.js +71 -0
- package/dist/hookStopTest.js.map +1 -0
- package/dist/init.d.ts +24 -0
- package/dist/init.js +172 -0
- package/dist/init.js.map +1 -0
- package/dist/initTest.d.ts +1 -0
- package/dist/initTest.js +88 -0
- package/dist/initTest.js.map +1 -0
- package/dist/iterate.d.ts +9 -0
- package/dist/iterate.js +79 -0
- package/dist/iterate.js.map +1 -0
- package/dist/iterateTest.d.ts +1 -0
- package/dist/iterateTest.js +91 -0
- package/dist/iterateTest.js.map +1 -0
- package/dist/leakPatterns.d.ts +5 -0
- package/dist/leakPatterns.js +22 -0
- package/dist/leakPatterns.js.map +1 -0
- package/dist/leaks.d.ts +8 -0
- package/dist/leaks.js +211 -0
- package/dist/leaks.js.map +1 -0
- package/dist/leaksRed.d.ts +1 -0
- package/dist/leaksRed.js +62 -0
- package/dist/leaksRed.js.map +1 -0
- package/dist/metrics.d.ts +10 -0
- package/dist/metrics.js +113 -0
- package/dist/metrics.js.map +1 -0
- package/dist/metricsAggregate.d.ts +5 -0
- package/dist/metricsAggregate.js +78 -0
- package/dist/metricsAggregate.js.map +1 -0
- package/dist/metricsBaseline.d.ts +21 -0
- package/dist/metricsBaseline.js +88 -0
- package/dist/metricsBaseline.js.map +1 -0
- package/dist/metricsCollect.d.ts +8 -0
- package/dist/metricsCollect.js +295 -0
- package/dist/metricsCollect.js.map +1 -0
- package/dist/metricsCycle.d.ts +1 -0
- package/dist/metricsCycle.js +79 -0
- package/dist/metricsCycle.js.map +1 -0
- package/dist/metricsTypes.d.ts +102 -0
- package/dist/metricsTypes.js +2 -0
- package/dist/metricsTypes.js.map +1 -0
- package/dist/packagePaths.d.ts +7 -0
- package/dist/packagePaths.js +11 -0
- package/dist/packagePaths.js.map +1 -0
- package/dist/pluginApi.d.ts +24 -0
- package/dist/pluginApi.js +12 -0
- package/dist/pluginApi.js.map +1 -0
- package/dist/pluginResolve.d.ts +6 -0
- package/dist/pluginResolve.js +48 -0
- package/dist/pluginResolve.js.map +1 -0
- package/dist/screenScope.d.ts +21 -0
- package/dist/screenScope.js +81 -0
- package/dist/screenScope.js.map +1 -0
- package/dist/screenScopeTest.d.ts +1 -0
- package/dist/screenScopeTest.js +116 -0
- package/dist/screenScopeTest.js.map +1 -0
- package/dist/severity.d.ts +9 -0
- package/dist/severity.js +62 -0
- package/dist/severity.js.map +1 -0
- package/dist/severityTest.d.ts +1 -0
- package/dist/severityTest.js +63 -0
- package/dist/severityTest.js.map +1 -0
- package/dist/sourceLines.d.ts +9 -0
- package/dist/sourceLines.js +52 -0
- package/dist/sourceLines.js.map +1 -0
- package/dist/structure.d.ts +2 -0
- package/dist/structure.js +98 -0
- package/dist/structure.js.map +1 -0
- package/dist/testRules.d.ts +1 -0
- package/dist/testRules.js +285 -0
- package/dist/testRules.js.map +1 -0
- package/dist/textGrep.d.ts +2 -0
- package/dist/textGrep.js +52 -0
- package/dist/textGrep.js.map +1 -0
- package/dist/tomlTable.d.ts +2 -0
- package/dist/tomlTable.js +26 -0
- package/dist/tomlTable.js.map +1 -0
- package/dist/toolResolve.d.ts +18 -0
- package/dist/toolResolve.js +50 -0
- package/dist/toolResolve.js.map +1 -0
- package/dist/verify.d.ts +3 -0
- package/dist/verify.js +49 -0
- package/dist/verify.js.map +1 -0
- package/dist/verifyDiff.d.ts +18 -0
- package/dist/verifyDiff.js +126 -0
- package/dist/verifyDiff.js.map +1 -0
- package/dist/workspaces.d.ts +6 -0
- package/dist/workspaces.js +84 -0
- package/dist/workspaces.js.map +1 -0
- package/mise.toml +9 -0
- package/package.json +62 -0
- package/rules/__fixtures__/as-any-escape/bad.ts +8 -0
- package/rules/__fixtures__/as-any-escape/good.ts +14 -0
- package/rules/__fixtures__/catch-empty/bad.ts +7 -0
- package/rules/__fixtures__/catch-empty/good.ts +14 -0
- package/rules/__fixtures__/direct-store-import/bad.tsx +23 -0
- package/rules/__fixtures__/direct-store-import/good.tsx +34 -0
- package/rules/__fixtures__/direct-store-import/showcase/ignored.tsx +9 -0
- package/rules/__fixtures__/discriminator-ternary/bad.tsx +10 -0
- package/rules/__fixtures__/discriminator-ternary/good.tsx +23 -0
- package/rules/__fixtures__/hardcoded-url-in-component/components/bad.tsx +8 -0
- package/rules/__fixtures__/hardcoded-url-in-component/components/good.tsx +14 -0
- package/rules/__fixtures__/inline-map-row/bad.tsx +72 -0
- package/rules/__fixtures__/inline-map-row/good.tsx +51 -0
- package/rules/__fixtures__/instanceof-map-set/bad.ts +7 -0
- package/rules/__fixtures__/instanceof-map-set/good.ts +8 -0
- package/rules/__fixtures__/literal-union-in-component/components/bad.ts +8 -0
- package/rules/__fixtures__/literal-union-in-component/components/good.ts +23 -0
- package/rules/__fixtures__/missing-observer/bad.tsx +66 -0
- package/rules/__fixtures__/missing-observer/good.tsx +69 -0
- package/rules/__fixtures__/no-local-kit-clone/bad.ts +30 -0
- package/rules/__fixtures__/no-local-kit-clone/bad.tsx +30 -0
- package/rules/__fixtures__/no-local-kit-clone/good.ts +31 -0
- package/rules/__fixtures__/no-local-kit-clone/good.tsx +31 -0
- package/rules/__fixtures__/non-exhaustive-match/bad.ts +8 -0
- package/rules/__fixtures__/non-exhaustive-match/good.ts +14 -0
- package/rules/__fixtures__/page-store-bare-usememo/bad.ts +15 -0
- package/rules/__fixtures__/page-store-bare-usememo/bad.tsx +13 -0
- package/rules/__fixtures__/page-store-bare-usememo/good.ts +17 -0
- package/rules/__fixtures__/page-store-bare-usememo/good.tsx +14 -0
- package/rules/__fixtures__/screen-file-styling/bad.screen.tsx +10 -0
- package/rules/__fixtures__/screen-file-styling/good.screen.tsx +8 -0
- package/rules/__fixtures__/state-loading-boolean-shape/bad.ts +36 -0
- package/rules/__fixtures__/state-loading-boolean-shape/bad.tsx +36 -0
- package/rules/__fixtures__/state-loading-boolean-shape/good.ts +44 -0
- package/rules/__fixtures__/state-loading-boolean-shape/good.tsx +44 -0
- package/rules/__fixtures__/store-async-method/bad.ts +37 -0
- package/rules/__fixtures__/store-async-method/bad.tsx +37 -0
- package/rules/__fixtures__/store-async-method/good.ts +73 -0
- package/rules/__fixtures__/store-async-method/good.tsx +73 -0
- package/rules/__fixtures__/store-context-provider/bad.ts +16 -0
- package/rules/__fixtures__/store-context-provider/bad.tsx +24 -0
- package/rules/__fixtures__/store-context-provider/good.ts +13 -0
- package/rules/__fixtures__/store-context-provider/good.tsx +11 -0
- package/rules/__fixtures__/store-delegation-getter/bad.ts +30 -0
- package/rules/__fixtures__/store-delegation-getter/bad.tsx +30 -0
- package/rules/__fixtures__/store-delegation-getter/good.ts +65 -0
- package/rules/__fixtures__/store-delegation-getter/good.tsx +65 -0
- package/rules/__fixtures__/store-new-map/bad.ts +22 -0
- package/rules/__fixtures__/store-new-map/bad.tsx +22 -0
- package/rules/__fixtures__/store-new-map/good.ts +41 -0
- package/rules/__fixtures__/store-new-map/good.tsx +41 -0
- package/rules/__fixtures__/store-no-runinaction/bad.ts +67 -0
- package/rules/__fixtures__/store-no-runinaction/bad.tsx +67 -0
- package/rules/__fixtures__/store-no-runinaction/good.ts +39 -0
- package/rules/__fixtures__/store-no-runinaction/good.tsx +39 -0
- package/rules/__fixtures__/store-no-setinterval-poll/bad.ts +19 -0
- package/rules/__fixtures__/store-no-setinterval-poll/bad.tsx +19 -0
- package/rules/__fixtures__/store-no-setinterval-poll/good.ts +29 -0
- package/rules/__fixtures__/store-no-setinterval-poll/good.tsx +29 -0
- package/rules/__fixtures__/ts-pattern-dangling-match/bad.ts +20 -0
- package/rules/__fixtures__/ts-pattern-dangling-match/good.ts +35 -0
- package/rules/__fixtures__/ui-imports-app-store/src/ui/bad.tsx +13 -0
- package/rules/__fixtures__/ui-imports-app-store/src/ui/good.tsx +23 -0
- package/rules/__fixtures__/view-file-logic/bad.view.tsx +16 -0
- package/rules/__fixtures__/view-file-logic/good.view.tsx +7 -0
- package/rules/__fixtures__/view-imports-store/bad.view.tsx +13 -0
- package/rules/__fixtures__/view-imports-store/good.view.tsx +23 -0
- package/rules/__fixtures__/zod-optional-nullable/bad.ts +10 -0
- package/rules/__fixtures__/zod-optional-nullable/bad.tsx +17 -0
- package/rules/__fixtures__/zod-optional-nullable/good.ts +16 -0
- package/rules/__fixtures__/zod-optional-nullable/good.tsx +20 -0
- package/rules/as-any-escape-tsx.yml +53 -0
- package/rules/as-any-escape.yml +29 -0
- package/rules/catch-empty-tsx.yml +55 -0
- package/rules/catch-empty.yml +31 -0
- package/rules/classname-not-composed.yml +28 -0
- package/rules/classname-visual-identity.yml +34 -0
- package/rules/demo-mode-by-default-ts.yml +76 -0
- package/rules/demo-mode-by-default.yml +54 -0
- package/rules/direct-store-import.yml +118 -0
- package/rules/discriminator-ternary.yml +70 -0
- package/rules/hardcoded-url-in-component.yml +44 -0
- package/rules/inline-map-row.yml +107 -0
- package/rules/instanceof-map-set-tsx.yml +46 -0
- package/rules/instanceof-map-set.yml +22 -0
- package/rules/json-roundtrip.yml +9 -0
- package/rules/jsx-cond-and.yml +16 -0
- package/rules/jsx-ternary.yml +35 -0
- package/rules/kind-if-without-match.yml +13 -0
- package/rules/literal-union-in-component.yml +71 -0
- package/rules/match-bool-to-null.yml +15 -0
- package/rules/missing-observer.yml +189 -0
- package/rules/mobx-effect-observable-dep.yml +23 -0
- package/rules/mobx-effect-store-write.yml +21 -0
- package/rules/mobx-usestate-from-store.yml +20 -0
- package/rules/no-local-kit-clone-tsx.yml +104 -0
- package/rules/no-local-kit-clone.yml +93 -0
- package/rules/non-exhaustive-match.yml +34 -0
- package/rules/page-store-bare-usememo-tsx.yml +47 -0
- package/rules/page-store-bare-usememo.yml +43 -0
- package/rules/screen-file-styling.yml +29 -0
- package/rules/silent-default-return.yml +17 -0
- package/rules/state-loading-boolean-shape-tsx.yml +102 -0
- package/rules/state-loading-boolean-shape.yml +91 -0
- package/rules/store-async-method-tsx.yml +108 -0
- package/rules/store-async-method.yml +98 -0
- package/rules/store-context-provider-tsx.yml +68 -0
- package/rules/store-context-provider.yml +78 -0
- package/rules/store-delegation-getter-tsx.yml +104 -0
- package/rules/store-delegation-getter.yml +93 -0
- package/rules/store-new-map-tsx.yml +84 -0
- package/rules/store-new-map.yml +73 -0
- package/rules/store-no-runinaction-tsx.yml +71 -0
- package/rules/store-no-runinaction.yml +60 -0
- package/rules/store-no-setinterval-poll-tsx.yml +62 -0
- package/rules/store-no-setinterval-poll.yml +51 -0
- package/rules/ts-pattern-dangling-match-tsx.yml +41 -0
- package/rules/ts-pattern-dangling-match.yml +63 -0
- package/rules/ui-imports-app-store.yml +49 -0
- package/rules/view-file-logic.yml +53 -0
- package/rules/view-imports-store.yml +64 -0
- package/rules/zod-optional-nullable-tsx.yml +56 -0
- package/rules/zod-optional-nullable.yml +29 -0
- package/sgconfig.yml +8 -0
- package/structure/rules/component-decl.yml +48 -0
- package/structure/sgconfig.yml +7 -0
|
@@ -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;
|
package/dist/hookStop.js
ADDED
|
@@ -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
|
package/dist/init.js.map
ADDED
|
@@ -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 {};
|
package/dist/initTest.js
ADDED
|
@@ -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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"}
|