@slowcook-ai/cli 0.4.3 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/cli.js +19 -1
  2. package/dist/cli.js.map +1 -1
  3. package/dist/commands/init/plan.d.ts.map +1 -1
  4. package/dist/commands/init/plan.js +4 -1
  5. package/dist/commands/init/plan.js.map +1 -1
  6. package/dist/commands/init/templates.d.ts +2 -1
  7. package/dist/commands/init/templates.d.ts.map +1 -1
  8. package/dist/commands/init/templates.js +61 -1
  9. package/dist/commands/init/templates.js.map +1 -1
  10. package/dist/commands/refine/agent.d.ts.map +1 -1
  11. package/dist/commands/refine/agent.js +53 -18
  12. package/dist/commands/refine/agent.js.map +1 -1
  13. package/dist/commands/refine/context.d.ts +17 -0
  14. package/dist/commands/refine/context.d.ts.map +1 -0
  15. package/dist/commands/refine/context.js +69 -0
  16. package/dist/commands/refine/context.js.map +1 -0
  17. package/dist/commands/refine/prompts.d.ts +1 -1
  18. package/dist/commands/refine/prompts.d.ts.map +1 -1
  19. package/dist/commands/refine/prompts.js +8 -1
  20. package/dist/commands/refine/prompts.js.map +1 -1
  21. package/dist/commands/refine/spec-yaml.d.ts +13 -3
  22. package/dist/commands/refine/spec-yaml.d.ts.map +1 -1
  23. package/dist/commands/refine/spec-yaml.js +31 -5
  24. package/dist/commands/refine/spec-yaml.js.map +1 -1
  25. package/dist/commands/testgen/agent.d.ts +55 -0
  26. package/dist/commands/testgen/agent.d.ts.map +1 -0
  27. package/dist/commands/testgen/agent.js +317 -0
  28. package/dist/commands/testgen/agent.js.map +1 -0
  29. package/dist/commands/testgen/index.d.ts +2 -0
  30. package/dist/commands/testgen/index.d.ts.map +1 -0
  31. package/dist/commands/testgen/index.js +173 -0
  32. package/dist/commands/testgen/index.js.map +1 -0
  33. package/dist/commands/testgen/prompts.d.ts +12 -0
  34. package/dist/commands/testgen/prompts.d.ts.map +1 -0
  35. package/dist/commands/testgen/prompts.js +54 -0
  36. package/dist/commands/testgen/prompts.js.map +1 -0
  37. package/package.json +4 -4
@@ -98,12 +98,38 @@ export function listActiveSpecs(repoRoot) {
98
98
  }
99
99
  return specs;
100
100
  }
101
- /** Allocate the next unused story ID (zero-padded 3 digits). */
102
- export function nextStoryId(repoRoot) {
101
+ /**
102
+ * Allocate the next unused story ID (zero-padded 3 digits).
103
+ *
104
+ * Considers BOTH the local index (specs/_index.yaml on the checked-out branch)
105
+ * AND remote branches matching `slowcook/spec/story-*`. This prevents story-ID
106
+ * collisions when a spec PR is in flight (branch exists on remote but index on
107
+ * main doesn't reflect it yet) or when two refinement runs overlap.
108
+ *
109
+ * `forge` is optional so callers without a forge (e.g., local tooling) can still
110
+ * use the function — they just get a weaker guarantee (index-only).
111
+ */
112
+ export async function nextStoryId(repoRoot, forge) {
103
113
  const index = readIndex(repoRoot);
104
- const existing = Object.keys(index.stories).map((id) => parseInt(id, 10)).filter((n) => !isNaN(n));
105
- const max = existing.length > 0 ? Math.max(...existing) : 0;
106
- return String(max + 1).padStart(3, "0");
114
+ const fromIndex = Object.keys(index.stories)
115
+ .map((id) => parseInt(id, 10))
116
+ .filter((n) => !isNaN(n));
117
+ let fromBranches = [];
118
+ if (forge) {
119
+ try {
120
+ const branches = await forge.listBranchesMatching("slowcook/spec/story-");
121
+ fromBranches = branches
122
+ .map((b) => b.replace(/^slowcook\/spec\/story-/, ""))
123
+ .map((id) => parseInt(id, 10))
124
+ .filter((n) => !isNaN(n));
125
+ }
126
+ catch {
127
+ // Best-effort: if the listing fails (rate limit, etc.), fall back to
128
+ // index-only. Collision risk returns but behaviour stays sane.
129
+ }
130
+ }
131
+ const all = [...fromIndex, ...fromBranches, 0];
132
+ return String(Math.max(...all) + 1).padStart(3, "0");
107
133
  }
108
134
  /** Public accessors for tests. */
109
135
  export const schemas = {
@@ -1 +1 @@
1
- {"version":3,"file":"spec-yaml.js","sourceRoot":"","sources":["../../../src/commands/refine/spec-yaml.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"spec-yaml.js","sourceRoot":"","sources":["../../../src/commands/refine/spec-yaml.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ import type { ForgeAdapter } from "@slowcook-ai/core";
2
+ import type { LlmClient } from "../refine/llm.js";
3
+ export declare const LABEL_TESTS_READY = "tests-ready";
4
+ export declare const LABEL_OVERRIDE_FREEZE = "override-freeze";
5
+ export declare const TESTS_INTEGRATION_DIR = "tests/integration";
6
+ export declare const MANIFESTS_DIR = ".brewing/manifests";
7
+ export interface TestgenContext {
8
+ repoRoot: string;
9
+ forge: ForgeAdapter;
10
+ llm: LlmClient;
11
+ model: string;
12
+ cliVersion: string;
13
+ baseBranch: string;
14
+ /** When true, generate tests for every active spec that lacks them (CI path). */
15
+ all: boolean;
16
+ /** When set, generate tests for this spec ID only (ops path / re-runs). */
17
+ specId: string | null;
18
+ /** Idempotent base branch name. */
19
+ branchName: string;
20
+ /** Injectable now for tests. */
21
+ now: Date;
22
+ }
23
+ export type TestgenOutcome = {
24
+ kind: "tests-emitted";
25
+ storyIds: string[];
26
+ prUrl: string;
27
+ prNumber: number;
28
+ removedStoryIds: string[];
29
+ } | {
30
+ kind: "nothing-to-generate";
31
+ reason: string;
32
+ } | {
33
+ kind: "pr-creation-blocked";
34
+ storyIds: string[];
35
+ branchName: string;
36
+ };
37
+ /**
38
+ * Main entry: for every spec in scope, if tests don't exist yet, generate them.
39
+ * If the spec supersedes prior stories, remove the old tests + manifests for
40
+ * those stories as part of the same PR (auto-applies `override-freeze` label,
41
+ * citing the supersede chain in the PR body for auditability).
42
+ */
43
+ export declare function runTestgen(ctx: TestgenContext): Promise<TestgenOutcome>;
44
+ /**
45
+ * Parse the emitted TS file and pull out test IDs in the format
46
+ * <file> > <describe-chain> > <test-name>
47
+ * matching what `vitest list` would emit.
48
+ *
49
+ * This is a lightweight text-level extraction — we parse for `describe(` and
50
+ * `it(` invocations and their string literal arguments, tracking nesting.
51
+ * Robust against typical usage; falls back to a single synthetic entry if
52
+ * nothing recognisable is found.
53
+ */
54
+ export declare function extractTestIdsFromFile(filePath: string, source: string): string[];
55
+ //# sourceMappingURL=agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../../src/commands/testgen/agent.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,YAAY,EAAQ,MAAM,mBAAmB,CAAC;AAC5D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAQlD,eAAO,MAAM,iBAAiB,gBAAgB,CAAC;AAC/C,eAAO,MAAM,qBAAqB,oBAAoB,CAAC;AAEvD,eAAO,MAAM,qBAAqB,sBAAsB,CAAC;AACzD,eAAO,MAAM,aAAa,uBAAuB,CAAC;AAElD,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,YAAY,CAAC;IACpB,GAAG,EAAE,SAAS,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,iFAAiF;IACjF,GAAG,EAAE,OAAO,CAAC;IACb,2EAA2E;IAC3E,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,mCAAmC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,gCAAgC;IAChC,GAAG,EAAE,IAAI,CAAC;CACX;AAED,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,EAAE,CAAA;CAAE,GACzG;IAAE,IAAI,EAAE,qBAAqB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,qBAAqB,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5E;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CAgG7E;AA2FD;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAyCjF"}
@@ -0,0 +1,317 @@
1
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync, readdirSync, } from "node:fs";
2
+ import { join, dirname } from "node:path";
3
+ import YAML from "yaml";
4
+ import { buildManifest, } from "@slowcook-ai/core";
5
+ import { readIndex, readSpec } from "../refine/spec-yaml.js";
6
+ import { TESTGEN_SYSTEM } from "./prompts.js";
7
+ export const LABEL_TESTS_READY = "tests-ready";
8
+ export const LABEL_OVERRIDE_FREEZE = "override-freeze";
9
+ export const TESTS_INTEGRATION_DIR = "tests/integration";
10
+ export const MANIFESTS_DIR = ".brewing/manifests";
11
+ /**
12
+ * Main entry: for every spec in scope, if tests don't exist yet, generate them.
13
+ * If the spec supersedes prior stories, remove the old tests + manifests for
14
+ * those stories as part of the same PR (auto-applies `override-freeze` label,
15
+ * citing the supersede chain in the PR body for auditability).
16
+ */
17
+ export async function runTestgen(ctx) {
18
+ const specs = collectTargetSpecs(ctx);
19
+ if (specs.length === 0) {
20
+ return { kind: "nothing-to-generate", reason: "no active specs without tests" };
21
+ }
22
+ // Produce per-spec artifacts in a scratch area; commit together at the end.
23
+ const generated = [];
24
+ const toRemove = [];
25
+ for (const spec of specs) {
26
+ const projectContext = buildProjectContext(ctx.repoRoot);
27
+ const fileContents = await generateTestFile(spec, ctx, projectContext);
28
+ const testPath = join(TESTS_INTEGRATION_DIR, `story-${spec.story_id}.test.ts`);
29
+ const manifestIds = extractTestIdsFromFile(testPath, fileContents);
30
+ const manifest = buildManifest({
31
+ slowcookVersion: ctx.cliVersion,
32
+ storyId: spec.story_id,
33
+ tests: manifestIds.map((id) => ({ id, file: testPath })),
34
+ suites: [{ suite: "backend", command: "npx vitest list", test_count: manifestIds.length }],
35
+ now: ctx.now,
36
+ });
37
+ generated.push({ spec, testPath, fileContents, manifest });
38
+ for (const superseded of spec.supersedes) {
39
+ toRemove.push(superseded);
40
+ }
41
+ }
42
+ // Apply to disk: write new, delete superseded
43
+ for (const g of generated) {
44
+ writeFileAt(ctx.repoRoot, g.testPath, g.fileContents);
45
+ const manifestPath = join(MANIFESTS_DIR, `story-${g.spec.story_id}.json`);
46
+ writeFileAt(ctx.repoRoot, manifestPath, JSON.stringify(g.manifest, null, 2) + "\n");
47
+ }
48
+ const actuallyRemoved = [];
49
+ for (const id of toRemove) {
50
+ const removed = removeIfExists(ctx.repoRoot, [
51
+ join(TESTS_INTEGRATION_DIR, `story-${id}.test.ts`),
52
+ join(MANIFESTS_DIR, `story-${id}.json`),
53
+ ]);
54
+ if (removed > 0)
55
+ actuallyRemoved.push(id);
56
+ }
57
+ // Git: branch, stage, commit, push
58
+ await ctx.forge.git.createBranch(ctx.branchName);
59
+ for (const g of generated) {
60
+ await ctx.forge.git.stage(g.testPath);
61
+ await ctx.forge.git.stage(join(MANIFESTS_DIR, `story-${g.spec.story_id}.json`));
62
+ }
63
+ for (const id of actuallyRemoved) {
64
+ await ctx.forge.git.stage(join(TESTS_INTEGRATION_DIR, `story-${id}.test.ts`));
65
+ await ctx.forge.git.stage(join(MANIFESTS_DIR, `story-${id}.json`));
66
+ }
67
+ const storyIds = generated.map((g) => g.spec.story_id);
68
+ await ctx.forge.git.commit(`slowcook: tests for ${storyIds.map((s) => `story-${s}`).join(", ")}` +
69
+ (actuallyRemoved.length > 0
70
+ ? `\n\nRemoves tests for superseded: ${actuallyRemoved.map((s) => `story-${s}`).join(", ")}`
71
+ : ""));
72
+ await ctx.forge.git.push(ctx.branchName);
73
+ // PR
74
+ const labels = ["slowcook-tests"];
75
+ if (actuallyRemoved.length > 0) {
76
+ labels.push(LABEL_OVERRIDE_FREEZE);
77
+ }
78
+ try {
79
+ const pr = await ctx.forge.createPullRequest({
80
+ title: `tests: ${storyIds.map((s) => `story-${s}`).join(", ")}`,
81
+ body: buildPrBody({ generated, removedStoryIds: actuallyRemoved }),
82
+ head: ctx.branchName,
83
+ base: ctx.baseBranch,
84
+ draft: true,
85
+ labels,
86
+ });
87
+ return {
88
+ kind: "tests-emitted",
89
+ storyIds,
90
+ prUrl: pr.url,
91
+ prNumber: pr.number,
92
+ removedStoryIds: actuallyRemoved,
93
+ };
94
+ }
95
+ catch (e) {
96
+ const status = e.status;
97
+ if (status === 403) {
98
+ return {
99
+ kind: "pr-creation-blocked",
100
+ storyIds,
101
+ branchName: ctx.branchName,
102
+ };
103
+ }
104
+ throw e;
105
+ }
106
+ }
107
+ function collectTargetSpecs(ctx) {
108
+ const index = readIndex(ctx.repoRoot);
109
+ const all = Object.entries(index.stories)
110
+ .filter(([, entry]) => entry.status === "active")
111
+ .map(([id]) => id);
112
+ const targetIds = ctx.specId ? [ctx.specId] : all;
113
+ const specs = [];
114
+ for (const id of targetIds) {
115
+ const testPath = join(ctx.repoRoot, TESTS_INTEGRATION_DIR, `story-${id}.test.ts`);
116
+ if (existsSync(testPath) && !ctx.specId)
117
+ continue; // skip specs already tested, unless explicit --spec
118
+ try {
119
+ specs.push(readSpec(ctx.repoRoot, id));
120
+ }
121
+ catch {
122
+ // spec file missing despite being in index — skip
123
+ }
124
+ }
125
+ return specs;
126
+ }
127
+ function buildProjectContext(repoRoot) {
128
+ const bits = [];
129
+ try {
130
+ const pkg = JSON.parse(readFileSync(join(repoRoot, "package.json"), "utf8"));
131
+ bits.push(`package.json name: ${pkg.name}`);
132
+ if (pkg.description)
133
+ bits.push(`package.json description: ${pkg.description}`);
134
+ if (pkg.scripts?.test)
135
+ bits.push(`test script: ${pkg.scripts.test}`);
136
+ }
137
+ catch {
138
+ /* ignore */
139
+ }
140
+ // Show up to 2 existing integration test files as style references
141
+ const testsDir = join(repoRoot, TESTS_INTEGRATION_DIR);
142
+ if (existsSync(testsDir)) {
143
+ const existing = readdirSync(testsDir).filter((f) => f.endsWith(".test.ts")).slice(0, 2);
144
+ for (const f of existing) {
145
+ const p = join(testsDir, f);
146
+ try {
147
+ const content = readFileSync(p, "utf8");
148
+ const excerpt = content.split("\n").slice(0, 40).join("\n");
149
+ bits.push(`\n### Style reference: ${f} (first 40 lines)\n\n\`\`\`ts\n${excerpt}\n\`\`\``);
150
+ }
151
+ catch {
152
+ /* ignore */
153
+ }
154
+ }
155
+ }
156
+ else {
157
+ bits.push("No existing integration tests — this will be the first in the repo.");
158
+ }
159
+ return bits.join("\n");
160
+ }
161
+ async function generateTestFile(spec, ctx, projectContext) {
162
+ const systemPrompt = TESTGEN_SYSTEM(projectContext);
163
+ const userMessage = `Here is the spec YAML. Generate the Vitest integration test file:\n\n\`\`\`yaml\n${YAML.stringify(spec)}\n\`\`\``;
164
+ const raw = await ctx.llm.complete({
165
+ system: systemPrompt,
166
+ cacheSystem: true,
167
+ model: ctx.model,
168
+ messages: [{ role: "user", content: userMessage }],
169
+ maxTokens: 8192,
170
+ });
171
+ return stripCodeFence(raw);
172
+ }
173
+ function stripCodeFence(raw) {
174
+ const t = raw.trim();
175
+ const fence = t.match(/^```(?:typescript|ts)?\s*\n([\s\S]*)\n```$/);
176
+ if (fence && fence[1])
177
+ return fence[1];
178
+ return t;
179
+ }
180
+ /**
181
+ * Parse the emitted TS file and pull out test IDs in the format
182
+ * <file> > <describe-chain> > <test-name>
183
+ * matching what `vitest list` would emit.
184
+ *
185
+ * This is a lightweight text-level extraction — we parse for `describe(` and
186
+ * `it(` invocations and their string literal arguments, tracking nesting.
187
+ * Robust against typical usage; falls back to a single synthetic entry if
188
+ * nothing recognisable is found.
189
+ */
190
+ export function extractTestIdsFromFile(filePath, source) {
191
+ const ids = [];
192
+ const stack = [];
193
+ // Match describe("...", OR it("...", — minimal, single-quote-or-double-quote strings only
194
+ const re = /\b(describe|it|test)\s*\(\s*(['"])((?:\\.|[^\\])*?)\2/g;
195
+ // Track blocks by matching braces simply — naive but works for well-formed
196
+ // source. We use line-by-line depth for describes because inferring block
197
+ // close on a braces level requires a full parser.
198
+ //
199
+ // Simpler heuristic: treat each `describe(...)` as pushing onto the stack,
200
+ // and assume it stays on the stack for everything until a balanced close.
201
+ // For initial impl, we approximate by scanning in order and inserting
202
+ // markers at `});` following each opening. For the shape Vitest produces,
203
+ // this works in practice.
204
+ //
205
+ // To keep this minimal, use a pre-pass to find describe block ranges by
206
+ // brace balancing, then walk the match list in order.
207
+ const blocks = findDescribeBlocks(source);
208
+ let m;
209
+ while ((m = re.exec(source)) !== null) {
210
+ const kind = m[1];
211
+ const name = m[3] ?? "";
212
+ const idx = m.index;
213
+ const describesHere = blocks
214
+ .filter((b) => b.start <= idx && idx <= b.end)
215
+ .map((b) => b.name);
216
+ if (kind === "describe") {
217
+ // describe itself contributes its name to the stack; skip adding it as a test id
218
+ continue;
219
+ }
220
+ // it / test → build id
221
+ const parts = [filePath, ...describesHere, name];
222
+ ids.push(parts.join(" > "));
223
+ }
224
+ if (ids.length === 0) {
225
+ ids.push(`${filePath} > (no tests parsed — review generated file)`);
226
+ }
227
+ // Deduplicate while preserving order
228
+ return Array.from(new Set(ids));
229
+ }
230
+ function findDescribeBlocks(source) {
231
+ const blocks = [];
232
+ const describeRe = /\bdescribe\s*\(\s*(['"])((?:\\.|[^\\])*?)\1\s*,/g;
233
+ let m;
234
+ while ((m = describeRe.exec(source)) !== null) {
235
+ const name = m[2] ?? "";
236
+ // Find the opening `{` of the describe's callback body
237
+ const open = source.indexOf("{", m.index);
238
+ if (open === -1)
239
+ continue;
240
+ let depth = 1;
241
+ let i = open + 1;
242
+ while (i < source.length && depth > 0) {
243
+ const c = source[i];
244
+ if (c === "{")
245
+ depth++;
246
+ else if (c === "}")
247
+ depth--;
248
+ else if (c === "/" && source[i + 1] === "/") {
249
+ // line comment — skip to EOL
250
+ while (i < source.length && source[i] !== "\n")
251
+ i++;
252
+ }
253
+ else if (c === "/" && source[i + 1] === "*") {
254
+ // block comment
255
+ i += 2;
256
+ while (i < source.length - 1 && !(source[i] === "*" && source[i + 1] === "/"))
257
+ i++;
258
+ i += 2;
259
+ continue;
260
+ }
261
+ else if (c === '"' || c === "'" || c === "`") {
262
+ // skip string literal
263
+ const quote = c;
264
+ i++;
265
+ while (i < source.length && source[i] !== quote) {
266
+ if (source[i] === "\\")
267
+ i++;
268
+ i++;
269
+ }
270
+ }
271
+ i++;
272
+ }
273
+ blocks.push({ name, start: m.index, end: i });
274
+ }
275
+ return blocks;
276
+ }
277
+ function writeFileAt(repoRoot, rel, content) {
278
+ const full = join(repoRoot, rel);
279
+ mkdirSync(dirname(full), { recursive: true });
280
+ writeFileSync(full, content, "utf8");
281
+ }
282
+ function removeIfExists(repoRoot, rels) {
283
+ let count = 0;
284
+ for (const rel of rels) {
285
+ const full = join(repoRoot, rel);
286
+ if (existsSync(full)) {
287
+ rmSync(full);
288
+ count++;
289
+ }
290
+ }
291
+ return count;
292
+ }
293
+ function buildPrBody(args) {
294
+ const sections = [];
295
+ sections.push(`Generated Vitest integration tests for ${args.generated.length} spec(s), written by the slowcook testgen agent.`);
296
+ sections.push("");
297
+ sections.push("## Tests added");
298
+ for (const g of args.generated) {
299
+ const manifestCount = g.manifest.tests.length;
300
+ sections.push(`- \`story-${g.spec.story_id}\` — *${g.spec.title}* — ${manifestCount} test(s) in \`${g.testPath}\``);
301
+ }
302
+ if (args.removedStoryIds.length > 0) {
303
+ sections.push("");
304
+ sections.push("## Tests removed (supersede chain)");
305
+ sections.push(`The following stories are superseded by specs in this PR; their frozen tests were removed as part of the same change:`);
306
+ for (const id of args.removedStoryIds) {
307
+ sections.push(`- \`story-${id}\``);
308
+ }
309
+ sections.push("");
310
+ sections.push(`Because this PR modifies frozen paths (removing old tests + manifests), the \`override-freeze\` label is applied automatically. Reviewer can audit the supersede chain via the \`supersedes\` field on each new spec.`);
311
+ }
312
+ sections.push("");
313
+ sections.push("---");
314
+ sections.push("*Generated by `slowcook testgen`.*");
315
+ return sections.join("\n");
316
+ }
317
+ //# sourceMappingURL=agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.js","sourceRoot":"","sources":["../../../src/commands/testgen/agent.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function testgen(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/testgen/index.ts"],"names":[],"mappings":"AA6GA,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAkF/E"}
@@ -0,0 +1,173 @@
1
+ import { execSync } from "node:child_process";
2
+ import { GitHubAdapter } from "@slowcook-ai/forge-github";
3
+ import { AnthropicClient } from "../refine/llm.js";
4
+ import { runTestgen } from "./agent.js";
5
+ function parseArgs(argv) {
6
+ const args = {
7
+ specId: null,
8
+ all: false,
9
+ repoRoot: process.cwd(),
10
+ baseBranch: "main",
11
+ model: "claude-opus-4-7",
12
+ };
13
+ for (let i = 0; i < argv.length; i++) {
14
+ const arg = argv[i];
15
+ const next = argv[i + 1];
16
+ if (arg === "--spec" && next) {
17
+ args.specId = next;
18
+ i++;
19
+ }
20
+ else if (arg === "--all") {
21
+ args.all = true;
22
+ }
23
+ else if (arg === "--cwd" && next) {
24
+ args.repoRoot = next;
25
+ i++;
26
+ }
27
+ else if (arg === "--owner" && next) {
28
+ args.owner = next;
29
+ i++;
30
+ }
31
+ else if (arg === "--repo" && next) {
32
+ args.repo = next;
33
+ i++;
34
+ }
35
+ else if (arg === "--base" && next) {
36
+ args.baseBranch = next;
37
+ i++;
38
+ }
39
+ else if (arg === "--model" && next) {
40
+ args.model = next;
41
+ i++;
42
+ }
43
+ else if (arg === "--help" || arg === "-h") {
44
+ printHelp();
45
+ process.exit(0);
46
+ }
47
+ }
48
+ // Default: operate on all active specs lacking tests
49
+ if (!args.specId)
50
+ args.all = true;
51
+ return args;
52
+ }
53
+ function printHelp() {
54
+ console.log(`
55
+ slowcook testgen — generate Vitest integration tests from merged specs
56
+
57
+ Runs over active specs that lack test files and produces a single draft PR
58
+ containing:
59
+ - tests/integration/story-N.test.ts (Vitest, HTTP-style integration)
60
+ - .brewing/manifests/story-N.json (the frozen test manifest for N)
61
+
62
+ If any spec has \`supersedes: [X, Y]\`, the old tests/manifests for X and Y
63
+ are removed in the same PR; \`override-freeze\` is auto-applied because the
64
+ supersede chain in the spec provides the audit trail.
65
+
66
+ Usage:
67
+ slowcook testgen [--spec <id>] [--all] [options]
68
+
69
+ Options:
70
+ --spec <id> Generate tests for a specific story id (re-runs even if
71
+ tests exist).
72
+ --all Generate tests for every active spec that lacks them.
73
+ (Default when --spec is not set.)
74
+ --cwd <path> Repo working directory (default: .)
75
+ --owner <login> Repo owner (default: from \`git remote get-url origin\`)
76
+ --repo <name> Repo name (default: from \`git remote get-url origin\`)
77
+ --base <branch> Base branch for the tests PR (default: main)
78
+ --model <id> LLM model (default: claude-opus-4-7)
79
+ --help, -h Show this help
80
+
81
+ Environment:
82
+ ANTHROPIC_API_KEY (required) Anthropic API key
83
+ GITHUB_TOKEN (required) GitHub token with contents/pull-requests write
84
+
85
+ Exit codes:
86
+ 0 outcome reached (PR opened, or nothing to generate)
87
+ 2 script error (bad args, missing env, network failure)
88
+ `);
89
+ }
90
+ function detectOwnerRepo(cwd) {
91
+ try {
92
+ const url = execSync("git remote get-url origin", {
93
+ cwd,
94
+ encoding: "utf8",
95
+ stdio: ["ignore", "pipe", "ignore"],
96
+ }).trim();
97
+ const m = url.match(/github\.com[:/]([^/]+)\/([^/.]+)(?:\.git)?$/);
98
+ if (m && m[1] && m[2])
99
+ return { owner: m[1], repo: m[2] };
100
+ }
101
+ catch {
102
+ /* not a git repo */
103
+ }
104
+ return null;
105
+ }
106
+ export async function testgen(argv, cliVersion) {
107
+ const args = parseArgs(argv);
108
+ const anthropicKey = process.env["ANTHROPIC_API_KEY"];
109
+ if (!anthropicKey) {
110
+ console.error("ANTHROPIC_API_KEY environment variable is not set.");
111
+ process.exit(2);
112
+ }
113
+ const githubToken = process.env["GITHUB_TOKEN"];
114
+ if (!githubToken) {
115
+ console.error("GITHUB_TOKEN environment variable is not set.");
116
+ process.exit(2);
117
+ }
118
+ let owner = args.owner;
119
+ let repo = args.repo;
120
+ if (!owner || !repo) {
121
+ const detected = detectOwnerRepo(args.repoRoot);
122
+ if (!detected) {
123
+ console.error("Could not detect owner/repo from git remote. Pass --owner and --repo explicitly.");
124
+ process.exit(2);
125
+ }
126
+ owner = owner ?? detected.owner;
127
+ repo = repo ?? detected.repo;
128
+ }
129
+ const forge = new GitHubAdapter({ owner, repo, token: githubToken });
130
+ const llm = new AnthropicClient(anthropicKey);
131
+ const branchName = args.specId
132
+ ? `slowcook/tests/story-${args.specId}`
133
+ : `slowcook/tests/batch-${Date.now()}`;
134
+ const ctx = {
135
+ repoRoot: args.repoRoot,
136
+ forge,
137
+ llm,
138
+ model: args.model,
139
+ cliVersion,
140
+ baseBranch: args.baseBranch,
141
+ all: args.all,
142
+ specId: args.specId,
143
+ branchName,
144
+ now: new Date(),
145
+ };
146
+ console.log(`slowcook testgen · ${args.specId ? `story-${args.specId}` : "all active specs"} on ${owner}/${repo}`);
147
+ try {
148
+ const outcome = await runTestgen(ctx);
149
+ switch (outcome.kind) {
150
+ case "tests-emitted":
151
+ console.log(`Tests written for ${outcome.storyIds.map((s) => `story-${s}`).join(", ")}`);
152
+ if (outcome.removedStoryIds.length > 0) {
153
+ console.log(`Removed superseded tests: ${outcome.removedStoryIds.map((s) => `story-${s}`).join(", ")}`);
154
+ }
155
+ console.log(`Draft PR: ${outcome.prUrl}`);
156
+ break;
157
+ case "nothing-to-generate":
158
+ console.log(`Noop: ${outcome.reason}.`);
159
+ break;
160
+ case "pr-creation-blocked":
161
+ console.log(`Tests committed and pushed to branch '${outcome.branchName}', but PR creation was blocked (403). Enable "Allow GitHub Actions to create and approve pull requests" and re-run, or open the PR manually.`);
162
+ process.exit(2);
163
+ }
164
+ }
165
+ catch (e) {
166
+ console.error(`testgen failed: ${e.message}`);
167
+ if (process.env["SLOWCOOK_DEBUG"]) {
168
+ console.error(e);
169
+ }
170
+ process.exit(2);
171
+ }
172
+ }
173
+ //# sourceMappingURL=index.js.map