@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.
- package/dist/cli.js +19 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/init/plan.d.ts.map +1 -1
- package/dist/commands/init/plan.js +4 -1
- package/dist/commands/init/plan.js.map +1 -1
- package/dist/commands/init/templates.d.ts +2 -1
- package/dist/commands/init/templates.d.ts.map +1 -1
- package/dist/commands/init/templates.js +61 -1
- package/dist/commands/init/templates.js.map +1 -1
- package/dist/commands/refine/agent.d.ts.map +1 -1
- package/dist/commands/refine/agent.js +53 -18
- package/dist/commands/refine/agent.js.map +1 -1
- package/dist/commands/refine/context.d.ts +17 -0
- package/dist/commands/refine/context.d.ts.map +1 -0
- package/dist/commands/refine/context.js +69 -0
- package/dist/commands/refine/context.js.map +1 -0
- package/dist/commands/refine/prompts.d.ts +1 -1
- package/dist/commands/refine/prompts.d.ts.map +1 -1
- package/dist/commands/refine/prompts.js +8 -1
- package/dist/commands/refine/prompts.js.map +1 -1
- package/dist/commands/refine/spec-yaml.d.ts +13 -3
- package/dist/commands/refine/spec-yaml.d.ts.map +1 -1
- package/dist/commands/refine/spec-yaml.js +31 -5
- package/dist/commands/refine/spec-yaml.js.map +1 -1
- package/dist/commands/testgen/agent.d.ts +55 -0
- package/dist/commands/testgen/agent.d.ts.map +1 -0
- package/dist/commands/testgen/agent.js +317 -0
- package/dist/commands/testgen/agent.js.map +1 -0
- package/dist/commands/testgen/index.d.ts +2 -0
- package/dist/commands/testgen/index.d.ts.map +1 -0
- package/dist/commands/testgen/index.js +173 -0
- package/dist/commands/testgen/index.js.map +1 -0
- package/dist/commands/testgen/prompts.d.ts +12 -0
- package/dist/commands/testgen/prompts.d.ts.map +1 -0
- package/dist/commands/testgen/prompts.js +54 -0
- package/dist/commands/testgen/prompts.js.map +1 -0
- package/package.json +4 -4
|
@@ -98,12 +98,38 @@ export function listActiveSpecs(repoRoot) {
|
|
|
98
98
|
}
|
|
99
99
|
return specs;
|
|
100
100
|
}
|
|
101
|
-
/**
|
|
102
|
-
|
|
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
|
|
105
|
-
|
|
106
|
-
|
|
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":"AAAA,OAAO,EACL,YAAY,EACZ,aAAa,EACb,UAAU,EACV,WAAW,EACX,SAAS,GACV,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EACL,cAAc,
|
|
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 @@
|
|
|
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
|