@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
@@ -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<void> {
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
- } else if (fullSuitePassed) {
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
- } else {
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<void> {
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
- break;
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
- } else {
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 = lite ? buildTestWriterLitePrompt(story, contextMarkdown) : buildTestWriterPrompt(story, contextMarkdown);
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
- ? buildImplementerLitePrompt(story, contextMarkdown)
103
- : buildImplementerPrompt(story, contextMarkdown);
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 = buildVerifierPrompt(story);
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
+ });