@slowcook-ai/cli 0.1.0 → 0.3.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 CHANGED
@@ -8,7 +8,33 @@ CLI for the slowcook brewing harness. Installs the `slowcook` binary.
8
8
  npm i -D @slowcook-ai/cli
9
9
  ```
10
10
 
11
- ## Commands (v0.1)
11
+ ## Commands (v0.3)
12
+
13
+ ### `slowcook init`
14
+
15
+ Scaffold slowcook configuration in a consumer project. Writes `.brewing/*`, `.github/workflows/slowcook.yml`, and a `CODEOWNERS` section. Idempotent — re-running skips existing files unless `--force`.
16
+
17
+ ```bash
18
+ npx slowcook init [--owner <handle>] [--force] [--dry-run] [--cwd <path>]
19
+ ```
20
+
21
+ **Options:**
22
+
23
+ | Flag | Default | Description |
24
+ |---|---|---|
25
+ | `--cwd <path>` | `.` | Target project directory |
26
+ | `--owner <handle>` | detected from git remote | CODEOWNERS handle/team (e.g. `@aminazar`, `@acme/frontend`) |
27
+ | `--force` | false | Overwrite existing slowcook files |
28
+ | `--dry-run` | false | Print the plan without writing anything |
29
+
30
+ **Stack detection (0.3):** reads `package.json`. Requires Vitest in `devDependencies`. If Playwright is present, it's noted as a warning and left out of `stack.json` until slowcook supports Playwright discovery.
31
+
32
+ **CODEOWNERS handling:** uses `# --- slowcook:frozen-paths BEGIN/END ---` markers so re-running or adopting slowcook in a repo that already has a `CODEOWNERS` is safe.
33
+
34
+ **Exit codes:**
35
+
36
+ - `0` — success (or dry-run completed)
37
+ - `2` — script error (no `package.json`, vitest not found, invalid JSON)
12
38
 
13
39
  ### `slowcook guard`
14
40
 
@@ -79,6 +105,55 @@ jobs:
79
105
 
80
106
  The guard emits `::error file=...::` annotations and writes to `$GITHUB_STEP_SUMMARY` when run in GitHub Actions.
81
107
 
108
+ ### `slowcook manifest` (record / verify)
109
+
110
+ Captures the set of discoverable tests so agents can't silently remove or exclude them. 0.2 supports Vitest; Playwright is coming later.
111
+
112
+ ```bash
113
+ # Record a snapshot of every test currently discoverable
114
+ npx slowcook manifest record
115
+
116
+ # Verify later that the recorded set still fully resolves
117
+ npx slowcook manifest verify
118
+ ```
119
+
120
+ **Options (both subcommands):**
121
+
122
+ | Flag | Default | Description |
123
+ |---|---|---|
124
+ | `--stack-config <path>` | `.brewing/stack.json` | Consumer stack config |
125
+ | `--manifest <path>` | `.brewing/manifests/all.json` (or `.brewing/manifests/story-<id>.json` if `--story`) | Where to write / read the manifest |
126
+ | `--story <id>` | none | Tag manifest with a story id (enables per-story freezing) |
127
+ | `--cwd <path>` | `.` | Working directory for discovery commands |
128
+
129
+ **Config file** — `.brewing/stack.json` declares how to discover tests per suite:
130
+
131
+ ```json
132
+ {
133
+ "language": "typescript",
134
+ "test": {
135
+ "backend": {
136
+ "runner": "vitest",
137
+ "run_command": "npx vitest run",
138
+ "discover_command": "npx vitest list",
139
+ "reporter_format": "vitest-list-lines"
140
+ }
141
+ }
142
+ }
143
+ ```
144
+
145
+ **Exit codes:**
146
+
147
+ - `record`: `0` manifest written, `2` script error (bad config, suite discovery failed)
148
+ - `verify`: `0` manifest matches (new tests since record are informational), `1` recorded tests no longer discoverable, `2` script error
149
+
150
+ **Use in GitHub Actions** — after the frozen-paths guard:
151
+
152
+ ```yaml
153
+ - name: Verify test manifest
154
+ run: npx --yes @slowcook-ai/cli@latest manifest verify
155
+ ```
156
+
82
157
  ## Coming in later versions
83
158
 
84
159
  See the [monorepo README](../../README.md) for the roadmap.
package/dist/cli.js CHANGED
@@ -1,19 +1,26 @@
1
1
  #!/usr/bin/env node
2
2
  import { guard } from "./commands/guard.js";
3
- const VERSION = "0.1.0";
3
+ import { manifest } from "./commands/manifest.js";
4
+ import { init } from "./commands/init/index.js";
5
+ const VERSION = "0.3.0";
4
6
  const USAGE = `
5
7
  slowcook — TDD-first agentic development harness
6
8
 
7
9
  Usage:
10
+ slowcook init [--owner <handle>] [--force] [--dry-run] [--cwd <path>]
8
11
  slowcook guard --base <ref> --head <ref> [--override] [--config <path>]
12
+ slowcook manifest record [--stack-config <path>] [--manifest <path>] [--story <id>]
13
+ slowcook manifest verify [--stack-config <path>] [--manifest <path>] [--story <id>]
9
14
  slowcook version
10
15
  slowcook help
11
16
 
12
17
  Commands available in ${VERSION}:
18
+ init Scaffold slowcook configuration in a consumer project.
13
19
  guard Check for frozen-path violations between two git refs.
20
+ manifest Record or verify the set of discoverable tests.
14
21
 
15
22
  Coming in later versions:
16
- init, manifest, refine, testgen, brew, review, dashboard
23
+ refine, testgen, brew, review, dashboard
17
24
 
18
25
  Docs: https://github.com/aminazar/slowcook
19
26
  `;
@@ -21,9 +28,15 @@ async function main() {
21
28
  const args = process.argv.slice(2);
22
29
  const command = args[0];
23
30
  switch (command) {
31
+ case "init":
32
+ await init(args.slice(1), VERSION);
33
+ return;
24
34
  case "guard":
25
35
  await guard(args.slice(1));
26
36
  return;
37
+ case "manifest":
38
+ await manifest(args.slice(1));
39
+ return;
27
40
  case "version":
28
41
  case "--version":
29
42
  case "-v":
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAE5C,MAAM,OAAO,GAAG,OAAO,CAAC;AAExB,MAAM,KAAK,GAAG;;;;;;;;wBAQU,OAAO;;;;;;;CAO9B,CAAC;AAEF,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IAExB,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,OAAO;YACV,MAAM,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YAC3B,OAAO;QACT,KAAK,SAAS,CAAC;QACf,KAAK,WAAW,CAAC;QACjB,KAAK,IAAI;YACP,OAAO,CAAC,GAAG,CAAC,YAAY,OAAO,EAAE,CAAC,CAAC;YACnC,OAAO;QACT,KAAK,SAAS,CAAC;QACf,KAAK,MAAM,CAAC;QACZ,KAAK,QAAQ,CAAC;QACd,KAAK,IAAI;YACP,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YACnB,OAAO;QACT;YACE,OAAO,CAAC,KAAK,CAAC,oBAAoB,OAAO,KAAK,KAAK,EAAE,CAAC,CAAC;YACvD,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW;IACjC,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IAC5B,OAAO,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAC7E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAEhD,MAAM,OAAO,GAAG,OAAO,CAAC;AAExB,MAAM,KAAK,GAAG;;;;;;;;;;;wBAWU,OAAO;;;;;;;;;CAS9B,CAAC;AAEF,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IAExB,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,MAAM;YACT,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;YACnC,OAAO;QACT,KAAK,OAAO;YACV,MAAM,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YAC3B,OAAO;QACT,KAAK,UAAU;YACb,MAAM,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YAC9B,OAAO;QACT,KAAK,SAAS,CAAC;QACf,KAAK,WAAW,CAAC;QACjB,KAAK,IAAI;YACP,OAAO,CAAC,GAAG,CAAC,YAAY,OAAO,EAAE,CAAC,CAAC;YACnC,OAAO;QACT,KAAK,SAAS,CAAC;QACf,KAAK,MAAM,CAAC;QACZ,KAAK,QAAQ,CAAC;QACd,KAAK,IAAI;YACP,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YACnB,OAAO;QACT;YACE,OAAO,CAAC,KAAK,CAAC,oBAAoB,OAAO,KAAK,KAAK,EAAE,CAAC,CAAC;YACvD,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW;IACjC,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IAC5B,OAAO,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAC7E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function init(argv: string[], cliVersion: string): Promise<void>;
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/init/index.ts"],"names":[],"mappings":"AAmIA,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA2E5E"}
@@ -0,0 +1,181 @@
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, } from "node:fs";
2
+ import { dirname, resolve, relative, isAbsolute } from "node:path";
3
+ import { execSync } from "node:child_process";
4
+ import { buildPlan, InitError } from "./plan.js";
5
+ function parseArgs(argv) {
6
+ const args = {
7
+ cwd: process.cwd(),
8
+ owner: undefined,
9
+ force: false,
10
+ dryRun: false,
11
+ };
12
+ for (let i = 0; i < argv.length; i++) {
13
+ const arg = argv[i];
14
+ const next = argv[i + 1];
15
+ if (arg === "--cwd" && next) {
16
+ args.cwd = resolve(next);
17
+ i++;
18
+ }
19
+ else if (arg === "--owner" && next) {
20
+ args.owner = next;
21
+ i++;
22
+ }
23
+ else if (arg === "--force") {
24
+ args.force = true;
25
+ }
26
+ else if (arg === "--dry-run") {
27
+ args.dryRun = true;
28
+ }
29
+ else if (arg === "--help" || arg === "-h") {
30
+ printHelp();
31
+ process.exit(0);
32
+ }
33
+ }
34
+ return args;
35
+ }
36
+ function printHelp() {
37
+ console.log(`
38
+ slowcook init — scaffold slowcook configuration in a consumer project
39
+
40
+ Usage:
41
+ slowcook init [options]
42
+
43
+ Options:
44
+ --cwd <path> Target project directory (default: current directory)
45
+ --owner <handle> CODEOWNERS handle/team, e.g. "@aminazar" or "@acme/frontend".
46
+ If omitted, slowcook tries to detect from \`git remote get-url origin\`;
47
+ falls back to "@TODO-OWNER" with a warning.
48
+ --force Overwrite existing slowcook files (default: skip existing)
49
+ --dry-run Print the plan without writing anything to disk
50
+ --help, -h Show this help
51
+
52
+ Writes:
53
+ .brewing/frozen-paths.json
54
+ .brewing/stack.json
55
+ .brewing/README.md
56
+ .brewing/manifests/.gitkeep
57
+ .github/workflows/slowcook.yml
58
+ CODEOWNERS (appends slowcook section if file exists)
59
+
60
+ Exit codes:
61
+ 0 success (or dry-run completed)
62
+ 2 script error (no package.json, vitest not detected, etc.)
63
+ `);
64
+ }
65
+ function makeReader(cwd) {
66
+ return {
67
+ exists: (p) => existsSync(resolveIn(cwd, p)),
68
+ read: (p) => readFileSync(resolveIn(cwd, p), "utf8"),
69
+ };
70
+ }
71
+ function resolveIn(cwd, path) {
72
+ if (isAbsolute(path))
73
+ return path;
74
+ return resolve(cwd, path);
75
+ }
76
+ function detectOwnerFromGitRemote(cwd) {
77
+ try {
78
+ const url = execSync("git remote get-url origin", {
79
+ cwd,
80
+ encoding: "utf8",
81
+ stdio: ["ignore", "pipe", "ignore"],
82
+ }).trim();
83
+ // https://github.com/USER/REPO.git | git@github.com:USER/REPO.git
84
+ const m = url.match(/github\.com[:/]([^/]+)\/[^/.]+(?:\.git)?$/) ??
85
+ url.match(/gitlab\.com[:/]([^/]+)\/[^/.]+(?:\.git)?$/);
86
+ if (m && m[1])
87
+ return `@${m[1]}`;
88
+ }
89
+ catch {
90
+ // not a git repo, or no origin — fine, fall through
91
+ }
92
+ return null;
93
+ }
94
+ function formatAction(a) {
95
+ switch (a.kind) {
96
+ case "create":
97
+ return ` CREATE ${a.path}`;
98
+ case "overwrite":
99
+ return ` OVERWRITE ${a.path}`;
100
+ case "append":
101
+ return ` APPEND ${a.path} (preserving existing content)`;
102
+ case "skip-exists":
103
+ return ` SKIP ${a.path} (${a.reason})`;
104
+ case "conflict":
105
+ return ` CONFLICT ${a.path} (${a.reason})`;
106
+ }
107
+ }
108
+ function applyAction(cwd, a) {
109
+ if (a.kind === "skip-exists" || a.kind === "conflict")
110
+ return;
111
+ const full = resolveIn(cwd, a.path);
112
+ mkdirSync(dirname(full), { recursive: true });
113
+ const contents = a.kind === "create" || a.kind === "overwrite" || a.kind === "append"
114
+ ? a.contents
115
+ : "";
116
+ writeFileSync(full, contents, "utf8");
117
+ }
118
+ export async function init(argv, cliVersion) {
119
+ const args = parseArgs(argv);
120
+ const reader = makeReader(args.cwd);
121
+ let owner = args.owner;
122
+ if (!owner) {
123
+ const detected = detectOwnerFromGitRemote(args.cwd);
124
+ if (detected) {
125
+ owner = detected;
126
+ console.log(`Detected CODEOWNERS handle from git remote: ${owner}`);
127
+ }
128
+ else {
129
+ owner = "@TODO-OWNER";
130
+ console.log("::warning::Could not detect a CODEOWNERS handle from git remote. Using @TODO-OWNER as a placeholder — replace it after init.");
131
+ }
132
+ }
133
+ let plan;
134
+ try {
135
+ plan = buildPlan(reader, {
136
+ cwd: args.cwd,
137
+ owner,
138
+ force: args.force,
139
+ cliVersion,
140
+ });
141
+ }
142
+ catch (e) {
143
+ if (e instanceof InitError) {
144
+ console.error(`slowcook init: ${e.message}`);
145
+ process.exit(2);
146
+ }
147
+ throw e;
148
+ }
149
+ console.log(`Detected stack:`);
150
+ console.log(` language: ${plan.detected.language}`);
151
+ console.log(` vitest: ${plan.detected.hasVitest}`);
152
+ console.log(` playwright: ${plan.detected.hasPlaywright}`);
153
+ console.log();
154
+ if (plan.warnings.length > 0) {
155
+ console.log("Warnings:");
156
+ for (const w of plan.warnings)
157
+ console.log(` - ${w}`);
158
+ console.log();
159
+ }
160
+ console.log(`Planned file actions (cwd: ${relative(process.cwd(), args.cwd) || "."}):`);
161
+ for (const a of plan.actions)
162
+ console.log(formatAction(a));
163
+ console.log();
164
+ if (args.dryRun) {
165
+ console.log("Dry-run complete — no files written.");
166
+ return;
167
+ }
168
+ for (const a of plan.actions)
169
+ applyAction(args.cwd, a);
170
+ const skippedCount = plan.actions.filter((a) => a.kind === "skip-exists").length;
171
+ console.log(`slowcook init: ${plan.actions.length - skippedCount} file(s) written, ${skippedCount} skipped.`);
172
+ console.log();
173
+ console.log("Next steps:");
174
+ console.log(` 1. Review .brewing/frozen-paths.json — add/remove directories to match your repo.`);
175
+ if (owner === "@TODO-OWNER") {
176
+ console.log(` 2. Replace @TODO-OWNER in CODEOWNERS with your GitHub handle/team.`);
177
+ }
178
+ console.log(` ${owner === "@TODO-OWNER" ? "3." : "2."} Run \`slowcook manifest record\` once your test set is stable.`);
179
+ console.log(` ${owner === "@TODO-OWNER" ? "4." : "3."} Commit and open a PR; slowcook CI will run on it.`);
180
+ }
181
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/init/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,YAAY,EACZ,aAAa,EACb,SAAS,GACV,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACnE,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,SAAS,EAAoC,MAAM,WAAW,CAAC;AASnF,SAAS,SAAS,CAAC,IAAc;IAC/B,MAAM,IAAI,GAAa;QACrB,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;QAClB,KAAK,EAAE,SAAS;QAChB,KAAK,EAAE,KAAK;QACZ,MAAM,EAAE,KAAK;KACd,CAAC;IACF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACzB,IAAI,GAAG,KAAK,OAAO,IAAI,IAAI,EAAE,CAAC;YAC5B,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;YACzB,CAAC,EAAE,CAAC;QACN,CAAC;aAAM,IAAI,GAAG,KAAK,SAAS,IAAI,IAAI,EAAE,CAAC;YACrC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;YAClB,CAAC,EAAE,CAAC;QACN,CAAC;aAAM,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YAC7B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QACpB,CAAC;aAAM,IAAI,GAAG,KAAK,WAAW,EAAE,CAAC;YAC/B,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACrB,CAAC;aAAM,IAAI,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YAC5C,SAAS,EAAE,CAAC;YACZ,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,SAAS;IAChB,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;CA0Bb,CAAC,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,GAAW;IAC7B,OAAO;QACL,MAAM,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACpD,IAAI,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,YAAY,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC;KAC7D,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,GAAW,EAAE,IAAY;IAC1C,IAAI,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAClC,OAAO,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;AAC5B,CAAC;AAED,SAAS,wBAAwB,CAAC,GAAW;IAC3C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,QAAQ,CAAC,2BAA2B,EAAE;YAChD,GAAG;YACH,QAAQ,EAAE,MAAM;YAChB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;SACpC,CAAC,CAAC,IAAI,EAAE,CAAC;QACV,oEAAoE;QACpE,MAAM,CAAC,GACL,GAAG,CAAC,KAAK,CAAC,2CAA2C,CAAC;YACtD,GAAG,CAAC,KAAK,CAAC,2CAA2C,CAAC,CAAC;QACzD,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,oDAAoD;IACtD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,YAAY,CAAC,CAAa;IACjC,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;QACf,KAAK,QAAQ;YACX,OAAO,iBAAiB,CAAC,CAAC,IAAI,EAAE,CAAC;QACnC,KAAK,WAAW;YACd,OAAO,iBAAiB,CAAC,CAAC,IAAI,EAAE,CAAC;QACnC,KAAK,QAAQ;YACX,OAAO,iBAAiB,CAAC,CAAC,IAAI,iCAAiC,CAAC;QAClE,KAAK,aAAa;YAChB,OAAO,iBAAiB,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC;QAClD,KAAK,UAAU;YACb,OAAO,iBAAiB,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC;IACpD,CAAC;AACH,CAAC;AAED,SAAS,WAAW,CAAC,GAAW,EAAE,CAAa;IAC7C,IAAI,CAAC,CAAC,IAAI,KAAK,aAAa,IAAI,CAAC,CAAC,IAAI,KAAK,UAAU;QAAE,OAAO;IAC9D,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IACpC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,MAAM,QAAQ,GACZ,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,KAAK,WAAW,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ;QAClE,CAAC,CAAC,CAAC,CAAC,QAAQ;QACZ,CAAC,CAAC,EAAE,CAAC;IACT,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,IAAc,EAAE,UAAkB;IAC3D,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC7B,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAEpC,IAAI,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;IACvB,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,QAAQ,GAAG,wBAAwB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACpD,IAAI,QAAQ,EAAE,CAAC;YACb,KAAK,GAAG,QAAQ,CAAC;YACjB,OAAO,CAAC,GAAG,CAAC,+CAA+C,KAAK,EAAE,CAAC,CAAC;QACtE,CAAC;aAAM,CAAC;YACN,KAAK,GAAG,aAAa,CAAC;YACtB,OAAO,CAAC,GAAG,CACT,8HAA8H,CAC/H,CAAC;QACJ,CAAC;IACH,CAAC;IAED,IAAI,IAAI,CAAC;IACT,IAAI,CAAC;QACH,IAAI,GAAG,SAAS,CAAC,MAAM,EAAE;YACvB,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,KAAK;YACL,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,UAAU;SACX,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,CAAC,YAAY,SAAS,EAAE,CAAC;YAC3B,OAAO,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;YAC7C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,MAAM,CAAC,CAAC;IACV,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAC/B,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;IACrD,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC;IACpD,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,CAAC,QAAQ,CAAC,aAAa,EAAE,CAAC,CAAC;IAC5D,OAAO,CAAC,GAAG,EAAE,CAAC;IAEd,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7B,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QACzB,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACvD,OAAO,CAAC,GAAG,EAAE,CAAC;IAChB,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,8BAA8B,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IACxF,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO;QAAE,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3D,OAAO,CAAC,GAAG,EAAE,CAAC;IAEd,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC;QACpD,OAAO;IACT,CAAC;IAED,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO;QAAE,WAAW,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IAEvD,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,aAAa,CAAC,CAAC,MAAM,CAAC;IACjF,OAAO,CAAC,GAAG,CACT,kBAAkB,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,YAAY,qBAAqB,YAAY,WAAW,CACjG,CAAC;IACF,OAAO,CAAC,GAAG,EAAE,CAAC;IACd,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAC3B,OAAO,CAAC,GAAG,CACT,qFAAqF,CACtF,CAAC;IACF,IAAI,KAAK,KAAK,aAAa,EAAE,CAAC;QAC5B,OAAO,CAAC,GAAG,CAAC,sEAAsE,CAAC,CAAC;IACtF,CAAC;IACD,OAAO,CAAC,GAAG,CACT,KAAK,KAAK,KAAK,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,iEAAiE,CAC5G,CAAC;IACF,OAAO,CAAC,GAAG,CACT,KAAK,KAAK,KAAK,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,oDAAoD,CAC/F,CAAC;AACJ,CAAC"}
@@ -0,0 +1,58 @@
1
+ export interface PackageJson {
2
+ dependencies?: Record<string, string>;
3
+ devDependencies?: Record<string, string>;
4
+ scripts?: Record<string, string>;
5
+ }
6
+ export interface DetectedStack {
7
+ language: "typescript";
8
+ hasVitest: boolean;
9
+ hasPlaywright: boolean;
10
+ }
11
+ /** Minimal filesystem reader so planning is pure. */
12
+ export interface FileReader {
13
+ exists(path: string): boolean;
14
+ read(path: string): string;
15
+ }
16
+ export interface PlanOptions {
17
+ /** Consumer project root (where package.json lives). */
18
+ cwd: string;
19
+ /** CODEOWNERS handle/team (e.g., "@aminazar"). */
20
+ owner: string;
21
+ /** Overwrite existing files (default: skip-if-exists). */
22
+ force: boolean;
23
+ /** Version of the CLI invoking init; drives the CI workflow pin. */
24
+ cliVersion?: string;
25
+ }
26
+ export type FileAction = {
27
+ kind: "create";
28
+ path: string;
29
+ contents: string;
30
+ } | {
31
+ kind: "skip-exists";
32
+ path: string;
33
+ reason: string;
34
+ } | {
35
+ kind: "overwrite";
36
+ path: string;
37
+ contents: string;
38
+ } | {
39
+ kind: "append";
40
+ path: string;
41
+ contents: string;
42
+ existingContents: string;
43
+ } | {
44
+ kind: "conflict";
45
+ path: string;
46
+ reason: string;
47
+ };
48
+ export interface Plan {
49
+ detected: DetectedStack;
50
+ actions: FileAction[];
51
+ warnings: string[];
52
+ }
53
+ export declare function detectStack(pkg: PackageJson): DetectedStack;
54
+ export declare class InitError extends Error {
55
+ constructor(message: string);
56
+ }
57
+ export declare function buildPlan(reader: FileReader, options: PlanOptions): Plan;
58
+ //# sourceMappingURL=plan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../../src/commands/init/plan.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,WAAW;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,YAAY,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,qDAAqD;AACrD,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9B,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,WAAW;IAC1B,wDAAwD;IACxD,GAAG,EAAE,MAAM,CAAC;IACZ,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,0DAA0D;IAC1D,KAAK,EAAE,OAAO,CAAC;IACf,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACrD;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACrD;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAA;CAAE,GAC5E;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvD,MAAM,WAAW,IAAI;IACnB,QAAQ,EAAE,aAAa,CAAC;IACxB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAYD,wBAAgB,WAAW,CAAC,GAAG,EAAE,WAAW,GAAG,aAAa,CAO3D;AAED,qBAAa,SAAU,SAAQ,KAAK;gBACtB,OAAO,EAAE,MAAM;CAI5B;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW,GAAG,IAAI,CAiExE"}
@@ -0,0 +1,136 @@
1
+ // Pure planning logic for `slowcook init`. Takes a snapshot of the filesystem
2
+ // (via an injected reader), returns the planned actions. No I/O side effects
3
+ // here — all writes happen in the CLI wrapper so this is trivially testable.
4
+ import { frozenPathsJson, stackJson, brewingReadme, slowcookWorkflow, codeownersFullFile, codeownersSection, gitkeep, CLI_VERSION_FOR_TEMPLATES, SLOWCOOK_CODEOWNERS_MARKER_BEGIN, SLOWCOOK_CODEOWNERS_MARKER_END, } from "./templates.js";
5
+ const TARGETS = {
6
+ frozenPaths: ".brewing/frozen-paths.json",
7
+ stack: ".brewing/stack.json",
8
+ brewingReadme: ".brewing/README.md",
9
+ manifestsGitkeep: ".brewing/manifests/.gitkeep",
10
+ workflow: ".github/workflows/slowcook.yml",
11
+ codeowners: "CODEOWNERS",
12
+ packageJson: "package.json",
13
+ };
14
+ export function detectStack(pkg) {
15
+ const deps = { ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) };
16
+ return {
17
+ language: "typescript",
18
+ hasVitest: "vitest" in deps,
19
+ hasPlaywright: "@playwright/test" in deps || "playwright" in deps,
20
+ };
21
+ }
22
+ export class InitError extends Error {
23
+ constructor(message) {
24
+ super(message);
25
+ this.name = "InitError";
26
+ }
27
+ }
28
+ export function buildPlan(reader, options) {
29
+ if (!reader.exists(TARGETS.packageJson)) {
30
+ throw new InitError(`No package.json found at ${options.cwd}. \`slowcook init\` expects to run in a TS/JS project root.`);
31
+ }
32
+ let pkg;
33
+ try {
34
+ pkg = JSON.parse(reader.read(TARGETS.packageJson));
35
+ }
36
+ catch (e) {
37
+ throw new InitError(`package.json is not valid JSON: ${e.message}`);
38
+ }
39
+ const detected = detectStack(pkg);
40
+ const warnings = [];
41
+ const actions = [];
42
+ const cliVersion = options.cliVersion ?? CLI_VERSION_FOR_TEMPLATES;
43
+ if (!detected.hasVitest) {
44
+ throw new InitError(`No test runner detected. slowcook 0.3 requires Vitest (found in devDependencies). ` +
45
+ `Install it with \`npm i -D vitest\` and re-run.`);
46
+ }
47
+ if (detected.hasPlaywright) {
48
+ warnings.push("Playwright detected. slowcook 0.3 doesn't yet implement Playwright discovery, " +
49
+ "so the e2e suite is intentionally omitted from stack.json. Add it back when " +
50
+ "playwright discovery ships in a later slowcook release.");
51
+ }
52
+ const tmplParams = {
53
+ owner: options.owner,
54
+ hasPlaywright: detected.hasPlaywright,
55
+ };
56
+ // 1. .brewing/frozen-paths.json
57
+ addSimpleFile(actions, reader, options.force, TARGETS.frozenPaths, frozenPathsJson());
58
+ // 2. .brewing/stack.json
59
+ addSimpleFile(actions, reader, options.force, TARGETS.stack, stackJson(tmplParams));
60
+ // 3. .brewing/README.md
61
+ addSimpleFile(actions, reader, options.force, TARGETS.brewingReadme, brewingReadme());
62
+ // 4. .brewing/manifests/.gitkeep (only if we're creating the dir)
63
+ if (!reader.exists(".brewing/manifests/")) {
64
+ actions.push({ kind: "create", path: TARGETS.manifestsGitkeep, contents: gitkeep() });
65
+ }
66
+ // 5. .github/workflows/slowcook.yml
67
+ addSimpleFile(actions, reader, options.force, TARGETS.workflow, slowcookWorkflow(cliVersion));
68
+ // 6. CODEOWNERS — special case (append if exists without our markers)
69
+ actions.push(planCodeowners(reader, options, tmplParams));
70
+ return { detected, actions, warnings };
71
+ }
72
+ function addSimpleFile(actions, reader, force, path, contents) {
73
+ if (!reader.exists(path)) {
74
+ actions.push({ kind: "create", path, contents });
75
+ return;
76
+ }
77
+ const existing = reader.read(path);
78
+ if (existing === contents) {
79
+ actions.push({ kind: "skip-exists", path, reason: "file already matches template" });
80
+ return;
81
+ }
82
+ if (force) {
83
+ actions.push({ kind: "overwrite", path, contents });
84
+ return;
85
+ }
86
+ actions.push({
87
+ kind: "skip-exists",
88
+ path,
89
+ reason: "file exists and differs from template (use --force to overwrite)",
90
+ });
91
+ }
92
+ function planCodeowners(reader, options, params) {
93
+ const path = TARGETS.codeowners;
94
+ if (!reader.exists(path)) {
95
+ return { kind: "create", path, contents: codeownersFullFile(params) };
96
+ }
97
+ const existing = reader.read(path);
98
+ if (existing.includes(SLOWCOOK_CODEOWNERS_MARKER_BEGIN) &&
99
+ existing.includes(SLOWCOOK_CODEOWNERS_MARKER_END)) {
100
+ if (options.force) {
101
+ // Replace the slowcook section inline
102
+ const replaced = replaceCodeownersSection(existing, params);
103
+ return { kind: "overwrite", path, contents: replaced };
104
+ }
105
+ return {
106
+ kind: "skip-exists",
107
+ path,
108
+ reason: "slowcook section already present (use --force to regenerate)",
109
+ };
110
+ }
111
+ // Existing CODEOWNERS without our markers → append
112
+ const toAppend = (existing.endsWith("\n") ? existing : existing + "\n") +
113
+ "\n" +
114
+ codeownersSection(params);
115
+ return {
116
+ kind: "append",
117
+ path,
118
+ contents: toAppend,
119
+ existingContents: existing,
120
+ };
121
+ }
122
+ function replaceCodeownersSection(existing, params) {
123
+ const begin = existing.indexOf(SLOWCOOK_CODEOWNERS_MARKER_BEGIN);
124
+ const endMarkerStart = existing.indexOf(SLOWCOOK_CODEOWNERS_MARKER_END);
125
+ if (begin === -1 || endMarkerStart === -1 || endMarkerStart < begin) {
126
+ // Shouldn't happen if caller verified markers exist, but be defensive
127
+ return existing + "\n" + codeownersSection(params);
128
+ }
129
+ const endMarkerEnd = endMarkerStart + SLOWCOOK_CODEOWNERS_MARKER_END.length;
130
+ // Include the trailing newline if present
131
+ const after = existing[endMarkerEnd] === "\n"
132
+ ? existing.slice(endMarkerEnd + 1)
133
+ : existing.slice(endMarkerEnd);
134
+ return existing.slice(0, begin) + codeownersSection(params) + after;
135
+ }
136
+ //# sourceMappingURL=plan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.js","sourceRoot":"","sources":["../../../src/commands/init/plan.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,6EAA6E;AAC7E,6EAA6E;AAE7E,OAAO,EACL,eAAe,EACf,SAAS,EACT,aAAa,EACb,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,EACjB,OAAO,EACP,yBAAyB,EACzB,gCAAgC,EAChC,8BAA8B,GAE/B,MAAM,gBAAgB,CAAC;AA4CxB,MAAM,OAAO,GAAG;IACd,WAAW,EAAE,4BAA4B;IACzC,KAAK,EAAE,qBAAqB;IAC5B,aAAa,EAAE,oBAAoB;IACnC,gBAAgB,EAAE,6BAA6B;IAC/C,QAAQ,EAAE,gCAAgC;IAC1C,UAAU,EAAE,YAAY;IACxB,WAAW,EAAE,cAAc;CAC5B,CAAC;AAEF,MAAM,UAAU,WAAW,CAAC,GAAgB;IAC1C,MAAM,IAAI,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,YAAY,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,eAAe,IAAI,EAAE,CAAC,EAAE,CAAC;IAC7E,OAAO;QACL,QAAQ,EAAE,YAAY;QACtB,SAAS,EAAE,QAAQ,IAAI,IAAI;QAC3B,aAAa,EAAE,kBAAkB,IAAI,IAAI,IAAI,YAAY,IAAI,IAAI;KAClE,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,SAAU,SAAQ,KAAK;IAClC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;IAC1B,CAAC;CACF;AAED,MAAM,UAAU,SAAS,CAAC,MAAkB,EAAE,OAAoB;IAChE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,SAAS,CACjB,4BAA4B,OAAO,CAAC,GAAG,6DAA6D,CACrG,CAAC;IACJ,CAAC;IACD,IAAI,GAAgB,CAAC;IACrB,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC;IACrD,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,IAAI,SAAS,CAAC,mCAAoC,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;IACjF,CAAC;IAED,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,OAAO,GAAiB,EAAE,CAAC;IACjC,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,yBAAyB,CAAC;IAEnE,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC;QACxB,MAAM,IAAI,SAAS,CACjB,oFAAoF;YAClF,iDAAiD,CACpD,CAAC;IACJ,CAAC;IAED,IAAI,QAAQ,CAAC,aAAa,EAAE,CAAC;QAC3B,QAAQ,CAAC,IAAI,CACX,gFAAgF;YAC9E,8EAA8E;YAC9E,yDAAyD,CAC5D,CAAC;IACJ,CAAC;IAED,MAAM,UAAU,GAAmB;QACjC,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,aAAa,EAAE,QAAQ,CAAC,aAAa;KACtC,CAAC;IAEF,gCAAgC;IAChC,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,WAAW,EAAE,eAAe,EAAE,CAAC,CAAC;IAEtF,yBAAyB;IACzB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC;IAEpF,wBAAwB;IACxB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,aAAa,EAAE,aAAa,EAAE,CAAC,CAAC;IAEtF,kEAAkE;IAClE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,qBAAqB,CAAC,EAAE,CAAC;QAC1C,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,gBAAgB,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;IACxF,CAAC;IAED,oCAAoC;IACpC,aAAa,CACX,OAAO,EACP,MAAM,EACN,OAAO,CAAC,KAAK,EACb,OAAO,CAAC,QAAQ,EAChB,gBAAgB,CAAC,UAAU,CAAC,CAC7B,CAAC;IAEF,sEAAsE;IACtE,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC;IAE1D,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;AACzC,CAAC;AAED,SAAS,aAAa,CACpB,OAAqB,EACrB,MAAkB,EAClB,KAAc,EACd,IAAY,EACZ,QAAgB;IAEhB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QACzB,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;QACjD,OAAO;IACT,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnC,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC1B,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE,+BAA+B,EAAE,CAAC,CAAC;QACrF,OAAO;IACT,CAAC;IACD,IAAI,KAAK,EAAE,CAAC;QACV,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;QACpD,OAAO;IACT,CAAC;IACD,OAAO,CAAC,IAAI,CAAC;QACX,IAAI,EAAE,aAAa;QACnB,IAAI;QACJ,MAAM,EAAE,kEAAkE;KAC3E,CAAC,CAAC;AACL,CAAC;AAED,SAAS,cAAc,CACrB,MAAkB,EAClB,OAAoB,EACpB,MAAsB;IAEtB,MAAM,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC;IAChC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,kBAAkB,CAAC,MAAM,CAAC,EAAE,CAAC;IACxE,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnC,IACE,QAAQ,CAAC,QAAQ,CAAC,gCAAgC,CAAC;QACnD,QAAQ,CAAC,QAAQ,CAAC,8BAA8B,CAAC,EACjD,CAAC;QACD,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;YAClB,sCAAsC;YACtC,MAAM,QAAQ,GAAG,wBAAwB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YAC5D,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;QACzD,CAAC;QACD,OAAO;YACL,IAAI,EAAE,aAAa;YACnB,IAAI;YACJ,MAAM,EAAE,8DAA8D;SACvE,CAAC;IACJ,CAAC;IACD,mDAAmD;IACnD,MAAM,QAAQ,GACZ,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,GAAG,IAAI,CAAC;QACtD,IAAI;QACJ,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC5B,OAAO;QACL,IAAI,EAAE,QAAQ;QACd,IAAI;QACJ,QAAQ,EAAE,QAAQ;QAClB,gBAAgB,EAAE,QAAQ;KAC3B,CAAC;AACJ,CAAC;AAED,SAAS,wBAAwB,CAAC,QAAgB,EAAE,MAAsB;IACxE,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,gCAAgC,CAAC,CAAC;IACjE,MAAM,cAAc,GAAG,QAAQ,CAAC,OAAO,CAAC,8BAA8B,CAAC,CAAC;IACxE,IAAI,KAAK,KAAK,CAAC,CAAC,IAAI,cAAc,KAAK,CAAC,CAAC,IAAI,cAAc,GAAG,KAAK,EAAE,CAAC;QACpE,sEAAsE;QACtE,OAAO,QAAQ,GAAG,IAAI,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;IACrD,CAAC;IACD,MAAM,YAAY,GAAG,cAAc,GAAG,8BAA8B,CAAC,MAAM,CAAC;IAC5E,0CAA0C;IAC1C,MAAM,KAAK,GACT,QAAQ,CAAC,YAAY,CAAC,KAAK,IAAI;QAC7B,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,YAAY,GAAG,CAAC,CAAC;QAClC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IACnC,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,iBAAiB,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC;AACtE,CAAC"}
@@ -0,0 +1,17 @@
1
+ export declare const CLI_VERSION_FOR_TEMPLATES = "0.3.0";
2
+ export interface TemplateParams {
3
+ /** CODEOWNERS handle or team (e.g. "@aminazar" or "@acme/frontend"). */
4
+ owner: string;
5
+ /** Whether the project has Playwright installed (affects stack.json comments). */
6
+ hasPlaywright: boolean;
7
+ }
8
+ export declare const SLOWCOOK_CODEOWNERS_MARKER_BEGIN = "# --- slowcook:frozen-paths BEGIN ---";
9
+ export declare const SLOWCOOK_CODEOWNERS_MARKER_END = "# --- slowcook:frozen-paths END ---";
10
+ export declare function frozenPathsJson(): string;
11
+ export declare function stackJson(params: TemplateParams): string;
12
+ export declare function brewingReadme(): string;
13
+ export declare function slowcookWorkflow(cliVersion: string): string;
14
+ export declare function codeownersSection(params: TemplateParams): string;
15
+ export declare function codeownersFullFile(params: TemplateParams): string;
16
+ export declare function gitkeep(): string;
17
+ //# sourceMappingURL=templates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../../src/commands/init/templates.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,yBAAyB,UAAU,CAAC;AAEjD,MAAM,WAAW,cAAc;IAC7B,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAC;IACd,kFAAkF;IAClF,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,eAAO,MAAM,gCAAgC,0CAA0C,CAAC;AACxF,eAAO,MAAM,8BAA8B,wCAAwC,CAAC;AAEpF,wBAAgB,eAAe,IAAI,MAAM,CAiCxC;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAkCxD;AAED,wBAAgB,aAAa,IAAI,MAAM,CA+BtC;AAED,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CA2C3D;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAchE;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAQjE;AAED,wBAAgB,OAAO,IAAI,MAAM,CAEhC"}
@@ -0,0 +1,163 @@
1
+ // Static and parameterized file contents written by `slowcook init`.
2
+ // Version is bumped in lockstep with the CLI package.
3
+ export const CLI_VERSION_FOR_TEMPLATES = "0.3.0";
4
+ export const SLOWCOOK_CODEOWNERS_MARKER_BEGIN = "# --- slowcook:frozen-paths BEGIN ---";
5
+ export const SLOWCOOK_CODEOWNERS_MARKER_END = "# --- slowcook:frozen-paths END ---";
6
+ export function frozenPathsJson() {
7
+ return (JSON.stringify({
8
+ $schema: "./frozen-paths.schema.json",
9
+ $doc: "Paths frozen by slowcook. See https://github.com/aminazar/slowcook for the design. " +
10
+ "To modify any of these: either get CODEOWNERS approval, or add the 'override-freeze' label " +
11
+ "to the PR (guard runs in advisory mode, audit trail preserved).",
12
+ directories: [
13
+ "tests/",
14
+ "tests-fixtures/",
15
+ "tests-helpers/",
16
+ ".brewing/manifests/",
17
+ ],
18
+ files: [
19
+ "vitest.config.ts",
20
+ "vitest.config.mjs",
21
+ "vitest.config.js",
22
+ ".brewing/frozen-paths.json",
23
+ ".brewing/stack.json",
24
+ ".github/workflows/slowcook.yml",
25
+ ],
26
+ partial: {
27
+ "package.json": {
28
+ frozen_key_paths: ["scripts.test", "scripts.test:watch"],
29
+ },
30
+ },
31
+ }, null, 2) + "\n");
32
+ }
33
+ export function stackJson(params) {
34
+ const doc = "Project-level stack configuration consumed by slowcook (@slowcook-ai/stack-ts). " +
35
+ "Tells the harness how to discover and run tests. Only include suites that are " +
36
+ "actually runnable — slowcook refuses to record an incomplete manifest." +
37
+ (params.hasPlaywright
38
+ ? " (Playwright detected in package.json; slowcook's playwright discovery is not yet " +
39
+ "implemented, so the e2e suite is intentionally omitted. Add it back post-upgrade.)"
40
+ : "");
41
+ return (JSON.stringify({
42
+ $schema: "./stack.schema.json",
43
+ $doc: doc,
44
+ language: "typescript",
45
+ package_manager: "npm",
46
+ test: {
47
+ backend: {
48
+ runner: "vitest",
49
+ run_command: "npx vitest run",
50
+ discover_command: "npx vitest list",
51
+ reporter_format: "vitest-list-lines",
52
+ },
53
+ },
54
+ lint: {
55
+ lint_command: "npm run lint",
56
+ typecheck_command: "npm run typecheck",
57
+ },
58
+ }, null, 2) + "\n");
59
+ }
60
+ export function brewingReadme() {
61
+ return `# \`.brewing/\`
62
+
63
+ Consumer-side configuration for [slowcook](https://github.com/aminazar/slowcook), a TDD-first agentic development harness.
64
+
65
+ ## Contents
66
+
67
+ | Path | Purpose |
68
+ |---|---|
69
+ | \`frozen-paths.json\` | What's immutable during brewing (tests, configs, manifests) |
70
+ | \`stack.json\` | How slowcook invokes tests / coverage / lint for this project |
71
+ | \`manifests/\` | Per-story test manifests; populated by \`slowcook manifest record\` |
72
+
73
+ ## Running slowcook locally
74
+
75
+ \`\`\`bash
76
+ npx --yes @slowcook-ai/cli@latest guard --base origin/main --head HEAD
77
+ npx --yes @slowcook-ai/cli@latest manifest record
78
+ npx --yes @slowcook-ai/cli@latest manifest verify
79
+ \`\`\`
80
+
81
+ ## When you legitimately need to modify a frozen path
82
+
83
+ 1. Open a PR with the change.
84
+ 2. Add the \`override-freeze\` label to the PR.
85
+ 3. Guard runs in advisory mode (surfaces violations but doesn't fail).
86
+ 4. CODEOWNERS still requires explicit approval.
87
+ 5. Merge audit trail: PR number + \`override-freeze\` label + approval.
88
+
89
+ Deliberately slightly inconvenient. Frozen-path changes are rare events that deserve a reviewer's eyes.
90
+ `;
91
+ }
92
+ export function slowcookWorkflow(cliVersion) {
93
+ return `name: slowcook
94
+
95
+ on:
96
+ pull_request:
97
+ types: [opened, synchronize, reopened, labeled, unlabeled]
98
+
99
+ concurrency:
100
+ group: slowcook-\${{ github.event.pull_request.number }}
101
+ cancel-in-progress: true
102
+
103
+ # Pin CLI version for reproducibility; bump deliberately via a PR.
104
+ env:
105
+ SLOWCOOK_CLI: "@slowcook-ai/cli@${cliVersion}"
106
+
107
+ jobs:
108
+ check:
109
+ name: slowcook checks
110
+ runs-on: ubuntu-latest
111
+ steps:
112
+ - uses: actions/checkout@v4
113
+ with:
114
+ fetch-depth: 0
115
+
116
+ - uses: actions/setup-node@v4
117
+ with:
118
+ node-version: 20
119
+
120
+ - name: Guard — frozen paths
121
+ env:
122
+ HAS_OVERRIDE: \${{ contains(github.event.pull_request.labels.*.name, 'override-freeze') }}
123
+ run: |
124
+ set -eu
125
+ ARGS="--base origin/\${{ github.base_ref }} --head HEAD"
126
+ if [ "$HAS_OVERRIDE" = "true" ]; then
127
+ ARGS="$ARGS --override"
128
+ echo "::notice::'override-freeze' label present — guard runs in advisory mode."
129
+ fi
130
+ npx --yes "$SLOWCOOK_CLI" guard $ARGS
131
+
132
+ - name: Manifest — verify discoverable tests
133
+ run: npx --yes "$SLOWCOOK_CLI" manifest verify
134
+ `;
135
+ }
136
+ export function codeownersSection(params) {
137
+ return `${SLOWCOOK_CODEOWNERS_MARKER_BEGIN}
138
+ # Paths frozen by slowcook. Agent-authored PRs cannot modify them;
139
+ # human edits must be reviewed. See https://github.com/aminazar/slowcook.
140
+
141
+ /tests/ ${params.owner}
142
+ /tests-fixtures/ ${params.owner}
143
+ /tests-helpers/ ${params.owner}
144
+ /vitest.config.* ${params.owner}
145
+ /.brewing/ ${params.owner}
146
+ /.github/workflows/slowcook.yml ${params.owner}
147
+ /CODEOWNERS ${params.owner}
148
+ ${SLOWCOOK_CODEOWNERS_MARKER_END}
149
+ `;
150
+ }
151
+ export function codeownersFullFile(params) {
152
+ // For repos that don't have CODEOWNERS yet — prepend a short header.
153
+ return `# CODEOWNERS
154
+ #
155
+ # Generated by \`slowcook init\`. The slowcook-managed section is between
156
+ # the marker comments; edit outside those markers freely.
157
+
158
+ ${codeownersSection(params)}`;
159
+ }
160
+ export function gitkeep() {
161
+ return "";
162
+ }
163
+ //# sourceMappingURL=templates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templates.js","sourceRoot":"","sources":["../../../src/commands/init/templates.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,sDAAsD;AAEtD,MAAM,CAAC,MAAM,yBAAyB,GAAG,OAAO,CAAC;AASjD,MAAM,CAAC,MAAM,gCAAgC,GAAG,uCAAuC,CAAC;AACxF,MAAM,CAAC,MAAM,8BAA8B,GAAG,qCAAqC,CAAC;AAEpF,MAAM,UAAU,eAAe;IAC7B,OAAO,CACL,IAAI,CAAC,SAAS,CACZ;QACE,OAAO,EAAE,4BAA4B;QACrC,IAAI,EACF,qFAAqF;YACrF,6FAA6F;YAC7F,iEAAiE;QACnE,WAAW,EAAE;YACX,QAAQ;YACR,iBAAiB;YACjB,gBAAgB;YAChB,qBAAqB;SACtB;QACD,KAAK,EAAE;YACL,kBAAkB;YAClB,mBAAmB;YACnB,kBAAkB;YAClB,4BAA4B;YAC5B,qBAAqB;YACrB,gCAAgC;SACjC;QACD,OAAO,EAAE;YACP,cAAc,EAAE;gBACd,gBAAgB,EAAE,CAAC,cAAc,EAAE,oBAAoB,CAAC;aACzD;SACF;KACF,EACD,IAAI,EACJ,CAAC,CACF,GAAG,IAAI,CACT,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,MAAsB;IAC9C,MAAM,GAAG,GACP,kFAAkF;QAClF,gFAAgF;QAChF,wEAAwE;QACxE,CAAC,MAAM,CAAC,aAAa;YACnB,CAAC,CAAC,oFAAoF;gBACpF,oFAAoF;YACtF,CAAC,CAAC,EAAE,CAAC,CAAC;IAEV,OAAO,CACL,IAAI,CAAC,SAAS,CACZ;QACE,OAAO,EAAE,qBAAqB;QAC9B,IAAI,EAAE,GAAG;QACT,QAAQ,EAAE,YAAY;QACtB,eAAe,EAAE,KAAK;QACtB,IAAI,EAAE;YACJ,OAAO,EAAE;gBACP,MAAM,EAAE,QAAQ;gBAChB,WAAW,EAAE,gBAAgB;gBAC7B,gBAAgB,EAAE,iBAAiB;gBACnC,eAAe,EAAE,mBAAmB;aACrC;SACF;QACD,IAAI,EAAE;YACJ,YAAY,EAAE,cAAc;YAC5B,iBAAiB,EAAE,mBAAmB;SACvC;KACF,EACD,IAAI,EACJ,CAAC,CACF,GAAG,IAAI,CACT,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BR,CAAC;AACF,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,UAAkB;IACjD,OAAO;;;;;;;;;;;;oCAY2B,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6B7C,CAAC;AACF,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,MAAsB;IACtD,OAAO,GAAG,gCAAgC;;;;kCAIV,MAAM,CAAC,KAAK;kCACZ,MAAM,CAAC,KAAK;kCACZ,MAAM,CAAC,KAAK;kCACZ,MAAM,CAAC,KAAK;kCACZ,MAAM,CAAC,KAAK;kCACZ,MAAM,CAAC,KAAK;kCACZ,MAAM,CAAC,KAAK;EAC5C,8BAA8B;CAC/B,CAAC;AACF,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,MAAsB;IACvD,qEAAqE;IACrE,OAAO;;;;;EAKP,iBAAiB,CAAC,MAAM,CAAC,EAAE,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,OAAO;IACrB,OAAO,EAAE,CAAC;AACZ,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function manifest(argv: string[]): Promise<void>;
2
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/commands/manifest.ts"],"names":[],"mappings":"AA0IA,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ5D"}
@@ -0,0 +1,189 @@
1
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, appendFileSync, } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ import { buildManifest, diffManifest, } from "@slowcook-ai/core";
4
+ import { discoverTests, validateStackConfig, } from "@slowcook-ai/stack-ts";
5
+ const CLI_VERSION = "0.2.0";
6
+ function defaultManifestPath(storyId) {
7
+ return storyId
8
+ ? `.brewing/manifests/story-${storyId}.json`
9
+ : `.brewing/manifests/all.json`;
10
+ }
11
+ function parseArgs(argv) {
12
+ const sub = argv[0];
13
+ if (sub !== "record" && sub !== "verify") {
14
+ printHelp();
15
+ process.exit(64);
16
+ }
17
+ const args = {
18
+ subcommand: sub,
19
+ stackConfig: ".brewing/stack.json",
20
+ manifestPath: "",
21
+ storyId: null,
22
+ cwd: process.cwd(),
23
+ };
24
+ for (let i = 1; i < argv.length; i++) {
25
+ const arg = argv[i];
26
+ const next = argv[i + 1];
27
+ if (arg === "--stack-config" && next) {
28
+ args.stackConfig = next;
29
+ i++;
30
+ }
31
+ else if (arg === "--manifest" && next) {
32
+ args.manifestPath = next;
33
+ i++;
34
+ }
35
+ else if (arg === "--story" && next) {
36
+ args.storyId = next;
37
+ i++;
38
+ }
39
+ else if (arg === "--cwd" && next) {
40
+ args.cwd = next;
41
+ i++;
42
+ }
43
+ else if (arg === "--help" || arg === "-h") {
44
+ printHelp();
45
+ process.exit(0);
46
+ }
47
+ }
48
+ if (!args.manifestPath) {
49
+ args.manifestPath = defaultManifestPath(args.storyId);
50
+ }
51
+ return args;
52
+ }
53
+ function printHelp() {
54
+ console.log(`
55
+ slowcook manifest — record or verify the set of discoverable tests
56
+
57
+ Usage:
58
+ slowcook manifest record [options]
59
+ slowcook manifest verify [options]
60
+
61
+ Common options:
62
+ --stack-config <path> Path to stack.json (default: .brewing/stack.json)
63
+ --manifest <path> Path to write/read manifest JSON
64
+ (default: .brewing/manifests/all.json, or
65
+ .brewing/manifests/story-<id>.json if --story set)
66
+ --story <id> Tag manifest with this story id
67
+ --cwd <path> Working directory for discovery commands (default: .)
68
+ --help, -h Show this help
69
+
70
+ record:
71
+ Runs every suite's discover_command, writes a manifest capturing the set
72
+ of tests that exist right now. Meant for human-invoked freezing after a
73
+ story's tests are approved.
74
+
75
+ verify:
76
+ Re-runs discovery and compares against the recorded manifest. Exits 1 if
77
+ any recorded test is no longer discoverable (file deleted, renamed, or
78
+ broken). Newly-discovered tests are informational only.
79
+
80
+ Exit codes:
81
+ 0 = verify: manifest matches; record: manifest written
82
+ 1 = verify: missing tests detected
83
+ 2 = script error (missing config, exec failure, parse error)
84
+ `);
85
+ }
86
+ function sh(stackConfigPath, cwd) {
87
+ let raw;
88
+ try {
89
+ raw = JSON.parse(readFileSync(stackConfigPath, "utf8"));
90
+ }
91
+ catch (e) {
92
+ console.error(`Could not read stack config at ${stackConfigPath}: ${e.message}`);
93
+ process.exit(2);
94
+ }
95
+ try {
96
+ return validateStackConfig(raw);
97
+ }
98
+ catch (e) {
99
+ console.error(`Invalid stack config: ${e.message}`);
100
+ process.exit(2);
101
+ }
102
+ // unreachable — the two process.exit calls above terminate
103
+ }
104
+ function appendGhSummary(md) {
105
+ const summary = process.env["GITHUB_STEP_SUMMARY"];
106
+ if (!summary)
107
+ return;
108
+ try {
109
+ appendFileSync(summary, md);
110
+ }
111
+ catch {
112
+ // best effort
113
+ }
114
+ }
115
+ export async function manifest(argv) {
116
+ const args = parseArgs(argv);
117
+ const config = sh(args.stackConfig, args.cwd);
118
+ if (args.subcommand === "record") {
119
+ return recordManifest(args, config);
120
+ }
121
+ return verifyManifest(args, config);
122
+ }
123
+ function recordManifest(args, config) {
124
+ const { tests, suites, errors } = discoverTests(config, { cwd: args.cwd });
125
+ if (errors.length > 0) {
126
+ console.error("Discovery errors (refusing to record an incomplete manifest):");
127
+ for (const err of errors) {
128
+ console.error(` [${err.suite}] ${err.message}`);
129
+ }
130
+ process.exit(2);
131
+ }
132
+ const m = buildManifest({
133
+ slowcookVersion: CLI_VERSION,
134
+ storyId: args.storyId,
135
+ tests,
136
+ suites,
137
+ });
138
+ // Ensure directory exists
139
+ const dir = dirname(args.manifestPath);
140
+ if (!existsSync(dir))
141
+ mkdirSync(dir, { recursive: true });
142
+ writeFileSync(args.manifestPath, JSON.stringify(m, null, 2) + "\n", "utf8");
143
+ console.log(`Recorded ${m.tests.length} test(s) across ${m.suites.length} suite(s) → ${args.manifestPath}`);
144
+ for (const s of m.suites) {
145
+ console.log(` [${s.suite}] ${s.test_count} tests`);
146
+ }
147
+ appendGhSummary(`### Manifest recorded\n\n- ${m.tests.length} tests across ${m.suites.length} suites\n- Written to \`${args.manifestPath}\`\n`);
148
+ }
149
+ function verifyManifest(args, config) {
150
+ let manifest;
151
+ try {
152
+ manifest = JSON.parse(readFileSync(args.manifestPath, "utf8"));
153
+ }
154
+ catch (e) {
155
+ console.error(`Could not read manifest at ${args.manifestPath}: ${e.message}`);
156
+ process.exit(2);
157
+ }
158
+ const { tests, errors } = discoverTests(config, { cwd: args.cwd });
159
+ if (errors.length > 0) {
160
+ console.error("Discovery errors during verify:");
161
+ for (const err of errors) {
162
+ console.error(` [${err.suite}] ${err.message}`);
163
+ }
164
+ // Discovery errors mean we can't trust the current snapshot — exit 2.
165
+ process.exit(2);
166
+ }
167
+ const diff = diffManifest(manifest, tests);
168
+ if (diff.missing.length === 0) {
169
+ const msg = `Manifest verified: all ${manifest.tests.length} recorded tests still discoverable.${diff.added.length ? ` (${diff.added.length} new tests since record — informational.)` : ""}`;
170
+ console.log(msg);
171
+ appendGhSummary(`### Manifest verify\n\n✅ ${msg}\n`);
172
+ if (diff.added.length) {
173
+ console.log(`\nNew tests since record (informational):`);
174
+ for (const t of diff.added)
175
+ console.log(` + ${t.id}`);
176
+ }
177
+ process.exit(0);
178
+ }
179
+ console.error(`Manifest verify FAILED: ${diff.missing.length} recorded test(s) no longer discoverable.`);
180
+ for (const t of diff.missing) {
181
+ console.error(` - ${t.id}`);
182
+ console.log(`::error file=${t.file}::Manifest verify — test missing from current discovery: ${t.id}`);
183
+ }
184
+ appendGhSummary(`### Manifest verify\n\n❌ ${diff.missing.length} recorded test(s) missing from current discovery.\n\n${diff.missing
185
+ .map((t) => `- \`${t.id}\``)
186
+ .join("\n")}\n`);
187
+ process.exit(1);
188
+ }
189
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/commands/manifest.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.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "CLI for the slowcook brewing harness",
5
5
  "license": "MIT",
6
6
  "author": "aminazar",
@@ -33,7 +33,8 @@
33
33
  "README.md"
34
34
  ],
35
35
  "dependencies": {
36
- "@slowcook-ai/core": "^0.1.0"
36
+ "@slowcook-ai/core": "^0.2.0",
37
+ "@slowcook-ai/stack-ts": "^0.2.0"
37
38
  },
38
39
  "publishConfig": {
39
40
  "access": "public"