@nathapp/nax 0.22.3 → 0.23.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/README.md +21 -2
- package/docs/ROADMAP.md +6 -0
- package/docs/specs/central-run-registry.md +13 -1
- package/docs/tdd/strategies.md +97 -0
- package/nax/config.json +4 -3
- package/nax/features/diagnose/acceptance.test.ts +3 -1
- package/nax/features/status-file-consolidation/prd.json +52 -7
- package/nax/status.json +17 -8
- package/package.json +4 -4
- package/src/cli/diagnose.ts +1 -1
- package/src/cli/status-features.ts +55 -7
- package/src/config/schemas.ts +3 -0
- package/src/config/types.ts +2 -0
- package/src/execution/crash-recovery.ts +30 -7
- package/src/execution/lifecycle/run-setup.ts +6 -1
- package/src/execution/runner.ts +8 -0
- package/src/execution/status-writer.ts +42 -0
- package/src/pipeline/stages/verify.ts +21 -2
- package/src/verification/orchestrator-types.ts +2 -0
- package/src/verification/smart-runner.ts +5 -2
- package/src/verification/strategies/scoped.ts +9 -2
- package/src/verification/types.ts +2 -0
- package/src/version.ts +23 -0
- package/test/e2e/plan-analyze-run.test.ts +5 -0
- package/test/integration/cli/cli-diagnose.test.ts +3 -1
- package/test/integration/execution/feature-status-write.test.ts +302 -0
- package/test/integration/execution/status-file-integration.test.ts +1 -1
- package/test/integration/execution/status-writer.test.ts +112 -0
- package/test/unit/cli-status-project-level.test.ts +283 -0
- package/test/unit/config/quality-commands-schema.test.ts +72 -0
- package/test/unit/execution/sfc-004-dead-code-cleanup.test.ts +89 -0
- package/test/unit/verification/smart-runner.test.ts +16 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for project-level status display in status-features.ts
|
|
3
|
+
*
|
|
4
|
+
* Verifies that nax status shows current run info from nax/status.json at the top.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
8
|
+
import { mkdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { displayFeatureStatus } from "../../src/cli/status";
|
|
12
|
+
import type { NaxStatusFile } from "../../src/execution/status-file";
|
|
13
|
+
import type { PRD } from "../../src/prd";
|
|
14
|
+
|
|
15
|
+
describe("displayFeatureStatus - Project-level status (nax/status.json)", () => {
|
|
16
|
+
let testDir: string;
|
|
17
|
+
let originalCwd: string;
|
|
18
|
+
let consoleOutput: string[];
|
|
19
|
+
const originalLog = console.log;
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
// Create temp directory for test
|
|
23
|
+
const rawTestDir = join(tmpdir(), `nax-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
24
|
+
mkdirSync(rawTestDir, { recursive: true });
|
|
25
|
+
testDir = realpathSync(rawTestDir);
|
|
26
|
+
originalCwd = process.cwd();
|
|
27
|
+
|
|
28
|
+
// Mock console.log to capture output
|
|
29
|
+
consoleOutput = [];
|
|
30
|
+
console.log = mock((message: string) => {
|
|
31
|
+
consoleOutput.push(message);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
// Restore original CWD and console.log
|
|
37
|
+
process.chdir(originalCwd);
|
|
38
|
+
console.log = originalLog;
|
|
39
|
+
|
|
40
|
+
// Clean up test directory
|
|
41
|
+
if (testDir) {
|
|
42
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
function createTestPRD(featureName: string): PRD {
|
|
47
|
+
return {
|
|
48
|
+
project: "test-project",
|
|
49
|
+
feature: featureName,
|
|
50
|
+
branchName: `feat/${featureName}`,
|
|
51
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
52
|
+
updatedAt: "2026-01-01T00:00:00.000Z",
|
|
53
|
+
userStories: [
|
|
54
|
+
{
|
|
55
|
+
id: "US-001",
|
|
56
|
+
title: "First story",
|
|
57
|
+
description: "Test story 1",
|
|
58
|
+
acceptanceCriteria: ["AC-1"],
|
|
59
|
+
tags: [],
|
|
60
|
+
dependencies: [],
|
|
61
|
+
status: "passed",
|
|
62
|
+
passes: true,
|
|
63
|
+
escalations: [],
|
|
64
|
+
attempts: 1,
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function createProjectStatus(feature: string, overrides: Partial<NaxStatusFile> = {}): NaxStatusFile {
|
|
71
|
+
return {
|
|
72
|
+
version: 1,
|
|
73
|
+
run: {
|
|
74
|
+
id: "run-2026-01-01T00-00-00-000Z",
|
|
75
|
+
feature,
|
|
76
|
+
startedAt: "2026-01-01T10:00:00.000Z",
|
|
77
|
+
status: "running",
|
|
78
|
+
dryRun: false,
|
|
79
|
+
pid: process.pid, // Use current process PID (alive)
|
|
80
|
+
...overrides.run,
|
|
81
|
+
},
|
|
82
|
+
progress: {
|
|
83
|
+
total: 5,
|
|
84
|
+
passed: 2,
|
|
85
|
+
failed: 1,
|
|
86
|
+
paused: 0,
|
|
87
|
+
blocked: 0,
|
|
88
|
+
pending: 2,
|
|
89
|
+
...overrides.progress,
|
|
90
|
+
},
|
|
91
|
+
cost: {
|
|
92
|
+
spent: 0.5678,
|
|
93
|
+
limit: null,
|
|
94
|
+
...overrides.cost,
|
|
95
|
+
},
|
|
96
|
+
current: {
|
|
97
|
+
storyId: "US-002",
|
|
98
|
+
title: "Test current story",
|
|
99
|
+
complexity: "medium",
|
|
100
|
+
tddStrategy: "red-green-refactor",
|
|
101
|
+
model: "claude-opus",
|
|
102
|
+
attempt: 1,
|
|
103
|
+
phase: "implementation",
|
|
104
|
+
},
|
|
105
|
+
iterations: 10,
|
|
106
|
+
updatedAt: "2026-01-01T10:30:00.000Z",
|
|
107
|
+
durationMs: 1800000,
|
|
108
|
+
...overrides,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
describe("AC1: Shows project-level current run info at top", () => {
|
|
113
|
+
test("displays current run info when active run exists in nax/status.json", async () => {
|
|
114
|
+
// Setup: Create project with feature and project-level status
|
|
115
|
+
const naxDir = join(testDir, "nax");
|
|
116
|
+
const featuresDir = join(naxDir, "features");
|
|
117
|
+
mkdirSync(featuresDir, { recursive: true });
|
|
118
|
+
writeFileSync(join(naxDir, "config.json"), "{}");
|
|
119
|
+
|
|
120
|
+
const featureDir = join(featuresDir, "current-feature");
|
|
121
|
+
mkdirSync(featureDir, { recursive: true });
|
|
122
|
+
const prd = createTestPRD("current-feature");
|
|
123
|
+
writeFileSync(join(featureDir, "prd.json"), JSON.stringify(prd, null, 2));
|
|
124
|
+
|
|
125
|
+
// Create project-level status.json
|
|
126
|
+
const projectStatus = createProjectStatus("current-feature", {
|
|
127
|
+
run: {
|
|
128
|
+
id: "run-2026-01-01T00-00-00-000Z",
|
|
129
|
+
feature: "current-feature",
|
|
130
|
+
startedAt: "2026-01-01T10:00:00.000Z",
|
|
131
|
+
status: "running",
|
|
132
|
+
dryRun: false,
|
|
133
|
+
pid: process.pid,
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
mkdirSync(join(naxDir), { recursive: true });
|
|
137
|
+
writeFileSync(join(naxDir, "status.json"), JSON.stringify(projectStatus, null, 2));
|
|
138
|
+
|
|
139
|
+
// Act
|
|
140
|
+
await displayFeatureStatus({ dir: testDir });
|
|
141
|
+
|
|
142
|
+
// Assert
|
|
143
|
+
const output = consoleOutput.join("\n");
|
|
144
|
+
expect(output).toContain("⚡ Currently Running:");
|
|
145
|
+
expect(output).toContain("current-feature");
|
|
146
|
+
expect(output).toContain("run-2026-01-01T00-00-00-000Z");
|
|
147
|
+
expect(output).toContain("2026-01-01T10:00:00.000Z");
|
|
148
|
+
expect(output).toContain("2/5 stories");
|
|
149
|
+
expect(output).toContain("$0.5678");
|
|
150
|
+
expect(output).toContain("US-002");
|
|
151
|
+
expect(output).toContain("Test current story");
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("does not show current run info when nax/status.json missing", async () => {
|
|
155
|
+
// Setup: Create project with feature but no project-level status
|
|
156
|
+
const naxDir = join(testDir, "nax");
|
|
157
|
+
const featuresDir = join(naxDir, "features");
|
|
158
|
+
mkdirSync(featuresDir, { recursive: true });
|
|
159
|
+
writeFileSync(join(naxDir, "config.json"), "{}");
|
|
160
|
+
|
|
161
|
+
const featureDir = join(featuresDir, "no-status-feature");
|
|
162
|
+
mkdirSync(featureDir, { recursive: true });
|
|
163
|
+
const prd = createTestPRD("no-status-feature");
|
|
164
|
+
writeFileSync(join(featureDir, "prd.json"), JSON.stringify(prd, null, 2));
|
|
165
|
+
|
|
166
|
+
// Act (no status.json created)
|
|
167
|
+
await displayFeatureStatus({ dir: testDir });
|
|
168
|
+
|
|
169
|
+
// Assert
|
|
170
|
+
const output = consoleOutput.join("\n");
|
|
171
|
+
expect(output).not.toContain("⚡ Currently Running:");
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("shows crashed run detected when PID is dead", async () => {
|
|
175
|
+
// Setup: Create project with feature and crashed status
|
|
176
|
+
const naxDir = join(testDir, "nax");
|
|
177
|
+
const featuresDir = join(naxDir, "features");
|
|
178
|
+
mkdirSync(featuresDir, { recursive: true });
|
|
179
|
+
writeFileSync(join(naxDir, "config.json"), "{}");
|
|
180
|
+
|
|
181
|
+
const featureDir = join(featuresDir, "crashed-feature");
|
|
182
|
+
mkdirSync(featureDir, { recursive: true });
|
|
183
|
+
const prd = createTestPRD("crashed-feature");
|
|
184
|
+
writeFileSync(join(featureDir, "prd.json"), JSON.stringify(prd, null, 2));
|
|
185
|
+
|
|
186
|
+
// Create project-level status.json with dead PID
|
|
187
|
+
const projectStatus = createProjectStatus("crashed-feature", {
|
|
188
|
+
run: {
|
|
189
|
+
id: "run-2026-01-01T00-00-00-000Z",
|
|
190
|
+
feature: "crashed-feature",
|
|
191
|
+
startedAt: "2026-01-01T10:00:00.000Z",
|
|
192
|
+
status: "running",
|
|
193
|
+
dryRun: false,
|
|
194
|
+
pid: 999999, // Non-existent PID
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
writeFileSync(join(naxDir, "status.json"), JSON.stringify(projectStatus, null, 2));
|
|
198
|
+
|
|
199
|
+
// Act
|
|
200
|
+
await displayFeatureStatus({ dir: testDir });
|
|
201
|
+
|
|
202
|
+
// Assert
|
|
203
|
+
const output = consoleOutput.join("\n");
|
|
204
|
+
expect(output).toContain("💥 Crashed Run Detected:");
|
|
205
|
+
expect(output).toContain("999999");
|
|
206
|
+
expect(output).toContain("dead");
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("shows crash info when run status is 'crashed'", async () => {
|
|
210
|
+
// Setup: Create project with feature and crashed status
|
|
211
|
+
const naxDir = join(testDir, "nax");
|
|
212
|
+
const featuresDir = join(naxDir, "features");
|
|
213
|
+
mkdirSync(featuresDir, { recursive: true });
|
|
214
|
+
writeFileSync(join(naxDir, "config.json"), "{}");
|
|
215
|
+
|
|
216
|
+
const featureDir = join(featuresDir, "crashed-feature");
|
|
217
|
+
mkdirSync(featureDir, { recursive: true });
|
|
218
|
+
const prd = createTestPRD("crashed-feature");
|
|
219
|
+
writeFileSync(join(featureDir, "prd.json"), JSON.stringify(prd, null, 2));
|
|
220
|
+
|
|
221
|
+
// Create project-level status.json with crashed status
|
|
222
|
+
const projectStatus = createProjectStatus("crashed-feature", {
|
|
223
|
+
run: {
|
|
224
|
+
id: "run-2026-01-01T00-00-00-000Z",
|
|
225
|
+
feature: "crashed-feature",
|
|
226
|
+
startedAt: "2026-01-01T10:00:00.000Z",
|
|
227
|
+
status: "crashed",
|
|
228
|
+
dryRun: false,
|
|
229
|
+
pid: 12345,
|
|
230
|
+
crashedAt: "2026-01-01T10:15:00.000Z",
|
|
231
|
+
crashSignal: "SIGKILL",
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
writeFileSync(join(naxDir, "status.json"), JSON.stringify(projectStatus, null, 2));
|
|
235
|
+
|
|
236
|
+
// Act
|
|
237
|
+
await displayFeatureStatus({ dir: testDir });
|
|
238
|
+
|
|
239
|
+
// Assert
|
|
240
|
+
const output = consoleOutput.join("\n");
|
|
241
|
+
expect(output).toContain("💥 Crashed Run Detected:");
|
|
242
|
+
expect(output).toContain("SIGKILL");
|
|
243
|
+
expect(output).toContain("2026-01-01T10:15:00.000Z");
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe("AC2: Shows per-feature historical status", () => {
|
|
248
|
+
test("shows feature-level status from nax/features/<feature>/status.json", async () => {
|
|
249
|
+
// Setup: Create project with feature and feature-level status
|
|
250
|
+
const naxDir = join(testDir, "nax");
|
|
251
|
+
const featuresDir = join(naxDir, "features");
|
|
252
|
+
mkdirSync(featuresDir, { recursive: true });
|
|
253
|
+
writeFileSync(join(naxDir, "config.json"), "{}");
|
|
254
|
+
|
|
255
|
+
const featureDir = join(featuresDir, "test-feature");
|
|
256
|
+
mkdirSync(featureDir, { recursive: true });
|
|
257
|
+
const prd = createTestPRD("test-feature");
|
|
258
|
+
writeFileSync(join(featureDir, "prd.json"), JSON.stringify(prd, null, 2));
|
|
259
|
+
|
|
260
|
+
// Create feature-level status.json
|
|
261
|
+
const featureStatus = createProjectStatus("test-feature", {
|
|
262
|
+
run: {
|
|
263
|
+
id: "run-feature-level",
|
|
264
|
+
feature: "test-feature",
|
|
265
|
+
startedAt: "2026-01-01T09:00:00.000Z",
|
|
266
|
+
status: "completed",
|
|
267
|
+
dryRun: false,
|
|
268
|
+
pid: 12345,
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
writeFileSync(join(featureDir, "status.json"), JSON.stringify(featureStatus, null, 2));
|
|
272
|
+
|
|
273
|
+
// Act
|
|
274
|
+
await displayFeatureStatus({ dir: testDir, feature: "test-feature" });
|
|
275
|
+
|
|
276
|
+
// Assert
|
|
277
|
+
const output = consoleOutput.join("\n");
|
|
278
|
+
// Feature details view should show the feature-level status
|
|
279
|
+
expect(output).toContain("test-feature");
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// RE-ARCH: keep
|
|
2
|
+
/**
|
|
3
|
+
* quality.commands schema — testScoped and other optional command fields
|
|
4
|
+
*
|
|
5
|
+
* Regression test for BUG-043: testScoped was present in types.ts but missing
|
|
6
|
+
* from schemas.ts, causing Zod to silently strip it during config parsing.
|
|
7
|
+
* Result: testScopedTemplate was always undefined at runtime, so the {{files}}
|
|
8
|
+
* template was never applied and scoped tests fell back to buildSmartTestCommand.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, test, expect } from "bun:test";
|
|
12
|
+
import { NaxConfigSchema } from "../../../src/config/schemas";
|
|
13
|
+
import { DEFAULT_CONFIG } from "../../../src/config/defaults";
|
|
14
|
+
|
|
15
|
+
function buildConfigWithCommands(commands: Record<string, unknown>) {
|
|
16
|
+
return {
|
|
17
|
+
...DEFAULT_CONFIG,
|
|
18
|
+
quality: {
|
|
19
|
+
...DEFAULT_CONFIG.quality,
|
|
20
|
+
commands: {
|
|
21
|
+
...DEFAULT_CONFIG.quality.commands,
|
|
22
|
+
...commands,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe("quality.commands schema", () => {
|
|
29
|
+
test("testScoped is preserved after schema parse (BUG-043 regression)", () => {
|
|
30
|
+
const input = buildConfigWithCommands({
|
|
31
|
+
testScoped: "bun test --timeout=60000 {{files}}",
|
|
32
|
+
});
|
|
33
|
+
const result = NaxConfigSchema.parse(input);
|
|
34
|
+
expect(result.quality.commands.testScoped).toBe("bun test --timeout=60000 {{files}}");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("testScoped is optional — absent when not provided", () => {
|
|
38
|
+
const input = buildConfigWithCommands({});
|
|
39
|
+
const result = NaxConfigSchema.parse(input);
|
|
40
|
+
expect(result.quality.commands.testScoped).toBeUndefined();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("lintFix is preserved after schema parse", () => {
|
|
44
|
+
const input = buildConfigWithCommands({ lintFix: "bun run lint --fix" });
|
|
45
|
+
const result = NaxConfigSchema.parse(input);
|
|
46
|
+
expect(result.quality.commands.lintFix).toBe("bun run lint --fix");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("formatFix is preserved after schema parse", () => {
|
|
50
|
+
const input = buildConfigWithCommands({ formatFix: "bun run format --write" });
|
|
51
|
+
const result = NaxConfigSchema.parse(input);
|
|
52
|
+
expect(result.quality.commands.formatFix).toBe("bun run format --write");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("all command fields coexist correctly", () => {
|
|
56
|
+
const input = buildConfigWithCommands({
|
|
57
|
+
test: "bun run test",
|
|
58
|
+
testScoped: "bun test --timeout=60000 {{files}}",
|
|
59
|
+
typecheck: "bun run typecheck",
|
|
60
|
+
lint: "bun run lint",
|
|
61
|
+
lintFix: "bun run lint --fix",
|
|
62
|
+
formatFix: "bun run format --write",
|
|
63
|
+
});
|
|
64
|
+
const result = NaxConfigSchema.parse(input);
|
|
65
|
+
expect(result.quality.commands.test).toBe("bun run test");
|
|
66
|
+
expect(result.quality.commands.testScoped).toBe("bun test --timeout=60000 {{files}}");
|
|
67
|
+
expect(result.quality.commands.typecheck).toBe("bun run typecheck");
|
|
68
|
+
expect(result.quality.commands.lint).toBe("bun run lint");
|
|
69
|
+
expect(result.quality.commands.lintFix).toBe("bun run lint --fix");
|
|
70
|
+
expect(result.quality.commands.formatFix).toBe("bun run format --write");
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SFC-004: Clean up dead code — Acceptance Criteria Verification
|
|
3
|
+
*
|
|
4
|
+
* Verifies that:
|
|
5
|
+
* 1. No references to --status-file CLI option in codebase
|
|
6
|
+
* 2. No references to .nax-status.json in codebase
|
|
7
|
+
* 3. RunOptions.statusFile is required (not optional)
|
|
8
|
+
* 4. All existing tests pass
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, expect, test } from "bun:test";
|
|
12
|
+
import type { RunOptions } from "../../../src/execution/runner";
|
|
13
|
+
import { DEFAULT_CONFIG } from "../../../src/config";
|
|
14
|
+
|
|
15
|
+
describe("SFC-004: Dead code cleanup — Acceptance Criteria", () => {
|
|
16
|
+
test("AC-1: RunOptions.statusFile is required (not optional)", () => {
|
|
17
|
+
// Verify that statusFile is a required field by creating a valid RunOptions object
|
|
18
|
+
const validRunOptions: RunOptions = {
|
|
19
|
+
prdPath: "/tmp/prd.json",
|
|
20
|
+
workdir: "/tmp",
|
|
21
|
+
config: DEFAULT_CONFIG,
|
|
22
|
+
hooks: { hooks: {} },
|
|
23
|
+
feature: "test-feature",
|
|
24
|
+
dryRun: false,
|
|
25
|
+
statusFile: "/tmp/nax/status.json", // Required field
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
expect(validRunOptions.statusFile).toBe("/tmp/nax/status.json");
|
|
29
|
+
expect(typeof validRunOptions.statusFile).toBe("string");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("AC-2: CLI auto-computes statusFile to <workdir>/nax/status.json", () => {
|
|
33
|
+
// This is verified in bin/nax.ts line 334:
|
|
34
|
+
// const statusFilePath = join(workdir, "nax", "status.json");
|
|
35
|
+
// And passed to run() on line 357
|
|
36
|
+
|
|
37
|
+
const workdir = "/home/user/project";
|
|
38
|
+
const expectedStatusFile = `${workdir}/nax/status.json`;
|
|
39
|
+
|
|
40
|
+
// Simulate what bin/nax.ts does
|
|
41
|
+
const statusFilePath = `${workdir}/nax/status.json`;
|
|
42
|
+
expect(statusFilePath).toBe(expectedStatusFile);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("AC-3: No .nax-status.json (old pattern) in codebase", () => {
|
|
46
|
+
// The old pattern was .nax-status.json
|
|
47
|
+
// The new pattern is nax/status.json (auto-computed in CLI)
|
|
48
|
+
// This test documents the change
|
|
49
|
+
|
|
50
|
+
const oldPattern = ".nax-status.json";
|
|
51
|
+
const newPattern = "nax/status.json";
|
|
52
|
+
|
|
53
|
+
// The new pattern should be used
|
|
54
|
+
expect(newPattern).toBe("nax/status.json");
|
|
55
|
+
expect(oldPattern).not.toBe(newPattern);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("AC-4: statusFile path structure matches <workdir>/nax/status.json", () => {
|
|
59
|
+
// Verify that the status file is stored at the correct location
|
|
60
|
+
const workdir = "/Users/username/project";
|
|
61
|
+
const naxDir = `${workdir}/nax`;
|
|
62
|
+
const statusFile = `${naxDir}/status.json`;
|
|
63
|
+
|
|
64
|
+
expect(statusFile).toBe("/Users/username/project/nax/status.json");
|
|
65
|
+
expect(statusFile).toContain("/nax/status.json");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("AC-5: RunOptions requires statusFile parameter in all run() calls", () => {
|
|
69
|
+
// This verifies that statusFile must be passed to run()
|
|
70
|
+
// Type checking ensures all call sites pass it
|
|
71
|
+
|
|
72
|
+
interface RunOptionsWithStatusFile extends RunOptions {
|
|
73
|
+
statusFile: string; // Always required
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const opts: RunOptionsWithStatusFile = {
|
|
77
|
+
prdPath: "/tmp/prd.json",
|
|
78
|
+
workdir: "/tmp",
|
|
79
|
+
config: DEFAULT_CONFIG,
|
|
80
|
+
hooks: { hooks: {} },
|
|
81
|
+
feature: "test",
|
|
82
|
+
dryRun: false,
|
|
83
|
+
statusFile: "/tmp/nax/status.json",
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
expect(opts.statusFile).toBeDefined();
|
|
87
|
+
expect(typeof opts.statusFile).toBe("string");
|
|
88
|
+
});
|
|
89
|
+
});
|
|
@@ -50,6 +50,22 @@ describe("buildSmartTestCommand", () => {
|
|
|
50
50
|
);
|
|
51
51
|
expect(result).toBe("bun test --coverage test/unit/foo.test.ts");
|
|
52
52
|
});
|
|
53
|
+
|
|
54
|
+
test("preserves trailing flags after path argument (BUG-043)", () => {
|
|
55
|
+
const result = buildSmartTestCommand(
|
|
56
|
+
["test/unit/foo.test.ts"],
|
|
57
|
+
"bun test test/ --timeout=60000",
|
|
58
|
+
);
|
|
59
|
+
expect(result).toBe("bun test test/unit/foo.test.ts --timeout=60000");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("preserves trailing flags with multiple test files", () => {
|
|
63
|
+
const result = buildSmartTestCommand(
|
|
64
|
+
["test/unit/foo.test.ts", "test/unit/bar.test.ts"],
|
|
65
|
+
"bun test test/ --timeout=60000 --bail",
|
|
66
|
+
);
|
|
67
|
+
expect(result).toBe("bun test test/unit/foo.test.ts test/unit/bar.test.ts --timeout=60000 --bail");
|
|
68
|
+
});
|
|
53
69
|
});
|
|
54
70
|
|
|
55
71
|
// ---------------------------------------------------------------------------
|