@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.
- package/CLAUDE.md +38 -8
- package/docs/ROADMAP.md +42 -17
- package/nax/features/prompt-builder/prd.json +152 -0
- package/nax/features/prompt-builder/progress.txt +3 -0
- package/nax/status.json +14 -14
- package/package.json +1 -1
- package/src/cli/config.ts +40 -1
- package/src/cli/prompts.ts +18 -6
- package/src/config/defaults.ts +1 -0
- package/src/config/schemas.ts +10 -0
- package/src/config/types.ts +7 -0
- package/src/pipeline/runner.ts +2 -1
- package/src/pipeline/stages/autofix.ts +5 -0
- package/src/pipeline/stages/execution.ts +5 -0
- package/src/pipeline/stages/prompt.ts +13 -4
- package/src/pipeline/stages/rectify.ts +5 -0
- package/src/pipeline/stages/regression.ts +6 -1
- package/src/pipeline/stages/verify.ts +2 -1
- package/src/pipeline/types.ts +9 -0
- package/src/precheck/checks-warnings.ts +37 -0
- package/src/precheck/checks.ts +1 -0
- package/src/precheck/index.ts +14 -7
- package/src/prompts/builder.ts +178 -0
- package/src/prompts/index.ts +2 -0
- package/src/prompts/loader.ts +43 -0
- package/src/prompts/sections/conventions.ts +15 -0
- package/src/prompts/sections/index.ts +11 -0
- package/src/prompts/sections/isolation.ts +24 -0
- package/src/prompts/sections/role-task.ts +32 -0
- package/src/prompts/sections/story.ts +13 -0
- package/src/prompts/sections/verdict.ts +70 -0
- package/src/prompts/templates/implementer.ts +6 -0
- package/src/prompts/templates/single-session.ts +6 -0
- package/src/prompts/templates/test-writer.ts +6 -0
- package/src/prompts/templates/verifier.ts +6 -0
- package/src/prompts/types.ts +21 -0
- package/src/tdd/orchestrator.ts +11 -1
- package/src/tdd/rectification-gate.ts +18 -13
- package/src/tdd/session-runner.ts +12 -12
- package/src/tdd/types.ts +2 -0
- package/test/integration/cli/cli-config-prompts-explain.test.ts +74 -0
- package/test/integration/prompts/pb-004-migration.test.ts +523 -0
- package/test/unit/precheck/checks-warnings.test.ts +114 -0
- package/test/unit/prompts/builder.test.ts +258 -0
- package/test/unit/prompts/loader.test.ts +355 -0
- package/test/unit/prompts/sections/conventions.test.ts +30 -0
- package/test/unit/prompts/sections/isolation.test.ts +35 -0
- package/test/unit/prompts/sections/role-task.test.ts +40 -0
- package/test/unit/prompts/sections/sections.test.ts +238 -0
- package/test/unit/prompts/sections/story.test.ts +45 -0
- package/test/unit/prompts/sections/verdict.test.ts +58 -0
|
@@ -34,9 +34,9 @@ export async function runFullSuiteGate(
|
|
|
34
34
|
contextMarkdown: string | undefined,
|
|
35
35
|
lite: boolean,
|
|
36
36
|
logger: ReturnType<typeof getLogger>,
|
|
37
|
-
): Promise<
|
|
37
|
+
): Promise<boolean> {
|
|
38
38
|
const rectificationEnabled = config.execution.rectification?.enabled ?? false;
|
|
39
|
-
if (!rectificationEnabled) return;
|
|
39
|
+
if (!rectificationEnabled) return false;
|
|
40
40
|
|
|
41
41
|
const rectificationConfig = config.execution.rectification;
|
|
42
42
|
const testCmd = config.quality?.commands?.test ?? "bun test";
|
|
@@ -54,7 +54,7 @@ export async function runFullSuiteGate(
|
|
|
54
54
|
const testSummary = parseBunTestOutput(fullSuiteResult.output);
|
|
55
55
|
|
|
56
56
|
if (testSummary.failed > 0) {
|
|
57
|
-
await runRectificationLoop(
|
|
57
|
+
return await runRectificationLoop(
|
|
58
58
|
story,
|
|
59
59
|
config,
|
|
60
60
|
workdir,
|
|
@@ -69,14 +69,18 @@ export async function runFullSuiteGate(
|
|
|
69
69
|
fullSuiteTimeout,
|
|
70
70
|
);
|
|
71
71
|
}
|
|
72
|
-
|
|
72
|
+
// No failures detected despite non-zero exit — treat as passed
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
if (fullSuitePassed) {
|
|
73
76
|
logger.info("tdd", "Full suite gate passed", { storyId: story.id });
|
|
74
|
-
|
|
75
|
-
logger.warn("tdd", "Full suite gate execution failed (no output)", {
|
|
76
|
-
storyId: story.id,
|
|
77
|
-
exitCode: fullSuiteResult.exitCode,
|
|
78
|
-
});
|
|
77
|
+
return true;
|
|
79
78
|
}
|
|
79
|
+
logger.warn("tdd", "Full suite gate execution failed (no output)", {
|
|
80
|
+
storyId: story.id,
|
|
81
|
+
exitCode: fullSuiteResult.exitCode,
|
|
82
|
+
});
|
|
83
|
+
return false;
|
|
80
84
|
}
|
|
81
85
|
|
|
82
86
|
/** Run the rectification retry loop when full suite gate detects regressions. */
|
|
@@ -93,7 +97,7 @@ async function runRectificationLoop(
|
|
|
93
97
|
rectificationConfig: NonNullable<NaxConfig["execution"]["rectification"]>,
|
|
94
98
|
testCmd: string,
|
|
95
99
|
fullSuiteTimeout: number,
|
|
96
|
-
): Promise<
|
|
100
|
+
): Promise<boolean> {
|
|
97
101
|
const rectificationState: RectificationState = {
|
|
98
102
|
attempt: 0,
|
|
99
103
|
initialFailures: testSummary.failed,
|
|
@@ -156,7 +160,7 @@ async function runRectificationLoop(
|
|
|
156
160
|
storyId: story.id,
|
|
157
161
|
attempt: rectificationState.attempt,
|
|
158
162
|
});
|
|
159
|
-
|
|
163
|
+
return true;
|
|
160
164
|
}
|
|
161
165
|
|
|
162
166
|
if (retryFullSuite.output) {
|
|
@@ -177,7 +181,8 @@ async function runRectificationLoop(
|
|
|
177
181
|
attempts: rectificationState.attempt,
|
|
178
182
|
remainingFailures: rectificationState.currentFailures,
|
|
179
183
|
});
|
|
180
|
-
|
|
181
|
-
logger.info("tdd", "Full suite gate passed", { storyId: story.id });
|
|
184
|
+
return false;
|
|
182
185
|
}
|
|
186
|
+
logger.info("tdd", "Full suite gate passed", { storyId: story.id });
|
|
187
|
+
return true;
|
|
183
188
|
}
|
|
@@ -9,15 +9,9 @@ import type { ModelTier, NaxConfig } from "../config";
|
|
|
9
9
|
import { resolveModel } from "../config";
|
|
10
10
|
import { getLogger } from "../logger";
|
|
11
11
|
import type { UserStory } from "../prd";
|
|
12
|
+
import { PromptBuilder } from "../prompts";
|
|
12
13
|
import { cleanupProcessTree } from "./cleanup";
|
|
13
14
|
import { getChangedFiles, verifyImplementerIsolation, verifyTestWriterIsolation } from "./isolation";
|
|
14
|
-
import {
|
|
15
|
-
buildImplementerLitePrompt,
|
|
16
|
-
buildImplementerPrompt,
|
|
17
|
-
buildTestWriterLitePrompt,
|
|
18
|
-
buildTestWriterPrompt,
|
|
19
|
-
buildVerifierPrompt,
|
|
20
|
-
} from "./prompts";
|
|
21
15
|
import type { IsolationCheck } from "./types";
|
|
22
16
|
import type { TddSessionResult, TddSessionRole } from "./types";
|
|
23
17
|
|
|
@@ -95,15 +89,21 @@ export async function runTddSession(
|
|
|
95
89
|
let prompt: string;
|
|
96
90
|
switch (role) {
|
|
97
91
|
case "test-writer":
|
|
98
|
-
prompt =
|
|
92
|
+
prompt = await PromptBuilder.for("test-writer", { isolation: lite ? "lite" : "strict" })
|
|
93
|
+
.withLoader(workdir, config)
|
|
94
|
+
.story(story)
|
|
95
|
+
.context(contextMarkdown)
|
|
96
|
+
.build();
|
|
99
97
|
break;
|
|
100
98
|
case "implementer":
|
|
101
|
-
prompt = lite
|
|
102
|
-
|
|
103
|
-
|
|
99
|
+
prompt = await PromptBuilder.for("implementer", { variant: lite ? "lite" : "standard" })
|
|
100
|
+
.withLoader(workdir, config)
|
|
101
|
+
.story(story)
|
|
102
|
+
.context(contextMarkdown)
|
|
103
|
+
.build();
|
|
104
104
|
break;
|
|
105
105
|
case "verifier":
|
|
106
|
-
prompt =
|
|
106
|
+
prompt = await PromptBuilder.for("verifier").withLoader(workdir, config).story(story).build();
|
|
107
107
|
break;
|
|
108
108
|
}
|
|
109
109
|
|
package/src/tdd/types.ts
CHANGED
|
@@ -78,4 +78,6 @@ export interface ThreeSessionTddResult {
|
|
|
78
78
|
* undefined = verdict was not attempted (e.g. early-exit before session 3 ran)
|
|
79
79
|
*/
|
|
80
80
|
verdict?: import("./verdict").VerifierVerdict | null;
|
|
81
|
+
/** Whether the TDD full-suite gate passed (used by verify stage to skip redundant run, BUG-054) */
|
|
82
|
+
fullSuiteGatePassed?: boolean;
|
|
81
83
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests for `nax config --explain` prompts section (PB-005)
|
|
3
|
+
*
|
|
4
|
+
* Verifies that the prompts.overrides config block is documented in the
|
|
5
|
+
* --explain output with example paths.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
12
|
+
import { configCommand } from "../../../src/cli/config";
|
|
13
|
+
import { loadConfig } from "../../../src/config/loader";
|
|
14
|
+
|
|
15
|
+
describe("config --explain: prompts section", () => {
|
|
16
|
+
let tempDir: string;
|
|
17
|
+
let originalCwd: string;
|
|
18
|
+
let consoleOutput: string[];
|
|
19
|
+
let originalConsoleLog: typeof console.log;
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
tempDir = mkdtempSync(join(tmpdir(), "nax-config-prompts-test-"));
|
|
23
|
+
originalCwd = process.cwd();
|
|
24
|
+
|
|
25
|
+
consoleOutput = [];
|
|
26
|
+
originalConsoleLog = console.log;
|
|
27
|
+
console.log = (...args: unknown[]) => {
|
|
28
|
+
consoleOutput.push(args.map((a) => String(a)).join(" "));
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
console.log = originalConsoleLog;
|
|
34
|
+
process.chdir(originalCwd);
|
|
35
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("explain output includes a prompts section", async () => {
|
|
39
|
+
const config = await loadConfig(tempDir);
|
|
40
|
+
await configCommand(config, { explain: true });
|
|
41
|
+
|
|
42
|
+
const output = consoleOutput.join("\n");
|
|
43
|
+
expect(output).toContain("prompts");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("explain output documents prompts.overrides", async () => {
|
|
47
|
+
const config = await loadConfig(tempDir);
|
|
48
|
+
await configCommand(config, { explain: true });
|
|
49
|
+
|
|
50
|
+
const output = consoleOutput.join("\n");
|
|
51
|
+
expect(output).toContain("prompts.overrides");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("explain output includes example path .nax/prompts/test-writer.md", async () => {
|
|
55
|
+
const config = await loadConfig(tempDir);
|
|
56
|
+
await configCommand(config, { explain: true });
|
|
57
|
+
|
|
58
|
+
const output = consoleOutput.join("\n");
|
|
59
|
+
expect(output).toContain(".nax/prompts/test-writer.md");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("explain output mentions override roles (test-writer, implementer, verifier)", async () => {
|
|
63
|
+
const config = await loadConfig(tempDir);
|
|
64
|
+
await configCommand(config, { explain: true });
|
|
65
|
+
|
|
66
|
+
const output = consoleOutput.join("\n");
|
|
67
|
+
// At least one of the roles should appear in the prompts documentation
|
|
68
|
+
const mentionsRole =
|
|
69
|
+
output.includes("test-writer") ||
|
|
70
|
+
output.includes("implementer") ||
|
|
71
|
+
output.includes("verifier");
|
|
72
|
+
expect(mentionsRole).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
});
|