@slowcook-ai/cli 0.23.0 → 0.25.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/dist/commands/check/index.d.ts.map +1 -1
- package/dist/commands/check/index.js +8 -0
- package/dist/commands/check/index.js.map +1 -1
- package/dist/commands/check/prod-bundle.d.ts +48 -0
- package/dist/commands/check/prod-bundle.d.ts.map +1 -0
- package/dist/commands/check/prod-bundle.js +130 -0
- package/dist/commands/check/prod-bundle.js.map +1 -0
- package/dist/commands/check/prod-honesty.d.ts +54 -0
- package/dist/commands/check/prod-honesty.d.ts.map +1 -0
- package/dist/commands/check/prod-honesty.js +238 -0
- package/dist/commands/check/prod-honesty.js.map +1 -0
- package/package.json +6 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/check/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/check/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAOH,wBAAsB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsB9E"}
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { runMockIsolationCheck } from "./mock-isolation.js";
|
|
16
16
|
import { runSpecValidateCli } from "./spec-validate.js";
|
|
17
|
+
import { runProdHonestyCli } from "./prod-honesty.js";
|
|
18
|
+
import { runProdBundleCli } from "./prod-bundle.js";
|
|
17
19
|
export async function check(argv, _cliVersion) {
|
|
18
20
|
const sub = argv[0];
|
|
19
21
|
switch (sub) {
|
|
@@ -21,6 +23,10 @@ export async function check(argv, _cliVersion) {
|
|
|
21
23
|
return runMockIsolationCli(argv.slice(1));
|
|
22
24
|
case "spec":
|
|
23
25
|
return runSpecValidateCli(argv.slice(1));
|
|
26
|
+
case "prod-honesty":
|
|
27
|
+
return runProdHonestyCli(argv.slice(1));
|
|
28
|
+
case "prod-bundle":
|
|
29
|
+
return runProdBundleCli(argv.slice(1));
|
|
24
30
|
case undefined:
|
|
25
31
|
case "help":
|
|
26
32
|
case "--help":
|
|
@@ -39,6 +45,8 @@ slowcook check — static structural checks (0.16-α.13)
|
|
|
39
45
|
|
|
40
46
|
Usage:
|
|
41
47
|
slowcook check mock-isolation [--cwd <path>]
|
|
48
|
+
slowcook check prod-honesty [--cwd <path>] [--dir <src>]
|
|
49
|
+
slowcook check prod-bundle [--cwd <path>] [--dist <dist>]
|
|
42
50
|
slowcook check spec [file...] [--cwd <path>]
|
|
43
51
|
|
|
44
52
|
Subcommands:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/check/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/check/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `slowcook check prod-bundle` — no mock-data ENGINE in the prod bundle.
|
|
3
|
+
*
|
|
4
|
+
* `prod-honesty` (its sibling) checks whether fixture data *renders* in prod;
|
|
5
|
+
* this checks something deeper the directive demands: that the mock's data
|
|
6
|
+
* ENGINE is not even *present* in the shipped bundle. A design-first app runs
|
|
7
|
+
* an in-browser data engine (sql.js / SQLite WASM, or slowcook's own
|
|
8
|
+
* mock-runtime fixture provider) so the mock is reviewable. Prod must be
|
|
9
|
+
* ABRUPTLY DISCONNECTED from it — wired only to the real backend — so that a
|
|
10
|
+
* misconfigured flag can never resurrect mock data. A runtime flag that merely
|
|
11
|
+
* *bypasses* the engine is not enough: if the engine is in the bundle, it is a
|
|
12
|
+
* flag flip away from serving fake data.
|
|
13
|
+
*
|
|
14
|
+
* This scans a BUILT dist directory (post-`vite build`/`next build`) and fails
|
|
15
|
+
* when a forbidden engine marker appears in any emitted `.js`/`.wasm`. The
|
|
16
|
+
* clean fix is a build-time sever (conditional/dynamic import gated by the
|
|
17
|
+
* prod env literal) so the bundler tree-shakes the engine out entirely — then
|
|
18
|
+
* this check passes because the engine is genuinely gone, not hidden.
|
|
19
|
+
*
|
|
20
|
+
* Pure-disk, no LLM. Runs in the consumer's deploy (after build, before
|
|
21
|
+
* publish) and in CI.
|
|
22
|
+
*/
|
|
23
|
+
export interface BundleViolation {
|
|
24
|
+
file: string;
|
|
25
|
+
marker: string;
|
|
26
|
+
reason: string;
|
|
27
|
+
}
|
|
28
|
+
export interface ProdBundleResult {
|
|
29
|
+
violations: BundleViolation[];
|
|
30
|
+
filesScanned: number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Default forbidden engine markers. Each is a string that only appears when a
|
|
34
|
+
* client-side data engine is bundled. `.wasm` filename patterns are matched
|
|
35
|
+
* against basenames; text markers are matched against `.js` contents.
|
|
36
|
+
*/
|
|
37
|
+
export declare const DEFAULT_FORBIDDEN: {
|
|
38
|
+
marker: string;
|
|
39
|
+
where: "wasm-name" | "js-text";
|
|
40
|
+
label: string;
|
|
41
|
+
}[];
|
|
42
|
+
export declare function runProdBundleCheck(repoRoot: string, distRel: string, forbidden?: {
|
|
43
|
+
marker: string;
|
|
44
|
+
where: "wasm-name" | "js-text";
|
|
45
|
+
label: string;
|
|
46
|
+
}[]): ProdBundleResult;
|
|
47
|
+
export declare function runProdBundleCli(argv: string[]): void;
|
|
48
|
+
//# sourceMappingURL=prod-bundle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prod-bundle.d.ts","sourceRoot":"","sources":["../../../src/commands/check/prod-bundle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAKH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,WAAW,GAAG,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAQhG,CAAC;AAgBF,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,SAAS;YA3B+B,MAAM;WAAS,WAAW,GAAG,SAAS;WAAS,MAAM;GA2BhE,GAC5B,gBAAgB,CA+BlB;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAsBrD"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `slowcook check prod-bundle` — no mock-data ENGINE in the prod bundle.
|
|
3
|
+
*
|
|
4
|
+
* `prod-honesty` (its sibling) checks whether fixture data *renders* in prod;
|
|
5
|
+
* this checks something deeper the directive demands: that the mock's data
|
|
6
|
+
* ENGINE is not even *present* in the shipped bundle. A design-first app runs
|
|
7
|
+
* an in-browser data engine (sql.js / SQLite WASM, or slowcook's own
|
|
8
|
+
* mock-runtime fixture provider) so the mock is reviewable. Prod must be
|
|
9
|
+
* ABRUPTLY DISCONNECTED from it — wired only to the real backend — so that a
|
|
10
|
+
* misconfigured flag can never resurrect mock data. A runtime flag that merely
|
|
11
|
+
* *bypasses* the engine is not enough: if the engine is in the bundle, it is a
|
|
12
|
+
* flag flip away from serving fake data.
|
|
13
|
+
*
|
|
14
|
+
* This scans a BUILT dist directory (post-`vite build`/`next build`) and fails
|
|
15
|
+
* when a forbidden engine marker appears in any emitted `.js`/`.wasm`. The
|
|
16
|
+
* clean fix is a build-time sever (conditional/dynamic import gated by the
|
|
17
|
+
* prod env literal) so the bundler tree-shakes the engine out entirely — then
|
|
18
|
+
* this check passes because the engine is genuinely gone, not hidden.
|
|
19
|
+
*
|
|
20
|
+
* Pure-disk, no LLM. Runs in the consumer's deploy (after build, before
|
|
21
|
+
* publish) and in CI.
|
|
22
|
+
*/
|
|
23
|
+
import { readFileSync, existsSync, statSync, readdirSync } from "node:fs";
|
|
24
|
+
import { join, relative, basename } from "node:path";
|
|
25
|
+
/**
|
|
26
|
+
* Default forbidden engine markers. Each is a string that only appears when a
|
|
27
|
+
* client-side data engine is bundled. `.wasm` filename patterns are matched
|
|
28
|
+
* against basenames; text markers are matched against `.js` contents.
|
|
29
|
+
*/
|
|
30
|
+
export const DEFAULT_FORBIDDEN = [
|
|
31
|
+
{ marker: "sql-wasm", where: "wasm-name", label: "sql.js SQLite WASM" },
|
|
32
|
+
{ marker: "sql-wasm", where: "js-text", label: "sql.js loader" },
|
|
33
|
+
{ marker: "initSqlJs", where: "js-text", label: "sql.js runtime" },
|
|
34
|
+
{ marker: "SQLite format 3", where: "js-text", label: "embedded SQLite db" },
|
|
35
|
+
{ marker: "better-sqlite3", where: "js-text", label: "better-sqlite3 engine" },
|
|
36
|
+
{ marker: "@slowcook-ai/mock-runtime", where: "js-text", label: "slowcook mock-runtime fixture engine" },
|
|
37
|
+
{ marker: "useScenarioFixture", where: "js-text", label: "mock fixture provider" },
|
|
38
|
+
];
|
|
39
|
+
function walkAssets(dir, acc = []) {
|
|
40
|
+
let entries;
|
|
41
|
+
try {
|
|
42
|
+
entries = readdirSync(dir);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return acc;
|
|
46
|
+
}
|
|
47
|
+
for (const name of entries) {
|
|
48
|
+
if (name.startsWith(".") || name === "node_modules")
|
|
49
|
+
continue;
|
|
50
|
+
const full = join(dir, name);
|
|
51
|
+
let st;
|
|
52
|
+
try {
|
|
53
|
+
st = statSync(full);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (st.isDirectory())
|
|
59
|
+
walkAssets(full, acc);
|
|
60
|
+
else if (/\.(js|mjs|cjs|wasm)$/.test(name))
|
|
61
|
+
acc.push(full);
|
|
62
|
+
}
|
|
63
|
+
return acc;
|
|
64
|
+
}
|
|
65
|
+
export function runProdBundleCheck(repoRoot, distRel, forbidden = DEFAULT_FORBIDDEN) {
|
|
66
|
+
const distDir = join(repoRoot, distRel);
|
|
67
|
+
if (!existsSync(distDir))
|
|
68
|
+
return { violations: [], filesScanned: 0 };
|
|
69
|
+
const files = walkAssets(distDir);
|
|
70
|
+
const violations = [];
|
|
71
|
+
const seen = new Set(); // dedupe (file, label)
|
|
72
|
+
for (const abs of files) {
|
|
73
|
+
const rel = relative(repoRoot, abs).replace(/\\/g, "/");
|
|
74
|
+
const isWasm = abs.endsWith(".wasm");
|
|
75
|
+
const name = basename(abs);
|
|
76
|
+
if (isWasm) {
|
|
77
|
+
for (const f of forbidden) {
|
|
78
|
+
if (f.where === "wasm-name" && name.includes(f.marker)) {
|
|
79
|
+
const key = rel + f.label;
|
|
80
|
+
if (!seen.has(key)) {
|
|
81
|
+
seen.add(key);
|
|
82
|
+
violations.push({ file: rel, marker: f.marker, reason: f.label });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
// text scan for .js
|
|
89
|
+
let body;
|
|
90
|
+
try {
|
|
91
|
+
body = readFileSync(abs, "utf8");
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
for (const f of forbidden) {
|
|
97
|
+
if (f.where === "js-text" && body.includes(f.marker)) {
|
|
98
|
+
const key = rel + f.label;
|
|
99
|
+
if (!seen.has(key)) {
|
|
100
|
+
seen.add(key);
|
|
101
|
+
violations.push({ file: rel, marker: f.marker, reason: f.label });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return { violations, filesScanned: files.length };
|
|
107
|
+
}
|
|
108
|
+
export function runProdBundleCli(argv) {
|
|
109
|
+
const cwdIdx = argv.indexOf("--cwd");
|
|
110
|
+
const repoRoot = cwdIdx >= 0 ? argv[cwdIdx + 1] : process.cwd();
|
|
111
|
+
const distIdx = argv.indexOf("--dist");
|
|
112
|
+
const dist = distIdx >= 0 ? argv[distIdx + 1] : "dist";
|
|
113
|
+
const { violations, filesScanned } = runProdBundleCheck(repoRoot, dist);
|
|
114
|
+
if (filesScanned === 0) {
|
|
115
|
+
console.error(`✗ prod-bundle: no built assets found under ${dist}/ — build first, then check the dist.`);
|
|
116
|
+
process.exit(2);
|
|
117
|
+
}
|
|
118
|
+
if (violations.length === 0) {
|
|
119
|
+
console.log(`✓ prod-bundle: ${filesScanned} assets clean — no mock-data engine in ${dist}/`);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
console.error(`✗ prod-bundle: a mock-data engine is present in ${dist}/ (${violations.length} marker(s))\n`);
|
|
123
|
+
for (const v of violations) {
|
|
124
|
+
console.error(` ${v.file}`);
|
|
125
|
+
console.error(` contains ${v.reason} (\`${v.marker}\`) — prod must be SEVERED from the mock engine, not just flag-bypassed.`);
|
|
126
|
+
console.error(` Fix: gate the engine's import behind a build-time env literal so the bundler tree-shakes it out of prod.\n`);
|
|
127
|
+
}
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=prod-bundle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prod-bundle.js","sourceRoot":"","sources":["../../../src/commands/check/prod-bundle.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `slowcook check prod-honesty` — the mock→prod honesty backbone.
|
|
3
|
+
*
|
|
4
|
+
* The mock is design scaffolding: it is *populated* (fixtures), *open* (every
|
|
5
|
+
* route reachable, no auth walls), and *theatrical* (buttons do local-state
|
|
6
|
+
* transitions) — all so a human can review it. Those three affordances must
|
|
7
|
+
* NOT survive into production. The port→brew pipeline strips them when the
|
|
8
|
+
* mock declares them through the seams; this check is the teeth that fail the
|
|
9
|
+
* build when scaffolding leaks anyway.
|
|
10
|
+
*
|
|
11
|
+
* Why a check and not a test: fixtures, open routes, and dead CTAs are
|
|
12
|
+
* *absence* obligations ("this fake thing is gone"), and a green test suite
|
|
13
|
+
* proves presence, not absence. The defect is precisely the code no test
|
|
14
|
+
* covers — so the enforcement cannot itself be a test.
|
|
15
|
+
*
|
|
16
|
+
* Pure-disk, no LLM, no tsc. Regex + readFileSync, mirroring mock-isolation.
|
|
17
|
+
*
|
|
18
|
+
* Scans the PRODUCTION tree (default `src/`, override `--dir`). Flags three
|
|
19
|
+
* classes, each mapping to a field-reported defect:
|
|
20
|
+
*
|
|
21
|
+
* A (fixtures) — an inline literal array of ≥2 object literals that is
|
|
22
|
+
* rendered (`.map(`) in the same file, and the file has no
|
|
23
|
+
* data seam (`useDataDomain` / a `*BackendOn()` live-mode
|
|
24
|
+
* gate). The canonical leak: `const ITEMS = [{…},{…}]` +
|
|
25
|
+
* `{ITEMS.map(…)}` shipped verbatim from the mock.
|
|
26
|
+
* B (gating) — a file under a routes/pages/app tree that renders route
|
|
27
|
+
* definitions but contains no auth-guard reference
|
|
28
|
+
* (`requireAuth` / `RequireAuth` / `getSession` / redirect
|
|
29
|
+
* to a sign-in path). Heuristic; tuned to catch a router
|
|
30
|
+
* that gates nothing.
|
|
31
|
+
* C (dead CTA) — an `onClick`/`onSubmit` handler whose body only calls a
|
|
32
|
+
* `setState`-shaped setter with no `fetch`/`navigate`/router
|
|
33
|
+
* push and no explicit `deferred`/disabled acknowledgement.
|
|
34
|
+
*
|
|
35
|
+
* Escape hatch: a line or file carrying `// @slowcook-honest` (optionally
|
|
36
|
+
* `// @slowcook-honest: reason`) is exempt — for the deliberate exceptions
|
|
37
|
+
* (a genuinely public route, a label-map constant, a demo-only file). Every
|
|
38
|
+
* exemption is greppable, so the reviewer sees exactly what was waived.
|
|
39
|
+
*/
|
|
40
|
+
export type HonestyClass = "fixture" | "gating" | "dead_cta";
|
|
41
|
+
export interface HonestyViolation {
|
|
42
|
+
file: string;
|
|
43
|
+
line: number;
|
|
44
|
+
cls: HonestyClass;
|
|
45
|
+
reason: string;
|
|
46
|
+
}
|
|
47
|
+
export interface ProdHonestyResult {
|
|
48
|
+
violations: HonestyViolation[];
|
|
49
|
+
filesChecked: number;
|
|
50
|
+
}
|
|
51
|
+
export declare function checkFileHonesty(absFile: string, body: string, repoRoot: string): HonestyViolation[];
|
|
52
|
+
export declare function runProdHonestyCheck(repoRoot: string, dir?: string): ProdHonestyResult;
|
|
53
|
+
export declare function runProdHonestyCli(argv: string[]): void;
|
|
54
|
+
//# sourceMappingURL=prod-honesty.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prod-honesty.d.ts","sourceRoot":"","sources":["../../../src/commands/check/prod-honesty.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAKH,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,QAAQ,GAAG,UAAU,CAAC;AAE7D,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,YAAY,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,gBAAgB,EAAE,CAAC;IAC/B,YAAY,EAAE,MAAM,CAAC;CACtB;AA+HD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,gBAAgB,EAAE,CASpG;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,SAAQ,GAAG,iBAAiB,CASpF;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAsBtD"}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `slowcook check prod-honesty` — the mock→prod honesty backbone.
|
|
3
|
+
*
|
|
4
|
+
* The mock is design scaffolding: it is *populated* (fixtures), *open* (every
|
|
5
|
+
* route reachable, no auth walls), and *theatrical* (buttons do local-state
|
|
6
|
+
* transitions) — all so a human can review it. Those three affordances must
|
|
7
|
+
* NOT survive into production. The port→brew pipeline strips them when the
|
|
8
|
+
* mock declares them through the seams; this check is the teeth that fail the
|
|
9
|
+
* build when scaffolding leaks anyway.
|
|
10
|
+
*
|
|
11
|
+
* Why a check and not a test: fixtures, open routes, and dead CTAs are
|
|
12
|
+
* *absence* obligations ("this fake thing is gone"), and a green test suite
|
|
13
|
+
* proves presence, not absence. The defect is precisely the code no test
|
|
14
|
+
* covers — so the enforcement cannot itself be a test.
|
|
15
|
+
*
|
|
16
|
+
* Pure-disk, no LLM, no tsc. Regex + readFileSync, mirroring mock-isolation.
|
|
17
|
+
*
|
|
18
|
+
* Scans the PRODUCTION tree (default `src/`, override `--dir`). Flags three
|
|
19
|
+
* classes, each mapping to a field-reported defect:
|
|
20
|
+
*
|
|
21
|
+
* A (fixtures) — an inline literal array of ≥2 object literals that is
|
|
22
|
+
* rendered (`.map(`) in the same file, and the file has no
|
|
23
|
+
* data seam (`useDataDomain` / a `*BackendOn()` live-mode
|
|
24
|
+
* gate). The canonical leak: `const ITEMS = [{…},{…}]` +
|
|
25
|
+
* `{ITEMS.map(…)}` shipped verbatim from the mock.
|
|
26
|
+
* B (gating) — a file under a routes/pages/app tree that renders route
|
|
27
|
+
* definitions but contains no auth-guard reference
|
|
28
|
+
* (`requireAuth` / `RequireAuth` / `getSession` / redirect
|
|
29
|
+
* to a sign-in path). Heuristic; tuned to catch a router
|
|
30
|
+
* that gates nothing.
|
|
31
|
+
* C (dead CTA) — an `onClick`/`onSubmit` handler whose body only calls a
|
|
32
|
+
* `setState`-shaped setter with no `fetch`/`navigate`/router
|
|
33
|
+
* push and no explicit `deferred`/disabled acknowledgement.
|
|
34
|
+
*
|
|
35
|
+
* Escape hatch: a line or file carrying `// @slowcook-honest` (optionally
|
|
36
|
+
* `// @slowcook-honest: reason`) is exempt — for the deliberate exceptions
|
|
37
|
+
* (a genuinely public route, a label-map constant, a demo-only file). Every
|
|
38
|
+
* exemption is greppable, so the reviewer sees exactly what was waived.
|
|
39
|
+
*/
|
|
40
|
+
import { readFileSync, existsSync, statSync, readdirSync } from "node:fs";
|
|
41
|
+
import { join, relative } from "node:path";
|
|
42
|
+
const EXEMPT = /@slowcook-honest\b/;
|
|
43
|
+
function walkTsxFiles(dir, acc = []) {
|
|
44
|
+
let entries;
|
|
45
|
+
try {
|
|
46
|
+
entries = readdirSync(dir);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return acc;
|
|
50
|
+
}
|
|
51
|
+
for (const name of entries) {
|
|
52
|
+
if (name.startsWith("."))
|
|
53
|
+
continue;
|
|
54
|
+
if (name === "node_modules" || name === ".next" || name === "dist" || name === "build")
|
|
55
|
+
continue;
|
|
56
|
+
// never scan test files — they legitimately hold fixtures + theater
|
|
57
|
+
if (/\.(test|spec)\.(tsx?|jsx?)$/.test(name))
|
|
58
|
+
continue;
|
|
59
|
+
if (/(^|\/)(test|tests|__tests__|__mocks__|fixtures)$/.test(name))
|
|
60
|
+
continue;
|
|
61
|
+
const full = join(dir, name);
|
|
62
|
+
let st;
|
|
63
|
+
try {
|
|
64
|
+
st = statSync(full);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (st.isDirectory()) {
|
|
70
|
+
if (name === "test" || name === "tests" || name === "__tests__" || name === "__mocks__" || name === "fixtures")
|
|
71
|
+
continue;
|
|
72
|
+
walkTsxFiles(full, acc);
|
|
73
|
+
}
|
|
74
|
+
else if (/\.(tsx|jsx)$/.test(name)) {
|
|
75
|
+
acc.push(full);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return acc;
|
|
79
|
+
}
|
|
80
|
+
/** true if the file routes data through a live backend seam. */
|
|
81
|
+
function hasDataSeam(body) {
|
|
82
|
+
return /useDataDomain\s*[<(]/.test(body)
|
|
83
|
+
|| /\b\w*[Bb]ackendOn\s*\(\)/.test(body) // dataBackendOn(), authBackendOn(), etc.
|
|
84
|
+
|| /import\.meta\.env\.\w*_BACKEND/.test(body)
|
|
85
|
+
|| /process\.env\.\w*_BACKEND/.test(body);
|
|
86
|
+
}
|
|
87
|
+
/** A (fixtures): an inline ≥2-entry object-literal array that is .map-rendered. */
|
|
88
|
+
function findFixtureLeaks(lines, body) {
|
|
89
|
+
if (hasDataSeam(body))
|
|
90
|
+
return []; // behind the seam → the port/flag handles it
|
|
91
|
+
const out = [];
|
|
92
|
+
// const NAME ... = [ ... ] spanning up to the closing bracket; detect ≥2 `{`
|
|
93
|
+
const declRe = /\bconst\s+([A-Za-z_$][\w$]*)\s*(?::[^=]+)?=\s*\[/;
|
|
94
|
+
for (let i = 0; i < lines.length; i++) {
|
|
95
|
+
const m = lines[i].match(declRe);
|
|
96
|
+
if (!m)
|
|
97
|
+
continue;
|
|
98
|
+
const name = m[1];
|
|
99
|
+
// gather until the array closes (bounded scan)
|
|
100
|
+
let depth = 0, started = false, objectCount = 0, endLine = i;
|
|
101
|
+
for (let j = i; j < Math.min(lines.length, i + 60); j++) {
|
|
102
|
+
for (const ch of lines[j]) {
|
|
103
|
+
if (ch === "[") {
|
|
104
|
+
depth++;
|
|
105
|
+
started = true;
|
|
106
|
+
}
|
|
107
|
+
else if (ch === "]") {
|
|
108
|
+
depth--;
|
|
109
|
+
}
|
|
110
|
+
else if (ch === "{" && depth >= 1)
|
|
111
|
+
objectCount++;
|
|
112
|
+
}
|
|
113
|
+
if (started && depth <= 0) {
|
|
114
|
+
endLine = j;
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (objectCount < 2)
|
|
119
|
+
continue; // not a fixture-shaped list
|
|
120
|
+
// is it rendered? `{NAME.map(` or `NAME.map(` anywhere in the file
|
|
121
|
+
if (new RegExp(`\\b${name}\\.map\\s*\\(`).test(body)) {
|
|
122
|
+
if (EXEMPT.test(lines[i]))
|
|
123
|
+
continue;
|
|
124
|
+
out.push({
|
|
125
|
+
line: i + 1,
|
|
126
|
+
reason: `\`${name}\` is an inline fixture (${objectCount} entries) rendered in this file with no data seam. In prod this ships sample data. Route it through useDataDomain / a live-mode gate, or mark \`// @slowcook-honest\` if it's a config map.`,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
i = endLine;
|
|
130
|
+
}
|
|
131
|
+
return out;
|
|
132
|
+
}
|
|
133
|
+
/** B (gating): a route/page file that defines routes but references no guard. */
|
|
134
|
+
function findGatingGaps(relFile, body) {
|
|
135
|
+
const isRouteFile = /(^|\/)(App|routes?|router|pages?|app)\.(tsx|jsx)$/i.test(relFile)
|
|
136
|
+
|| /(^|\/)(routes?|router)\//i.test(relFile);
|
|
137
|
+
if (!isRouteFile)
|
|
138
|
+
return [];
|
|
139
|
+
const routeCount = (body.match(/<Route\b/g) ?? []).length;
|
|
140
|
+
if (routeCount < 3)
|
|
141
|
+
return []; // a leaf page, not a router
|
|
142
|
+
const hasGuard = /requireAuth|RequireAuth|getSession|getSessionMember|useSession|redirect\(["'`]\/(sign-?in|login)/i.test(body);
|
|
143
|
+
if (hasGuard || EXEMPT.test(body))
|
|
144
|
+
return [];
|
|
145
|
+
return [{
|
|
146
|
+
line: 1,
|
|
147
|
+
reason: `This router defines ${routeCount} routes but references no auth guard (RequireAuth/getSession/redirect-to-signin). In prod, guests reach internal pages. Gate non-public routes, or mark \`// @slowcook-honest\` if every route here is genuinely public.`,
|
|
148
|
+
}];
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* C (dead CTA): an onClick/onSubmit that flips a state CLAIMING AN OUTCOME
|
|
152
|
+
* (submitted / sent / done / success / provisioning / created / saved …)
|
|
153
|
+
* without a real effect. This is the precise "fakes success" tell — it does
|
|
154
|
+
* NOT flag legitimate local view-state (open a modal, switch a tab, toggle a
|
|
155
|
+
* filter), which keeps the check high-signal enough to gate CI.
|
|
156
|
+
*/
|
|
157
|
+
const OUTCOME_SETTER = /\bset(?:Submitted|Sent|Done|Success|Complete|Completed|Provisioning|Provisioned|Confirmed|Saved|Created|Saved|Redeemed|Approved|Accepted|Booked|Paid|Requested|Connected|Finished)\b/;
|
|
158
|
+
function findDeadCtas(lines) {
|
|
159
|
+
const out = [];
|
|
160
|
+
const handlerRe = /\bon(?:Click|Submit)\s*=\s*\{[^}]*=>\s*([^}]*)\}/;
|
|
161
|
+
for (let i = 0; i < lines.length; i++) {
|
|
162
|
+
const line = lines[i];
|
|
163
|
+
const m = line.match(handlerRe);
|
|
164
|
+
if (!m)
|
|
165
|
+
continue;
|
|
166
|
+
const inline = m[1].trim();
|
|
167
|
+
if (!inline)
|
|
168
|
+
continue; // named handler ref — inline judge out of scope for v1
|
|
169
|
+
// a FAKE success sets the outcome truthy; a reset (setSent(false)) is
|
|
170
|
+
// legitimate local view-state, not a dead CTA.
|
|
171
|
+
const outcomeM = inline.match(new RegExp(OUTCOME_SETTER.source + "\\s*\\(\\s*([^,)]*)"));
|
|
172
|
+
const claimsOutcome = !!outcomeM && !/^(false|null|undefined|""|''|``|0)$/.test((outcomeM[1] ?? "").trim());
|
|
173
|
+
const hasRealEffect = /\bfetch\s*\(|\bnavigate\s*\(|\.push\s*\(|\brouter\.|\baxios|href\s*=|window\.location|\bmutate\w*\s*\(|void\s+\w+\(|await\s/.test(inline);
|
|
174
|
+
// a BARE `disabled` (always-off) or `disabled={true}` is an honest
|
|
175
|
+
// acknowledgement; a CONDITIONAL `disabled={expr}` is NOT — the button is
|
|
176
|
+
// live when the condition is false, so a fake-success there still ships.
|
|
177
|
+
const bareDisabled = /\bdisabled(?![=\w])/.test(line) || /\bdisabled\s*=\s*\{?\s*true\s*\}?/.test(line);
|
|
178
|
+
// a leading `// @slowcook-honest` (this line or up to 2 lines above, like
|
|
179
|
+
// eslint-disable-next-line) is a valid, greppable acknowledgement.
|
|
180
|
+
const markerWindow = [line, lines[i - 1] ?? "", lines[i - 2] ?? ""].some((l) => EXEMPT.test(l));
|
|
181
|
+
const acknowledged = /deferred|coming.?soon/i.test(line) || bareDisabled || markerWindow;
|
|
182
|
+
if (claimsOutcome && !hasRealEffect && !acknowledged) {
|
|
183
|
+
out.push({
|
|
184
|
+
line: i + 1,
|
|
185
|
+
reason: `This onClick flips a success/outcome state with no fetch/navigation — a button that FAKES success. Wire the real effect, render an honest disabled/deferred state, or mark \`// @slowcook-honest\`.`,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return out;
|
|
190
|
+
}
|
|
191
|
+
export function checkFileHonesty(absFile, body, repoRoot) {
|
|
192
|
+
const relFile = relative(repoRoot, absFile).replace(/\\/g, "/");
|
|
193
|
+
if (EXEMPT.test(body.split(/\r?\n/)[0] ?? ""))
|
|
194
|
+
return []; // whole-file exemption on line 1
|
|
195
|
+
const lines = body.split(/\r?\n/);
|
|
196
|
+
const v = [];
|
|
197
|
+
for (const f of findFixtureLeaks(lines, body))
|
|
198
|
+
v.push({ file: relFile, line: f.line, cls: "fixture", reason: f.reason });
|
|
199
|
+
for (const f of findGatingGaps(relFile, body))
|
|
200
|
+
v.push({ file: relFile, line: f.line, cls: "gating", reason: f.reason });
|
|
201
|
+
for (const f of findDeadCtas(lines))
|
|
202
|
+
v.push({ file: relFile, line: f.line, cls: "dead_cta", reason: f.reason });
|
|
203
|
+
return v;
|
|
204
|
+
}
|
|
205
|
+
export function runProdHonestyCheck(repoRoot, dir = "src") {
|
|
206
|
+
const target = join(repoRoot, dir);
|
|
207
|
+
if (!existsSync(target))
|
|
208
|
+
return { violations: [], filesChecked: 0 };
|
|
209
|
+
const files = walkTsxFiles(target);
|
|
210
|
+
const violations = [];
|
|
211
|
+
for (const absFile of files) {
|
|
212
|
+
violations.push(...checkFileHonesty(absFile, readFileSync(absFile, "utf8"), repoRoot));
|
|
213
|
+
}
|
|
214
|
+
return { violations, filesChecked: files.length };
|
|
215
|
+
}
|
|
216
|
+
export function runProdHonestyCli(argv) {
|
|
217
|
+
const cwdIdx = argv.indexOf("--cwd");
|
|
218
|
+
const repoRoot = cwdIdx >= 0 ? argv[cwdIdx + 1] : process.cwd();
|
|
219
|
+
const dirIdx = argv.indexOf("--dir");
|
|
220
|
+
const dir = dirIdx >= 0 ? argv[dirIdx + 1] : "src";
|
|
221
|
+
const { violations, filesChecked } = runProdHonestyCheck(repoRoot, dir);
|
|
222
|
+
const LABEL = {
|
|
223
|
+
fixture: "A · fixture data in prod",
|
|
224
|
+
gating: "B · route not gated",
|
|
225
|
+
dead_cta: "C · CTA with no real effect",
|
|
226
|
+
};
|
|
227
|
+
if (violations.length === 0) {
|
|
228
|
+
console.log(`✓ prod-honesty: ${filesChecked} files clean (no fixture/gating/dead-CTA leaks in ${dir}/)`);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
console.error(`✗ prod-honesty: ${violations.length} issue(s) across ${filesChecked} files in ${dir}/\n`);
|
|
232
|
+
for (const v of violations) {
|
|
233
|
+
console.error(` ${v.file}:${v.line} [${LABEL[v.cls]}]`);
|
|
234
|
+
console.error(` ${v.reason}\n`);
|
|
235
|
+
}
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=prod-honesty.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prod-honesty.js","sourceRoot":"","sources":["../../../src/commands/check/prod-honesty.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slowcook-ai/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"description": "CLI for the slowcook brewing harness",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "aminazar",
|
|
@@ -41,13 +41,13 @@
|
|
|
41
41
|
"ts-morph": "^28.0.0",
|
|
42
42
|
"yaml": "^2.9.0",
|
|
43
43
|
"zod": "^4.4.3",
|
|
44
|
-
"@slowcook-ai/core": "^0.
|
|
45
|
-
"@slowcook-ai/forge-github": "^0.14.0",
|
|
44
|
+
"@slowcook-ai/core": "^0.17.0",
|
|
46
45
|
"@slowcook-ai/stack-ts": "^0.9.9",
|
|
47
|
-
"@slowcook-ai/
|
|
46
|
+
"@slowcook-ai/forge-github": "^0.14.0",
|
|
47
|
+
"@slowcook-ai/llm-anthropic": "^0.21.0",
|
|
48
|
+
"@slowcook-ai/gates": "^0.11.0",
|
|
48
49
|
"@slowcook-ai/review-overlay": "^0.11.0",
|
|
49
|
-
"@slowcook-ai/
|
|
50
|
-
"@slowcook-ai/gates": "^0.11.0"
|
|
50
|
+
"@slowcook-ai/recorder": "^0.9.1"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@types/node": "^26.0.1"
|