@slowcook-ai/cli 0.25.0 → 0.27.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 +10 -0
- package/dist/commands/check/index.js.map +1 -1
- package/dist/commands/check/surface-parity.d.ts +123 -0
- package/dist/commands/check/surface-parity.d.ts.map +1 -0
- package/dist/commands/check/surface-parity.js +328 -0
- package/dist/commands/check/surface-parity.js.map +1 -0
- package/dist/commands/extract/as-built-cli.d.ts +6 -0
- package/dist/commands/extract/as-built-cli.d.ts.map +1 -0
- package/dist/commands/extract/as-built-cli.js +67 -0
- package/dist/commands/extract/as-built-cli.js.map +1 -0
- package/dist/commands/extract/as-built.d.ts +21 -0
- package/dist/commands/extract/as-built.d.ts.map +1 -0
- package/dist/commands/extract/as-built.js +113 -0
- package/dist/commands/extract/as-built.js.map +1 -0
- package/dist/commands/extract/index.d.ts.map +1 -1
- package/dist/commands/extract/index.js +50 -3
- package/dist/commands/extract/index.js.map +1 -1
- package/dist/commands/extract/survey.d.ts +17 -0
- package/dist/commands/extract/survey.d.ts.map +1 -0
- package/dist/commands/extract/survey.js +176 -0
- package/dist/commands/extract/survey.js.map +1 -0
- package/dist/commands/vibe/as-built.d.ts +25 -0
- package/dist/commands/vibe/as-built.d.ts.map +1 -0
- package/dist/commands/vibe/as-built.js +111 -0
- package/dist/commands/vibe/as-built.js.map +1 -0
- package/dist/commands/vibe/index.d.ts.map +1 -1
- package/dist/commands/vibe/index.js +47 -0
- package/dist/commands/vibe/index.js.map +1 -1
- package/package.json +5 -5
|
@@ -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;AAQH,wBAAsB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAwB9E"}
|
|
@@ -16,6 +16,7 @@ import { runMockIsolationCheck } from "./mock-isolation.js";
|
|
|
16
16
|
import { runSpecValidateCli } from "./spec-validate.js";
|
|
17
17
|
import { runProdHonestyCli } from "./prod-honesty.js";
|
|
18
18
|
import { runProdBundleCli } from "./prod-bundle.js";
|
|
19
|
+
import { runSurfaceParityCli } from "./surface-parity.js";
|
|
19
20
|
export async function check(argv, _cliVersion) {
|
|
20
21
|
const sub = argv[0];
|
|
21
22
|
switch (sub) {
|
|
@@ -27,6 +28,8 @@ export async function check(argv, _cliVersion) {
|
|
|
27
28
|
return runProdHonestyCli(argv.slice(1));
|
|
28
29
|
case "prod-bundle":
|
|
29
30
|
return runProdBundleCli(argv.slice(1));
|
|
31
|
+
case "surface-parity":
|
|
32
|
+
return runSurfaceParityCli(argv.slice(1));
|
|
30
33
|
case undefined:
|
|
31
34
|
case "help":
|
|
32
35
|
case "--help":
|
|
@@ -47,12 +50,19 @@ Usage:
|
|
|
47
50
|
slowcook check mock-isolation [--cwd <path>]
|
|
48
51
|
slowcook check prod-honesty [--cwd <path>] [--dir <src>]
|
|
49
52
|
slowcook check prod-bundle [--cwd <path>] [--dist <dist>]
|
|
53
|
+
slowcook check surface-parity [--cwd <path>] [--update-baseline]
|
|
50
54
|
slowcook check spec [file...] [--cwd <path>]
|
|
51
55
|
|
|
52
56
|
Subcommands:
|
|
53
57
|
mock-isolation Verify every import in mock/ stays inside mock/.
|
|
54
58
|
Catches vibe-prompt slippage where a mock component
|
|
55
59
|
tries to import from the consumer's production src/.
|
|
60
|
+
surface-parity Mock/prod drift detector (#261): builds each profile in
|
|
61
|
+
.slowcook/parity.yaml, verifies every review-node id
|
|
62
|
+
survives into every profile's bundle (unless baselined
|
|
63
|
+
with a reason in .slowcook/parity-baseline.yaml), and
|
|
64
|
+
that every import.meta.env flag read in src is DECLARED
|
|
65
|
+
in prod-like profiles. New unwaived drift fails (ratchet).
|
|
56
66
|
spec Re-run spec content validators on one or more spec
|
|
57
67
|
files. Catches drift on amendment commits that
|
|
58
68
|
bypass refine's in-process lint.
|
|
@@ -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;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/check/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `slowcook check surface-parity` — mock/prod drift detector (#261).
|
|
3
|
+
*
|
|
4
|
+
* Design-first apps ship ONE source tree whose surfaces carry a canned branch
|
|
5
|
+
* (the reviewable mock) and a live branch (the real product), selected by
|
|
6
|
+
* build-time flags. The two silently diverge: a dead CTA fixed only in one
|
|
7
|
+
* mode, copy updated in one branch, or an entire live branch dead-code-
|
|
8
|
+
* eliminated because a flag was never set at build time (the 2026-07 dash
|
|
9
|
+
* wallet page shipped CANNED to prod for weeks that way).
|
|
10
|
+
*
|
|
11
|
+
* Two sub-checks, both static (no browser):
|
|
12
|
+
*
|
|
13
|
+
* 1. FLAG COMPLETENESS — every `import.meta.env.<PREFIX>…` the source reads
|
|
14
|
+
* must be *declared* in each profile marked `require_all_flags` (the value
|
|
15
|
+
* may be "" = deliberately off; UNDECLARED means nobody ever considered it,
|
|
16
|
+
* which is how live branches vanish).
|
|
17
|
+
*
|
|
18
|
+
* 2. NODE PARITY — the universe of review nodes is parsed from source
|
|
19
|
+
* (`rn("…")` / `data-review-node`, the @slowcook-ai/review-overlay
|
|
20
|
+
* convention). Each profile is built with its env; a node's id LITERAL
|
|
21
|
+
* surviving into the bundle means its branch was not DCE'd. Divergent
|
|
22
|
+
* presence across profiles = drift. Known/deliberate divergence lives in a
|
|
23
|
+
* BASELINE (`.slowcook/parity-baseline.yaml`) with a reason + direction;
|
|
24
|
+
* new unwaived divergence fails (a ratchet), healed entries are flagged
|
|
25
|
+
* for cleanup.
|
|
26
|
+
*
|
|
27
|
+
* v1 limit (documented in #261): runtime-gated modes (localStorage flags)
|
|
28
|
+
* don't DCE, so literal survival can't see them — the rendered-profile pass
|
|
29
|
+
* is the v2 follow-up.
|
|
30
|
+
*
|
|
31
|
+
* Config (`.slowcook/parity.yaml` at the repo root):
|
|
32
|
+
* app: mock # build cwd, relative to root
|
|
33
|
+
* src: mock/src # scanned for flags + node ids
|
|
34
|
+
* build: npx vite build --outDir {outDir} --emptyOutDir
|
|
35
|
+
* env_prefix: VITE_
|
|
36
|
+
* markers: [data-review-node] # or e.g. [data-testid] for repos without rn()
|
|
37
|
+
* baseline: .slowcook/parity-baseline.yaml # per-app in multi-app repos
|
|
38
|
+
* profiles:
|
|
39
|
+
* mock: { env: {} }
|
|
40
|
+
* prod:
|
|
41
|
+
* env: { VITE_DATA_BACKEND: "1", … }
|
|
42
|
+
* require_all_flags: true
|
|
43
|
+
* allow_unset: [VITE_REVIEW_TOOLS] # deliberately absent (qa-only)
|
|
44
|
+
*/
|
|
45
|
+
export interface ParityProfile {
|
|
46
|
+
env: Record<string, string>;
|
|
47
|
+
require_all_flags?: boolean;
|
|
48
|
+
allow_unset?: string[];
|
|
49
|
+
/** markers that must NOT survive into this profile's bundle (e.g. fixture
|
|
50
|
+
* emails, preview gates, review tooling in the real/prod profile) —
|
|
51
|
+
* the prod-bundle sever philosophy, per profile. */
|
|
52
|
+
forbid?: string[];
|
|
53
|
+
}
|
|
54
|
+
export interface ParityConfig {
|
|
55
|
+
app: string;
|
|
56
|
+
/** one or several source dirs (shared component packages count too). */
|
|
57
|
+
src: string | string[];
|
|
58
|
+
build: string;
|
|
59
|
+
env_prefix: string;
|
|
60
|
+
/** attribute names harvested as node ids (besides rn() and *node= props).
|
|
61
|
+
* Default: data-review-node. Repos without review nodes can point this at
|
|
62
|
+
* their own stable-marker convention, e.g. data-testid. */
|
|
63
|
+
markers: string[];
|
|
64
|
+
/** baseline path (relative to root) — lets multi-app repos keep one per app. */
|
|
65
|
+
baseline: string;
|
|
66
|
+
profiles: Record<string, ParityProfile>;
|
|
67
|
+
}
|
|
68
|
+
export interface BaselineEntry {
|
|
69
|
+
node: string;
|
|
70
|
+
absent_from: string[];
|
|
71
|
+
reason?: string;
|
|
72
|
+
owner?: string;
|
|
73
|
+
/** who is truth for this surface: mock-first (greenfield) | prod-first (as-built) */
|
|
74
|
+
direction?: string;
|
|
75
|
+
}
|
|
76
|
+
export interface FlagViolation {
|
|
77
|
+
flag: string;
|
|
78
|
+
profile: string;
|
|
79
|
+
readAt: string;
|
|
80
|
+
}
|
|
81
|
+
export interface NodeDrift {
|
|
82
|
+
node: string;
|
|
83
|
+
absent_from: string[];
|
|
84
|
+
presentIn: string[];
|
|
85
|
+
}
|
|
86
|
+
export interface ForbidViolation {
|
|
87
|
+
marker: string;
|
|
88
|
+
profile: string;
|
|
89
|
+
}
|
|
90
|
+
export interface ParityResult {
|
|
91
|
+
flagViolations: FlagViolation[];
|
|
92
|
+
/** forbidden markers found in a profile's bundle — always failing. */
|
|
93
|
+
forbidViolations: ForbidViolation[];
|
|
94
|
+
/** divergent nodes NOT covered by the baseline — these fail the check. */
|
|
95
|
+
newDrift: NodeDrift[];
|
|
96
|
+
/** divergent nodes covered by the baseline — reported, never failing. */
|
|
97
|
+
waivedDrift: NodeDrift[];
|
|
98
|
+
/** baseline entries whose drift no longer exists — prune them. */
|
|
99
|
+
healed: BaselineEntry[];
|
|
100
|
+
/** node ids absent from EVERY profile bundle — likely dead code. */
|
|
101
|
+
deadNodes: string[];
|
|
102
|
+
nodesChecked: number;
|
|
103
|
+
flagsChecked: number;
|
|
104
|
+
profilesBuilt: string[];
|
|
105
|
+
}
|
|
106
|
+
/** every `import.meta.env.<PREFIX>X` / `["<PREFIX>X"]` read, with a source location. */
|
|
107
|
+
export declare function scanFlagReads(root: string, srcRel: string | string[], prefix: string): Map<string, string>;
|
|
108
|
+
/**
|
|
109
|
+
* review-node ids from source: rn("id", …), literal data-review-node="id",
|
|
110
|
+
* and ids passed through props whose NAME mentions node (e.g.
|
|
111
|
+
* `<RateInput node="budget/assumption/labor-rate" …>` — the wrapper calls
|
|
112
|
+
* rn() internally). The prop form requires the slashed `area/name` shape so
|
|
113
|
+
* ordinary strings don't false-positive.
|
|
114
|
+
*/
|
|
115
|
+
export declare function scanNodeIds(root: string, srcRel: string | string[], markers?: string[]): Set<string>;
|
|
116
|
+
export declare function loadConfig(root: string, path?: string): ParityConfig;
|
|
117
|
+
export declare function loadBaseline(root: string, path?: string): BaselineEntry[];
|
|
118
|
+
export declare function runSurfaceParityCheck(root: string, config: ParityConfig, baseline: BaselineEntry[], opts?: {
|
|
119
|
+
build?: (profile: string, env: Record<string, string>, outDir: string) => void;
|
|
120
|
+
}): ParityResult;
|
|
121
|
+
export declare function writeBaseline(root: string, drift: NodeDrift[], prior: BaselineEntry[], path?: string): void;
|
|
122
|
+
export declare function runSurfaceParityCli(argv: string[]): void;
|
|
123
|
+
//# sourceMappingURL=surface-parity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"surface-parity.d.ts","sourceRoot":"","sources":["../../../src/commands/check/surface-parity.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `slowcook check surface-parity` — mock/prod drift detector (#261).
|
|
3
|
+
*
|
|
4
|
+
* Design-first apps ship ONE source tree whose surfaces carry a canned branch
|
|
5
|
+
* (the reviewable mock) and a live branch (the real product), selected by
|
|
6
|
+
* build-time flags. The two silently diverge: a dead CTA fixed only in one
|
|
7
|
+
* mode, copy updated in one branch, or an entire live branch dead-code-
|
|
8
|
+
* eliminated because a flag was never set at build time (the 2026-07 dash
|
|
9
|
+
* wallet page shipped CANNED to prod for weeks that way).
|
|
10
|
+
*
|
|
11
|
+
* Two sub-checks, both static (no browser):
|
|
12
|
+
*
|
|
13
|
+
* 1. FLAG COMPLETENESS — every `import.meta.env.<PREFIX>…` the source reads
|
|
14
|
+
* must be *declared* in each profile marked `require_all_flags` (the value
|
|
15
|
+
* may be "" = deliberately off; UNDECLARED means nobody ever considered it,
|
|
16
|
+
* which is how live branches vanish).
|
|
17
|
+
*
|
|
18
|
+
* 2. NODE PARITY — the universe of review nodes is parsed from source
|
|
19
|
+
* (`rn("…")` / `data-review-node`, the @slowcook-ai/review-overlay
|
|
20
|
+
* convention). Each profile is built with its env; a node's id LITERAL
|
|
21
|
+
* surviving into the bundle means its branch was not DCE'd. Divergent
|
|
22
|
+
* presence across profiles = drift. Known/deliberate divergence lives in a
|
|
23
|
+
* BASELINE (`.slowcook/parity-baseline.yaml`) with a reason + direction;
|
|
24
|
+
* new unwaived divergence fails (a ratchet), healed entries are flagged
|
|
25
|
+
* for cleanup.
|
|
26
|
+
*
|
|
27
|
+
* v1 limit (documented in #261): runtime-gated modes (localStorage flags)
|
|
28
|
+
* don't DCE, so literal survival can't see them — the rendered-profile pass
|
|
29
|
+
* is the v2 follow-up.
|
|
30
|
+
*
|
|
31
|
+
* Config (`.slowcook/parity.yaml` at the repo root):
|
|
32
|
+
* app: mock # build cwd, relative to root
|
|
33
|
+
* src: mock/src # scanned for flags + node ids
|
|
34
|
+
* build: npx vite build --outDir {outDir} --emptyOutDir
|
|
35
|
+
* env_prefix: VITE_
|
|
36
|
+
* markers: [data-review-node] # or e.g. [data-testid] for repos without rn()
|
|
37
|
+
* baseline: .slowcook/parity-baseline.yaml # per-app in multi-app repos
|
|
38
|
+
* profiles:
|
|
39
|
+
* mock: { env: {} }
|
|
40
|
+
* prod:
|
|
41
|
+
* env: { VITE_DATA_BACKEND: "1", … }
|
|
42
|
+
* require_all_flags: true
|
|
43
|
+
* allow_unset: [VITE_REVIEW_TOOLS] # deliberately absent (qa-only)
|
|
44
|
+
*/
|
|
45
|
+
import { execSync } from "node:child_process";
|
|
46
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
47
|
+
import { tmpdir } from "node:os";
|
|
48
|
+
import { join, relative } from "node:path";
|
|
49
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
50
|
+
const SRC_EXT = /\.(tsx?|jsx?|mjs|cjs|svelte|vue)$/;
|
|
51
|
+
// test files never ship — their ids would sit in the universe as permanent
|
|
52
|
+
// dead-node noise (found via delgoosh's button.test.tsx data-testid="custom").
|
|
53
|
+
const TEST_FILE = /\.(test|spec)\.[a-z]+$/;
|
|
54
|
+
function walk(dir, pred, acc = []) {
|
|
55
|
+
let entries;
|
|
56
|
+
try {
|
|
57
|
+
entries = readdirSync(dir);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return acc;
|
|
61
|
+
}
|
|
62
|
+
for (const name of entries) {
|
|
63
|
+
if (name.startsWith(".") || name === "node_modules" || name === "dist")
|
|
64
|
+
continue;
|
|
65
|
+
const full = join(dir, name);
|
|
66
|
+
let st;
|
|
67
|
+
try {
|
|
68
|
+
st = statSync(full);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (st.isDirectory())
|
|
74
|
+
walk(full, pred, acc);
|
|
75
|
+
else if (pred(name))
|
|
76
|
+
acc.push(full);
|
|
77
|
+
}
|
|
78
|
+
return acc;
|
|
79
|
+
}
|
|
80
|
+
/** every `import.meta.env.<PREFIX>X` / `["<PREFIX>X"]` read, with a source location. */
|
|
81
|
+
export function scanFlagReads(root, srcRel, prefix) {
|
|
82
|
+
const srcDirs = (Array.isArray(srcRel) ? srcRel : [srcRel]).map((s) => join(root, s));
|
|
83
|
+
const reads = new Map(); // flag -> first "file:line"
|
|
84
|
+
const re = new RegExp(String.raw `import\.meta\.env(?:\.(${prefix}[A-Z0-9_]+)|\[\s*["'](${prefix}[A-Z0-9_]+)["']\s*\])`, "g");
|
|
85
|
+
for (const file of srcDirs.flatMap((d) => walk(d, (n) => SRC_EXT.test(n)))) {
|
|
86
|
+
const body = readFileSync(file, "utf8");
|
|
87
|
+
let m;
|
|
88
|
+
while ((m = re.exec(body)) !== null) {
|
|
89
|
+
const flag = (m[1] ?? m[2]);
|
|
90
|
+
if (!reads.has(flag)) {
|
|
91
|
+
const line = body.slice(0, m.index).split("\n").length;
|
|
92
|
+
reads.set(flag, `${relative(root, file).replace(/\\/g, "/")}:${line}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return reads;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* review-node ids from source: rn("id", …), literal data-review-node="id",
|
|
100
|
+
* and ids passed through props whose NAME mentions node (e.g.
|
|
101
|
+
* `<RateInput node="budget/assumption/labor-rate" …>` — the wrapper calls
|
|
102
|
+
* rn() internally). The prop form requires the slashed `area/name` shape so
|
|
103
|
+
* ordinary strings don't false-positive.
|
|
104
|
+
*/
|
|
105
|
+
export function scanNodeIds(root, srcRel, markers = ["data-review-node"]) {
|
|
106
|
+
const srcDirs = (Array.isArray(srcRel) ? srcRel : [srcRel]).map((s) => join(root, s));
|
|
107
|
+
const ids = new Set();
|
|
108
|
+
const rnCall = /\brn\(\s*["'`]([^"'`]+)["'`]/g;
|
|
109
|
+
const markerRes = markers.map((attr) => new RegExp(String.raw `${attr.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}["']?\s*[:=]\s*["'\`]([^"'\`]+)["'\`]`, "g"));
|
|
110
|
+
const nodeProp = /\b[a-zA-Z]*[nN]ode\s*=\s*["'`]([a-z0-9_-]+(?:\/[a-z0-9_-]+)+)["'`]/g;
|
|
111
|
+
for (const file of srcDirs.flatMap((d) => walk(d, (n) => SRC_EXT.test(n) && !TEST_FILE.test(n)))) {
|
|
112
|
+
const body = readFileSync(file, "utf8");
|
|
113
|
+
let m;
|
|
114
|
+
while ((m = rnCall.exec(body)) !== null)
|
|
115
|
+
if (!m[1].includes("${"))
|
|
116
|
+
ids.add(m[1]);
|
|
117
|
+
for (const re of markerRes) {
|
|
118
|
+
re.lastIndex = 0;
|
|
119
|
+
while ((m = re.exec(body)) !== null)
|
|
120
|
+
if (!m[1].includes("${"))
|
|
121
|
+
ids.add(m[1]);
|
|
122
|
+
}
|
|
123
|
+
while ((m = nodeProp.exec(body)) !== null)
|
|
124
|
+
ids.add(m[1]);
|
|
125
|
+
}
|
|
126
|
+
return ids;
|
|
127
|
+
}
|
|
128
|
+
/** all emitted .js text of a built dist, concatenated for literal probing. */
|
|
129
|
+
function bundleText(distDir) {
|
|
130
|
+
return walk(distDir, (n) => /\.(js|mjs|cjs|html)$/.test(n))
|
|
131
|
+
.map((f) => { try {
|
|
132
|
+
return readFileSync(f, "utf8");
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return "";
|
|
136
|
+
} })
|
|
137
|
+
.join("\n");
|
|
138
|
+
}
|
|
139
|
+
export function loadConfig(root, path = ".slowcook/parity.yaml") {
|
|
140
|
+
const abs = join(root, path);
|
|
141
|
+
if (!existsSync(abs))
|
|
142
|
+
throw new Error(`no parity config at ${path} — write one (see \`slowcook check help\`)`);
|
|
143
|
+
const raw = parseYaml(readFileSync(abs, "utf8"));
|
|
144
|
+
if (!raw || typeof raw.build !== "string" || !raw.profiles || Object.keys(raw.profiles).length < 2) {
|
|
145
|
+
throw new Error(`${path} needs at least: build, src, and TWO profiles`);
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
app: raw.app ?? ".",
|
|
149
|
+
src: raw.src ?? "src",
|
|
150
|
+
build: raw.build,
|
|
151
|
+
env_prefix: raw.env_prefix ?? "VITE_",
|
|
152
|
+
markers: raw.markers && raw.markers.length > 0 ? raw.markers : ["data-review-node"],
|
|
153
|
+
baseline: raw.baseline ?? ".slowcook/parity-baseline.yaml",
|
|
154
|
+
profiles: Object.fromEntries(Object.entries(raw.profiles).map(([k, v]) => [k, { env: v?.env ?? {}, require_all_flags: v?.require_all_flags, allow_unset: v?.allow_unset ?? [], forbid: v?.forbid ?? [] }])),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
export function loadBaseline(root, path = ".slowcook/parity-baseline.yaml") {
|
|
158
|
+
const abs = join(root, path);
|
|
159
|
+
if (!existsSync(abs))
|
|
160
|
+
return [];
|
|
161
|
+
const raw = parseYaml(readFileSync(abs, "utf8"));
|
|
162
|
+
return raw?.divergences ?? [];
|
|
163
|
+
}
|
|
164
|
+
export function runSurfaceParityCheck(root, config, baseline, opts = {}) {
|
|
165
|
+
const flags = scanFlagReads(root, config.src, config.env_prefix);
|
|
166
|
+
const nodeIds = scanNodeIds(root, config.src, config.markers);
|
|
167
|
+
// 1. flag completeness
|
|
168
|
+
const flagViolations = [];
|
|
169
|
+
for (const [name, profile] of Object.entries(config.profiles)) {
|
|
170
|
+
if (!profile.require_all_flags)
|
|
171
|
+
continue;
|
|
172
|
+
for (const [flag, readAt] of flags) {
|
|
173
|
+
if (!(flag in profile.env) && !profile.allow_unset.includes(flag)) {
|
|
174
|
+
flagViolations.push({ flag, profile: name, readAt });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// 2. build each profile, probe node-literal survival + forbidden markers
|
|
179
|
+
const presence = new Map();
|
|
180
|
+
const forbidViolations = [];
|
|
181
|
+
const tmp = mkdtempSync(join(tmpdir(), "sc-parity-"));
|
|
182
|
+
const profilesBuilt = [];
|
|
183
|
+
try {
|
|
184
|
+
for (const [name, profile] of Object.entries(config.profiles)) {
|
|
185
|
+
const outDir = join(tmp, name);
|
|
186
|
+
mkdirSync(outDir, { recursive: true });
|
|
187
|
+
if (opts.build) {
|
|
188
|
+
opts.build(name, profile.env, outDir);
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
const cmd = config.build.replace(/\{outDir\}/g, outDir);
|
|
192
|
+
// start from the caller's env MINUS any prefixed flags: a profile's
|
|
193
|
+
// absent flag must be truly absent, not inherited from the shell.
|
|
194
|
+
const env = {};
|
|
195
|
+
for (const [k, v] of Object.entries(process.env)) {
|
|
196
|
+
if (v !== undefined && !k.startsWith(config.env_prefix))
|
|
197
|
+
env[k] = v;
|
|
198
|
+
}
|
|
199
|
+
Object.assign(env, profile.env);
|
|
200
|
+
execSync(cmd, { cwd: join(root, config.app), env, stdio: "pipe" });
|
|
201
|
+
}
|
|
202
|
+
profilesBuilt.push(name);
|
|
203
|
+
const text = bundleText(outDir);
|
|
204
|
+
for (const marker of profile.forbid ?? []) {
|
|
205
|
+
if (text.includes(marker))
|
|
206
|
+
forbidViolations.push({ marker, profile: name });
|
|
207
|
+
}
|
|
208
|
+
for (const id of nodeIds) {
|
|
209
|
+
const rec = presence.get(id) ?? {};
|
|
210
|
+
rec[name] = text.includes(id);
|
|
211
|
+
presence.set(id, rec);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
finally {
|
|
216
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
217
|
+
}
|
|
218
|
+
// 3. classify
|
|
219
|
+
const profileNames = Object.keys(config.profiles);
|
|
220
|
+
const drift = [];
|
|
221
|
+
const deadNodes = [];
|
|
222
|
+
for (const [id, rec] of presence) {
|
|
223
|
+
const present = profileNames.filter((p) => rec[p]);
|
|
224
|
+
if (present.length === profileNames.length)
|
|
225
|
+
continue;
|
|
226
|
+
if (present.length === 0) {
|
|
227
|
+
deadNodes.push(id);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
drift.push({ node: id, absent_from: profileNames.filter((p) => !rec[p]).sort(), presentIn: present.sort() });
|
|
231
|
+
}
|
|
232
|
+
const byNode = new Map(baseline.map((b) => [b.node, b]));
|
|
233
|
+
const newDrift = [];
|
|
234
|
+
const waivedDrift = [];
|
|
235
|
+
for (const d of drift) {
|
|
236
|
+
const b = byNode.get(d.node);
|
|
237
|
+
// a baseline entry waives EXACTLY the recorded shape; drift that moved
|
|
238
|
+
// (absent from a different profile set) is NEW — that's the conflict case.
|
|
239
|
+
if (b && sameSet(b.absent_from, d.absent_from))
|
|
240
|
+
waivedDrift.push(d);
|
|
241
|
+
else
|
|
242
|
+
newDrift.push(d);
|
|
243
|
+
}
|
|
244
|
+
const drifted = new Set(drift.map((d) => d.node));
|
|
245
|
+
const healed = baseline.filter((b) => !drifted.has(b.node));
|
|
246
|
+
return {
|
|
247
|
+
flagViolations, forbidViolations, newDrift, waivedDrift, healed, deadNodes,
|
|
248
|
+
nodesChecked: nodeIds.size, flagsChecked: flags.size, profilesBuilt,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
function sameSet(a, b) {
|
|
252
|
+
if (a.length !== b.length)
|
|
253
|
+
return false;
|
|
254
|
+
const sa = [...a].sort(), sb = [...b].sort();
|
|
255
|
+
return sa.every((x, i) => x === sb[i]);
|
|
256
|
+
}
|
|
257
|
+
export function writeBaseline(root, drift, prior, path = ".slowcook/parity-baseline.yaml") {
|
|
258
|
+
const byNode = new Map(prior.map((b) => [b.node, b]));
|
|
259
|
+
const divergences = drift.map((d) => {
|
|
260
|
+
const existing = byNode.get(d.node);
|
|
261
|
+
return {
|
|
262
|
+
node: d.node,
|
|
263
|
+
absent_from: d.absent_from,
|
|
264
|
+
reason: existing?.reason ?? "TODO: why is this divergence deliberate?",
|
|
265
|
+
...(existing?.owner ? { owner: existing.owner } : {}),
|
|
266
|
+
...(existing?.direction ? { direction: existing.direction } : {}),
|
|
267
|
+
};
|
|
268
|
+
});
|
|
269
|
+
const abs = join(root, path);
|
|
270
|
+
mkdirSync(join(root, ".slowcook"), { recursive: true });
|
|
271
|
+
writeFileSync(abs, stringifyYaml({
|
|
272
|
+
"#": "surface-parity baseline — every entry is a DELIBERATE mock/prod divergence with a reason. New unwaived drift fails CI (the ratchet).",
|
|
273
|
+
divergences,
|
|
274
|
+
}), "utf8");
|
|
275
|
+
}
|
|
276
|
+
export function runSurfaceParityCli(argv) {
|
|
277
|
+
const cwdIdx = argv.indexOf("--cwd");
|
|
278
|
+
const root = cwdIdx >= 0 ? argv[cwdIdx + 1] : process.cwd();
|
|
279
|
+
const cfgIdx = argv.indexOf("--config");
|
|
280
|
+
const configPath = cfgIdx >= 0 ? argv[cfgIdx + 1] : ".slowcook/parity.yaml";
|
|
281
|
+
const update = argv.includes("--update-baseline");
|
|
282
|
+
let config;
|
|
283
|
+
try {
|
|
284
|
+
config = loadConfig(root, configPath);
|
|
285
|
+
}
|
|
286
|
+
catch (e) {
|
|
287
|
+
console.error(`✗ surface-parity: ${e.message}`);
|
|
288
|
+
process.exit(2);
|
|
289
|
+
}
|
|
290
|
+
const baseline = loadBaseline(root, config.baseline);
|
|
291
|
+
let result;
|
|
292
|
+
try {
|
|
293
|
+
result = runSurfaceParityCheck(root, config, baseline);
|
|
294
|
+
}
|
|
295
|
+
catch (e) {
|
|
296
|
+
console.error(`✗ surface-parity: build failed — ${e.message.slice(0, 400)}`);
|
|
297
|
+
process.exit(2);
|
|
298
|
+
}
|
|
299
|
+
const { flagViolations, forbidViolations, newDrift, waivedDrift, healed, deadNodes } = result;
|
|
300
|
+
console.log(`surface-parity: ${result.nodesChecked} nodes · ${result.flagsChecked} flags · profiles: ${result.profilesBuilt.join(", ")}`);
|
|
301
|
+
for (const v of flagViolations) {
|
|
302
|
+
console.error(` ✗ flag ${v.flag} is read (${v.readAt}) but UNDECLARED in profile '${v.profile}' — its live branch would be silently dropped. Declare it (value may be "") or list it under allow_unset.`);
|
|
303
|
+
}
|
|
304
|
+
for (const v of forbidViolations) {
|
|
305
|
+
console.error(` ✗ FORBIDDEN marker ${JSON.stringify(v.marker)} survives into profile '${v.profile}' — this profile must be SEVERED from it at build time, not runtime-bypassed.`);
|
|
306
|
+
}
|
|
307
|
+
for (const d of newDrift) {
|
|
308
|
+
console.error(` ✗ node '${d.node}' is absent from [${d.absent_from.join(", ")}] but present in [${d.presentIn.join(", ")}] — NEW drift. Fix the missing branch, or record it in .slowcook/parity-baseline.yaml with a reason (--update-baseline scaffolds it).`);
|
|
309
|
+
}
|
|
310
|
+
for (const d of waivedDrift) {
|
|
311
|
+
console.log(` · waived: '${d.node}' absent from [${d.absent_from.join(", ")}] (baselined)`);
|
|
312
|
+
}
|
|
313
|
+
for (const b of healed) {
|
|
314
|
+
console.log(` ✚ healed: '${b.node}' no longer diverges — prune it from the baseline.`);
|
|
315
|
+
}
|
|
316
|
+
if (deadNodes.length > 0) {
|
|
317
|
+
console.log(` ⚠ ${deadNodes.length} node id(s) absent from EVERY profile (dead code?): ${deadNodes.slice(0, 5).join(", ")}${deadNodes.length > 5 ? ", …" : ""}`);
|
|
318
|
+
}
|
|
319
|
+
if (update) {
|
|
320
|
+
writeBaseline(root, [...newDrift, ...waivedDrift], baseline, config.baseline);
|
|
321
|
+
console.log(` baseline updated (${newDrift.length + waivedDrift.length} divergence(s)) — fill in the TODO reasons before committing.`);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
if (flagViolations.length > 0 || forbidViolations.length > 0 || newDrift.length > 0)
|
|
325
|
+
process.exit(1);
|
|
326
|
+
console.log(`✓ surface-parity: no new drift (${waivedDrift.length} waived, ${healed.length} healed)`);
|
|
327
|
+
}
|
|
328
|
+
//# sourceMappingURL=surface-parity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"surface-parity.js","sourceRoot":"","sources":["../../../src/commands/check/surface-parity.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"as-built-cli.d.ts","sourceRoot":"","sources":["../../../src/commands/extract/as-built-cli.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAkBhD,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,YAAY,EACpB,IAAI,EAAE;IAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,YAAY,EAAE,OAAO,CAAA;CAAE,GACpD,OAAO,CAAC,IAAI,CAAC,CA8Bf"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/** CLI half of `extract --as-built` — wires the survey + dossier + LLM +
|
|
2
|
+
* citation validator together and owns process exit semantics. Kept apart
|
|
3
|
+
* from as-built.ts so the core stays pure for tests. */
|
|
4
|
+
import { readdirSync, statSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { join, relative } from "node:path";
|
|
6
|
+
import { createLlmClient } from "../refine/llm.js";
|
|
7
|
+
import { buildDossier, generateAsBuilt, validateCitations, validateSections } from "./as-built.js";
|
|
8
|
+
const DEFAULT_MODEL = "claude-opus-4-8";
|
|
9
|
+
function walk(dir, root, acc = []) {
|
|
10
|
+
let entries;
|
|
11
|
+
try {
|
|
12
|
+
entries = readdirSync(dir);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return acc;
|
|
16
|
+
}
|
|
17
|
+
for (const name of entries) {
|
|
18
|
+
if (name === "node_modules" || name === ".git" || name === "dist")
|
|
19
|
+
continue;
|
|
20
|
+
const full = join(dir, name);
|
|
21
|
+
let st;
|
|
22
|
+
try {
|
|
23
|
+
st = statSync(full);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (st.isDirectory())
|
|
29
|
+
walk(full, root, acc);
|
|
30
|
+
else
|
|
31
|
+
acc.push(relative(root, full).replace(/\\/g, "/"));
|
|
32
|
+
}
|
|
33
|
+
return acc;
|
|
34
|
+
}
|
|
35
|
+
export async function runAsBuiltCli(repoRoot, survey, opts) {
|
|
36
|
+
let llm;
|
|
37
|
+
try {
|
|
38
|
+
llm = await createLlmClient();
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
console.error(`extract --as-built: ${err instanceof Error ? err.message : String(err)}`);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
const paths = walk(repoRoot, repoRoot);
|
|
45
|
+
const dossier = buildDossier(repoRoot, survey, paths);
|
|
46
|
+
console.log(`extract --as-built: dossier holds ${dossier.filesIncluded.length} files + survey (${survey.claims.length} claims); calling the model…`);
|
|
47
|
+
const { md } = await generateAsBuilt(llm, opts.model ?? DEFAULT_MODEL, dossier);
|
|
48
|
+
const missing = validateSections(md);
|
|
49
|
+
if (missing.length > 0) {
|
|
50
|
+
console.error(`extract --as-built: output is missing required section(s): ${missing.join(", ")} — not writing.`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
const cites = validateCitations(md);
|
|
54
|
+
if (cites.uncited.length > 0 && !opts.allowUncited) {
|
|
55
|
+
console.error(`extract --as-built: ${cites.uncited.length}/${cites.bullets} bullets carry NO citation — an uncited claim is a guess. Offenders:`);
|
|
56
|
+
for (const u of cites.uncited.slice(0, 10))
|
|
57
|
+
console.error(` · ${u}`);
|
|
58
|
+
console.error(`Re-run, or accept with --allow-uncited (the doc will keep an UNCITED marker).`);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
const banner = `> Generated by \`slowcook extract --as-built\` from ${survey.repo_head ?? "HEAD"} on ${survey.generated_at.slice(0, 10)}. Code-as-truth, as observed; regenerate when main moves.${cites.uncited.length > 0 ? ` ⚠ ${cites.uncited.length} UNCITED bullet(s) accepted via --allow-uncited.` : ""}\n\n`;
|
|
62
|
+
const withBanner = md.replace(/^(# .+\n)/, `$1\n${banner}`);
|
|
63
|
+
const file = join(repoRoot, ".brewing", "as-built.md");
|
|
64
|
+
writeFileSync(file, withBanner, "utf8");
|
|
65
|
+
console.log(`Wrote ${file} (${cites.bullets} cited bullets, ${md.split("\n## ").length - 1} sections).`);
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=as-built-cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"as-built-cli.js","sourceRoot":"","sources":["../../../src/commands/extract/as-built-cli.ts"],"names":[],"mappings":"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"}
|