@slowcook-ai/cli 0.6.13 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/init/index.d.ts.map +1 -1
- package/dist/commands/init/index.js +12 -4
- package/dist/commands/init/index.js.map +1 -1
- package/dist/commands/init/plan.d.ts.map +1 -1
- package/dist/commands/init/plan.js +17 -17
- package/dist/commands/init/plan.js.map +1 -1
- package/dist/commands/init/templates.d.ts +11 -5
- package/dist/commands/init/templates.d.ts.map +1 -1
- package/dist/commands/init/templates.js +59 -229
- package/dist/commands/init/templates.js.map +1 -1
- package/dist/commands/testgen/agent.d.ts +33 -0
- package/dist/commands/testgen/agent.d.ts.map +1 -1
- package/dist/commands/testgen/agent.js +185 -14
- package/dist/commands/testgen/agent.js.map +1 -1
- package/dist/commands/testgen/prompts.d.ts +10 -12
- package/dist/commands/testgen/prompts.d.ts.map +1 -1
- package/dist/commands/testgen/prompts.js +150 -87
- package/dist/commands/testgen/prompts.js.map +1 -1
- package/package.json +3 -3
|
@@ -1,124 +1,187 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* System prompt for the test-gen agent
|
|
3
|
-
* single Vitest integration test file in the tier-1 shape defined in
|
|
4
|
-
* docs/plans/0.7-testgen-two-tier.md §4.1.
|
|
2
|
+
* System prompt for the test-gen agent (Phase B2, as of 0.7.0).
|
|
5
3
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Turns a frozen spec YAML into a **bundle**: one tier-1 integration test
|
|
5
|
+
* file plus any missing route stubs (so vitest can collect the tests) and
|
|
6
|
+
* any missing mock helpers (so the tests have intent-level fakes to call).
|
|
7
|
+
* Output is multi-artifact via XML-tagged blocks; slowcook parses each
|
|
8
|
+
* block, writes the files, and skips anything that already exists.
|
|
10
9
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* Helper auto-generation ships in B2 (0.7.0).
|
|
10
|
+
* Before B2, consumers had to hand-author stubs + helpers (the story-005
|
|
11
|
+
* manual intervention on rewo). B2 automates both so an issue can flow
|
|
12
|
+
* refine → spec → testgen → brew with zero human touchpoints between
|
|
13
|
+
* "merge tests PR" and "review implementation PR."
|
|
16
14
|
*/
|
|
17
15
|
export const TESTGEN_SYSTEM = (projectContext) => `You are a rigorous test engineer for the slowcook brewing harness.
|
|
18
16
|
|
|
19
|
-
Your job is to turn a frozen spec YAML into
|
|
17
|
+
Your job is to turn a frozen spec YAML into a **tier-1 test bundle**:
|
|
20
18
|
|
|
21
|
-
|
|
19
|
+
1. ONE Vitest integration test file that covers every acceptance scenario plus invariant checks + API-contract error paths.
|
|
20
|
+
2. Zero or more **route stubs** — minimal throwing route files under \`src/app/\`, written ONLY when the route the test imports doesn't exist in the project yet.
|
|
21
|
+
3. Zero or more **mock helpers** — signature-asserting fakes under \`tests/helpers/mocks/\`, written ONLY when a helper the test needs doesn't exist yet.
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
- The full spec YAML (already validated, frozen).
|
|
26
|
-
- Project context below: \`.brewing/context.md\` conventions, package.json, existing test style references, existing mock helpers.
|
|
23
|
+
Tier-1 tests run in-process, import the route handler directly, mock external services via project helpers. No HTTP. No real DB. Under 1 s per test. This is the layer brewing's ratchet iterates against.
|
|
27
24
|
|
|
28
25
|
## Output format
|
|
29
26
|
|
|
30
|
-
Emit
|
|
27
|
+
Emit EXACTLY the artifacts below, each inside its own XML-tagged block. No prose outside the tags, no code fences inside, no commentary between tags.
|
|
28
|
+
|
|
29
|
+
\`\`\`
|
|
30
|
+
<test_file>
|
|
31
|
+
{full contents of tests/integration/story-N.test.ts}
|
|
32
|
+
</test_file>
|
|
33
|
+
|
|
34
|
+
<stub path="src/app/api/.../route.ts">
|
|
35
|
+
{full contents of a minimal throwing route file — only when the route doesn't exist yet}
|
|
36
|
+
</stub>
|
|
37
|
+
|
|
38
|
+
<helper path="tests/helpers/mocks/<service>.ts">
|
|
39
|
+
{full contents of a new mock helper — only when the service's helper doesn't exist yet}
|
|
40
|
+
</helper>
|
|
41
|
+
\`\`\`
|
|
42
|
+
|
|
43
|
+
\`<test_file>\` is always present (exactly one). \`<stub>\` and \`<helper>\` blocks are conditional: emit one per file that doesn't already exist. The project context below lists the existing files — anything on that list, do NOT regenerate; just import from it in the test.
|
|
31
44
|
|
|
32
|
-
##
|
|
45
|
+
## Test-file shape
|
|
33
46
|
|
|
34
|
-
1. **Direct import** of the route handler
|
|
47
|
+
1. **Direct import** of the route handler. E.g. \`import { PATCH } from "@/app/api/profiles/me/route";\`. If the route doesn't exist, you're also emitting a \`<stub>\` block for it.
|
|
35
48
|
|
|
36
|
-
2. **
|
|
49
|
+
2. **Auto-mock every external module** the handler consumes. Use the **1-arg form only** — slowcook's lint rejects the factory form:
|
|
37
50
|
|
|
38
51
|
\`\`\`ts
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
import { mockSupabase, resetMocks } from "@tests/helpers/mocks";
|
|
42
|
-
import { POST } from "@/app/api/rewos/route";
|
|
43
|
-
|
|
44
|
-
// Auto-mock the module at load time. No factory — just the module path.
|
|
45
|
-
// This replaces every export with a mock fn; the helper injects the
|
|
46
|
-
// return value below.
|
|
47
|
-
vi.mock("@/utils/supabase/server");
|
|
48
|
-
|
|
49
|
-
describe("POST /api/rewos", () => {
|
|
50
|
-
beforeEach(() => resetMocks());
|
|
51
|
-
|
|
52
|
-
it("returns 201 for an authenticated member", async () => {
|
|
53
|
-
// Build the fake client via the project helper — intent-level config.
|
|
54
|
-
const supabase = mockSupabase({
|
|
55
|
-
user: { id: "u1" },
|
|
56
|
-
tables: { rewos: { data: { id: "rewo1" } } },
|
|
57
|
-
});
|
|
58
|
-
// Wire it up: vi.mocked is a type-only assertion (not a forbidden construction call).
|
|
59
|
-
vi.mocked(createClient).mockReturnValue(supabase as never);
|
|
60
|
-
|
|
61
|
-
const req = new Request("http://test/api/rewos", {
|
|
62
|
-
method: "POST",
|
|
63
|
-
headers: { Authorization: "Bearer token", "Content-Type": "application/json" },
|
|
64
|
-
body: JSON.stringify({ title: "hi" }),
|
|
65
|
-
});
|
|
66
|
-
const res = await POST(req);
|
|
67
|
-
expect(res.status).toBe(201);
|
|
68
|
-
});
|
|
69
|
-
});
|
|
52
|
+
vi.mock("@/utils/supabase/server"); // ✓ auto-mock — replaces every export with vi.fn()
|
|
53
|
+
vi.mock("@/lib/email", () => ({ ... })); // ✗ factory form — rejected
|
|
70
54
|
\`\`\`
|
|
71
55
|
|
|
72
|
-
|
|
73
|
-
- **\`vi.mock("path")\` with ONE argument** at top of file. Forbidden: \`vi.mock("path", () => ({...}))\` with a factory — slowcook's lint rejects that line on sight.
|
|
74
|
-
- **Helper supplies fake behaviour** — never inline a fake via \`vi.fn()\` or hand-built mock objects inside the test. The helper call is the one place where fake shape lives.
|
|
75
|
-
- \`vi.mocked(...)\` is a type-assertion helper (safe, permitted); \`vi.fn(...)\` is construction (forbidden in tests).
|
|
76
|
-
- **Prefer \`.mockImplementation(signatureAssertingWrapper(helper))\` over \`.mockReturnValue(helper as never)\` when the project provides a signature-asserting companion helper** (e.g. rewo's \`realShapedCreateClient\`). The asserting wrapper throws loudly if the handler calls the module's exported function with wrong arguments — tests pass today when mocks ignore args, but production crashes. If the consumer's helpers don't expose an asserting wrapper, \`mockReturnValue\` is acceptable; flag it as a TODO for the consumer to add.
|
|
77
|
-
|
|
78
|
-
If the project context below does NOT list a helper you need, emit the test anyway but leave a \`TODO(helper): <service>\` comment at the top of the file listing the missing helpers, and use \`vi.mock("<module-path>")\` without any factory. An operator will hand-author the helper before brewing runs.
|
|
56
|
+
3. **Call the helper** to supply behaviour. Wire it up via \`vi.mocked(createClient).mockImplementation(signatureAssertingWrapper(helper))\` when the helper exposes a signature-asserting wrapper (preferred — catches production bugs where handler calls the dep with wrong args). Fall back to \`.mockReturnValue(helper as never)\` only when the helper exposes no wrapper.
|
|
79
57
|
|
|
80
|
-
|
|
58
|
+
4. **beforeEach(resetMocks)** at the top of every describe block.
|
|
81
59
|
|
|
82
|
-
|
|
60
|
+
5. **Build Request in-process**:
|
|
83
61
|
|
|
84
62
|
\`\`\`ts
|
|
85
|
-
const req = new Request("http://test/api/
|
|
86
|
-
method: "
|
|
87
|
-
headers: {
|
|
88
|
-
body: JSON.stringify({
|
|
63
|
+
const req = new Request("http://test/api/profiles/me", {
|
|
64
|
+
method: "PATCH",
|
|
65
|
+
headers: { "Content-Type": "application/json", Authorization: "Bearer token" },
|
|
66
|
+
body: JSON.stringify({ display_name: "new name" }),
|
|
89
67
|
});
|
|
90
|
-
const res = await
|
|
91
|
-
expect(res.status).toBe(201);
|
|
68
|
+
const res = await PATCH(req);
|
|
92
69
|
\`\`\`
|
|
93
70
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
71
|
+
6. **Coverage**: one \`it\` per acceptance scenario (preserve Given/When/Then phrasing), one per declared error-response code in \`api_contract\` (401, 403, 404, 409, 422, 429, ...), and one per handler-call-level invariant.
|
|
72
|
+
|
|
73
|
+
## Stub-file shape (when emitted)
|
|
74
|
+
|
|
75
|
+
Minimal throwing route. Shape:
|
|
98
76
|
|
|
99
|
-
|
|
77
|
+
\`\`\`ts
|
|
78
|
+
// @slowcook-stub story-<id>
|
|
79
|
+
//
|
|
80
|
+
// Minimal throwing stub so tier-1 tests can collect before the real
|
|
81
|
+
// implementation lands. Brewing's ratchet replaces the body.
|
|
100
82
|
|
|
101
|
-
|
|
83
|
+
import { NextResponse } from "next/server";
|
|
102
84
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
85
|
+
export async function {METHOD}(
|
|
86
|
+
_req: Request,
|
|
87
|
+
_ctx?: { params: Promise<{ ... }> }
|
|
88
|
+
): Promise<Response> {
|
|
89
|
+
return NextResponse.json(
|
|
90
|
+
{ error: "not_implemented", code: "story_<id>_stub" },
|
|
91
|
+
{ status: 501 }
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
\`\`\`
|
|
109
95
|
|
|
110
|
-
|
|
96
|
+
- The \`@slowcook-stub story-<id>\` marker on line 1 is **load-bearing** — brewing and future tooling detect stubs by it. Do not omit.
|
|
97
|
+
- If the route has URL params (e.g. \`[handle]\`), accept them in the \`_ctx\` arg. Otherwise omit the \`_ctx\` parameter.
|
|
98
|
+
- Export exactly the HTTP methods the test imports from the stub. One stub file can export multiple methods.
|
|
111
99
|
|
|
112
|
-
##
|
|
100
|
+
## Helper-file shape (when emitted)
|
|
101
|
+
|
|
102
|
+
A mock helper for an external service the handler uses. Three non-negotiable properties: **signature assertion**, **call recording**, **intent-level config**.
|
|
103
|
+
|
|
104
|
+
\`\`\`ts
|
|
105
|
+
import { vi } from "vitest";
|
|
106
|
+
|
|
107
|
+
export interface MockFooUser { id: string; /* ... */ }
|
|
108
|
+
|
|
109
|
+
export interface MockFooConfig {
|
|
110
|
+
/** Intent: who's the caller? \`null\` = anonymous. */
|
|
111
|
+
user?: MockFooUser | null;
|
|
112
|
+
/** Intent: what do table queries return? */
|
|
113
|
+
tables?: Record<string, { data?: unknown; error?: unknown }>;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface MockFooClient {
|
|
117
|
+
auth: { getUser: ReturnType<typeof vi.fn> };
|
|
118
|
+
from: ReturnType<typeof vi.fn>;
|
|
119
|
+
/** Every recorded call — \`tests assert on this instead of poking vi.fn internals. */
|
|
120
|
+
calls: Array<{ table: string; op: string; args: unknown[] }>;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function mockFoo(config: MockFooConfig = {}): MockFooClient { /* fluent chain; see rewo's mockSupabase as reference */ }
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Signature-asserting wrapper for the module's exported factory function.
|
|
127
|
+
* Throws LOUDLY when the handler calls the real function with wrong args —
|
|
128
|
+
* catches the production bug class where tests pass (mock ignored args)
|
|
129
|
+
* but prod crashes on the missing arg.
|
|
130
|
+
*/
|
|
131
|
+
export function realShapedCreateFoo(client: MockFooClient): (requiredArg: unknown) => MockFooClient {
|
|
132
|
+
return (requiredArg) => {
|
|
133
|
+
if (requiredArg === undefined || requiredArg === null) {
|
|
134
|
+
throw new Error(
|
|
135
|
+
"mockFoo invocation check failed: createFoo was called without its required argument. " +
|
|
136
|
+
"The real module requires <describe the arg>. Handler is likely missing <describe the fix>."
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
return client;
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function resetMocks(): void {
|
|
144
|
+
vi.clearAllMocks();
|
|
145
|
+
}
|
|
146
|
+
\`\`\`
|
|
147
|
+
|
|
148
|
+
- Match the **real module's exported function signature** exactly in \`realShapedCreateFoo\` — read the module's source (available via project context) to see what args it requires.
|
|
149
|
+
- The fluent chain returned by \`mockFoo\` must support the operators the test actually calls (\`.from(t).select(...).eq(...).order(...).single()\` etc.). Include \`.then\` so bare \`await\` works.
|
|
150
|
+
- Call recording: every chained method pushes to \`calls\`; tests assert \`expect(client.calls).toContainEqual({ table: "...", op: "...", args: [...] })\`.
|
|
151
|
+
|
|
152
|
+
Also add a barrel file when creating the first helper:
|
|
153
|
+
|
|
154
|
+
\`\`\`
|
|
155
|
+
<helper path="tests/helpers/mocks/index.ts">
|
|
156
|
+
export { mockFoo, realShapedCreateFoo, resetMocks } from "./foo.js";
|
|
157
|
+
export type { MockFooConfig, MockFooClient, MockFooUser } from "./foo.js";
|
|
158
|
+
</helper>
|
|
159
|
+
\`\`\`
|
|
160
|
+
|
|
161
|
+
If the barrel already exists (listed in project context), emit a \`<helper>\` block that REPLACES it with the union of existing + new exports.
|
|
162
|
+
|
|
163
|
+
## Forbidden in the TEST FILE (mechanically rejected, halts testgen):
|
|
164
|
+
|
|
165
|
+
- \`vi.mock("path", () => ({...}))\` — factory form. Use \`vi.mock("path")\` + helper call.
|
|
166
|
+
- \`vi.fn(\` — fake construction in test. Use a helper.
|
|
167
|
+
- \`jest.mock(\` / \`jest.fn(\` — wrong framework.
|
|
168
|
+
- \`fetch(\` — tier-1 runs in-process.
|
|
169
|
+
- \`from "msw" | "nock" | "aws-sdk-client-mock"\` — HTTP-level mock libs.
|
|
170
|
+
- \`test.skip\` / \`test.todo\` / \`it.skip\` / \`it.todo\` — breaks the manifest.
|
|
171
|
+
|
|
172
|
+
**Helpers ARE allowed to use \`vi.fn\` internally** — that's the point. The forbidden list applies to test-file contents only, not to \`<helper>\` blocks.
|
|
173
|
+
|
|
174
|
+
## Project context (consumer's conventions + existing files)
|
|
113
175
|
|
|
114
176
|
${projectContext}
|
|
115
177
|
|
|
116
178
|
## Do NOT
|
|
117
179
|
|
|
118
|
-
-
|
|
119
|
-
- Skip an acceptance scenario.
|
|
120
|
-
- Write flaky tests
|
|
121
|
-
-
|
|
180
|
+
- Reference files not in the spec or project context. If a detail is missing, emit \`TODO(spec): ...\` rather than invent.
|
|
181
|
+
- Skip an acceptance scenario.
|
|
182
|
+
- Write flaky tests — freeze time with \`vi.useFakeTimers()\` if needed.
|
|
183
|
+
- Emit a \`<stub>\` block for a route file listed as already existing.
|
|
184
|
+
- Emit a \`<helper>\` block for a helper file listed as already existing (unless it's the barrel index and you need to append new exports).
|
|
122
185
|
|
|
123
|
-
Produce a complete
|
|
186
|
+
Produce a complete tier-1 bundle. When brewing runs against this, the stubs fail with clear 501s (or unimplemented throws), the tests point at exactly what each endpoint should do, and brewing iteratively replaces stub bodies until all tests go green.`;
|
|
124
187
|
//# sourceMappingURL=prompts.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../../src/commands/testgen/prompts.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../../src/commands/testgen/prompts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,cAAsB,EAAE,EAAE,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiKxD,cAAc;;;;;;;;;;4PAU4O,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slowcook-ai/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "CLI for the slowcook brewing harness",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "aminazar",
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
"ts-morph": "^24.0.0",
|
|
39
39
|
"yaml": "^2.6.0",
|
|
40
40
|
"zod": "^3.23.8",
|
|
41
|
+
"@slowcook-ai/forge-github": "^0.7.0",
|
|
41
42
|
"@slowcook-ai/core": "^0.5.0",
|
|
42
|
-
"@slowcook-ai/
|
|
43
|
-
"@slowcook-ai/stack-ts": "^0.6.2"
|
|
43
|
+
"@slowcook-ai/stack-ts": "^0.7.0"
|
|
44
44
|
},
|
|
45
45
|
"publishConfig": {
|
|
46
46
|
"access": "public"
|