@cephalization/math 0.2.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 +190 -0
- package/index.ts +155 -0
- package/package.json +50 -0
- package/src/agent.test.ts +179 -0
- package/src/agent.ts +275 -0
- package/src/commands/init.ts +65 -0
- package/src/commands/iterate.ts +92 -0
- package/src/commands/plan.ts +16 -0
- package/src/commands/prune.ts +63 -0
- package/src/commands/run.test.ts +27 -0
- package/src/commands/run.ts +16 -0
- package/src/commands/status.ts +55 -0
- package/src/constants.ts +1 -0
- package/src/loop.test.ts +537 -0
- package/src/loop.ts +325 -0
- package/src/plan.ts +263 -0
- package/src/prune.test.ts +174 -0
- package/src/prune.ts +146 -0
- package/src/tasks.ts +204 -0
- package/src/templates.ts +172 -0
- package/src/ui/app.test.ts +228 -0
- package/src/ui/buffer.test.ts +222 -0
- package/src/ui/buffer.ts +131 -0
- package/src/ui/server.test.ts +271 -0
- package/src/ui/server.ts +124 -0
package/src/loop.test.ts
ADDED
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
import { test, expect, describe, beforeEach, afterEach } from "bun:test";
|
|
2
|
+
import { mkdtemp, rm, mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { runLoop } from "./loop";
|
|
6
|
+
import { createMockAgent } from "./agent";
|
|
7
|
+
import { createOutputBuffer } from "./ui/buffer";
|
|
8
|
+
import { DEFAULT_PORT } from "./ui/server";
|
|
9
|
+
|
|
10
|
+
describe("runLoop dry-run mode", () => {
|
|
11
|
+
let testDir: string;
|
|
12
|
+
let originalCwd: string;
|
|
13
|
+
|
|
14
|
+
beforeEach(async () => {
|
|
15
|
+
// Create a temp directory for each test
|
|
16
|
+
testDir = await mkdtemp(join(tmpdir(), "math-loop-test-"));
|
|
17
|
+
originalCwd = process.cwd();
|
|
18
|
+
process.chdir(testDir);
|
|
19
|
+
|
|
20
|
+
// Create the todo directory with required files
|
|
21
|
+
const todoDir = join(testDir, "todo");
|
|
22
|
+
await mkdir(todoDir, { recursive: true });
|
|
23
|
+
|
|
24
|
+
// Create PROMPT.md
|
|
25
|
+
await writeFile(
|
|
26
|
+
join(todoDir, "PROMPT.md"),
|
|
27
|
+
"# Test Prompt\n\nTest instructions."
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
// Create TASKS.md with all tasks complete (so the loop exits after one iteration)
|
|
31
|
+
await writeFile(
|
|
32
|
+
join(todoDir, "TASKS.md"),
|
|
33
|
+
`# Tasks
|
|
34
|
+
|
|
35
|
+
### test-task
|
|
36
|
+
- content: Test task
|
|
37
|
+
- status: complete
|
|
38
|
+
- dependencies: none
|
|
39
|
+
`
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
afterEach(async () => {
|
|
44
|
+
// Restore original working directory
|
|
45
|
+
process.chdir(originalCwd);
|
|
46
|
+
|
|
47
|
+
// Clean up temp directory
|
|
48
|
+
await rm(testDir, { recursive: true, force: true });
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("dry-run mode uses custom mock agent", async () => {
|
|
52
|
+
// Use a pending task so the agent gets invoked
|
|
53
|
+
await writeFile(
|
|
54
|
+
join(testDir, "todo", "TASKS.md"),
|
|
55
|
+
`# Tasks
|
|
56
|
+
|
|
57
|
+
### test-task
|
|
58
|
+
- content: Test task
|
|
59
|
+
- status: pending
|
|
60
|
+
- dependencies: none
|
|
61
|
+
`
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const mockAgent = createMockAgent({
|
|
65
|
+
logs: [
|
|
66
|
+
{ category: "info", message: "Custom mock log" },
|
|
67
|
+
{ category: "success", message: "Custom mock success" },
|
|
68
|
+
],
|
|
69
|
+
output: ["Custom mock output\n"],
|
|
70
|
+
exitCode: 0,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const logs: string[] = [];
|
|
74
|
+
const outputs: string[] = [];
|
|
75
|
+
const originalLog = console.log;
|
|
76
|
+
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
77
|
+
|
|
78
|
+
console.log = (...args: unknown[]) => {
|
|
79
|
+
logs.push(args.join(" "));
|
|
80
|
+
};
|
|
81
|
+
process.stdout.write = (chunk: string | Uint8Array) => {
|
|
82
|
+
if (typeof chunk === "string") {
|
|
83
|
+
outputs.push(chunk);
|
|
84
|
+
}
|
|
85
|
+
return true;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
await runLoop({
|
|
90
|
+
dryRun: true,
|
|
91
|
+
agent: mockAgent,
|
|
92
|
+
maxIterations: 1,
|
|
93
|
+
pauseSeconds: 0,
|
|
94
|
+
ui: false,
|
|
95
|
+
});
|
|
96
|
+
} catch {
|
|
97
|
+
// Expected: max iterations exceeded since mock doesn't complete tasks
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Verify custom mock agent logs were emitted
|
|
101
|
+
const logText = logs.join("\n");
|
|
102
|
+
expect(logText).toContain("Custom mock log");
|
|
103
|
+
expect(logText).toContain("Custom mock success");
|
|
104
|
+
|
|
105
|
+
// Verify custom mock output was emitted
|
|
106
|
+
const outputText = outputs.join("");
|
|
107
|
+
expect(outputText).toContain("Custom mock output");
|
|
108
|
+
|
|
109
|
+
console.log = originalLog;
|
|
110
|
+
process.stdout.write = originalStdoutWrite;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("dry-run mode with pending tasks runs iteration", async () => {
|
|
114
|
+
// Update TASKS.md to have a pending task
|
|
115
|
+
await writeFile(
|
|
116
|
+
join(testDir, "todo", "TASKS.md"),
|
|
117
|
+
`# Tasks
|
|
118
|
+
|
|
119
|
+
### test-task
|
|
120
|
+
- content: Test task
|
|
121
|
+
- status: pending
|
|
122
|
+
- dependencies: none
|
|
123
|
+
`
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const logs: string[] = [];
|
|
127
|
+
const originalLog = console.log;
|
|
128
|
+
console.log = (...args: unknown[]) => {
|
|
129
|
+
logs.push(args.join(" "));
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
await runLoop({
|
|
134
|
+
dryRun: true,
|
|
135
|
+
maxIterations: 1,
|
|
136
|
+
pauseSeconds: 0,
|
|
137
|
+
ui: false,
|
|
138
|
+
});
|
|
139
|
+
} catch (e) {
|
|
140
|
+
// Expected: will exceed max iterations since mock doesn't complete tasks
|
|
141
|
+
} finally {
|
|
142
|
+
console.log = originalLog;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Verify iteration ran
|
|
146
|
+
const logText = logs.join("\n");
|
|
147
|
+
expect(logText).toContain("=== Iteration 1 ===");
|
|
148
|
+
expect(logText).toContain("Invoking agent");
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("agent option allows injecting custom agent", async () => {
|
|
152
|
+
const callCount = { value: 0 };
|
|
153
|
+
const mockAgent = createMockAgent({
|
|
154
|
+
logs: [{ category: "info", message: "Injected agent running" }],
|
|
155
|
+
exitCode: 0,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Wrap the run method to count calls
|
|
159
|
+
const originalRun = mockAgent.run.bind(mockAgent);
|
|
160
|
+
mockAgent.run = async (options) => {
|
|
161
|
+
callCount.value++;
|
|
162
|
+
return originalRun(options);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
await runLoop({
|
|
166
|
+
dryRun: true,
|
|
167
|
+
agent: mockAgent,
|
|
168
|
+
maxIterations: 1,
|
|
169
|
+
pauseSeconds: 0,
|
|
170
|
+
ui: false,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Agent should not be called since all tasks are complete
|
|
174
|
+
// (the task file has a complete task)
|
|
175
|
+
expect(callCount.value).toBe(0);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("agent option with pending task invokes agent", async () => {
|
|
179
|
+
// Update TASKS.md to have a pending task
|
|
180
|
+
await writeFile(
|
|
181
|
+
join(testDir, "todo", "TASKS.md"),
|
|
182
|
+
`# Tasks
|
|
183
|
+
|
|
184
|
+
### test-task
|
|
185
|
+
- content: Test task
|
|
186
|
+
- status: pending
|
|
187
|
+
- dependencies: none
|
|
188
|
+
`
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
const callCount = { value: 0 };
|
|
192
|
+
const mockAgent = createMockAgent({
|
|
193
|
+
logs: [{ category: "success", message: "Agent ran" }],
|
|
194
|
+
exitCode: 0,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const originalRun = mockAgent.run.bind(mockAgent);
|
|
198
|
+
mockAgent.run = async (options) => {
|
|
199
|
+
callCount.value++;
|
|
200
|
+
return originalRun(options);
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
await runLoop({
|
|
205
|
+
dryRun: true,
|
|
206
|
+
agent: mockAgent,
|
|
207
|
+
maxIterations: 1,
|
|
208
|
+
pauseSeconds: 0,
|
|
209
|
+
ui: false,
|
|
210
|
+
});
|
|
211
|
+
} catch {
|
|
212
|
+
// Expected: max iterations exceeded
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Agent should be called at least once
|
|
216
|
+
expect(callCount.value).toBeGreaterThanOrEqual(1);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
describe("runLoop stream-capture with buffer", () => {
|
|
221
|
+
let testDir: string;
|
|
222
|
+
let originalCwd: string;
|
|
223
|
+
|
|
224
|
+
beforeEach(async () => {
|
|
225
|
+
// Create a temp directory for each test
|
|
226
|
+
testDir = await mkdtemp(join(tmpdir(), "math-loop-test-"));
|
|
227
|
+
originalCwd = process.cwd();
|
|
228
|
+
process.chdir(testDir);
|
|
229
|
+
|
|
230
|
+
// Create the todo directory with required files
|
|
231
|
+
const todoDir = join(testDir, "todo");
|
|
232
|
+
await mkdir(todoDir, { recursive: true });
|
|
233
|
+
|
|
234
|
+
// Create PROMPT.md
|
|
235
|
+
await writeFile(
|
|
236
|
+
join(todoDir, "PROMPT.md"),
|
|
237
|
+
"# Test Prompt\n\nTest instructions."
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
// Create TASKS.md with all tasks complete
|
|
241
|
+
await writeFile(
|
|
242
|
+
join(todoDir, "TASKS.md"),
|
|
243
|
+
`# Tasks
|
|
244
|
+
|
|
245
|
+
### test-task
|
|
246
|
+
- content: Test task
|
|
247
|
+
- status: complete
|
|
248
|
+
- dependencies: none
|
|
249
|
+
`
|
|
250
|
+
);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
afterEach(async () => {
|
|
254
|
+
process.chdir(originalCwd);
|
|
255
|
+
await rm(testDir, { recursive: true, force: true });
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test("loop logs are captured to buffer", async () => {
|
|
259
|
+
const buffer = createOutputBuffer();
|
|
260
|
+
|
|
261
|
+
// Suppress console output during test
|
|
262
|
+
const originalLog = console.log;
|
|
263
|
+
console.log = () => {};
|
|
264
|
+
|
|
265
|
+
try {
|
|
266
|
+
await runLoop({
|
|
267
|
+
dryRun: true,
|
|
268
|
+
maxIterations: 1,
|
|
269
|
+
pauseSeconds: 0,
|
|
270
|
+
buffer,
|
|
271
|
+
ui: false,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// Verify logs were captured
|
|
275
|
+
const logs = buffer.getLogs();
|
|
276
|
+
expect(logs.length).toBeGreaterThan(0);
|
|
277
|
+
|
|
278
|
+
// Should have info logs
|
|
279
|
+
const infoLogs = logs.filter((l) => l.category === "info");
|
|
280
|
+
expect(infoLogs.length).toBeGreaterThan(0);
|
|
281
|
+
|
|
282
|
+
// Check for expected log messages
|
|
283
|
+
const messages = logs.map((l) => l.message);
|
|
284
|
+
expect(messages.some((m) => m.includes("Starting math loop"))).toBe(true);
|
|
285
|
+
} finally {
|
|
286
|
+
console.log = originalLog;
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test("loop success logs are captured with correct category", async () => {
|
|
291
|
+
const buffer = createOutputBuffer();
|
|
292
|
+
|
|
293
|
+
const originalLog = console.log;
|
|
294
|
+
console.log = () => {};
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
await runLoop({
|
|
298
|
+
dryRun: true,
|
|
299
|
+
maxIterations: 1,
|
|
300
|
+
pauseSeconds: 0,
|
|
301
|
+
buffer,
|
|
302
|
+
ui: false,
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
const logs = buffer.getLogs();
|
|
306
|
+
const successLogs = logs.filter((l) => l.category === "success");
|
|
307
|
+
|
|
308
|
+
// Should have success logs (tasks complete)
|
|
309
|
+
expect(successLogs.length).toBeGreaterThan(0);
|
|
310
|
+
expect(
|
|
311
|
+
successLogs.some((l) => l.message.includes("tasks complete"))
|
|
312
|
+
).toBe(true);
|
|
313
|
+
} finally {
|
|
314
|
+
console.log = originalLog;
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
test("agent output is captured to buffer", async () => {
|
|
319
|
+
// Use a pending task so the agent gets invoked
|
|
320
|
+
await writeFile(
|
|
321
|
+
join(testDir, "todo", "TASKS.md"),
|
|
322
|
+
`# Tasks
|
|
323
|
+
|
|
324
|
+
### test-task
|
|
325
|
+
- content: Test task
|
|
326
|
+
- status: pending
|
|
327
|
+
- dependencies: none
|
|
328
|
+
`
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
const buffer = createOutputBuffer();
|
|
332
|
+
const mockAgent = createMockAgent({
|
|
333
|
+
logs: [{ category: "info", message: "Agent working" }],
|
|
334
|
+
output: ["Agent output text\n", "More output\n"],
|
|
335
|
+
exitCode: 0,
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
const originalLog = console.log;
|
|
339
|
+
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
340
|
+
console.log = () => {};
|
|
341
|
+
process.stdout.write = () => true;
|
|
342
|
+
|
|
343
|
+
try {
|
|
344
|
+
await runLoop({
|
|
345
|
+
dryRun: true,
|
|
346
|
+
agent: mockAgent,
|
|
347
|
+
maxIterations: 1,
|
|
348
|
+
pauseSeconds: 0,
|
|
349
|
+
buffer,
|
|
350
|
+
ui: false,
|
|
351
|
+
});
|
|
352
|
+
} catch {
|
|
353
|
+
// Expected: max iterations exceeded
|
|
354
|
+
} finally {
|
|
355
|
+
console.log = originalLog;
|
|
356
|
+
process.stdout.write = originalStdoutWrite;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Verify agent output was captured
|
|
360
|
+
const output = buffer.getOutput();
|
|
361
|
+
expect(output.length).toBe(2);
|
|
362
|
+
expect(output[0]!.text).toBe("Agent output text\n");
|
|
363
|
+
expect(output[1]!.text).toBe("More output\n");
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test("buffer subscribers receive logs in real-time", async () => {
|
|
367
|
+
const buffer = createOutputBuffer();
|
|
368
|
+
const receivedLogs: string[] = [];
|
|
369
|
+
|
|
370
|
+
// Subscribe before running loop
|
|
371
|
+
buffer.subscribeLogs((entry) => {
|
|
372
|
+
receivedLogs.push(entry.message);
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
const originalLog = console.log;
|
|
376
|
+
console.log = () => {};
|
|
377
|
+
|
|
378
|
+
try {
|
|
379
|
+
await runLoop({
|
|
380
|
+
dryRun: true,
|
|
381
|
+
maxIterations: 1,
|
|
382
|
+
pauseSeconds: 0,
|
|
383
|
+
buffer,
|
|
384
|
+
ui: false,
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// Verify subscriber received logs
|
|
388
|
+
expect(receivedLogs.length).toBeGreaterThan(0);
|
|
389
|
+
expect(receivedLogs.some((m) => m.includes("Starting math loop"))).toBe(
|
|
390
|
+
true
|
|
391
|
+
);
|
|
392
|
+
} finally {
|
|
393
|
+
console.log = originalLog;
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
test("buffer subscribers receive agent output in real-time", async () => {
|
|
398
|
+
await writeFile(
|
|
399
|
+
join(testDir, "todo", "TASKS.md"),
|
|
400
|
+
`# Tasks
|
|
401
|
+
|
|
402
|
+
### test-task
|
|
403
|
+
- content: Test task
|
|
404
|
+
- status: pending
|
|
405
|
+
- dependencies: none
|
|
406
|
+
`
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
const buffer = createOutputBuffer();
|
|
410
|
+
const receivedOutput: string[] = [];
|
|
411
|
+
|
|
412
|
+
// Subscribe before running loop
|
|
413
|
+
buffer.subscribeOutput((output) => {
|
|
414
|
+
receivedOutput.push(output.text);
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
const mockAgent = createMockAgent({
|
|
418
|
+
output: ["Streamed output\n"],
|
|
419
|
+
exitCode: 0,
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
const originalLog = console.log;
|
|
423
|
+
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
424
|
+
console.log = () => {};
|
|
425
|
+
process.stdout.write = () => true;
|
|
426
|
+
|
|
427
|
+
try {
|
|
428
|
+
await runLoop({
|
|
429
|
+
dryRun: true,
|
|
430
|
+
agent: mockAgent,
|
|
431
|
+
maxIterations: 1,
|
|
432
|
+
pauseSeconds: 0,
|
|
433
|
+
buffer,
|
|
434
|
+
ui: false,
|
|
435
|
+
});
|
|
436
|
+
} catch {
|
|
437
|
+
// Expected: max iterations exceeded
|
|
438
|
+
} finally {
|
|
439
|
+
console.log = originalLog;
|
|
440
|
+
process.stdout.write = originalStdoutWrite;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Verify subscriber received output
|
|
444
|
+
expect(receivedOutput).toContain("Streamed output\n");
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
test("console.log still works without buffer", async () => {
|
|
448
|
+
const logs: string[] = [];
|
|
449
|
+
const originalLog = console.log;
|
|
450
|
+
console.log = (...args: unknown[]) => {
|
|
451
|
+
logs.push(args.join(" "));
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
try {
|
|
455
|
+
// Run without buffer - console.log should still work
|
|
456
|
+
await runLoop({
|
|
457
|
+
dryRun: true,
|
|
458
|
+
maxIterations: 1,
|
|
459
|
+
pauseSeconds: 0,
|
|
460
|
+
ui: false,
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
// Verify console.log was called
|
|
464
|
+
const logText = logs.join("\n");
|
|
465
|
+
expect(logText).toContain("Starting math loop");
|
|
466
|
+
} finally {
|
|
467
|
+
console.log = originalLog;
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
describe("runLoop UI server integration", () => {
|
|
473
|
+
let testDir: string;
|
|
474
|
+
let originalCwd: string;
|
|
475
|
+
|
|
476
|
+
beforeEach(async () => {
|
|
477
|
+
// Create a temp directory for each test
|
|
478
|
+
testDir = await mkdtemp(join(tmpdir(), "math-loop-ui-test-"));
|
|
479
|
+
originalCwd = process.cwd();
|
|
480
|
+
process.chdir(testDir);
|
|
481
|
+
|
|
482
|
+
// Create the todo directory with required files
|
|
483
|
+
const todoDir = join(testDir, "todo");
|
|
484
|
+
await mkdir(todoDir, { recursive: true });
|
|
485
|
+
|
|
486
|
+
// Create PROMPT.md
|
|
487
|
+
await writeFile(
|
|
488
|
+
join(todoDir, "PROMPT.md"),
|
|
489
|
+
"# Test Prompt\n\nTest instructions."
|
|
490
|
+
);
|
|
491
|
+
|
|
492
|
+
// Create TASKS.md with all tasks complete
|
|
493
|
+
await writeFile(
|
|
494
|
+
join(todoDir, "TASKS.md"),
|
|
495
|
+
`# Tasks
|
|
496
|
+
|
|
497
|
+
### test-task
|
|
498
|
+
- content: Test task
|
|
499
|
+
- status: complete
|
|
500
|
+
- dependencies: none
|
|
501
|
+
`
|
|
502
|
+
);
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
afterEach(async () => {
|
|
506
|
+
process.chdir(originalCwd);
|
|
507
|
+
await rm(testDir, { recursive: true, force: true });
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
// NOTE: UI server tests are skipped in automated testing because:
|
|
511
|
+
// 1. They require exclusive port access (can't run in parallel)
|
|
512
|
+
// 2. The server stays running after tests complete (as designed)
|
|
513
|
+
// Manual testing should verify UI server integration works correctly.
|
|
514
|
+
|
|
515
|
+
test("ui: false disables the server", async () => {
|
|
516
|
+
const logs: string[] = [];
|
|
517
|
+
const originalLog = console.log;
|
|
518
|
+
console.log = (...args: unknown[]) => {
|
|
519
|
+
logs.push(args.join(" "));
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
try {
|
|
523
|
+
await runLoop({
|
|
524
|
+
dryRun: true,
|
|
525
|
+
maxIterations: 1,
|
|
526
|
+
pauseSeconds: 0,
|
|
527
|
+
ui: false,
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
// Verify UI server URL is NOT logged
|
|
531
|
+
const logText = logs.join("\n");
|
|
532
|
+
expect(logText).not.toContain("Web UI available");
|
|
533
|
+
} finally {
|
|
534
|
+
console.log = originalLog;
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
});
|