@nathapp/nax 0.29.0 → 0.31.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nathapp/nax",
3
- "version": "0.29.0",
3
+ "version": "0.31.0",
4
4
  "description": "AI Coding Agent Orchestrator \u2014 loops until done",
5
5
  "type": "module",
6
6
  "bin": {
@@ -52,4 +52,4 @@
52
52
  "README.md",
53
53
  "CHANGELOG.md"
54
54
  ]
55
- }
55
+ }
@@ -15,6 +15,7 @@ import { resolveModel } from "../config/schema";
15
15
  import { getLogger } from "../logger";
16
16
  import type { PRD, UserStory } from "../prd";
17
17
  import { routeTask } from "../routing";
18
+ import { NAX_VERSION } from "../version";
18
19
  import {
19
20
  applyKeywordClassification,
20
21
  buildCodebaseContext,
@@ -63,13 +64,7 @@ export async function analyzeFeature(options: AnalyzeOptions): Promise<PRD> {
63
64
  );
64
65
  }
65
66
 
66
- let naxVersion = "unknown";
67
- try {
68
- const pkgPath = new URL("../../package.json", import.meta.url);
69
- naxVersion = (await Bun.file(pkgPath).json()).version;
70
- } catch {
71
- /* version is metadata only */
72
- }
67
+ const naxVersion = NAX_VERSION;
73
68
 
74
69
  const now = new Date().toISOString();
75
70
  const prd: PRD = {
package/src/cli/index.ts CHANGED
@@ -20,7 +20,9 @@ export {
20
20
  } from "./runs";
21
21
  export {
22
22
  promptsCommand,
23
+ promptsInitCommand,
23
24
  type PromptsCommandOptions,
25
+ type PromptsInitCommandOptions,
24
26
  } from "./prompts";
25
27
  export { initCommand, type InitOptions } from "./init";
26
28
  export { pluginsListCommand } from "./plugins";
@@ -18,6 +18,7 @@ import { constitutionStage, contextStage, promptStage, routingStage } from "../p
18
18
  import type { UserStory } from "../prd";
19
19
  import { loadPRD } from "../prd";
20
20
  import { PromptBuilder } from "../prompts";
21
+ import { buildRoleTaskSection } from "../prompts/sections/role-task";
21
22
 
22
23
  export interface PromptsCommandOptions {
23
24
  /** Feature name */
@@ -240,6 +241,189 @@ function buildFrontmatter(story: UserStory, ctx: PipelineContext, role?: string)
240
241
  return `${lines.join("\n")}\n`;
241
242
  }
242
243
 
244
+ export interface PromptsInitCommandOptions {
245
+ /** Working directory (project root) */
246
+ workdir: string;
247
+ /** Overwrite existing files if true */
248
+ force?: boolean;
249
+ }
250
+
251
+ const TEMPLATE_ROLES = [
252
+ { file: "test-writer.md", role: "test-writer" as const },
253
+ { file: "implementer.md", role: "implementer" as const, variant: "standard" as const },
254
+ { file: "verifier.md", role: "verifier" as const },
255
+ { file: "single-session.md", role: "single-session" as const },
256
+ ] as const;
257
+
258
+ const TEMPLATE_HEADER = `<!--
259
+ This file controls the role-body section of the nax prompt for this role.
260
+ Edit the content below to customize the task instructions given to the agent.
261
+
262
+ NON-OVERRIDABLE SECTIONS (always injected by nax, cannot be changed here):
263
+ - Isolation rules (scope, file access boundaries)
264
+ - Story context (acceptance criteria, description, dependencies)
265
+ - Conventions (project coding standards)
266
+
267
+ To activate overrides, add to your nax/config.json:
268
+ { "prompts": { "overrides": { "<role>": "nax/templates/<role>.md" } } }
269
+ -->
270
+
271
+ `;
272
+
273
+ /**
274
+ * Execute the `nax prompts --init` command.
275
+ *
276
+ * Creates nax/templates/ and writes 4 default role-body template files.
277
+ * Auto-wires prompts.overrides in nax.config.json if the file exists and overrides are not already set.
278
+ * Returns the list of file paths written. Returns empty array if files
279
+ * already exist and force is not set.
280
+ *
281
+ * @param options - Command options
282
+ * @returns Array of file paths written
283
+ */
284
+ export async function promptsInitCommand(options: PromptsInitCommandOptions): Promise<string[]> {
285
+ const { workdir, force = false } = options;
286
+ const templatesDir = join(workdir, "nax", "templates");
287
+
288
+ mkdirSync(templatesDir, { recursive: true });
289
+
290
+ // Check for existing files
291
+ const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync(join(templatesDir, f)));
292
+
293
+ if (existingFiles.length > 0 && !force) {
294
+ console.warn(
295
+ `[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.\n Pass --force to overwrite existing templates.`,
296
+ );
297
+ return [];
298
+ }
299
+
300
+ const written: string[] = [];
301
+
302
+ for (const template of TEMPLATE_ROLES) {
303
+ const filePath = join(templatesDir, template.file);
304
+ const roleBody =
305
+ template.role === "implementer"
306
+ ? buildRoleTaskSection(template.role, template.variant)
307
+ : buildRoleTaskSection(template.role);
308
+ const content = TEMPLATE_HEADER + roleBody;
309
+ await Bun.write(filePath, content);
310
+ written.push(filePath);
311
+ }
312
+
313
+ console.log(`[OK] Written ${written.length} template files to nax/templates/:`);
314
+ for (const filePath of written) {
315
+ console.log(` - ${filePath.replace(`${workdir}/`, "")}`);
316
+ }
317
+
318
+ // Auto-wire prompts.overrides in nax.config.json
319
+ await autoWirePromptsConfig(workdir);
320
+
321
+ return written;
322
+ }
323
+
324
+ /**
325
+ * Auto-wire prompts.overrides in nax.config.json after template init.
326
+ *
327
+ * If nax.config.json exists and prompts.overrides is not already set,
328
+ * add the override paths. If overrides are already set, print a note.
329
+ * If nax.config.json doesn't exist, print manual instructions.
330
+ *
331
+ * @param workdir - Project working directory
332
+ */
333
+ async function autoWirePromptsConfig(workdir: string): Promise<void> {
334
+ const configPath = join(workdir, "nax.config.json");
335
+
336
+ // If config file doesn't exist, print manual instructions
337
+ if (!existsSync(configPath)) {
338
+ const exampleConfig = JSON.stringify(
339
+ {
340
+ prompts: {
341
+ overrides: {
342
+ "test-writer": "nax/templates/test-writer.md",
343
+ implementer: "nax/templates/implementer.md",
344
+ verifier: "nax/templates/verifier.md",
345
+ "single-session": "nax/templates/single-session.md",
346
+ },
347
+ },
348
+ },
349
+ null,
350
+ 2,
351
+ );
352
+ console.log(`\nNo nax.config.json found. To activate overrides, create nax/config.json with:\n${exampleConfig}`);
353
+ return;
354
+ }
355
+
356
+ // Read existing config
357
+ const configFile = Bun.file(configPath);
358
+ const configContent = await configFile.text();
359
+ const config = JSON.parse(configContent);
360
+
361
+ // Check if prompts.overrides is already set
362
+ if (config.prompts?.overrides && Object.keys(config.prompts.overrides).length > 0) {
363
+ console.log(
364
+ "[INFO] prompts.overrides already configured in nax.config.json. Skipping auto-wiring.\n" +
365
+ " To reset overrides, remove the prompts.overrides section and re-run this command.",
366
+ );
367
+ return;
368
+ }
369
+
370
+ // Build the override paths
371
+ const overrides = {
372
+ "test-writer": "nax/templates/test-writer.md",
373
+ implementer: "nax/templates/implementer.md",
374
+ verifier: "nax/templates/verifier.md",
375
+ "single-session": "nax/templates/single-session.md",
376
+ };
377
+
378
+ // Add or update prompts section
379
+ if (!config.prompts) {
380
+ config.prompts = {};
381
+ }
382
+ config.prompts.overrides = overrides;
383
+
384
+ // Write config with custom formatting that avoids 4-space indentation
385
+ // by putting the overrides object on a single line
386
+ const updatedConfig = formatConfigJson(config);
387
+ await Bun.write(configPath, updatedConfig);
388
+
389
+ console.log("[OK] Auto-wired prompts.overrides in nax.config.json");
390
+ }
391
+
392
+ /**
393
+ * Format config JSON with 2-space indentation, keeping overrides object inline.
394
+ *
395
+ * This avoids 4-space indentation by putting the overrides object on the same line.
396
+ *
397
+ * @param config - Configuration object
398
+ * @returns Formatted JSON string
399
+ */
400
+ function formatConfigJson(config: Record<string, unknown>): string {
401
+ const lines: string[] = ["{"];
402
+
403
+ const keys = Object.keys(config);
404
+ for (let i = 0; i < keys.length; i++) {
405
+ const key = keys[i];
406
+ const value = config[key];
407
+ const isLast = i === keys.length - 1;
408
+
409
+ if (key === "prompts" && typeof value === "object" && value !== null) {
410
+ // Special handling for prompts object - keep overrides inline
411
+ const promptsObj = value as Record<string, unknown>;
412
+ if (promptsObj.overrides) {
413
+ const overridesJson = JSON.stringify(promptsObj.overrides);
414
+ lines.push(` "${key}": { "overrides": ${overridesJson} }${isLast ? "" : ","}`);
415
+ } else {
416
+ lines.push(` "${key}": ${JSON.stringify(value)}${isLast ? "" : ","}`);
417
+ }
418
+ } else {
419
+ lines.push(` "${key}": ${JSON.stringify(value)}${isLast ? "" : ","}`);
420
+ }
421
+ }
422
+
423
+ lines.push("}");
424
+ return lines.join("\n");
425
+ }
426
+
243
427
  /**
244
428
  * Handle three-session TDD prompts by building separate prompts for each role.
245
429
  *
@@ -266,7 +450,7 @@ async function handleThreeSessionTddPrompts(
266
450
  .story(story)
267
451
  .context(ctx.contextMarkdown)
268
452
  .build(),
269
- PromptBuilder.for("verifier").withLoader(ctx.workdir, ctx.config).story(story).build(),
453
+ PromptBuilder.for("verifier").withLoader(ctx.workdir, ctx.config).story(story).context(ctx.contextMarkdown).build(),
270
454
  ]);
271
455
 
272
456
  const sessions = [
@@ -5,10 +5,10 @@
5
5
  * Extracts run header and footer formatting logic from runner.ts.
6
6
  */
7
7
 
8
- import path from "node:path";
9
8
  import chalk from "chalk";
10
9
  import type { RunSummary } from "../../logging";
11
10
  import { formatRunSummary } from "../../logging";
11
+ import { NAX_VERSION } from "../../version";
12
12
 
13
13
  export interface RunHeaderOptions {
14
14
  feature: string;
@@ -42,11 +42,9 @@ export async function outputRunHeader(options: RunHeaderOptions): Promise<void>
42
42
  return;
43
43
  }
44
44
 
45
- const pkg = await Bun.file(path.join(import.meta.dir, "..", "..", "..", "package.json")).json();
46
-
47
45
  console.log("");
48
46
  console.log(chalk.bold(chalk.blue("═".repeat(60))));
49
- console.log(chalk.bold(chalk.blue(` ▶ NAX v${pkg.version} — RUN STARTED`)));
47
+ console.log(chalk.bold(chalk.blue(` ▶ NAX v${NAX_VERSION} — RUN STARTED`)));
50
48
  console.log(chalk.blue("═".repeat(60)));
51
49
  console.log(` ${chalk.gray("Feature:")} ${chalk.cyan(feature)}`);
52
50
  console.log(` ${chalk.gray("Stories:")} ${chalk.cyan(`${totalStories} total, ${pendingStories} pending`)}`);
package/src/prd/index.ts CHANGED
@@ -49,6 +49,10 @@ export async function loadPRD(path: string): Promise<PRD> {
49
49
  story.escalations = story.escalations ?? [];
50
50
  story.dependencies = story.dependencies ?? [];
51
51
  story.tags = story.tags ?? [];
52
+ // Normalize aliases: "open" → "pending", "done" → "passed"
53
+ const rawStatus = story.status as string;
54
+ if (rawStatus === "open") story.status = "pending";
55
+ if (rawStatus === "done") story.status = "passed";
52
56
  story.status = story.status ?? "pending";
53
57
  story.acceptanceCriteria = story.acceptanceCriteria ?? [];
54
58
  story.storyPoints = story.storyPoints ?? 1;
@@ -90,15 +90,19 @@ export async function checkPendingStories(prd: PRD): Promise<Check> {
90
90
  /**
91
91
  * Check if optional commands are configured.
92
92
  */
93
- export async function checkOptionalCommands(config: NaxConfig): Promise<Check> {
93
+ export async function checkOptionalCommands(config: NaxConfig, workdir: string): Promise<Check> {
94
94
  const missing: string[] = [];
95
95
 
96
- if (!config.execution.lintCommand) {
97
- missing.push("lint");
98
- }
99
- if (!config.execution.typecheckCommand) {
100
- missing.push("typecheck");
101
- }
96
+ // Check quality.commands first, then execution config, then package.json fallback
97
+ const hasLint =
98
+ config.quality?.commands?.lint || config.execution?.lintCommand || (await hasPackageScript(workdir, "lint"));
99
+ const hasTypecheck =
100
+ config.quality?.commands?.typecheck ||
101
+ config.execution?.typecheckCommand ||
102
+ (await hasPackageScript(workdir, "typecheck"));
103
+
104
+ if (!hasLint) missing.push("lint");
105
+ if (!hasTypecheck) missing.push("typecheck");
102
106
 
103
107
  const passed = missing.length === 0;
104
108
 
@@ -110,6 +114,16 @@ export async function checkOptionalCommands(config: NaxConfig): Promise<Check> {
110
114
  };
111
115
  }
112
116
 
117
+ /** Check if package.json has a script by name */
118
+ async function hasPackageScript(workdir: string, name: string): Promise<boolean> {
119
+ try {
120
+ const pkg = await Bun.file(`${workdir}/package.json`).json();
121
+ return Boolean(pkg?.scripts?.[name]);
122
+ } catch {
123
+ return false;
124
+ }
125
+ }
126
+
113
127
  /**
114
128
  * Check if .gitignore covers nax runtime files.
115
129
  * Patterns: nax.lock, runs/, test/tmp/
@@ -141,7 +141,7 @@ export async function runPrecheck(
141
141
  () => checkClaudeMdExists(workdir),
142
142
  () => checkDiskSpace(),
143
143
  () => checkPendingStories(prd),
144
- () => checkOptionalCommands(config),
144
+ () => checkOptionalCommands(config, workdir),
145
145
  () => checkGitignoreCoversNax(workdir),
146
146
  () => checkPromptOverrideFiles(config, workdir),
147
147
  ];
@@ -12,6 +12,10 @@
12
12
 
13
13
  import type { NaxConfig } from "../config/types";
14
14
  import type { UserStory } from "../prd";
15
+ import { buildConventionsSection } from "./sections/conventions";
16
+ import { buildIsolationSection } from "./sections/isolation";
17
+ import { buildRoleTaskSection } from "./sections/role-task";
18
+ import { buildStorySection } from "./sections/story";
15
19
  import type { PromptOptions, PromptRole } from "./types";
16
20
 
17
21
  const SECTION_SEP = "\n\n---\n\n";
@@ -69,16 +73,17 @@ export class PromptBuilder {
69
73
  sections.push(`# CONSTITUTION (follow these rules strictly)\n\n${this._constitution}`);
70
74
  }
71
75
 
72
- // (2) Role task body — user override or default template
76
+ // (2) Role task body — user override or default section
73
77
  sections.push(await this._resolveRoleBody());
74
78
 
75
79
  // (3) Story context — non-overridable
76
80
  if (this._story) {
77
- sections.push(buildStoryContext(this._story));
81
+ sections.push(buildStorySection(this._story));
78
82
  }
79
83
 
80
84
  // (4) Isolation rules — non-overridable
81
- sections.push(buildIsolationRules(this._role, this._options));
85
+ const isolation = this._options.isolation as string | undefined;
86
+ sections.push(buildIsolationSection(this._role, isolation as "strict" | "lite" | undefined));
82
87
 
83
88
  // (5) Context markdown
84
89
  if (this._contextMd) {
@@ -86,7 +91,7 @@ export class PromptBuilder {
86
91
  }
87
92
 
88
93
  // (6) Conventions footer — non-overridable, always last
89
- sections.push(CONVENTIONS_FOOTER);
94
+ sections.push(buildConventionsSection());
90
95
 
91
96
  return sections.join(SECTION_SEP);
92
97
  }
@@ -107,72 +112,10 @@ export class PromptBuilder {
107
112
  return await file.text();
108
113
  }
109
114
  } catch {
110
- // fall through to default template
115
+ // fall through to default section
111
116
  }
112
117
  }
113
- return buildDefaultRoleBody(this._role, this._story?.title, this._options);
118
+ const variant = this._options.variant as "standard" | "lite" | undefined;
119
+ return buildRoleTaskSection(this._role, variant);
114
120
  }
115
121
  }
116
-
117
- // ---------------------------------------------------------------------------
118
- // Section builders (module-private)
119
- // ---------------------------------------------------------------------------
120
-
121
- function buildDefaultRoleBody(role: PromptRole, title = "", options: PromptOptions = {}): string {
122
- const variant = options.variant as string | undefined;
123
- switch (role) {
124
- case "test-writer":
125
- return `# Test Writer — "${title}"\n\nYour role: Write failing tests ONLY. Do NOT implement any source code.`;
126
- case "implementer":
127
- if (variant === "lite") {
128
- return `# Implementer (Lite) — "${title}"\n\nYour role: Write tests AND implement the feature in a single session.`;
129
- }
130
- return `# Implementer — "${title}"\n\nYour role: Make all failing tests pass.`;
131
- case "verifier":
132
- return `# Verifier — "${title}"\n\nYour role: Verify the implementation and tests.`;
133
- case "single-session":
134
- return `# Task — "${title}"\n\nYour role: Write tests AND implement the feature in a single session.`;
135
- }
136
- }
137
-
138
- function buildStoryContext(story: UserStory): string {
139
- return `# Story Context
140
-
141
- **Story:** ${story.title}
142
-
143
- **Description:**
144
- ${story.description}
145
-
146
- **Acceptance Criteria:**
147
- ${story.acceptanceCriteria.map((c, i) => `${i + 1}. ${c}`).join("\n")}`;
148
- }
149
-
150
- const TEST_FILTER_RULE =
151
- "When running tests, run ONLY test files related to your changes" +
152
- " (e.g. `bun test ./test/specific.test.ts`). NEVER run `bun test` without a file filter" +
153
- " — full suite output will flood your context window and cause failures.";
154
-
155
- function buildIsolationRules(role: PromptRole, options: PromptOptions = {}): string {
156
- const header = "# Isolation Rules\n\n";
157
- const footer = `\n\n${TEST_FILTER_RULE}`;
158
- const isolation = options.isolation as string | undefined;
159
-
160
- switch (role) {
161
- case "test-writer":
162
- if (isolation === "lite") {
163
- return `${header}isolation scope: Primarily create test files in the test/ directory. You MAY read source files and MAY import from source files to ensure correct types/interfaces. Stub-only src/ files are allowed (empty exports, no logic). Tests must fail for the right reasons (feature not implemented).${footer}`;
164
- }
165
- return `${header}isolation scope: Only create or modify files in the test/ directory. Tests must fail because the feature is not yet implemented. Do NOT modify any source files in src/.${footer}`;
166
- case "implementer":
167
- return `${header}isolation scope: Implement source code in src/ to make the tests pass. Do NOT modify test files. Run tests frequently to track progress.${footer}`;
168
- case "verifier":
169
- return `${header}isolation scope: Verify and fix only — do not change behaviour unless it violates acceptance criteria. Ensure all tests pass and all criteria are met.${footer}`;
170
- case "single-session":
171
- return `${header}isolation scope: Write tests first (test/ directory), then implement (src/ directory). All tests must pass by the end.${footer}`;
172
- }
173
- }
174
-
175
- const CONVENTIONS_FOOTER =
176
- "# Conventions\n\n" +
177
- "Follow existing code patterns and conventions. Write idiomatic, maintainable code." +
178
- " Commit your changes when done.";
@@ -1,9 +1,15 @@
1
1
  /**
2
2
  * Isolation Rules Section
3
3
  *
4
- * Generates isolation rules based on mode:
5
- * - strict: No access to src/ files
6
- * - lite: May read src/ and create minimal stubs
4
+ * Generates isolation rules for all 4 roles:
5
+ * - test-writer: Strict/Lite modes for test-first TDD
6
+ * - implementer: Implement source while respecting test integrity
7
+ * - verifier: Read-only inspection
8
+ * - single-session: Both test/ and src/ modification allowed
9
+ *
10
+ * Backwards compatible: also accepts old API (mode only)
11
+ * - buildIsolationSection("strict") → test-writer, strict
12
+ * - buildIsolationSection("lite") → test-writer, lite
7
13
  */
8
14
 
9
15
  const TEST_FILTER_RULE =
@@ -11,14 +17,38 @@ const TEST_FILTER_RULE =
11
17
  "(e.g. `bun test ./test/specific.test.ts`). NEVER run `bun test` without a file filter " +
12
18
  "— full suite output will flood your context window and cause failures.";
13
19
 
14
- export function buildIsolationSection(mode: "strict" | "lite"): string {
20
+ export function buildIsolationSection(
21
+ roleOrMode: "implementer" | "test-writer" | "verifier" | "single-session" | "strict" | "lite",
22
+ mode?: "strict" | "lite",
23
+ ): string {
24
+ // Old API support: buildIsolationSection("strict") or buildIsolationSection("lite")
25
+ if ((roleOrMode === "strict" || roleOrMode === "lite") && mode === undefined) {
26
+ return buildIsolationSection("test-writer", roleOrMode);
27
+ }
28
+
29
+ const role = roleOrMode as "implementer" | "test-writer" | "verifier" | "single-session";
30
+
15
31
  const header = "# Isolation Rules\n\n";
16
32
  const footer = `\n\n${TEST_FILTER_RULE}`;
17
33
 
18
- if (mode === "strict") {
19
- return `${header}isolation scope: Isolation scope: Only create or modify files in the test/ directory. Tests must fail because the feature is not yet implemented. Do NOT modify any source files in src/.${footer}`;
34
+ if (role === "test-writer") {
35
+ const m = mode ?? "strict";
36
+ if (m === "strict") {
37
+ return `${header}isolation scope: Only create or modify files in the test/ directory. Tests must fail because the feature is not yet implemented. Do NOT modify any source files in src/.${footer}`;
38
+ }
39
+
40
+ // lite mode for test-writer
41
+ return `${header}isolation scope: Create test files in test/. MAY read src/ files and MAY import from src/ to ensure correct types/interfaces. May create minimal stubs in src/ if needed to make imports work, but do NOT implement real logic.${footer}`;
42
+ }
43
+
44
+ if (role === "implementer") {
45
+ return `${header}isolation scope: Implement source code in src/ to make tests pass. Do not modify test files. Run tests frequently to track progress.${footer}`;
46
+ }
47
+
48
+ if (role === "verifier") {
49
+ return `${header}isolation scope: Read-only inspection. Review all test results, implementation code, and acceptance criteria compliance. You MAY write a verdict file (.nax-verifier-verdict.json) and apply legitimate fixes if needed.${footer}`;
20
50
  }
21
51
 
22
- // lite mode
23
- return `${header}isolation scope: Create test files in test/. MAY read src/ files and MAY import from src/ to ensure correct types/interfaces. May create minimal stubs in src/ if needed to make imports work, but do NOT implement real logic.${footer}`;
52
+ // single-session role
53
+ return `${header}isolation scope: Create test files in test/ directory, then implement source code in src/ to make tests pass. Both directories are in scope for this session.${footer}`;
24
54
  }
@@ -1,34 +1,94 @@
1
1
  /**
2
2
  * Role-Task Section
3
3
  *
4
- * Generates role definition for:
5
- * - standard: Make failing tests pass (implementer role)
6
- * - lite: Write tests first then implement (combined role)
4
+ * Generates role definition for all 4 roles in nax prompt orchestration:
5
+ * - implementer: Make failing tests pass (standard/lite variants)
6
+ * - test-writer: Write tests first (RED phase)
7
+ * - verifier: Review and verify implementation
8
+ * - single-session: Write tests AND implement in one session
9
+ *
10
+ * Backwards compatible: also accepts old API (variant only)
11
+ * - buildRoleTaskSection("standard") → implementer, standard
12
+ * - buildRoleTaskSection("lite") → implementer, lite
7
13
  */
8
14
 
9
- export function buildRoleTaskSection(variant: "standard" | "lite"): string {
10
- if (variant === "standard") {
15
+ export function buildRoleTaskSection(
16
+ roleOrVariant: "implementer" | "test-writer" | "verifier" | "single-session" | "standard" | "lite",
17
+ variant?: "standard" | "lite",
18
+ ): string {
19
+ // Old API support: buildRoleTaskSection("standard") or buildRoleTaskSection("lite")
20
+ if ((roleOrVariant === "standard" || roleOrVariant === "lite") && variant === undefined) {
21
+ return buildRoleTaskSection("implementer", roleOrVariant);
22
+ }
23
+
24
+ const role = roleOrVariant as "implementer" | "test-writer" | "verifier" | "single-session";
25
+
26
+ if (role === "implementer") {
27
+ const v = variant ?? "standard";
28
+ if (v === "standard") {
29
+ return (
30
+ "# Role: Implementer\n\n" +
31
+ "Your task: make failing tests pass.\n\n" +
32
+ "Instructions:\n" +
33
+ "- Implement source code in src/ to make tests pass\n" +
34
+ "- Do NOT modify test files\n" +
35
+ "- Run tests frequently to track progress\n" +
36
+ "- When all tests are green, stage and commit ALL changed files with: git commit -m 'feat: <description>'\n" +
37
+ "- Goal: all tests green, all changes committed"
38
+ );
39
+ }
40
+
41
+ // lite variant
42
+ return (
43
+ "# Role: Implementer (Lite)\n\n" +
44
+ "Your task: Write tests AND implement the feature in a single session.\n\n" +
45
+ "Instructions:\n" +
46
+ "- Write tests first (test/ directory), then implement (src/ directory)\n" +
47
+ "- All tests must pass by the end\n" +
48
+ "- Use Bun test (describe/test/expect)\n" +
49
+ "- When all tests are green, stage and commit ALL changed files with: git commit -m 'feat: <description>'\n" +
50
+ "- Goal: all tests green, all criteria met, all changes committed"
51
+ );
52
+ }
53
+
54
+ if (role === "test-writer") {
55
+ return (
56
+ "# Role: Test-Writer\n\n" +
57
+ "Your task: Write comprehensive failing tests for the feature.\n\n" +
58
+ "Instructions:\n" +
59
+ "- Create test files in test/ directory that cover acceptance criteria\n" +
60
+ "- Tests must fail initially (RED phase) — the feature is not yet implemented\n" +
61
+ "- Use Bun test (describe/test/expect)\n" +
62
+ "- Write clear test names that document expected behavior\n" +
63
+ "- Focus on behavior, not implementation details\n" +
64
+ "- Goal: comprehensive test suite ready for implementation"
65
+ );
66
+ }
67
+
68
+ if (role === "verifier") {
11
69
  return (
12
- "# Role: Implementer\n\n" +
13
- "Your task: make failing tests pass.\n\n" +
70
+ "# Role: Verifier\n\n" +
71
+ "Your task: Review and verify the implementation against acceptance criteria.\n\n" +
14
72
  "Instructions:\n" +
15
- "- Implement source code in src/ to make tests pass\n" +
16
- "- Do NOT modify test files\n" +
17
- "- Run tests frequently to track progress\n" +
18
- "- When all tests are green, stage and commit ALL changed files with git commit -m 'feat: <description>'\n" +
19
- "- Goal: all tests green, all changes committed"
73
+ "- Review all test results verify tests pass\n" +
74
+ "- Check that implementation meets all acceptance criteria\n" +
75
+ "- Inspect code quality, error handling, and edge cases\n" +
76
+ "- Verify test modifications (if any) are legitimate fixes\n" +
77
+ "- Write a detailed verdict with reasoning\n" +
78
+ "- Goal: provide comprehensive verification and quality assurance"
20
79
  );
21
80
  }
22
81
 
23
- // lite variant
82
+ // single-session role
24
83
  return (
25
- "# Role: Implementer (Lite)\n\n" +
26
- "Your task: Write tests AND implement the feature in a single session.\n\n" +
84
+ "# Role: Single-Session\n\n" +
85
+ "Your task: Write tests AND implement the feature in a single focused session.\n\n" +
27
86
  "Instructions:\n" +
28
- "- Write tests first (test/ directory), then implement (src/ directory)\n" +
29
- "- All tests must pass by the end\n" +
87
+ "- Phase 1: Write comprehensive tests (test/ directory)\n" +
88
+ "- Phase 2: Implement to make all tests pass (src/ directory)\n" +
30
89
  "- Use Bun test (describe/test/expect)\n" +
31
- "- When all tests are green, stage and commit ALL changed files with git commit -m 'feat: <description>'\n" +
32
- "- Goal: all tests green, all criteria met, all changes committed"
90
+ "- Run tests frequently throughout implementation\n" +
91
+ "- When all tests are green, stage and commit ALL changed files with: git commit -m 'feat: <description>'\n" +
92
+ "- Goal: all tests passing, all changes committed, full story complete"
33
93
  );
34
94
  }
@@ -103,7 +103,11 @@ export async function runTddSession(
103
103
  .build();
104
104
  break;
105
105
  case "verifier":
106
- prompt = await PromptBuilder.for("verifier").withLoader(workdir, config).story(story).build();
106
+ prompt = await PromptBuilder.for("verifier")
107
+ .withLoader(workdir, config)
108
+ .story(story)
109
+ .context(contextMarkdown)
110
+ .build();
107
111
  break;
108
112
  }
109
113
 
@@ -1,6 +0,0 @@
1
- /**
2
- * Default template body for the implementer role.
3
- * Stub — logic not yet implemented.
4
- */
5
-
6
- export const defaultTemplate = "";