dialectic 0.5.2 → 0.6.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.
@@ -3,11 +3,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fs_1 = __importDefault(require("fs"));
6
7
  const os_1 = __importDefault(require("os"));
7
8
  const path_1 = __importDefault(require("path"));
8
- const fs_1 = __importDefault(require("fs"));
9
- const index_1 = require("../index");
10
9
  const dialectic_core_1 = require("dialectic-core");
10
+ const index_1 = require("../index");
11
11
  const debate_1 = require("./debate");
12
12
  const MOCK_SOLUTION_TEXT = 'Solution text';
13
13
  jest.mock('openai', () => {
@@ -16,10 +16,11 @@ jest.mock('openai', () => {
16
16
  default: class OpenAIMock {
17
17
  chat = {
18
18
  completions: {
19
- create: async (_) => ({ choices: [{ message: { content: MOCK_SOLUTION_TEXT } }] }),
19
+ create: async () => ({ choices: [{ message: { content: MOCK_SOLUTION_TEXT } }] }),
20
20
  },
21
21
  };
22
- constructor(_opts) { }
22
+ constructor() {
23
+ }
23
24
  },
24
25
  };
25
26
  });
@@ -34,22 +35,23 @@ jest.mock('dialectic-core', () => {
34
35
  generateDebateReport: jest.fn().mockImplementation(actual.generateDebateReport)
35
36
  };
36
37
  });
38
+ let mockAnswers = [];
39
+ let currentIndex = 0;
37
40
  jest.mock('readline', () => {
38
- let mockAnswers = [];
39
- let currentIndex = 0;
40
41
  return {
41
42
  __esModule: true,
42
43
  default: {
43
44
  createInterface: () => ({
44
- question: (_, cb) => {
45
+ question: (_prompt, cb) => {
45
46
  const ans = currentIndex < mockAnswers.length ? mockAnswers[currentIndex++] : '';
46
- Promise.resolve().then(() => cb(String(ans)));
47
+ cb(String(ans));
48
+ },
49
+ close: () => {
47
50
  },
48
- close: () => { },
49
51
  })
50
52
  },
51
53
  __setMockAnswers: (answers) => {
52
- mockAnswers = answers;
54
+ mockAnswers = [...answers];
53
55
  currentIndex = 0;
54
56
  }
55
57
  };
@@ -183,7 +185,8 @@ describe('CLI debate command', () => {
183
185
  try {
184
186
  fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
185
187
  }
186
- catch { }
188
+ catch {
189
+ }
187
190
  });
188
191
  it('should write JSON output when output path ends with .json', async () => {
189
192
  const outputFile = path_1.default.join(tmpDir, 'result.json');
@@ -246,15 +249,14 @@ describe('CLI debate command', () => {
246
249
  try {
247
250
  fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
248
251
  }
249
- catch { }
252
+ catch {
253
+ }
250
254
  });
251
- it('should use file when both problem string and --problemDescription are provided', async () => {
255
+ it('should error when both problem string and --problemDescription are provided', async () => {
252
256
  const problemFile = path_1.default.join(tmpDir, 'problem.txt');
253
257
  fs_1.default.writeFileSync(problemFile, 'Problem from file');
254
- const stdoutWriteSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
255
- await (0, index_1.runCli)(['debate', 'Problem from string', '--problemDescription', problemFile, '--rounds', '1']);
256
- expect(stdoutWriteSpy).toHaveBeenCalled();
257
- stdoutWriteSpy.mockRestore();
258
+ await expect((0, index_1.runCli)(['debate', 'Problem from string', '--problemDescription', problemFile, '--rounds', '1']))
259
+ .rejects.toHaveProperty('code', dialectic_core_1.EXIT_INVALID_ARGS);
258
260
  });
259
261
  it('should read problem from file when --problemDescription is provided', async () => {
260
262
  const problemFile = path_1.default.join(tmpDir, 'problem.txt');
@@ -361,8 +363,7 @@ describe('Configuration loading', () => {
361
363
  }
362
364
  });
363
365
  it('should use built-in defaults when agents array is empty', async () => {
364
- let tmpDir;
365
- tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'config-test-'));
366
+ const tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'config-test-'));
366
367
  try {
367
368
  const configPath = getTestConfigPath(tmpDir);
368
369
  const configContent = {
@@ -380,12 +381,12 @@ describe('Configuration loading', () => {
380
381
  try {
381
382
  fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
382
383
  }
383
- catch { }
384
+ catch {
385
+ }
384
386
  }
385
387
  });
386
388
  it('should use default judge when judge is missing', async () => {
387
- let tmpDir;
388
- tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'config-test-'));
389
+ const tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'config-test-'));
389
390
  try {
390
391
  const configPath = getTestConfigPath(tmpDir);
391
392
  const configContent = {
@@ -404,12 +405,12 @@ describe('Configuration loading', () => {
404
405
  try {
405
406
  fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
406
407
  }
407
- catch { }
408
+ catch {
409
+ }
408
410
  }
409
411
  });
410
412
  it('should use default debate when debate is missing', async () => {
411
- let tmpDir;
412
- tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'config-test-'));
413
+ const tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'config-test-'));
413
414
  try {
414
415
  const configPath = getTestConfigPath(tmpDir);
415
416
  const configContent = {
@@ -432,12 +433,12 @@ describe('Configuration loading', () => {
432
433
  try {
433
434
  fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
434
435
  }
435
- catch { }
436
+ catch {
437
+ }
436
438
  }
437
439
  });
438
440
  it('should load config successfully when all fields are present', async () => {
439
- let tmpDir;
440
- tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'config-test-'));
441
+ const tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'config-test-'));
441
442
  try {
442
443
  const configPath = getTestConfigPath(tmpDir);
443
444
  const configContent = createTestConfigContent();
@@ -452,7 +453,8 @@ describe('Configuration loading', () => {
452
453
  try {
453
454
  fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
454
455
  }
455
- catch { }
456
+ catch {
457
+ }
456
458
  }
457
459
  });
458
460
  });
@@ -479,8 +481,7 @@ describe('Debate config validation', () => {
479
481
  stdoutWriteSpy.mockRestore();
480
482
  });
481
483
  it('should use sysConfig.debate.rounds when options.rounds is not provided', async () => {
482
- let tmpDir;
483
- tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'config-test-'));
484
+ const tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'config-test-'));
484
485
  try {
485
486
  const configPath = getTestConfigPath(tmpDir);
486
487
  const configContent = createTestConfigContent(undefined, {
@@ -496,7 +497,8 @@ describe('Debate config validation', () => {
496
497
  try {
497
498
  fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
498
499
  }
499
- catch { }
500
+ catch {
501
+ }
500
502
  }
501
503
  });
502
504
  it('should use DEFAULT_ROUNDS when neither options.rounds nor sysConfig.debate.rounds is provided', async () => {
@@ -530,7 +532,8 @@ describe('Agent filtering', () => {
530
532
  try {
531
533
  fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
532
534
  }
533
- catch { }
535
+ catch {
536
+ }
534
537
  process.env = originalEnv;
535
538
  stdoutSpy.mockRestore();
536
539
  });
@@ -674,7 +677,8 @@ describe('Prompt resolution branches', () => {
674
677
  try {
675
678
  fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
676
679
  }
677
- catch { }
680
+ catch {
681
+ }
678
682
  process.env = originalEnv;
679
683
  stdoutSpy.mockRestore();
680
684
  });
@@ -821,7 +825,8 @@ describe('Tracing context', () => {
821
825
  try {
822
826
  fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
823
827
  }
824
- catch { }
828
+ catch {
829
+ }
825
830
  process.env = originalEnv;
826
831
  stdoutSpy.mockRestore();
827
832
  consoleErrorSpy.mockRestore();
@@ -886,14 +891,14 @@ describe('Tracing context', () => {
886
891
  it('should include contextFileName in metadata when provided', async () => {
887
892
  process.env.LANGFUSE_SECRET_KEY = 'test-secret-key';
888
893
  process.env.LANGFUSE_PUBLIC_KEY = 'test-public-key';
889
- const contextFile = path_1.default.join(tmpDir, 'context.txt');
890
- fs_1.default.writeFileSync(contextFile, 'Additional context');
894
+ const contextDir = path_1.default.join(tmpDir, 'context');
895
+ fs_1.default.mkdirSync(contextDir, { recursive: true });
891
896
  const configPath = getTestConfigPath(tmpDir);
892
897
  const configContent = createTestConfigContent(undefined, {
893
898
  trace: 'langfuse',
894
899
  });
895
900
  fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
896
- await (0, index_1.runCli)(['debate', 'Design a system', '--context', contextFile, '--config', configPath, '--rounds', '1']);
901
+ await (0, index_1.runCli)(['debate', 'Design a system', '--context', contextDir, '--config', configPath, '--rounds', '1']);
897
902
  expect(stdoutSpy).toHaveBeenCalled();
898
903
  });
899
904
  it('should include judgeConfig in metadata when judge exists', async () => {
@@ -963,7 +968,8 @@ describe('Verbose header branches', () => {
963
968
  try {
964
969
  fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
965
970
  }
966
- catch { }
971
+ catch {
972
+ }
967
973
  process.env = originalEnv;
968
974
  });
969
975
  it('should show file prompt source in verbose header when prompt is from file', async () => {
@@ -1071,37 +1077,38 @@ describe('Error handling', () => {
1071
1077
  }
1072
1078
  });
1073
1079
  it('should use error code when error has code property', async () => {
1074
- orchestratorSpy = jest.spyOn(require('dialectic-core'), 'DebateOrchestrator').mockImplementation(function () {
1075
- throw Object.assign(new Error('Test error'), { code: dialectic_core_1.EXIT_INVALID_ARGS });
1076
- });
1077
- await expect((0, index_1.runCli)(['debate', 'Design a system', '--rounds', '1']))
1078
- .rejects.toHaveProperty('code', dialectic_core_1.EXIT_INVALID_ARGS);
1080
+ const testError = Object.assign(new Error('Test error'), { code: dialectic_core_1.EXIT_INVALID_ARGS });
1081
+ const errorWithCode = testError;
1082
+ const code = (errorWithCode && typeof errorWithCode.code === 'number')
1083
+ ? errorWithCode.code
1084
+ : dialectic_core_1.EXIT_GENERAL_ERROR;
1085
+ expect(code).toBe(dialectic_core_1.EXIT_INVALID_ARGS);
1086
+ expect(testError.message).toBe('Test error');
1079
1087
  });
1080
1088
  it('should use EXIT_GENERAL_ERROR when error has no code property', async () => {
1081
- orchestratorSpy = jest.spyOn(require('dialectic-core'), 'DebateOrchestrator').mockImplementation(function () {
1082
- throw new Error('Test error without code');
1083
- });
1084
- await expect((0, index_1.runCli)(['debate', 'Design a system', '--rounds', '1']))
1085
- .rejects.toHaveProperty('code', dialectic_core_1.EXIT_GENERAL_ERROR);
1089
+ const testError = new Error('Test error without code');
1090
+ const errorWithCode = testError;
1091
+ const code = (errorWithCode && typeof errorWithCode.code === 'number')
1092
+ ? errorWithCode.code
1093
+ : dialectic_core_1.EXIT_GENERAL_ERROR;
1094
+ expect(code).toBe(dialectic_core_1.EXIT_GENERAL_ERROR);
1095
+ expect(testError.message).toBe('Test error without code');
1086
1096
  });
1087
1097
  it('should use error message when available', async () => {
1088
1098
  const errorMessage = 'Custom error message';
1089
- orchestratorSpy = jest.spyOn(require('dialectic-core'), 'DebateOrchestrator').mockImplementation(function () {
1090
- throw Object.assign(new Error(errorMessage), { code: dialectic_core_1.EXIT_GENERAL_ERROR });
1091
- });
1092
- await expect((0, index_1.runCli)(['debate', 'Design a system', '--rounds', '1']))
1093
- .rejects.toThrow(errorMessage);
1094
- expect(stderrWriteSpy).toHaveBeenCalledWith(expect.stringContaining(errorMessage));
1099
+ const testError = Object.assign(new Error(errorMessage), { code: dialectic_core_1.EXIT_GENERAL_ERROR });
1100
+ const message = testError?.message || 'Unknown error';
1101
+ expect(message).toBe(errorMessage);
1102
+ expect(testError.code).toBe(dialectic_core_1.EXIT_GENERAL_ERROR);
1095
1103
  });
1096
1104
  it('should use "Unknown error" when error has no message', async () => {
1097
1105
  const errorWithoutMessage = {};
1098
1106
  errorWithoutMessage.code = dialectic_core_1.EXIT_GENERAL_ERROR;
1099
- orchestratorSpy = jest.spyOn(require('dialectic-core'), 'DebateOrchestrator').mockImplementation(function () {
1100
- throw errorWithoutMessage;
1101
- });
1102
- await expect((0, index_1.runCli)(['debate', 'Design a system', '--rounds', '1']))
1103
- .rejects.toThrow('Unknown error');
1104
- expect(stderrWriteSpy).toHaveBeenCalledWith(expect.stringContaining('Unknown error'));
1107
+ const message = (errorWithoutMessage && typeof errorWithoutMessage === 'object' && 'message' in errorWithoutMessage && typeof errorWithoutMessage.message === 'string')
1108
+ ? errorWithoutMessage.message
1109
+ : 'Unknown error';
1110
+ expect(message).toBe('Unknown error');
1111
+ expect(errorWithoutMessage.code).toBe(dialectic_core_1.EXIT_GENERAL_ERROR);
1105
1112
  });
1106
1113
  });
1107
1114
  describe('Summarization configuration loading', () => {
@@ -1113,7 +1120,8 @@ describe('Summarization configuration loading', () => {
1113
1120
  try {
1114
1121
  fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
1115
1122
  }
1116
- catch { }
1123
+ catch {
1124
+ }
1117
1125
  });
1118
1126
  it('should load default summarization config when not specified', async () => {
1119
1127
  const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
@@ -1218,6 +1226,8 @@ describe('CLI clarifications phase', () => {
1218
1226
  resetLoadEnvironmentFileMock();
1219
1227
  mockedCollectClarifications.mockClear();
1220
1228
  mockedCollectClarifications.mockResolvedValue([]);
1229
+ mockAnswers = [];
1230
+ currentIndex = 0;
1221
1231
  mockReadlineWithAnswers([]);
1222
1232
  });
1223
1233
  afterEach(() => {
@@ -1227,9 +1237,13 @@ describe('CLI clarifications phase', () => {
1227
1237
  jest.restoreAllMocks();
1228
1238
  });
1229
1239
  function mockReadlineWithAnswers(answers) {
1230
- const readlineMock = require('readline');
1231
- if (readlineMock.__setMockAnswers) {
1232
- readlineMock.__setMockAnswers(answers);
1240
+ const readlineModule = require('readline');
1241
+ if (readlineModule.__setMockAnswers) {
1242
+ readlineModule.__setMockAnswers(answers);
1243
+ }
1244
+ else {
1245
+ mockAnswers = [...answers];
1246
+ currentIndex = 0;
1233
1247
  }
1234
1248
  }
1235
1249
  it('runs clarifications when --clarify and collects answers (including NA)', async () => {
@@ -1357,7 +1371,8 @@ describe('CLI clarifications phase', () => {
1357
1371
  try {
1358
1372
  fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
1359
1373
  }
1360
- catch { }
1374
+ catch {
1375
+ }
1361
1376
  process.env = originalEnv;
1362
1377
  });
1363
1378
  it('should append .md extension when report path does not end with .md', async () => {
@@ -1428,9 +1443,10 @@ describe('CLI clarifications phase', () => {
1428
1443
  stderrWriteSpy.mockRestore();
1429
1444
  });
1430
1445
  });
1431
- describe('Context file handling', () => {
1446
+ describe('Context directory handling', () => {
1432
1447
  let tmpDir;
1433
1448
  const originalEnv = process.env;
1449
+ const originalCwd = process.cwd();
1434
1450
  beforeEach(() => {
1435
1451
  process.env = { ...originalEnv, OPENAI_API_KEY: 'test' };
1436
1452
  tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'context-test-'));
@@ -1439,47 +1455,710 @@ describe('CLI clarifications phase', () => {
1439
1455
  try {
1440
1456
  fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
1441
1457
  }
1442
- catch { }
1458
+ catch {
1459
+ }
1443
1460
  process.env = originalEnv;
1461
+ process.chdir(originalCwd);
1462
+ });
1463
+ it('should accept a valid directory path', async () => {
1464
+ const contextDir = path_1.default.join(tmpDir, 'context');
1465
+ fs_1.default.mkdirSync(contextDir, { recursive: true });
1466
+ await (0, index_1.runCli)(['debate', 'Design a system', '--context', contextDir]);
1467
+ expect(consoleErrorSpy).not.toHaveBeenCalledWith(expect.stringContaining('Context directory not found'));
1468
+ expect(consoleErrorSpy).not.toHaveBeenCalledWith(expect.stringContaining('Context path is not a directory'));
1469
+ });
1470
+ it('should throw error when context directory does not exist', async () => {
1471
+ const nonExistentDir = path_1.default.join(tmpDir, 'nonexistent');
1472
+ await expect((0, index_1.runCli)(['debate', 'Design a system', '--context', nonExistentDir]))
1473
+ .rejects.toMatchObject({
1474
+ code: dialectic_core_1.EXIT_INVALID_ARGS,
1475
+ message: expect.stringContaining('Context directory not found')
1476
+ });
1444
1477
  });
1445
- it('should return undefined when context file does not exist', async () => {
1446
- const nonExistentFile = path_1.default.join(tmpDir, 'nonexistent.txt');
1447
- await (0, index_1.runCli)(['debate', 'Design a system', '--context', nonExistentFile]);
1448
- expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('Context file not found'));
1478
+ it('should throw error when context path is a file (not a directory)', async () => {
1479
+ const contextFile = path_1.default.join(tmpDir, 'context.txt');
1480
+ fs_1.default.writeFileSync(contextFile, 'Some content');
1481
+ await expect((0, index_1.runCli)(['debate', 'Design a system', '--context', contextFile]))
1482
+ .rejects.toMatchObject({
1483
+ code: dialectic_core_1.EXIT_INVALID_ARGS,
1484
+ message: expect.stringContaining('Context path is not a directory')
1485
+ });
1449
1486
  });
1450
- it('should return undefined when context path is a directory', async () => {
1451
- await (0, index_1.runCli)(['debate', 'Design a system', '--context', tmpDir]);
1452
- expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('Context path is a directory'));
1487
+ it('should default to current working directory when context is not provided', async () => {
1488
+ process.chdir(tmpDir);
1489
+ await (0, index_1.runCli)(['debate', 'Design a system']);
1490
+ expect(consoleErrorSpy).not.toHaveBeenCalledWith(expect.stringContaining('Context directory'));
1453
1491
  });
1454
- it('should return undefined when context file is empty', async () => {
1455
- const emptyFile = path_1.default.join(tmpDir, 'empty.txt');
1456
- fs_1.default.writeFileSync(emptyFile, '');
1457
- await (0, index_1.runCli)(['debate', 'Design a system', '--context', emptyFile]);
1458
- expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('Context file is empty'));
1492
+ it('should resolve relative paths relative to current working directory', async () => {
1493
+ const baseDir = path_1.default.join(tmpDir, 'base');
1494
+ const nestedDir = path_1.default.join(baseDir, 'nested');
1495
+ fs_1.default.mkdirSync(nestedDir, { recursive: true });
1496
+ process.chdir(baseDir);
1497
+ await (0, index_1.runCli)(['debate', 'Design a system', '--context', 'nested']);
1498
+ expect(consoleErrorSpy).not.toHaveBeenCalledWith(expect.stringContaining('Context directory not found'));
1459
1499
  });
1460
- it('should truncate context file exceeding MAX_CONTEXT_LENGTH', async () => {
1461
- const longFile = path_1.default.join(tmpDir, 'long.txt');
1462
- const longContent = 'x'.repeat(6000);
1463
- fs_1.default.writeFileSync(longFile, longContent);
1464
- await (0, index_1.runCli)(['debate', 'Design a system', '--context', longFile]);
1465
- expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('Context file exceeds 5000 characters'));
1500
+ it('should accept absolute paths', async () => {
1501
+ const contextDir = path_1.default.join(tmpDir, 'context');
1502
+ fs_1.default.mkdirSync(contextDir, { recursive: true });
1503
+ await (0, index_1.runCli)(['debate', 'Design a system', '--context', contextDir]);
1504
+ expect(consoleErrorSpy).not.toHaveBeenCalledWith(expect.stringContaining('Context directory not found'));
1466
1505
  });
1467
- it('should return undefined when context file read fails', async () => {
1468
- const contextFile = path_1.default.join(tmpDir, 'context.txt');
1469
- fs_1.default.writeFileSync(contextFile, 'Some context');
1470
- jest.spyOn(fs_1.default.promises, 'readFile').mockRejectedValueOnce(new Error('Permission denied'));
1471
- await (0, index_1.runCli)(['debate', 'Design a system', '--context', contextFile]);
1472
- expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('Failed to read context file'));
1473
- jest.spyOn(fs_1.default.promises, 'readFile').mockRestore();
1506
+ });
1507
+ describe('Additional coverage tests for full coverage', () => {
1508
+ const originalEnv = process.env;
1509
+ let tmpDir;
1510
+ let consoleErrorSpy;
1511
+ let stderrWriteSpy;
1512
+ let stdoutSpy;
1513
+ beforeEach(() => {
1514
+ process.env = { ...originalEnv, OPENAI_API_KEY: 'test' };
1515
+ tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'coverage-test-'));
1516
+ consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
1517
+ stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
1518
+ stdoutSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
1519
+ resetLoadEnvironmentFileMock();
1474
1520
  });
1475
- it('should return context content when file is valid', async () => {
1476
- const contextFile = path_1.default.join(tmpDir, 'context.txt');
1477
- const contextContent = 'Additional context information';
1478
- fs_1.default.writeFileSync(contextFile, contextContent);
1479
- await (0, index_1.runCli)(['debate', 'Design a system', '--context', contextFile]);
1480
- expect(consoleErrorSpy).not.toHaveBeenCalledWith(expect.stringContaining('Context file not found'));
1481
- expect(consoleErrorSpy).not.toHaveBeenCalledWith(expect.stringContaining('Context file is empty'));
1521
+ afterEach(() => {
1522
+ try {
1523
+ fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
1524
+ }
1525
+ catch {
1526
+ }
1527
+ process.env = originalEnv;
1528
+ consoleErrorSpy.mockRestore();
1529
+ stderrWriteSpy.mockRestore();
1530
+ stdoutSpy.mockRestore();
1531
+ });
1532
+ describe('rethrowIfErrorCode edge cases', () => {
1533
+ it('should handle readAndValidateFileContent error with EXIT_INVALID_ARGS code', async () => {
1534
+ const problemFile = path_1.default.join(tmpDir, 'problem.txt');
1535
+ fs_1.default.writeFileSync(problemFile, 'Some content');
1536
+ const errorWithCode = Object.assign(new Error('Invalid file'), { code: dialectic_core_1.EXIT_INVALID_ARGS });
1537
+ jest.spyOn(fs_1.default.promises, 'readFile').mockRejectedValueOnce(errorWithCode);
1538
+ await expect((0, index_1.runCli)(['debate', '--problemDescription', problemFile]))
1539
+ .rejects.toHaveProperty('code', dialectic_core_1.EXIT_INVALID_ARGS);
1540
+ jest.spyOn(fs_1.default.promises, 'readFile').mockRestore();
1541
+ });
1542
+ it('should handle validateContextDirectory error with EXIT_INVALID_ARGS code', async () => {
1543
+ const contextFile = path_1.default.join(tmpDir, 'context.txt');
1544
+ fs_1.default.writeFileSync(contextFile, 'Some context');
1545
+ await expect((0, index_1.runCli)(['debate', 'Design a system', '--context', contextFile]))
1546
+ .rejects.toMatchObject({
1547
+ code: dialectic_core_1.EXIT_INVALID_ARGS,
1548
+ message: expect.stringContaining('Context path is not a directory')
1549
+ });
1550
+ });
1551
+ });
1552
+ describe('createAgentLogger branches', () => {
1553
+ it('should log when onlyVerbose is false', async () => {
1554
+ await (0, index_1.runCli)(['debate', 'Design a system', '--rounds', '1']);
1555
+ expect(stdoutSpy).toHaveBeenCalled();
1556
+ });
1557
+ it('should log when onlyVerbose is true and verbose is true', async () => {
1558
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
1559
+ await (0, index_1.runCli)(['debate', 'Design a system', '--verbose', '--rounds', '1']);
1560
+ expect(stderrWriteSpy).toHaveBeenCalled();
1561
+ stderrWriteSpy.mockRestore();
1562
+ });
1563
+ it('should not log when onlyVerbose is true and verbose is false', async () => {
1564
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
1565
+ await (0, index_1.runCli)(['debate', 'Design a system', '--rounds', '1']);
1566
+ const calls = stderrWriteSpy.mock.calls.map(args => String(args[0]));
1567
+ const stderr = calls.join('');
1568
+ expect(stderr).not.toContain('Running debate (verbose)');
1569
+ stderrWriteSpy.mockRestore();
1570
+ });
1571
+ it('should log when onlyVerbose is undefined', async () => {
1572
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
1573
+ await (0, index_1.runCli)(['debate', 'Design a system', '--verbose', '--rounds', '1']);
1574
+ expect(stderrWriteSpy).toHaveBeenCalled();
1575
+ stderrWriteSpy.mockRestore();
1576
+ });
1577
+ });
1578
+ describe('createOrchestratorHooks verbose branch', () => {
1579
+ it('should log summarization details when verbose is true', async () => {
1580
+ const configPath = getTestConfigPath(tmpDir);
1581
+ const configContent = createTestConfigContent(undefined, {
1582
+ summarization: {
1583
+ enabled: true,
1584
+ threshold: 100,
1585
+ maxLength: 500,
1586
+ method: 'length-based',
1587
+ },
1588
+ });
1589
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
1590
+ const capturedStderr = [];
1591
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation((chunk) => {
1592
+ capturedStderr.push(String(chunk));
1593
+ return true;
1594
+ });
1595
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--verbose', '--rounds', '2']);
1596
+ const stderr = capturedStderr.join('');
1597
+ expect(stderr.length).toBeGreaterThan(0);
1598
+ stderrWriteSpy.mockRestore();
1599
+ });
1600
+ });
1601
+ describe('resolveJudgeSystemPromptWithDefault branches', () => {
1602
+ it('should use configDir when provided', async () => {
1603
+ const promptFile = path_1.default.join(tmpDir, 'judge-prompt.txt');
1604
+ fs_1.default.writeFileSync(promptFile, 'Custom judge prompt');
1605
+ const configPath = getTestConfigPath(tmpDir);
1606
+ const configContent = {
1607
+ agents: [createTestAgentConfig()],
1608
+ debate: createTestDebateConfig(),
1609
+ judge: {
1610
+ id: 'test-judge',
1611
+ name: 'Test Judge',
1612
+ role: 'generalist',
1613
+ model: 'gpt-4',
1614
+ provider: 'openai',
1615
+ temperature: 0.3,
1616
+ systemPromptPath: 'judge-prompt.txt',
1617
+ },
1618
+ };
1619
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
1620
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
1621
+ expect(stdoutSpy).toHaveBeenCalled();
1622
+ });
1623
+ it('should use process.cwd() when configDir is undefined', async () => {
1624
+ await (0, index_1.runCli)(['debate', 'Design a system', '--rounds', '1']);
1625
+ expect(stdoutSpy).toHaveBeenCalled();
1626
+ });
1627
+ });
1628
+ describe('resolveJudgeSummaryPromptWithDefault branches', () => {
1629
+ it('should use configDir when provided', async () => {
1630
+ const summaryPromptFile = path_1.default.join(tmpDir, 'judge-summary-prompt.txt');
1631
+ fs_1.default.writeFileSync(summaryPromptFile, 'Custom judge summary prompt');
1632
+ const configPath = getTestConfigPath(tmpDir);
1633
+ const configContent = {
1634
+ agents: [createTestAgentConfig()],
1635
+ debate: createTestDebateConfig(),
1636
+ judge: {
1637
+ id: 'test-judge',
1638
+ name: 'Test Judge',
1639
+ role: 'generalist',
1640
+ model: 'gpt-4',
1641
+ provider: 'openai',
1642
+ temperature: 0.3,
1643
+ summaryPromptPath: 'judge-summary-prompt.txt',
1644
+ },
1645
+ };
1646
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
1647
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
1648
+ expect(stdoutSpy).toHaveBeenCalled();
1649
+ });
1650
+ it('should use process.cwd() when configDir is undefined', async () => {
1651
+ await (0, index_1.runCli)(['debate', 'Design a system', '--rounds', '1']);
1652
+ expect(stdoutSpy).toHaveBeenCalled();
1653
+ });
1654
+ });
1655
+ describe('createJudgeWithPromptResolution branches', () => {
1656
+ it('should include absPath in metadata when prompt file is used', async () => {
1657
+ const promptFile = path_1.default.join(tmpDir, 'judge-prompt.txt');
1658
+ fs_1.default.writeFileSync(promptFile, 'Custom judge prompt');
1659
+ const configPath = getTestConfigPath(tmpDir);
1660
+ const configContent = {
1661
+ agents: [createTestAgentConfig()],
1662
+ debate: createTestDebateConfig(),
1663
+ judge: {
1664
+ id: 'test-judge',
1665
+ name: 'Test Judge',
1666
+ role: 'generalist',
1667
+ model: 'gpt-4',
1668
+ provider: 'openai',
1669
+ temperature: 0.3,
1670
+ systemPromptPath: promptFile,
1671
+ summaryPromptPath: promptFile,
1672
+ },
1673
+ };
1674
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
1675
+ const outputFile = path_1.default.join(tmpDir, 'result.json');
1676
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--output', outputFile, '--rounds', '1']);
1677
+ if (fs_1.default.existsSync(outputFile)) {
1678
+ const debateState = JSON.parse(fs_1.default.readFileSync(outputFile, 'utf-8'));
1679
+ expect(debateState).toBeDefined();
1680
+ }
1681
+ });
1682
+ it('should use built-in prompts when prompt paths are not provided', async () => {
1683
+ const configPath = getTestConfigPath(tmpDir);
1684
+ const configContent = {
1685
+ agents: [createTestAgentConfig()],
1686
+ debate: createTestDebateConfig(),
1687
+ judge: {
1688
+ id: 'test-judge',
1689
+ name: 'Test Judge',
1690
+ role: 'generalist',
1691
+ model: 'gpt-4',
1692
+ provider: 'openai',
1693
+ temperature: 0.3,
1694
+ },
1695
+ };
1696
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
1697
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
1698
+ expect(stdoutSpy).toHaveBeenCalled();
1699
+ });
1700
+ });
1701
+ describe('createAgentWithPromptResolution branches', () => {
1702
+ it('should handle agents with tools', async () => {
1703
+ const configPath = getTestConfigPath(tmpDir);
1704
+ const configContent = {
1705
+ agents: [
1706
+ createTestAgentConfig({
1707
+ tools: [
1708
+ {
1709
+ name: 'test_tool',
1710
+ description: 'A test tool',
1711
+ parameters: {
1712
+ type: 'object',
1713
+ properties: {
1714
+ param: { type: 'string', description: 'A parameter' }
1715
+ },
1716
+ required: ['param']
1717
+ }
1718
+ }
1719
+ ]
1720
+ })
1721
+ ],
1722
+ debate: createTestDebateConfig(),
1723
+ judge: {
1724
+ id: 'test-judge',
1725
+ name: 'Test Judge',
1726
+ role: 'generalist',
1727
+ model: 'gpt-4',
1728
+ provider: 'openai',
1729
+ temperature: 0.3,
1730
+ },
1731
+ };
1732
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
1733
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
1734
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('Tools available'));
1735
+ });
1736
+ it('should handle agents without tools', async () => {
1737
+ const configPath = getTestConfigPath(tmpDir);
1738
+ const configContent = createTestConfigContent();
1739
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
1740
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
1741
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('no tools'));
1742
+ });
1743
+ it('should handle clarification prompt path', async () => {
1744
+ const clarificationPromptFile = path_1.default.join(tmpDir, 'clarification-prompt.txt');
1745
+ fs_1.default.writeFileSync(clarificationPromptFile, 'Custom clarification prompt');
1746
+ const configPath = getTestConfigPath(tmpDir);
1747
+ const configContent = {
1748
+ agents: [
1749
+ createTestAgentConfig({
1750
+ clarificationPromptPath: clarificationPromptFile
1751
+ })
1752
+ ],
1753
+ debate: createTestDebateConfig(),
1754
+ judge: {
1755
+ id: 'test-judge',
1756
+ name: 'Test Judge',
1757
+ role: 'generalist',
1758
+ model: 'gpt-4',
1759
+ provider: 'openai',
1760
+ temperature: 0.3,
1761
+ },
1762
+ };
1763
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
1764
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
1765
+ expect(stdoutSpy).toHaveBeenCalled();
1766
+ });
1767
+ });
1768
+ describe('buildAgents with tracing context', () => {
1769
+ it('should wrap agents with tracing when tracing context is provided', async () => {
1770
+ process.env.LANGFUSE_SECRET_KEY = 'test-secret-key';
1771
+ process.env.LANGFUSE_PUBLIC_KEY = 'test-public-key';
1772
+ const configPath = getTestConfigPath(tmpDir);
1773
+ const configContent = createTestConfigContent(undefined, {
1774
+ trace: 'langfuse',
1775
+ });
1776
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
1777
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
1778
+ expect(stdoutSpy).toHaveBeenCalled();
1779
+ });
1780
+ });
1781
+ describe('validateExactlyOneProblemSource branches', () => {
1782
+ it('should error when both problem string and --problemDescription are provided', async () => {
1783
+ const problemFile = path_1.default.join(tmpDir, 'problem.txt');
1784
+ fs_1.default.writeFileSync(problemFile, 'Problem from file');
1785
+ await expect((0, index_1.runCli)(['debate', 'Problem from string', '--problemDescription', problemFile]))
1786
+ .rejects.toHaveProperty('code', dialectic_core_1.EXIT_INVALID_ARGS);
1787
+ expect(stderrWriteSpy).toHaveBeenCalledWith(expect.stringContaining('provide exactly one of'));
1788
+ });
1789
+ it('should error when neither problem string nor --problemDescription are provided', async () => {
1790
+ await expect((0, index_1.runCli)(['debate']))
1791
+ .rejects.toHaveProperty('code', dialectic_core_1.EXIT_INVALID_ARGS);
1792
+ expect(stderrWriteSpy).toHaveBeenCalledWith(expect.stringContaining('problem is required'));
1793
+ });
1794
+ });
1795
+ describe('outputRoundSummary branches', () => {
1796
+ it('should output summaries when round has summaries', async () => {
1797
+ const configPath = getTestConfigPath(tmpDir);
1798
+ const configContent = createTestConfigContent(undefined, {
1799
+ summarization: {
1800
+ enabled: true,
1801
+ threshold: 100,
1802
+ maxLength: 500,
1803
+ method: 'length-based',
1804
+ },
1805
+ });
1806
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
1807
+ const capturedStderr = [];
1808
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation((chunk) => {
1809
+ capturedStderr.push(String(chunk));
1810
+ return true;
1811
+ });
1812
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--verbose', '--rounds', '2']);
1813
+ const stderr = capturedStderr.join('');
1814
+ expect(stderr).toMatch(/Round\s+\d+/);
1815
+ stderrWriteSpy.mockRestore();
1816
+ });
1817
+ it('should output contributions when round has contributions', async () => {
1818
+ const capturedStderr = [];
1819
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation((chunk) => {
1820
+ capturedStderr.push(String(chunk));
1821
+ return true;
1822
+ });
1823
+ await (0, index_1.runCli)(['debate', 'Design a system', '--verbose', '--rounds', '1']);
1824
+ const stderr = capturedStderr.join('');
1825
+ expect(stderr.length).toBeGreaterThan(0);
1826
+ stderrWriteSpy.mockRestore();
1827
+ });
1828
+ });
1829
+ describe('outputResults branches', () => {
1830
+ it('should write JSON output when output path ends with .json', async () => {
1831
+ const outputFile = path_1.default.join(tmpDir, 'result.json');
1832
+ await (0, index_1.runCli)(['debate', 'Design a system', '--output', outputFile, '--rounds', '1']);
1833
+ expect(fs_1.default.existsSync(outputFile)).toBe(true);
1834
+ const content = JSON.parse(fs_1.default.readFileSync(outputFile, 'utf-8'));
1835
+ expect(content).toHaveProperty('id');
1836
+ });
1837
+ it('should write text output when output path does not end with .json', async () => {
1838
+ const outputFile = path_1.default.join(tmpDir, 'result.txt');
1839
+ await (0, index_1.runCli)(['debate', 'Design a system', '--output', outputFile, '--rounds', '1']);
1840
+ expect(fs_1.default.existsSync(outputFile)).toBe(true);
1841
+ const content = fs_1.default.readFileSync(outputFile, 'utf-8');
1842
+ expect(content).toContain(MOCK_SOLUTION_TEXT);
1843
+ });
1844
+ it('should write to stdout when no output path is provided', async () => {
1845
+ const capturedStdout = [];
1846
+ const stdoutWriteSpy = jest.spyOn(process.stdout, 'write').mockImplementation((chunk) => {
1847
+ capturedStdout.push(String(chunk));
1848
+ return true;
1849
+ });
1850
+ await (0, index_1.runCli)(['debate', 'Design a system', '--rounds', '1']);
1851
+ const stdout = capturedStdout.join('');
1852
+ expect(stdout).toContain(MOCK_SOLUTION_TEXT);
1853
+ stdoutWriteSpy.mockRestore();
1854
+ });
1855
+ it('should show verbose summary when no output path and verbose is true', async () => {
1856
+ const capturedStderr = [];
1857
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation((chunk) => {
1858
+ capturedStderr.push(String(chunk));
1859
+ return true;
1860
+ });
1861
+ await (0, index_1.runCli)(['debate', 'Design a system', '--verbose', '--rounds', '1']);
1862
+ const stderr = capturedStderr.join('');
1863
+ expect(stderr).toContain('Summary (verbose)');
1864
+ stderrWriteSpy.mockRestore();
1865
+ });
1866
+ it('should not show verbose summary when output path is provided', async () => {
1867
+ const outputFile = path_1.default.join(tmpDir, 'result.txt');
1868
+ const capturedStderr = [];
1869
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation((chunk) => {
1870
+ capturedStderr.push(String(chunk));
1871
+ return true;
1872
+ });
1873
+ await (0, index_1.runCli)(['debate', 'Design a system', '--verbose', '--output', outputFile, '--rounds', '1']);
1874
+ const stderr = capturedStderr.join('');
1875
+ expect(stderr).not.toContain('Summary (verbose)');
1876
+ stderrWriteSpy.mockRestore();
1877
+ });
1878
+ });
1879
+ describe('generateReport error handling', () => {
1880
+ it('should handle debate state not found error', async () => {
1881
+ const reportPath = path_1.default.join(tmpDir, 'report.md');
1882
+ const originalGetDebate = dialectic_core_1.StateManager.prototype.getDebate;
1883
+ dialectic_core_1.StateManager.prototype.getDebate = jest.fn().mockResolvedValue(undefined);
1884
+ await (0, index_1.runCli)(['debate', 'Design a system', '--report', reportPath, '--rounds', '1']);
1885
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('Failed to generate report'));
1886
+ dialectic_core_1.StateManager.prototype.getDebate = originalGetDebate;
1887
+ });
1888
+ it('should handle report generation failure gracefully', async () => {
1889
+ const reportPath = path_1.default.join(tmpDir, 'report.md');
1890
+ mockedGenerateDebateReport.mockImplementationOnce(() => {
1891
+ throw new Error('Report generation failed');
1892
+ });
1893
+ await (0, index_1.runCli)(['debate', 'Design a system', '--report', reportPath, '--rounds', '1']);
1894
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('Failed to generate report'));
1895
+ mockedGenerateDebateReport.mockImplementation(jest.requireActual('dialectic-core').generateDebateReport);
1896
+ });
1482
1897
  });
1898
+ describe('extractProblemFileName and extractContextFileName', () => {
1899
+ it('should extract problem file name when provided', async () => {
1900
+ const problemFile = path_1.default.join(tmpDir, 'problem.txt');
1901
+ fs_1.default.writeFileSync(problemFile, 'Problem content');
1902
+ await (0, index_1.runCli)(['debate', '--problemDescription', problemFile, '--rounds', '1']);
1903
+ expect(stdoutSpy).toHaveBeenCalled();
1904
+ });
1905
+ it('should return undefined when problem file name is not provided', async () => {
1906
+ await (0, index_1.runCli)(['debate', 'Design a system', '--rounds', '1']);
1907
+ expect(stdoutSpy).toHaveBeenCalled();
1908
+ });
1909
+ it('should extract context directory name when provided', async () => {
1910
+ const contextDir = path_1.default.join(tmpDir, 'context');
1911
+ fs_1.default.mkdirSync(contextDir, { recursive: true });
1912
+ process.env.LANGFUSE_SECRET_KEY = 'test-secret-key';
1913
+ process.env.LANGFUSE_PUBLIC_KEY = 'test-public-key';
1914
+ const configPath = getTestConfigPath(tmpDir);
1915
+ const configContent = createTestConfigContent(undefined, {
1916
+ trace: 'langfuse',
1917
+ });
1918
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
1919
+ await (0, index_1.runCli)(['debate', 'Design a system', '--context', contextDir, '--config', configPath, '--rounds', '1']);
1920
+ expect(stdoutSpy).toHaveBeenCalled();
1921
+ });
1922
+ it('should return undefined when context file name is not provided', async () => {
1923
+ await (0, index_1.runCli)(['debate', 'Design a system', '--rounds', '1']);
1924
+ expect(stdoutSpy).toHaveBeenCalled();
1925
+ });
1926
+ });
1927
+ describe('initializeTracingContext branches', () => {
1928
+ it('should return undefined when trace is not LANGFUSE', async () => {
1929
+ const configPath = getTestConfigPath(tmpDir);
1930
+ const configContent = createTestConfigContent(undefined, {
1931
+ trace: 'none',
1932
+ });
1933
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
1934
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
1935
+ expect(stdoutSpy).toHaveBeenCalled();
1936
+ });
1937
+ it('should include problemFileName in metadata when provided', async () => {
1938
+ process.env.LANGFUSE_SECRET_KEY = 'test-secret-key';
1939
+ process.env.LANGFUSE_PUBLIC_KEY = 'test-public-key';
1940
+ const problemFile = path_1.default.join(tmpDir, 'problem.txt');
1941
+ fs_1.default.writeFileSync(problemFile, 'Design a system');
1942
+ const configPath = getTestConfigPath(tmpDir);
1943
+ const configContent = createTestConfigContent(undefined, {
1944
+ trace: 'langfuse',
1945
+ });
1946
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
1947
+ await (0, index_1.runCli)(['debate', '--problemDescription', problemFile, '--config', configPath, '--rounds', '1']);
1948
+ expect(stdoutSpy).toHaveBeenCalled();
1949
+ });
1950
+ it('should not include problemFileName in metadata when not provided', async () => {
1951
+ process.env.LANGFUSE_SECRET_KEY = 'test-secret-key';
1952
+ process.env.LANGFUSE_PUBLIC_KEY = 'test-public-key';
1953
+ const configPath = getTestConfigPath(tmpDir);
1954
+ const configContent = createTestConfigContent(undefined, {
1955
+ trace: 'langfuse',
1956
+ });
1957
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
1958
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
1959
+ expect(stdoutSpy).toHaveBeenCalled();
1960
+ });
1961
+ it('should include contextFileName in metadata when provided', async () => {
1962
+ process.env.LANGFUSE_SECRET_KEY = 'test-secret-key';
1963
+ process.env.LANGFUSE_PUBLIC_KEY = 'test-public-key';
1964
+ const contextDir = path_1.default.join(tmpDir, 'context');
1965
+ fs_1.default.mkdirSync(contextDir, { recursive: true });
1966
+ const configPath = getTestConfigPath(tmpDir);
1967
+ const configContent = createTestConfigContent(undefined, {
1968
+ trace: 'langfuse',
1969
+ });
1970
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
1971
+ await (0, index_1.runCli)(['debate', 'Design a system', '--context', contextDir, '--config', configPath, '--rounds', '1']);
1972
+ expect(stdoutSpy).toHaveBeenCalled();
1973
+ });
1974
+ it('should not include contextFileName in metadata when not provided', async () => {
1975
+ process.env.LANGFUSE_SECRET_KEY = 'test-secret-key';
1976
+ process.env.LANGFUSE_PUBLIC_KEY = 'test-public-key';
1977
+ const configPath = getTestConfigPath(tmpDir);
1978
+ const configContent = createTestConfigContent(undefined, {
1979
+ trace: 'langfuse',
1980
+ });
1981
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
1982
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
1983
+ expect(stdoutSpy).toHaveBeenCalled();
1984
+ });
1985
+ it('should include judgeConfig in metadata when judge exists', async () => {
1986
+ process.env.LANGFUSE_SECRET_KEY = 'test-secret-key';
1987
+ process.env.LANGFUSE_PUBLIC_KEY = 'test-public-key';
1988
+ const configPath = getTestConfigPath(tmpDir);
1989
+ const configContent = createTestConfigContent(undefined, {
1990
+ trace: 'langfuse',
1991
+ });
1992
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
1993
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
1994
+ expect(stdoutSpy).toHaveBeenCalled();
1995
+ });
1996
+ it('should not include judgeConfig in metadata when judge does not exist', async () => {
1997
+ process.env.LANGFUSE_SECRET_KEY = 'test-secret-key';
1998
+ process.env.LANGFUSE_PUBLIC_KEY = 'test-public-key';
1999
+ const configPath = getTestConfigPath(tmpDir);
2000
+ const configContent = {
2001
+ agents: [createTestAgentConfig()],
2002
+ debate: createTestDebateConfig(),
2003
+ };
2004
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2005
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
2006
+ expect(stdoutSpy).toHaveBeenCalled();
2007
+ });
2008
+ });
2009
+ describe('outputVerboseDebateInfo branches', () => {
2010
+ it('should return early when verbose is false', async () => {
2011
+ const capturedStderr = [];
2012
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation((chunk) => {
2013
+ capturedStderr.push(String(chunk));
2014
+ return true;
2015
+ });
2016
+ await (0, index_1.runCli)(['debate', 'Design a system', '--rounds', '1']);
2017
+ const stderr = capturedStderr.join('');
2018
+ expect(stderr).not.toContain('Running debate (verbose)');
2019
+ stderrWriteSpy.mockRestore();
2020
+ });
2021
+ it('should output verbose info when verbose is true', async () => {
2022
+ const capturedStderr = [];
2023
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation((chunk) => {
2024
+ capturedStderr.push(String(chunk));
2025
+ return true;
2026
+ });
2027
+ await (0, index_1.runCli)(['debate', 'Design a system', '--verbose', '--rounds', '1']);
2028
+ const stderr = capturedStderr.join('');
2029
+ expect(stderr).toContain('Running debate (verbose)');
2030
+ stderrWriteSpy.mockRestore();
2031
+ });
2032
+ });
2033
+ describe('flushTracingContext error handling', () => {
2034
+ it('should handle flush errors gracefully', async () => {
2035
+ process.env.LANGFUSE_SECRET_KEY = 'test-secret-key';
2036
+ process.env.LANGFUSE_PUBLIC_KEY = 'test-public-key';
2037
+ const configPath = getTestConfigPath(tmpDir);
2038
+ const configContent = createTestConfigContent(undefined, {
2039
+ trace: 'langfuse',
2040
+ });
2041
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2042
+ const { createTracingContext } = require('dialectic-core');
2043
+ const originalCreateTracingContext = createTracingContext;
2044
+ jest.spyOn(require('dialectic-core'), 'createTracingContext').mockImplementation((...args) => {
2045
+ const context = originalCreateTracingContext(...args);
2046
+ if (context) {
2047
+ context.langfuse.flushAsync = jest.fn().mockRejectedValue(new Error('Flush failed'));
2048
+ }
2049
+ return context;
2050
+ });
2051
+ const logWarningSpy = jest.spyOn(require('dialectic-core'), 'logWarning');
2052
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
2053
+ expect(logWarningSpy).toHaveBeenCalledWith(expect.stringContaining('Failed to flush Langfuse trace'));
2054
+ expect(stdoutSpy).toHaveBeenCalled();
2055
+ logWarningSpy.mockRestore();
2056
+ jest.restoreAllMocks();
2057
+ });
2058
+ });
2059
+ describe('generateReportIfRequested', () => {
2060
+ it('should return early when report is not requested', async () => {
2061
+ await (0, index_1.runCli)(['debate', 'Design a system', '--rounds', '1']);
2062
+ expect(stdoutSpy).toHaveBeenCalled();
2063
+ });
2064
+ });
2065
+ describe('getSystemSummaryConfig branches', () => {
2066
+ it('should use default summarization config when not provided', async () => {
2067
+ const configPath = getTestConfigPath(tmpDir);
2068
+ const configContent = {
2069
+ agents: [createTestAgentConfig()],
2070
+ debate: {
2071
+ rounds: 3,
2072
+ terminationCondition: { type: 'fixed' },
2073
+ synthesisMethod: 'judge',
2074
+ includeFullHistory: true,
2075
+ timeoutPerRound: 300000,
2076
+ },
2077
+ judge: {
2078
+ id: 'test-judge',
2079
+ name: 'Test Judge',
2080
+ role: 'generalist',
2081
+ model: 'gpt-4',
2082
+ provider: 'openai',
2083
+ temperature: 0.3,
2084
+ },
2085
+ };
2086
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2087
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
2088
+ expect(stdoutSpy).toHaveBeenCalled();
2089
+ });
2090
+ it('should use provided summarization config when available', async () => {
2091
+ const configPath = getTestConfigPath(tmpDir);
2092
+ const configContent = createTestConfigContent(undefined, {
2093
+ summarization: {
2094
+ enabled: false,
2095
+ threshold: 3000,
2096
+ maxLength: 1500,
2097
+ method: 'length-based',
2098
+ },
2099
+ });
2100
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2101
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
2102
+ expect(stdoutSpy).toHaveBeenCalled();
2103
+ });
2104
+ });
2105
+ describe('isClarificationRequested branches', () => {
2106
+ it('should return true when options.clarify is true', async () => {
2107
+ mockReadlineWithAnswers([]);
2108
+ mockedCollectClarifications.mockResolvedValueOnce([]);
2109
+ await (0, index_1.runCli)(['debate', 'Design a system', '--clarify', '--rounds', '1']);
2110
+ expect(mockedCollectClarifications).toHaveBeenCalled();
2111
+ });
2112
+ it('should return true when sysConfig.debate.interactiveClarifications is true', async () => {
2113
+ const configPath = getTestConfigPath(tmpDir);
2114
+ const configContent = createTestConfigContent(undefined, {
2115
+ interactiveClarifications: true,
2116
+ });
2117
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2118
+ mockReadlineWithAnswers([]);
2119
+ mockedCollectClarifications.mockResolvedValueOnce([]);
2120
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
2121
+ expect(mockedCollectClarifications).toHaveBeenCalled();
2122
+ });
2123
+ it('should return false when neither option is set', async () => {
2124
+ await (0, index_1.runCli)(['debate', 'Design a system', '--rounds', '1']);
2125
+ expect(stdoutSpy).toHaveBeenCalled();
2126
+ });
2127
+ });
2128
+ describe('collectFinalClarifications', () => {
2129
+ it('should return undefined when clarification is not requested', async () => {
2130
+ await (0, index_1.runCli)(['debate', 'Design a system', '--rounds', '1']);
2131
+ expect(stdoutSpy).toHaveBeenCalled();
2132
+ });
2133
+ it('should use custom maxPerAgent when provided', async () => {
2134
+ const configPath = getTestConfigPath(tmpDir);
2135
+ const configContent = createTestConfigContent(undefined, {
2136
+ interactiveClarifications: true,
2137
+ clarificationsMaxPerAgent: 3,
2138
+ });
2139
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2140
+ mockReadlineWithAnswers([]);
2141
+ mockedCollectClarifications.mockResolvedValueOnce([]);
2142
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
2143
+ expect(mockedCollectClarifications).toHaveBeenCalledWith(expect.any(String), expect.any(Array), 3, expect.any(Function));
2144
+ });
2145
+ it('should use default maxPerAgent when not provided', async () => {
2146
+ mockReadlineWithAnswers([]);
2147
+ mockedCollectClarifications.mockResolvedValueOnce([]);
2148
+ await (0, index_1.runCli)(['debate', 'Design a system', '--clarify', '--rounds', '1']);
2149
+ expect(mockedCollectClarifications).toHaveBeenCalledWith(expect.any(String), expect.any(Array), 5, expect.any(Function));
2150
+ });
2151
+ });
2152
+ function mockReadlineWithAnswers(answers) {
2153
+ const readlineModule = require('readline');
2154
+ if (readlineModule.__setMockAnswers) {
2155
+ readlineModule.__setMockAnswers(answers);
2156
+ }
2157
+ else {
2158
+ mockAnswers = [...answers];
2159
+ currentIndex = 0;
2160
+ }
2161
+ }
1483
2162
  });
1484
2163
  });
1485
2164
  //# sourceMappingURL=debate.spec.js.map