@nathapp/nax 0.22.1 → 0.22.3
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 +17 -0
- package/bin/nax.ts +3 -4
- package/docs/ROADMAP.md +55 -43
- package/docs/specs/central-run-registry.md +104 -0
- package/docs/specs/status-file-consolidation.md +93 -0
- package/nax/features/post-rearch-bugfix/prd.json +137 -0
- package/nax/features/status-file-consolidation/prd.json +61 -0
- package/nax/status.json +13 -12
- package/package.json +1 -1
- package/src/execution/crash-recovery.ts +7 -0
- package/src/execution/lifecycle/run-setup.ts +50 -42
- package/src/execution/lock.ts +30 -14
- package/src/execution/parallel.ts +4 -11
- package/src/execution/pipeline-result-handler.ts +1 -1
- package/src/execution/runner.ts +7 -4
- package/src/execution/status-writer.ts +4 -4
- package/src/pipeline/stages/acceptance.ts +5 -3
- package/src/pipeline/stages/autofix.ts +5 -3
- package/src/routing/strategies/llm.ts +11 -10
- package/src/verification/executor.ts +18 -6
- package/test/helpers/helpers.test.ts +2 -2
- package/test/integration/execution/status-file-integration.test.ts +20 -57
- package/test/integration/execution/status-writer.test.ts +1 -12
- package/test/unit/routing/strategies/llm.test.ts +64 -9
|
@@ -112,17 +112,15 @@ afterEach(() => {
|
|
|
112
112
|
resetLogger();
|
|
113
113
|
});
|
|
114
114
|
|
|
115
|
-
describe("BUG-039:
|
|
116
|
-
test("
|
|
117
|
-
const { proc, stdoutCancelled, stderrCancelled, killCalled
|
|
115
|
+
describe("BUG-039/BUG-040: stream cleanup on timeout", () => {
|
|
116
|
+
test("kills process on timeout without calling cancel() on locked streams", async () => {
|
|
117
|
+
const { proc, stdoutCancelled, stderrCancelled, killCalled } = makeHangingProc();
|
|
118
118
|
|
|
119
119
|
const originalSpawn = _deps.spawn;
|
|
120
120
|
_deps.spawn = mock(() => proc as PipedProc);
|
|
121
121
|
|
|
122
122
|
const config = makeConfig({ timeoutMs: 30 });
|
|
123
123
|
|
|
124
|
-
// Import callLlmOnce indirectly through llmStrategy to trigger the private function.
|
|
125
|
-
// We test via the exported llmStrategy.route() which calls callLlm → callLlmOnce.
|
|
126
124
|
const { llmStrategy } = await import("../../../../src/routing/strategies/llm");
|
|
127
125
|
|
|
128
126
|
const story = {
|
|
@@ -147,15 +145,72 @@ describe("BUG-039: callLlmOnce stream drain on timeout", () => {
|
|
|
147
145
|
// Should resolve promptly — within 500ms of the 30ms timeout
|
|
148
146
|
expect(elapsed).toBeLessThan(500);
|
|
149
147
|
|
|
150
|
-
|
|
151
|
-
|
|
148
|
+
// BUG-040: cancel() must NOT be called on locked streams — it returns a rejected
|
|
149
|
+
// Promise (per Web Streams spec) which becomes an unhandled rejection crash.
|
|
150
|
+
expect(stdoutCancelled.value).toBe(false);
|
|
151
|
+
expect(stderrCancelled.value).toBe(false);
|
|
152
152
|
expect(killCalled.value).toBe(true);
|
|
153
|
-
// kill() was called after both streams were cancelled
|
|
154
|
-
expect(killCalledAfterCancel.value).toBe(true);
|
|
155
153
|
|
|
156
154
|
_deps.spawn = originalSpawn;
|
|
157
155
|
});
|
|
158
156
|
|
|
157
|
+
test("no unhandled rejection when Response.text() locks streams and proc is killed", async () => {
|
|
158
|
+
// Simulate the exact BUG-040 scenario:
|
|
159
|
+
// 1. Spawn proc with piped streams
|
|
160
|
+
// 2. Response(proc.stdout).text() locks the streams
|
|
161
|
+
// 3. Timeout fires, proc.kill() called
|
|
162
|
+
// 4. No unhandled rejection should occur
|
|
163
|
+
|
|
164
|
+
const unhandledRejections: Error[] = [];
|
|
165
|
+
const handler = (event: PromiseRejectionEvent) => {
|
|
166
|
+
unhandledRejections.push(event.reason as Error);
|
|
167
|
+
event.preventDefault();
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// biome-ignore lint/suspicious/noGlobalAssign: test-only override
|
|
171
|
+
globalThis.addEventListener("unhandledrejection", handler);
|
|
172
|
+
|
|
173
|
+
// Create a proc where streams are locked by Response readers
|
|
174
|
+
const stdout = new ReadableStream({ start() {} });
|
|
175
|
+
const stderr = new ReadableStream({ start() {} });
|
|
176
|
+
const proc = {
|
|
177
|
+
stdout,
|
|
178
|
+
stderr,
|
|
179
|
+
exited: new Promise<number>(() => {}),
|
|
180
|
+
kill: mock(() => {}),
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const originalSpawn = _deps.spawn;
|
|
184
|
+
_deps.spawn = mock(() => proc as PipedProc);
|
|
185
|
+
|
|
186
|
+
const config = makeConfig({ timeoutMs: 20, retries: 0 });
|
|
187
|
+
|
|
188
|
+
const { llmStrategy } = await import("../../../../src/routing/strategies/llm");
|
|
189
|
+
const story = {
|
|
190
|
+
id: "BUG040",
|
|
191
|
+
title: "Bug test",
|
|
192
|
+
description: "Test",
|
|
193
|
+
acceptanceCriteria: ["AC1"],
|
|
194
|
+
tags: [],
|
|
195
|
+
dependencies: [],
|
|
196
|
+
status: "pending" as const,
|
|
197
|
+
passes: false,
|
|
198
|
+
escalations: [],
|
|
199
|
+
attempts: 0,
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
await expect(llmStrategy.route(story, { config })).rejects.toThrow(/timeout/i);
|
|
203
|
+
|
|
204
|
+
// Give microtasks time to settle
|
|
205
|
+
await Bun.sleep(50);
|
|
206
|
+
|
|
207
|
+
globalThis.removeEventListener("unhandledrejection", handler);
|
|
208
|
+
_deps.spawn = originalSpawn;
|
|
209
|
+
|
|
210
|
+
// No unhandled rejections should have occurred
|
|
211
|
+
expect(unhandledRejections).toHaveLength(0);
|
|
212
|
+
});
|
|
213
|
+
|
|
159
214
|
test("clearTimeout is called on success path (no resource leak)", async () => {
|
|
160
215
|
const originalSpawn = _deps.spawn;
|
|
161
216
|
|