@nathapp/nax 0.27.0 → 0.28.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.
Files changed (51) hide show
  1. package/CLAUDE.md +38 -8
  2. package/docs/ROADMAP.md +42 -17
  3. package/nax/features/prompt-builder/prd.json +152 -0
  4. package/nax/features/prompt-builder/progress.txt +3 -0
  5. package/nax/status.json +14 -14
  6. package/package.json +1 -1
  7. package/src/cli/config.ts +40 -1
  8. package/src/cli/prompts.ts +18 -6
  9. package/src/config/defaults.ts +1 -0
  10. package/src/config/schemas.ts +10 -0
  11. package/src/config/types.ts +7 -0
  12. package/src/pipeline/runner.ts +2 -1
  13. package/src/pipeline/stages/autofix.ts +5 -0
  14. package/src/pipeline/stages/execution.ts +5 -0
  15. package/src/pipeline/stages/prompt.ts +13 -4
  16. package/src/pipeline/stages/rectify.ts +5 -0
  17. package/src/pipeline/stages/regression.ts +6 -1
  18. package/src/pipeline/stages/verify.ts +2 -1
  19. package/src/pipeline/types.ts +9 -0
  20. package/src/precheck/checks-warnings.ts +37 -0
  21. package/src/precheck/checks.ts +1 -0
  22. package/src/precheck/index.ts +14 -7
  23. package/src/prompts/builder.ts +178 -0
  24. package/src/prompts/index.ts +2 -0
  25. package/src/prompts/loader.ts +43 -0
  26. package/src/prompts/sections/conventions.ts +15 -0
  27. package/src/prompts/sections/index.ts +11 -0
  28. package/src/prompts/sections/isolation.ts +24 -0
  29. package/src/prompts/sections/role-task.ts +32 -0
  30. package/src/prompts/sections/story.ts +13 -0
  31. package/src/prompts/sections/verdict.ts +70 -0
  32. package/src/prompts/templates/implementer.ts +6 -0
  33. package/src/prompts/templates/single-session.ts +6 -0
  34. package/src/prompts/templates/test-writer.ts +6 -0
  35. package/src/prompts/templates/verifier.ts +6 -0
  36. package/src/prompts/types.ts +21 -0
  37. package/src/tdd/orchestrator.ts +11 -1
  38. package/src/tdd/rectification-gate.ts +18 -13
  39. package/src/tdd/session-runner.ts +12 -12
  40. package/src/tdd/types.ts +2 -0
  41. package/test/integration/cli/cli-config-prompts-explain.test.ts +74 -0
  42. package/test/integration/prompts/pb-004-migration.test.ts +523 -0
  43. package/test/unit/precheck/checks-warnings.test.ts +114 -0
  44. package/test/unit/prompts/builder.test.ts +258 -0
  45. package/test/unit/prompts/loader.test.ts +355 -0
  46. package/test/unit/prompts/sections/conventions.test.ts +30 -0
  47. package/test/unit/prompts/sections/isolation.test.ts +35 -0
  48. package/test/unit/prompts/sections/role-task.test.ts +40 -0
  49. package/test/unit/prompts/sections/sections.test.ts +238 -0
  50. package/test/unit/prompts/sections/story.test.ts +45 -0
  51. package/test/unit/prompts/sections/verdict.test.ts +58 -0
@@ -26,12 +26,17 @@ export const regressionStage: PipelineStage = {
26
26
  const mode = ctx.config.execution.regressionGate?.mode ?? "deferred";
27
27
  if (mode !== "per-story") return false;
28
28
  // Only run when verify passed (or was skipped/not set)
29
- // Only run when verify passed (or was skipped/not set)
30
29
  if (ctx.verifyResult && !ctx.verifyResult.success) return false;
31
30
  const gateEnabled = ctx.config.execution.regressionGate?.enabled ?? true;
32
31
  return gateEnabled;
33
32
  },
34
33
 
34
+ skipReason(ctx: PipelineContext): string {
35
+ const mode = ctx.config.execution.regressionGate?.mode ?? "deferred";
36
+ if (mode !== "per-story") return `not needed (regression mode is '${mode}', not 'per-story')`;
37
+ return "disabled (regression gate not enabled in config)";
38
+ },
39
+
35
40
  async execute(ctx: PipelineContext): Promise<StageResult> {
36
41
  const logger = getLogger();
37
42
  const testCommand = ctx.config.review?.commands?.test ?? ctx.config.quality.commands.test ?? "bun test";
@@ -45,7 +45,8 @@ function buildScopedCommand(testFiles: string[], baseCommand: string, testScoped
45
45
 
46
46
  export const verifyStage: PipelineStage = {
47
47
  name: "verify",
48
- enabled: () => true,
48
+ enabled: (ctx: PipelineContext) => !ctx.fullSuiteGatePassed,
49
+ skipReason: () => "not needed (full-suite gate already passed)",
49
50
 
50
51
  async execute(ctx: PipelineContext): Promise<StageResult> {
51
52
  const logger = getLogger();
@@ -108,6 +108,8 @@ export interface PipelineContext {
108
108
  retryAsLite?: boolean;
109
109
  /** Failure category from TDD orchestrator (set by executionStage on TDD failure) */
110
110
  tddFailureCategory?: FailureCategory;
111
+ /** Set to true when TDD full-suite gate already passed — verify stage skips to avoid redundant run (BUG-054) */
112
+ fullSuiteGatePassed?: boolean;
111
113
  }
112
114
 
113
115
  /**
@@ -167,6 +169,13 @@ export interface PipelineStage {
167
169
  */
168
170
  enabled: (ctx: PipelineContext) => boolean;
169
171
 
172
+ /**
173
+ * Optional human-readable reason why the stage was skipped.
174
+ * Distinguishes "not needed" (conditions not met) from "disabled" (config).
175
+ * Used by the pipeline runner for better observability (BUG-055).
176
+ */
177
+ skipReason?: (ctx: PipelineContext) => string;
178
+
170
179
  /**
171
180
  * Execute the stage logic.
172
181
  *
@@ -140,3 +140,40 @@ export async function checkGitignoreCoversNax(workdir: string): Promise<Check> {
140
140
  message: passed ? ".gitignore covers nax runtime files" : `.gitignore missing patterns: ${missing.join(", ")}`,
141
141
  };
142
142
  }
143
+
144
+ /**
145
+ * Check if configured prompt override files exist.
146
+ *
147
+ * For each role in config.prompts.overrides, verify the file exists.
148
+ * Emits one warning per missing file (non-blocking).
149
+ * Returns empty array if config.prompts is absent or overrides is empty.
150
+ *
151
+ * @param config - nax configuration
152
+ * @param workdir - working directory for resolving relative paths
153
+ * @returns Array of warning checks (one per missing file)
154
+ */
155
+ export async function checkPromptOverrideFiles(config: NaxConfig, workdir: string): Promise<Check[]> {
156
+ // Skip if prompts config is absent or overrides is empty
157
+ if (!config.prompts?.overrides || Object.keys(config.prompts.overrides).length === 0) {
158
+ return [];
159
+ }
160
+
161
+ const checks: Check[] = [];
162
+
163
+ // Check each override file
164
+ for (const [role, relativePath] of Object.entries(config.prompts.overrides)) {
165
+ const resolvedPath = `${workdir}/${relativePath}`;
166
+ const exists = existsSync(resolvedPath);
167
+
168
+ if (!exists) {
169
+ checks.push({
170
+ name: `prompt-override-${role}`,
171
+ tier: "warning",
172
+ passed: false,
173
+ message: `Prompt override file not found for role ${role}: ${resolvedPath}`,
174
+ });
175
+ }
176
+ }
177
+
178
+ return checks;
179
+ }
@@ -27,4 +27,5 @@ export {
27
27
  checkPendingStories,
28
28
  checkOptionalCommands,
29
29
  checkGitignoreCoversNax,
30
+ checkPromptOverrideFiles,
30
31
  } from "./checks-warnings";
@@ -20,6 +20,7 @@ import {
20
20
  checkOptionalCommands,
21
21
  checkPRDValid,
22
22
  checkPendingStories,
23
+ checkPromptOverrideFiles,
23
24
  checkStaleLock,
24
25
  checkTestCommand,
25
26
  checkTypecheckCommand,
@@ -142,19 +143,25 @@ export async function runPrecheck(
142
143
  () => checkPendingStories(prd),
143
144
  () => checkOptionalCommands(config),
144
145
  () => checkGitignoreCoversNax(workdir),
146
+ () => checkPromptOverrideFiles(config, workdir),
145
147
  ];
146
148
 
147
149
  for (const checkFn of tier2Checks) {
148
150
  const result = await checkFn();
149
151
 
150
- if (format === "human") {
151
- printCheckResult(result);
152
- }
152
+ // Handle both single checks and arrays of checks
153
+ const checksToProcess = Array.isArray(result) ? result : [result];
154
+
155
+ for (const check of checksToProcess) {
156
+ if (format === "human") {
157
+ printCheckResult(check);
158
+ }
153
159
 
154
- if (result.passed) {
155
- passed.push(result);
156
- } else {
157
- warnings.push(result);
160
+ if (check.passed) {
161
+ passed.push(check);
162
+ } else {
163
+ warnings.push(check);
164
+ }
158
165
  }
159
166
  }
160
167
 
@@ -0,0 +1,178 @@
1
+ /**
2
+ * PromptBuilder — unified entry point for composing agent prompts.
3
+ *
4
+ * Composes prompts from ordered sections:
5
+ * (1) Constitution
6
+ * (2) Role task body (user override OR default template)
7
+ * (3) Story context [non-overridable]
8
+ * (4) Isolation rules [non-overridable]
9
+ * (5) Context markdown
10
+ * (6) Conventions footer [non-overridable, always last]
11
+ */
12
+
13
+ import type { NaxConfig } from "../config/types";
14
+ import type { UserStory } from "../prd";
15
+ import type { PromptOptions, PromptRole } from "./types";
16
+
17
+ const SECTION_SEP = "\n\n---\n\n";
18
+
19
+ export class PromptBuilder {
20
+ private _role: PromptRole;
21
+ private _options: PromptOptions;
22
+ private _story: UserStory | undefined;
23
+ private _contextMd: string | undefined;
24
+ private _constitution: string | undefined;
25
+ private _overridePath: string | undefined;
26
+ private _workdir: string | undefined;
27
+ private _loaderConfig: NaxConfig | undefined;
28
+
29
+ private constructor(role: PromptRole, options: PromptOptions = {}) {
30
+ this._role = role;
31
+ this._options = options;
32
+ }
33
+
34
+ static for(role: PromptRole, options?: PromptOptions): PromptBuilder {
35
+ return new PromptBuilder(role, options ?? {});
36
+ }
37
+
38
+ story(story: UserStory): PromptBuilder {
39
+ this._story = story;
40
+ return this;
41
+ }
42
+
43
+ context(md: string | undefined): PromptBuilder {
44
+ if (md) this._contextMd = md;
45
+ return this;
46
+ }
47
+
48
+ constitution(c: string | undefined): PromptBuilder {
49
+ if (c) this._constitution = c;
50
+ return this;
51
+ }
52
+
53
+ override(path: string): PromptBuilder {
54
+ this._overridePath = path;
55
+ return this;
56
+ }
57
+
58
+ withLoader(workdir: string, config: NaxConfig): PromptBuilder {
59
+ this._workdir = workdir;
60
+ this._loaderConfig = config;
61
+ return this;
62
+ }
63
+
64
+ async build(): Promise<string> {
65
+ const sections: string[] = [];
66
+
67
+ // (1) Constitution
68
+ if (this._constitution) {
69
+ sections.push(`# CONSTITUTION (follow these rules strictly)\n\n${this._constitution}`);
70
+ }
71
+
72
+ // (2) Role task body — user override or default template
73
+ sections.push(await this._resolveRoleBody());
74
+
75
+ // (3) Story context — non-overridable
76
+ if (this._story) {
77
+ sections.push(buildStoryContext(this._story));
78
+ }
79
+
80
+ // (4) Isolation rules — non-overridable
81
+ sections.push(buildIsolationRules(this._role, this._options));
82
+
83
+ // (5) Context markdown
84
+ if (this._contextMd) {
85
+ sections.push(this._contextMd);
86
+ }
87
+
88
+ // (6) Conventions footer — non-overridable, always last
89
+ sections.push(CONVENTIONS_FOOTER);
90
+
91
+ return sections.join(SECTION_SEP);
92
+ }
93
+
94
+ private async _resolveRoleBody(): Promise<string> {
95
+ // withLoader takes priority over explicit override path
96
+ if (this._workdir && this._loaderConfig) {
97
+ const { loadOverride } = await import("./loader");
98
+ const content = await loadOverride(this._role, this._workdir, this._loaderConfig);
99
+ if (content !== null) {
100
+ return content;
101
+ }
102
+ }
103
+ if (this._overridePath) {
104
+ try {
105
+ const file = Bun.file(this._overridePath);
106
+ if (await file.exists()) {
107
+ return await file.text();
108
+ }
109
+ } catch {
110
+ // fall through to default template
111
+ }
112
+ }
113
+ return buildDefaultRoleBody(this._role, this._story?.title, this._options);
114
+ }
115
+ }
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.";
@@ -0,0 +1,2 @@
1
+ export { PromptBuilder } from "./builder";
2
+ export type { PromptRole, PromptSection, PromptOptions } from "./types";
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Prompt Override Loader
3
+ *
4
+ * Resolves and reads user-supplied override files relative to workdir.
5
+ */
6
+
7
+ import { join } from "node:path";
8
+ import type { NaxConfig } from "../config/types";
9
+ import type { PromptRole } from "./types";
10
+
11
+ /**
12
+ * Load a user override for the given role from the path specified in config.
13
+ *
14
+ * @param role - The prompt role
15
+ * @param workdir - The project working directory
16
+ * @param config - The merged NaxConfig
17
+ * @returns The override file contents, or null if absent/missing
18
+ * @throws Error when file path is set but file is unreadable (e.g. permissions error)
19
+ */
20
+ export async function loadOverride(role: PromptRole, workdir: string, config: NaxConfig): Promise<string | null> {
21
+ const overridePath = config.prompts?.overrides?.[role];
22
+
23
+ if (!overridePath) {
24
+ return null;
25
+ }
26
+
27
+ const absolutePath = join(workdir, overridePath);
28
+ const file = Bun.file(absolutePath);
29
+
30
+ if (!(await file.exists())) {
31
+ return null;
32
+ }
33
+
34
+ try {
35
+ return await file.text();
36
+ } catch (err) {
37
+ throw new Error(
38
+ `Cannot read prompt override for role "${role}" at "${absolutePath}": ${
39
+ err instanceof Error ? err.message : String(err)
40
+ }`,
41
+ );
42
+ }
43
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Conventions Section
3
+ *
4
+ * Includes bun test scoping warning and commit message instructions (non-overridable).
5
+ */
6
+
7
+ export function buildConventionsSection(): string {
8
+ return (
9
+ "# Conventions\n\n" +
10
+ "Follow existing code patterns and conventions. Write idiomatic, maintainable code.\n\n" +
11
+ "When running tests, run ONLY test files related to your changes (e.g. `bun test ./test/specific.test.ts`). " +
12
+ "NEVER run `bun test` without a file filter — full suite output will flood your context window and cause failures.\n\n" +
13
+ "Commit your changes when done using conventional commit format (e.g. `feat:`, `fix:`, `test:`)."
14
+ );
15
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Prompt Sections
3
+ *
4
+ * Non-overridable section builders for the PromptBuilder.
5
+ */
6
+
7
+ export { buildIsolationSection } from "./isolation";
8
+ export { buildRoleTaskSection } from "./role-task";
9
+ export { buildStorySection } from "./story";
10
+ export { buildVerdictSection } from "./verdict";
11
+ export { buildConventionsSection } from "./conventions";
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Isolation Rules Section
3
+ *
4
+ * Generates isolation rules based on mode:
5
+ * - strict: No access to src/ files
6
+ * - lite: May read src/ and create minimal stubs
7
+ */
8
+
9
+ const TEST_FILTER_RULE =
10
+ "When running tests, run ONLY test files related to your changes " +
11
+ "(e.g. `bun test ./test/specific.test.ts`). NEVER run `bun test` without a file filter " +
12
+ "— full suite output will flood your context window and cause failures.";
13
+
14
+ export function buildIsolationSection(mode: "strict" | "lite"): string {
15
+ const header = "# Isolation Rules\n\n";
16
+ const footer = `\n\n${TEST_FILTER_RULE}`;
17
+
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}`;
20
+ }
21
+
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}`;
24
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Role-Task Section
3
+ *
4
+ * Generates role definition for:
5
+ * - standard: Make failing tests pass (implementer role)
6
+ * - lite: Write tests first then implement (combined role)
7
+ */
8
+
9
+ export function buildRoleTaskSection(variant: "standard" | "lite"): string {
10
+ if (variant === "standard") {
11
+ return (
12
+ "# Role: Implementer\n\n" +
13
+ "Your task: make failing tests pass.\n\n" +
14
+ "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
+ "- Goal: all tests green"
19
+ );
20
+ }
21
+
22
+ // lite variant
23
+ return (
24
+ "# Role: Implementer (Lite)\n\n" +
25
+ "Your task: Write tests AND implement the feature in a single session.\n\n" +
26
+ "Instructions:\n" +
27
+ "- Write tests first (test/ directory), then implement (src/ directory)\n" +
28
+ "- All tests must pass by the end\n" +
29
+ "- Use Bun test (describe/test/expect)\n" +
30
+ "- Goal: all tests green, all criteria met"
31
+ );
32
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Story Section
3
+ *
4
+ * Formats story title, description, and numbered acceptance criteria.
5
+ */
6
+
7
+ import type { UserStory } from "../../prd/types";
8
+
9
+ export function buildStorySection(story: UserStory): string {
10
+ const criteria = story.acceptanceCriteria.map((c, i) => `${i + 1}. ${c}`).join("\n");
11
+
12
+ return `# Story Context\n\n**Story:** ${story.title}\n\n**Description:**\n${story.description}\n\n**Acceptance Criteria:**\n${criteria}`;
13
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Verdict Section
3
+ *
4
+ * Verifier verdict JSON schema instructions (non-overridable).
5
+ * Provides instructions for writing the .nax-verifier-verdict.json file.
6
+ */
7
+
8
+ import type { UserStory } from "../../prd/types";
9
+
10
+ export function buildVerdictSection(story: UserStory): string {
11
+ return `# Verdict Instructions
12
+
13
+ ## Write Verdict File
14
+
15
+ After completing your verification, you **MUST** write a verdict file at the **project root**:
16
+
17
+ **File:** \`.nax-verifier-verdict.json\`
18
+
19
+ Set \`approved: true\` when ALL of these conditions are met:
20
+ - All tests pass
21
+ - Implementation is clean and follows conventions
22
+ - All acceptance criteria met
23
+ - Any test modifications by implementer are legitimate fixes
24
+
25
+ Set \`approved: false\` when ANY of these conditions are true:
26
+ - Tests are failing and you cannot fix them
27
+ - The implementer loosened test assertions to mask bugs
28
+ - Critical acceptance criteria are not met
29
+ - Code quality is poor (security issues, severe bugs, etc.)
30
+
31
+ **Full JSON schema example** (fill in all fields with real values):
32
+
33
+ \`\`\`json
34
+ {
35
+ "version": 1,
36
+ "approved": true,
37
+ "tests": {
38
+ "allPassing": true,
39
+ "passCount": 42,
40
+ "failCount": 0
41
+ },
42
+ "testModifications": {
43
+ "detected": false,
44
+ "files": [],
45
+ "legitimate": true,
46
+ "reasoning": "No test files were modified by the implementer"
47
+ },
48
+ "acceptanceCriteria": {
49
+ "allMet": true,
50
+ "criteria": [
51
+ { "criterion": "Example criterion", "met": true }
52
+ ]
53
+ },
54
+ "quality": {
55
+ "rating": "good",
56
+ "issues": []
57
+ },
58
+ "fixes": [],
59
+ "reasoning": "All tests pass, implementation is clean, all acceptance criteria are met."
60
+ }
61
+ \`\`\`
62
+
63
+ **Field notes:**
64
+ - \`quality.rating\` must be one of: \`"good"\`, \`"acceptable"\`, \`"poor"\`
65
+ - \`testModifications.files\` — list any test files the implementer changed
66
+ - \`fixes\` — list any fixes you applied yourself during this verification session
67
+ - \`reasoning\` — brief summary of your overall assessment
68
+
69
+ When done, commit any fixes with message: "fix: verify and adjust ${story.title}"`;
70
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Default template body for the implementer role.
3
+ * Stub — logic not yet implemented.
4
+ */
5
+
6
+ export const defaultTemplate = "";
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Default template body for the single-session role.
3
+ * Stub — logic not yet implemented.
4
+ */
5
+
6
+ export const defaultTemplate = "";
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Default template body for the test-writer role.
3
+ * Stub — logic not yet implemented.
4
+ */
5
+
6
+ export const defaultTemplate = "";
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Default template body for the verifier role.
3
+ * Stub — logic not yet implemented.
4
+ */
5
+
6
+ export const defaultTemplate = "";
@@ -0,0 +1,21 @@
1
+ /**
2
+ * PromptBuilder Types
3
+ *
4
+ * Shared types for the unified prompt building system.
5
+ */
6
+
7
+ /** Role determining which default template body to use */
8
+ export type PromptRole = "test-writer" | "implementer" | "verifier" | "single-session";
9
+
10
+ /** A single section of a composed prompt */
11
+ export interface PromptSection {
12
+ /** Unique section identifier */
13
+ id: string;
14
+ /** Section content */
15
+ content: string;
16
+ /** Whether this section can be removed by user override */
17
+ overridable: boolean;
18
+ }
19
+
20
+ /** Options passed to PromptBuilder.for() */
21
+ export type PromptOptions = Record<string, unknown>;
@@ -255,7 +255,16 @@ export async function runThreeSessionTdd(options: ThreeSessionTddOptions): Promi
255
255
  }
256
256
 
257
257
  // Full-Suite Gate (v0.11 Rectification)
258
- await runFullSuiteGate(story, config, workdir, agent, implementerTier, contextMarkdown, lite, logger);
258
+ const fullSuiteGatePassed = await runFullSuiteGate(
259
+ story,
260
+ config,
261
+ workdir,
262
+ agent,
263
+ implementerTier,
264
+ contextMarkdown,
265
+ lite,
266
+ logger,
267
+ );
259
268
 
260
269
  // Session 3: Verifier
261
270
  const session3Ref = (await captureGitRef(workdir)) ?? "HEAD";
@@ -379,5 +388,6 @@ export async function runThreeSessionTdd(options: ThreeSessionTddOptions): Promi
379
388
  verdict,
380
389
  totalCost,
381
390
  lite,
391
+ fullSuiteGatePassed,
382
392
  };
383
393
  }