dialectic 0.5.2 → 0.7.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.
@@ -1,13 +1,48 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
5
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
+ const fs_1 = __importDefault(require("fs"));
6
40
  const os_1 = __importDefault(require("os"));
7
41
  const path_1 = __importDefault(require("path"));
8
- const fs_1 = __importDefault(require("fs"));
9
- const index_1 = require("../index");
42
+ const dialecticCore = __importStar(require("dialectic-core"));
10
43
  const dialectic_core_1 = require("dialectic-core");
44
+ const indexModule = __importStar(require("../index"));
45
+ const index_1 = require("../index");
11
46
  const debate_1 = require("./debate");
12
47
  const MOCK_SOLUTION_TEXT = 'Solution text';
13
48
  jest.mock('openai', () => {
@@ -16,10 +51,11 @@ jest.mock('openai', () => {
16
51
  default: class OpenAIMock {
17
52
  chat = {
18
53
  completions: {
19
- create: async (_) => ({ choices: [{ message: { content: MOCK_SOLUTION_TEXT } }] }),
54
+ create: async () => ({ choices: [{ message: { content: MOCK_SOLUTION_TEXT } }] }),
20
55
  },
21
56
  };
22
- constructor(_opts) { }
57
+ constructor() {
58
+ }
23
59
  },
24
60
  };
25
61
  });
@@ -31,25 +67,29 @@ jest.mock('dialectic-core', () => {
31
67
  return undefined;
32
68
  }),
33
69
  collectClarifications: jest.fn().mockResolvedValue([]),
34
- generateDebateReport: jest.fn().mockImplementation(actual.generateDebateReport)
70
+ generateDebateReport: jest.fn().mockImplementation(actual.generateDebateReport),
71
+ validateLangfuseConfig: jest.fn().mockImplementation(actual.validateLangfuseConfig),
72
+ createTracingContext: jest.fn().mockImplementation(actual.createTracingContext),
73
+ logWarning: jest.fn().mockImplementation(actual.logWarning),
35
74
  };
36
75
  });
76
+ let mockAnswers = [];
77
+ let currentIndex = 0;
37
78
  jest.mock('readline', () => {
38
- let mockAnswers = [];
39
- let currentIndex = 0;
40
79
  return {
41
80
  __esModule: true,
42
81
  default: {
43
82
  createInterface: () => ({
44
- question: (_, cb) => {
83
+ question: (_prompt, cb) => {
45
84
  const ans = currentIndex < mockAnswers.length ? mockAnswers[currentIndex++] : '';
46
- Promise.resolve().then(() => cb(String(ans)));
85
+ cb(String(ans));
86
+ },
87
+ close: () => {
47
88
  },
48
- close: () => { },
49
89
  })
50
90
  },
51
91
  __setMockAnswers: (answers) => {
52
- mockAnswers = answers;
92
+ mockAnswers = [...answers];
53
93
  currentIndex = 0;
54
94
  }
55
95
  };
@@ -183,7 +223,8 @@ describe('CLI debate command', () => {
183
223
  try {
184
224
  fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
185
225
  }
186
- catch { }
226
+ catch {
227
+ }
187
228
  });
188
229
  it('should write JSON output when output path ends with .json', async () => {
189
230
  const outputFile = path_1.default.join(tmpDir, 'result.json');
@@ -246,15 +287,14 @@ describe('CLI debate command', () => {
246
287
  try {
247
288
  fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
248
289
  }
249
- catch { }
290
+ catch {
291
+ }
250
292
  });
251
- it('should use file when both problem string and --problemDescription are provided', async () => {
293
+ it('should error when both problem string and --problemDescription are provided', async () => {
252
294
  const problemFile = path_1.default.join(tmpDir, 'problem.txt');
253
295
  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();
296
+ await expect((0, index_1.runCli)(['debate', 'Problem from string', '--problemDescription', problemFile, '--rounds', '1']))
297
+ .rejects.toHaveProperty('code', dialectic_core_1.EXIT_INVALID_ARGS);
258
298
  });
259
299
  it('should read problem from file when --problemDescription is provided', async () => {
260
300
  const problemFile = path_1.default.join(tmpDir, 'problem.txt');
@@ -361,8 +401,7 @@ describe('Configuration loading', () => {
361
401
  }
362
402
  });
363
403
  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-'));
404
+ const tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'config-test-'));
366
405
  try {
367
406
  const configPath = getTestConfigPath(tmpDir);
368
407
  const configContent = {
@@ -380,12 +419,12 @@ describe('Configuration loading', () => {
380
419
  try {
381
420
  fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
382
421
  }
383
- catch { }
422
+ catch {
423
+ }
384
424
  }
385
425
  });
386
426
  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-'));
427
+ const tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'config-test-'));
389
428
  try {
390
429
  const configPath = getTestConfigPath(tmpDir);
391
430
  const configContent = {
@@ -404,12 +443,12 @@ describe('Configuration loading', () => {
404
443
  try {
405
444
  fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
406
445
  }
407
- catch { }
446
+ catch {
447
+ }
408
448
  }
409
449
  });
410
450
  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-'));
451
+ const tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'config-test-'));
413
452
  try {
414
453
  const configPath = getTestConfigPath(tmpDir);
415
454
  const configContent = {
@@ -432,12 +471,12 @@ describe('Configuration loading', () => {
432
471
  try {
433
472
  fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
434
473
  }
435
- catch { }
474
+ catch {
475
+ }
436
476
  }
437
477
  });
438
478
  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-'));
479
+ const tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'config-test-'));
441
480
  try {
442
481
  const configPath = getTestConfigPath(tmpDir);
443
482
  const configContent = createTestConfigContent();
@@ -452,7 +491,8 @@ describe('Configuration loading', () => {
452
491
  try {
453
492
  fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
454
493
  }
455
- catch { }
494
+ catch {
495
+ }
456
496
  }
457
497
  });
458
498
  });
@@ -479,8 +519,7 @@ describe('Debate config validation', () => {
479
519
  stdoutWriteSpy.mockRestore();
480
520
  });
481
521
  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-'));
522
+ const tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'config-test-'));
484
523
  try {
485
524
  const configPath = getTestConfigPath(tmpDir);
486
525
  const configContent = createTestConfigContent(undefined, {
@@ -496,7 +535,8 @@ describe('Debate config validation', () => {
496
535
  try {
497
536
  fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
498
537
  }
499
- catch { }
538
+ catch {
539
+ }
500
540
  }
501
541
  });
502
542
  it('should use DEFAULT_ROUNDS when neither options.rounds nor sysConfig.debate.rounds is provided', async () => {
@@ -530,7 +570,8 @@ describe('Agent filtering', () => {
530
570
  try {
531
571
  fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
532
572
  }
533
- catch { }
573
+ catch {
574
+ }
534
575
  process.env = originalEnv;
535
576
  stdoutSpy.mockRestore();
536
577
  });
@@ -674,7 +715,8 @@ describe('Prompt resolution branches', () => {
674
715
  try {
675
716
  fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
676
717
  }
677
- catch { }
718
+ catch {
719
+ }
678
720
  process.env = originalEnv;
679
721
  stdoutSpy.mockRestore();
680
722
  });
@@ -821,7 +863,8 @@ describe('Tracing context', () => {
821
863
  try {
822
864
  fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
823
865
  }
824
- catch { }
866
+ catch {
867
+ }
825
868
  process.env = originalEnv;
826
869
  stdoutSpy.mockRestore();
827
870
  consoleErrorSpy.mockRestore();
@@ -886,14 +929,14 @@ describe('Tracing context', () => {
886
929
  it('should include contextFileName in metadata when provided', async () => {
887
930
  process.env.LANGFUSE_SECRET_KEY = 'test-secret-key';
888
931
  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');
932
+ const contextDir = path_1.default.join(tmpDir, 'context');
933
+ fs_1.default.mkdirSync(contextDir, { recursive: true });
891
934
  const configPath = getTestConfigPath(tmpDir);
892
935
  const configContent = createTestConfigContent(undefined, {
893
936
  trace: 'langfuse',
894
937
  });
895
938
  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']);
939
+ await (0, index_1.runCli)(['debate', 'Design a system', '--context', contextDir, '--config', configPath, '--rounds', '1']);
897
940
  expect(stdoutSpy).toHaveBeenCalled();
898
941
  });
899
942
  it('should include judgeConfig in metadata when judge exists', async () => {
@@ -963,7 +1006,8 @@ describe('Verbose header branches', () => {
963
1006
  try {
964
1007
  fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
965
1008
  }
966
- catch { }
1009
+ catch {
1010
+ }
967
1011
  process.env = originalEnv;
968
1012
  });
969
1013
  it('should show file prompt source in verbose header when prompt is from file', async () => {
@@ -1071,37 +1115,38 @@ describe('Error handling', () => {
1071
1115
  }
1072
1116
  });
1073
1117
  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);
1118
+ const testError = Object.assign(new Error('Test error'), { code: dialectic_core_1.EXIT_INVALID_ARGS });
1119
+ const errorWithCode = testError;
1120
+ const code = (errorWithCode && typeof errorWithCode.code === 'number')
1121
+ ? errorWithCode.code
1122
+ : dialectic_core_1.EXIT_GENERAL_ERROR;
1123
+ expect(code).toBe(dialectic_core_1.EXIT_INVALID_ARGS);
1124
+ expect(testError.message).toBe('Test error');
1079
1125
  });
1080
1126
  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);
1127
+ const testError = new Error('Test error without code');
1128
+ const errorWithCode = testError;
1129
+ const code = (errorWithCode && typeof errorWithCode.code === 'number')
1130
+ ? errorWithCode.code
1131
+ : dialectic_core_1.EXIT_GENERAL_ERROR;
1132
+ expect(code).toBe(dialectic_core_1.EXIT_GENERAL_ERROR);
1133
+ expect(testError.message).toBe('Test error without code');
1086
1134
  });
1087
1135
  it('should use error message when available', async () => {
1088
1136
  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));
1137
+ const testError = Object.assign(new Error(errorMessage), { code: dialectic_core_1.EXIT_GENERAL_ERROR });
1138
+ const message = testError?.message || 'Unknown error';
1139
+ expect(message).toBe(errorMessage);
1140
+ expect(testError.code).toBe(dialectic_core_1.EXIT_GENERAL_ERROR);
1095
1141
  });
1096
1142
  it('should use "Unknown error" when error has no message', async () => {
1097
1143
  const errorWithoutMessage = {};
1098
1144
  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'));
1145
+ const message = (errorWithoutMessage && typeof errorWithoutMessage === 'object' && 'message' in errorWithoutMessage && typeof errorWithoutMessage.message === 'string')
1146
+ ? errorWithoutMessage.message
1147
+ : 'Unknown error';
1148
+ expect(message).toBe('Unknown error');
1149
+ expect(errorWithoutMessage.code).toBe(dialectic_core_1.EXIT_GENERAL_ERROR);
1105
1150
  });
1106
1151
  });
1107
1152
  describe('Summarization configuration loading', () => {
@@ -1113,7 +1158,8 @@ describe('Summarization configuration loading', () => {
1113
1158
  try {
1114
1159
  fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
1115
1160
  }
1116
- catch { }
1161
+ catch {
1162
+ }
1117
1163
  });
1118
1164
  it('should load default summarization config when not specified', async () => {
1119
1165
  const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
@@ -1218,6 +1264,8 @@ describe('CLI clarifications phase', () => {
1218
1264
  resetLoadEnvironmentFileMock();
1219
1265
  mockedCollectClarifications.mockClear();
1220
1266
  mockedCollectClarifications.mockResolvedValue([]);
1267
+ mockAnswers = [];
1268
+ currentIndex = 0;
1221
1269
  mockReadlineWithAnswers([]);
1222
1270
  });
1223
1271
  afterEach(() => {
@@ -1227,12 +1275,20 @@ describe('CLI clarifications phase', () => {
1227
1275
  jest.restoreAllMocks();
1228
1276
  });
1229
1277
  function mockReadlineWithAnswers(answers) {
1230
- const readlineMock = require('readline');
1231
- if (readlineMock.__setMockAnswers) {
1232
- readlineMock.__setMockAnswers(answers);
1278
+ const readlineModule = require('readline');
1279
+ if (readlineModule.__setMockAnswers) {
1280
+ readlineModule.__setMockAnswers(answers);
1281
+ }
1282
+ else {
1283
+ mockAnswers = [...answers];
1284
+ currentIndex = 0;
1233
1285
  }
1234
1286
  }
1235
1287
  it('runs clarifications when --clarify and collects answers (including NA)', async () => {
1288
+ const tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'clarify-na-'));
1289
+ const configPath = path_1.default.join(tmpDir, 'debate-config.json');
1290
+ const configContent = createTestConfigContent(undefined, { orchestratorType: 'classic' });
1291
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
1236
1292
  mockReadlineWithAnswers(['My answer', '']);
1237
1293
  mockedCollectClarifications.mockResolvedValueOnce([
1238
1294
  {
@@ -1249,7 +1305,7 @@ describe('CLI clarifications phase', () => {
1249
1305
  }
1250
1306
  ]);
1251
1307
  const tmpReport = path_1.default.join(os_1.default.tmpdir(), `clarify-report-${Date.now()}.md`);
1252
- await (0, index_1.runCli)(['debate', 'Design Y', '--clarify', '--report', tmpReport, '--rounds', '1']);
1308
+ await (0, index_1.runCli)(['debate', 'Design Y', '--config', configPath, '--clarify', '--report', tmpReport, '--rounds', '1']);
1253
1309
  expect(mockedCollectClarifications).toHaveBeenCalled();
1254
1310
  const content = fs_1.default.readFileSync(tmpReport, 'utf-8');
1255
1311
  expect(content).toContain('## Clarifications');
@@ -1265,6 +1321,10 @@ describe('CLI clarifications phase', () => {
1265
1321
  expect(spy).not.toHaveBeenCalled();
1266
1322
  });
1267
1323
  it('truncates questions per agent and warns', async () => {
1324
+ const tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'clarify-trunc-'));
1325
+ const configPath = path_1.default.join(tmpDir, 'debate-config.json');
1326
+ const configContent = createTestConfigContent(undefined, { orchestratorType: 'classic' });
1327
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
1268
1328
  const truncatedQuestions = Array.from({ length: 5 }, (_, i) => ({
1269
1329
  id: `q${i + 1}`,
1270
1330
  question: `Q${i + 1}`,
@@ -1280,7 +1340,7 @@ describe('CLI clarifications phase', () => {
1280
1340
  }]);
1281
1341
  });
1282
1342
  mockReadlineWithAnswers(new Array(10).fill('A'));
1283
- await (0, index_1.runCli)(['debate', 'Design W', '--clarify']);
1343
+ await (0, index_1.runCli)(['debate', 'Design W', '--config', configPath, '--clarify']);
1284
1344
  expect(mockedCollectClarifications).toHaveBeenCalled();
1285
1345
  const stderr = (consoleErrorSpy.mock.calls.map(args => String(args[0])).join(''));
1286
1346
  expect(stderr).toMatch(/limited to 5/);
@@ -1288,6 +1348,10 @@ describe('CLI clarifications phase', () => {
1288
1348
  mockedCollectClarifications.mockResolvedValue([]);
1289
1349
  });
1290
1350
  it('should skip groups with empty items array', async () => {
1351
+ const tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'clarify-empty-'));
1352
+ const configPath = path_1.default.join(tmpDir, 'debate-config.json');
1353
+ const configContent = createTestConfigContent(undefined, { orchestratorType: 'classic' });
1354
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
1291
1355
  mockedCollectClarifications.mockResolvedValueOnce([
1292
1356
  {
1293
1357
  agentId: 'agent1',
@@ -1304,7 +1368,7 @@ describe('CLI clarifications phase', () => {
1304
1368
  ]);
1305
1369
  mockReadlineWithAnswers(['Answer 1']);
1306
1370
  const tmpReport = path_1.default.join(os_1.default.tmpdir(), `clarify-report-${Date.now()}.md`);
1307
- await (0, index_1.runCli)(['debate', 'Design Y', '--clarify', '--report', tmpReport, '--rounds', '1']);
1371
+ await (0, index_1.runCli)(['debate', 'Design Y', '--config', configPath, '--clarify', '--report', tmpReport, '--rounds', '1']);
1308
1372
  const content = fs_1.default.readFileSync(tmpReport, 'utf-8');
1309
1373
  expect(content).toContain('Question (q1)');
1310
1374
  expect(content).toContain('Answer 1');
@@ -1312,6 +1376,10 @@ describe('CLI clarifications phase', () => {
1312
1376
  mockedCollectClarifications.mockResolvedValue([]);
1313
1377
  });
1314
1378
  it('should set answer to NA when user input is empty', async () => {
1379
+ const tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'clarify-na-input-'));
1380
+ const configPath = path_1.default.join(tmpDir, 'debate-config.json');
1381
+ const configContent = createTestConfigContent(undefined, { orchestratorType: 'classic' });
1382
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
1315
1383
  mockReadlineWithAnswers(['']);
1316
1384
  mockedCollectClarifications.mockResolvedValueOnce([
1317
1385
  {
@@ -1322,13 +1390,17 @@ describe('CLI clarifications phase', () => {
1322
1390
  }
1323
1391
  ]);
1324
1392
  const tmpReport = path_1.default.join(os_1.default.tmpdir(), `clarify-report-${Date.now()}.md`);
1325
- await (0, index_1.runCli)(['debate', 'Design Y', '--clarify', '--report', tmpReport, '--rounds', '1']);
1393
+ await (0, index_1.runCli)(['debate', 'Design Y', '--config', configPath, '--clarify', '--report', tmpReport, '--rounds', '1']);
1326
1394
  const content = fs_1.default.readFileSync(tmpReport, 'utf-8');
1327
1395
  expect(content).toContain('\n```text\nNA\n```');
1328
1396
  mockedCollectClarifications.mockClear();
1329
1397
  mockedCollectClarifications.mockResolvedValue([]);
1330
1398
  });
1331
1399
  it('should set answer to user input when provided', async () => {
1400
+ const tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'clarify-user-input-'));
1401
+ const configPath = path_1.default.join(tmpDir, 'debate-config.json');
1402
+ const configContent = createTestConfigContent(undefined, { orchestratorType: 'classic' });
1403
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
1332
1404
  const userAnswer = 'The requirement is X';
1333
1405
  mockReadlineWithAnswers([userAnswer]);
1334
1406
  mockedCollectClarifications.mockResolvedValueOnce([
@@ -1340,7 +1412,7 @@ describe('CLI clarifications phase', () => {
1340
1412
  }
1341
1413
  ]);
1342
1414
  const tmpReport = path_1.default.join(os_1.default.tmpdir(), `clarify-report-${Date.now()}.md`);
1343
- await (0, index_1.runCli)(['debate', 'Design Y', '--clarify', '--report', tmpReport, '--rounds', '1']);
1415
+ await (0, index_1.runCli)(['debate', 'Design Y', '--config', configPath, '--clarify', '--report', tmpReport, '--rounds', '1']);
1344
1416
  const content = fs_1.default.readFileSync(tmpReport, 'utf-8');
1345
1417
  expect(content).toContain(userAnswer);
1346
1418
  mockedCollectClarifications.mockClear();
@@ -1357,7 +1429,8 @@ describe('CLI clarifications phase', () => {
1357
1429
  try {
1358
1430
  fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
1359
1431
  }
1360
- catch { }
1432
+ catch {
1433
+ }
1361
1434
  process.env = originalEnv;
1362
1435
  });
1363
1436
  it('should append .md extension when report path does not end with .md', async () => {
@@ -1428,9 +1501,10 @@ describe('CLI clarifications phase', () => {
1428
1501
  stderrWriteSpy.mockRestore();
1429
1502
  });
1430
1503
  });
1431
- describe('Context file handling', () => {
1504
+ describe('Context directory handling', () => {
1432
1505
  let tmpDir;
1433
1506
  const originalEnv = process.env;
1507
+ const originalCwd = process.cwd();
1434
1508
  beforeEach(() => {
1435
1509
  process.env = { ...originalEnv, OPENAI_API_KEY: 'test' };
1436
1510
  tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'context-test-'));
@@ -1439,46 +1513,1667 @@ describe('CLI clarifications phase', () => {
1439
1513
  try {
1440
1514
  fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
1441
1515
  }
1442
- catch { }
1516
+ catch {
1517
+ }
1443
1518
  process.env = originalEnv;
1519
+ process.chdir(originalCwd);
1520
+ });
1521
+ it('should accept a valid directory path', async () => {
1522
+ const contextDir = path_1.default.join(tmpDir, 'context');
1523
+ fs_1.default.mkdirSync(contextDir, { recursive: true });
1524
+ await (0, index_1.runCli)(['debate', 'Design a system', '--context', contextDir]);
1525
+ expect(consoleErrorSpy).not.toHaveBeenCalledWith(expect.stringContaining('Context directory not found'));
1526
+ expect(consoleErrorSpy).not.toHaveBeenCalledWith(expect.stringContaining('Context path is not a directory'));
1527
+ });
1528
+ it('should throw error when context directory does not exist', async () => {
1529
+ const nonExistentDir = path_1.default.join(tmpDir, 'nonexistent');
1530
+ await expect((0, index_1.runCli)(['debate', 'Design a system', '--context', nonExistentDir]))
1531
+ .rejects.toMatchObject({
1532
+ code: dialectic_core_1.EXIT_INVALID_ARGS,
1533
+ message: expect.stringContaining('Context directory not found')
1534
+ });
1444
1535
  });
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'));
1536
+ it('should throw error when context path is a file (not a directory)', async () => {
1537
+ const contextFile = path_1.default.join(tmpDir, 'context.txt');
1538
+ fs_1.default.writeFileSync(contextFile, 'Some content');
1539
+ await expect((0, index_1.runCli)(['debate', 'Design a system', '--context', contextFile]))
1540
+ .rejects.toMatchObject({
1541
+ code: dialectic_core_1.EXIT_INVALID_ARGS,
1542
+ message: expect.stringContaining('Context path is not a directory')
1543
+ });
1449
1544
  });
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'));
1545
+ it('should default to current working directory when context is not provided', async () => {
1546
+ process.chdir(tmpDir);
1547
+ await (0, index_1.runCli)(['debate', 'Design a system']);
1548
+ expect(consoleErrorSpy).not.toHaveBeenCalledWith(expect.stringContaining('Context directory'));
1453
1549
  });
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'));
1550
+ it('should resolve relative paths relative to current working directory', async () => {
1551
+ const baseDir = path_1.default.join(tmpDir, 'base');
1552
+ const nestedDir = path_1.default.join(baseDir, 'nested');
1553
+ fs_1.default.mkdirSync(nestedDir, { recursive: true });
1554
+ process.chdir(baseDir);
1555
+ await (0, index_1.runCli)(['debate', 'Design a system', '--context', 'nested']);
1556
+ expect(consoleErrorSpy).not.toHaveBeenCalledWith(expect.stringContaining('Context directory not found'));
1459
1557
  });
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'));
1558
+ it('should accept absolute paths', async () => {
1559
+ const contextDir = path_1.default.join(tmpDir, 'context');
1560
+ fs_1.default.mkdirSync(contextDir, { recursive: true });
1561
+ await (0, index_1.runCli)(['debate', 'Design a system', '--context', contextDir]);
1562
+ expect(consoleErrorSpy).not.toHaveBeenCalledWith(expect.stringContaining('Context directory not found'));
1466
1563
  });
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();
1564
+ });
1565
+ describe('Additional coverage tests for full coverage', () => {
1566
+ const originalEnv = process.env;
1567
+ let tmpDir;
1568
+ let consoleErrorSpy;
1569
+ let stderrWriteSpy;
1570
+ let stdoutSpy;
1571
+ beforeEach(() => {
1572
+ process.env = { ...originalEnv, OPENAI_API_KEY: 'test' };
1573
+ tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'coverage-test-'));
1574
+ consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
1575
+ stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
1576
+ stdoutSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
1577
+ resetLoadEnvironmentFileMock();
1578
+ mockedCollectClarifications.mockReset();
1579
+ mockedCollectClarifications.mockResolvedValue([]);
1580
+ mockedGenerateDebateReport.mockReset();
1581
+ mockedGenerateDebateReport.mockImplementation(jest.requireActual('dialectic-core').generateDebateReport);
1474
1582
  });
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'));
1583
+ afterEach(() => {
1584
+ try {
1585
+ fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
1586
+ }
1587
+ catch {
1588
+ }
1589
+ process.env = originalEnv;
1590
+ consoleErrorSpy.mockRestore();
1591
+ stderrWriteSpy.mockRestore();
1592
+ stdoutSpy.mockRestore();
1593
+ mockedCollectClarifications.mockReset();
1594
+ mockedCollectClarifications.mockResolvedValue([]);
1595
+ mockedGenerateDebateReport.mockReset();
1596
+ mockedGenerateDebateReport.mockImplementation(jest.requireActual('dialectic-core').generateDebateReport);
1597
+ if (dialectic_core_1.StateManager.prototype.getDebate && typeof dialectic_core_1.StateManager.prototype.getDebate.mockRestore === 'function') {
1598
+ dialectic_core_1.StateManager.prototype.getDebate.mockRestore();
1599
+ }
1600
+ });
1601
+ describe('rethrowIfErrorCode edge cases', () => {
1602
+ it('should handle readAndValidateFileContent error with EXIT_INVALID_ARGS code', async () => {
1603
+ const problemFile = path_1.default.join(tmpDir, 'problem.txt');
1604
+ fs_1.default.writeFileSync(problemFile, 'Some content');
1605
+ const errorWithCode = Object.assign(new Error('Invalid file'), { code: dialectic_core_1.EXIT_INVALID_ARGS });
1606
+ jest.spyOn(fs_1.default.promises, 'readFile').mockRejectedValueOnce(errorWithCode);
1607
+ await expect((0, index_1.runCli)(['debate', '--problemDescription', problemFile]))
1608
+ .rejects.toHaveProperty('code', dialectic_core_1.EXIT_INVALID_ARGS);
1609
+ jest.spyOn(fs_1.default.promises, 'readFile').mockRestore();
1610
+ });
1611
+ it('should handle validateContextDirectory error with EXIT_INVALID_ARGS code', async () => {
1612
+ const contextFile = path_1.default.join(tmpDir, 'context.txt');
1613
+ fs_1.default.writeFileSync(contextFile, 'Some context');
1614
+ await expect((0, index_1.runCli)(['debate', 'Design a system', '--context', contextFile]))
1615
+ .rejects.toMatchObject({
1616
+ code: dialectic_core_1.EXIT_INVALID_ARGS,
1617
+ message: expect.stringContaining('Context path is not a directory')
1618
+ });
1619
+ });
1620
+ });
1621
+ describe('createAgentLogger branches', () => {
1622
+ it('should log when onlyVerbose is false', async () => {
1623
+ await (0, index_1.runCli)(['debate', 'Design a system', '--rounds', '1']);
1624
+ expect(stdoutSpy).toHaveBeenCalled();
1625
+ });
1626
+ it('should log when onlyVerbose is true and verbose is true', async () => {
1627
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
1628
+ await (0, index_1.runCli)(['debate', 'Design a system', '--verbose', '--rounds', '1']);
1629
+ expect(stderrWriteSpy).toHaveBeenCalled();
1630
+ stderrWriteSpy.mockRestore();
1631
+ });
1632
+ it('should not log when onlyVerbose is true and verbose is false', async () => {
1633
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
1634
+ await (0, index_1.runCli)(['debate', 'Design a system', '--rounds', '1']);
1635
+ const calls = stderrWriteSpy.mock.calls.map(args => String(args[0]));
1636
+ const stderr = calls.join('');
1637
+ expect(stderr).not.toContain('Running debate (verbose)');
1638
+ stderrWriteSpy.mockRestore();
1639
+ });
1640
+ it('should log when onlyVerbose is undefined', async () => {
1641
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
1642
+ await (0, index_1.runCli)(['debate', 'Design a system', '--verbose', '--rounds', '1']);
1643
+ expect(stderrWriteSpy).toHaveBeenCalled();
1644
+ stderrWriteSpy.mockRestore();
1645
+ });
1646
+ });
1647
+ describe('createOrchestratorHooks verbose branch', () => {
1648
+ it('should log summarization details when verbose is true', async () => {
1649
+ const configPath = getTestConfigPath(tmpDir);
1650
+ const configContent = createTestConfigContent(undefined, {
1651
+ summarization: {
1652
+ enabled: true,
1653
+ threshold: 100,
1654
+ maxLength: 500,
1655
+ method: 'length-based',
1656
+ },
1657
+ });
1658
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
1659
+ const capturedStderr = [];
1660
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation((chunk) => {
1661
+ capturedStderr.push(String(chunk));
1662
+ return true;
1663
+ });
1664
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--verbose', '--rounds', '2']);
1665
+ const stderr = capturedStderr.join('');
1666
+ expect(stderr.length).toBeGreaterThan(0);
1667
+ stderrWriteSpy.mockRestore();
1668
+ });
1669
+ });
1670
+ describe('resolveJudgeSystemPromptWithDefault branches', () => {
1671
+ it('should use configDir when provided', async () => {
1672
+ const promptFile = path_1.default.join(tmpDir, 'judge-prompt.txt');
1673
+ fs_1.default.writeFileSync(promptFile, 'Custom judge prompt');
1674
+ const configPath = getTestConfigPath(tmpDir);
1675
+ const configContent = {
1676
+ agents: [createTestAgentConfig()],
1677
+ debate: createTestDebateConfig(),
1678
+ judge: {
1679
+ id: 'test-judge',
1680
+ name: 'Test Judge',
1681
+ role: 'generalist',
1682
+ model: 'gpt-4',
1683
+ provider: 'openai',
1684
+ temperature: 0.3,
1685
+ systemPromptPath: 'judge-prompt.txt',
1686
+ },
1687
+ };
1688
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
1689
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
1690
+ expect(stdoutSpy).toHaveBeenCalled();
1691
+ });
1692
+ it('should use process.cwd() when configDir is undefined', async () => {
1693
+ await (0, index_1.runCli)(['debate', 'Design a system', '--rounds', '1']);
1694
+ expect(stdoutSpy).toHaveBeenCalled();
1695
+ });
1696
+ });
1697
+ describe('resolveJudgeSummaryPromptWithDefault branches', () => {
1698
+ it('should use configDir when provided', async () => {
1699
+ const summaryPromptFile = path_1.default.join(tmpDir, 'judge-summary-prompt.txt');
1700
+ fs_1.default.writeFileSync(summaryPromptFile, 'Custom judge summary prompt');
1701
+ const configPath = getTestConfigPath(tmpDir);
1702
+ const configContent = {
1703
+ agents: [createTestAgentConfig()],
1704
+ debate: createTestDebateConfig(),
1705
+ judge: {
1706
+ id: 'test-judge',
1707
+ name: 'Test Judge',
1708
+ role: 'generalist',
1709
+ model: 'gpt-4',
1710
+ provider: 'openai',
1711
+ temperature: 0.3,
1712
+ summaryPromptPath: 'judge-summary-prompt.txt',
1713
+ },
1714
+ };
1715
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
1716
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
1717
+ expect(stdoutSpy).toHaveBeenCalled();
1718
+ });
1719
+ it('should use process.cwd() when configDir is undefined', async () => {
1720
+ await (0, index_1.runCli)(['debate', 'Design a system', '--rounds', '1']);
1721
+ expect(stdoutSpy).toHaveBeenCalled();
1722
+ });
1723
+ });
1724
+ describe('createJudgeWithPromptResolution branches', () => {
1725
+ it('should include absPath in metadata when prompt file is used', async () => {
1726
+ const promptFile = path_1.default.join(tmpDir, 'judge-prompt.txt');
1727
+ fs_1.default.writeFileSync(promptFile, 'Custom judge prompt');
1728
+ const configPath = getTestConfigPath(tmpDir);
1729
+ const configContent = {
1730
+ agents: [createTestAgentConfig()],
1731
+ debate: createTestDebateConfig(),
1732
+ judge: {
1733
+ id: 'test-judge',
1734
+ name: 'Test Judge',
1735
+ role: 'generalist',
1736
+ model: 'gpt-4',
1737
+ provider: 'openai',
1738
+ temperature: 0.3,
1739
+ systemPromptPath: promptFile,
1740
+ summaryPromptPath: promptFile,
1741
+ },
1742
+ };
1743
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
1744
+ const outputFile = path_1.default.join(tmpDir, 'result.json');
1745
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--output', outputFile, '--rounds', '1']);
1746
+ if (fs_1.default.existsSync(outputFile)) {
1747
+ const debateState = JSON.parse(fs_1.default.readFileSync(outputFile, 'utf-8'));
1748
+ expect(debateState).toBeDefined();
1749
+ }
1750
+ });
1751
+ it('should use built-in prompts when prompt paths are not provided', async () => {
1752
+ const configPath = getTestConfigPath(tmpDir);
1753
+ const configContent = {
1754
+ agents: [createTestAgentConfig()],
1755
+ debate: createTestDebateConfig(),
1756
+ judge: {
1757
+ id: 'test-judge',
1758
+ name: 'Test Judge',
1759
+ role: 'generalist',
1760
+ model: 'gpt-4',
1761
+ provider: 'openai',
1762
+ temperature: 0.3,
1763
+ },
1764
+ };
1765
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
1766
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
1767
+ expect(stdoutSpy).toHaveBeenCalled();
1768
+ });
1769
+ });
1770
+ describe('createAgentWithPromptResolution branches', () => {
1771
+ it('should handle agents with tools', async () => {
1772
+ const configPath = getTestConfigPath(tmpDir);
1773
+ const configContent = {
1774
+ agents: [
1775
+ createTestAgentConfig({
1776
+ tools: [
1777
+ {
1778
+ name: 'test_tool',
1779
+ description: 'A test tool',
1780
+ parameters: {
1781
+ type: 'object',
1782
+ properties: {
1783
+ param: { type: 'string', description: 'A parameter' }
1784
+ },
1785
+ required: ['param']
1786
+ }
1787
+ }
1788
+ ]
1789
+ })
1790
+ ],
1791
+ debate: createTestDebateConfig(),
1792
+ judge: {
1793
+ id: 'test-judge',
1794
+ name: 'Test Judge',
1795
+ role: 'generalist',
1796
+ model: 'gpt-4',
1797
+ provider: 'openai',
1798
+ temperature: 0.3,
1799
+ },
1800
+ };
1801
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
1802
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
1803
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('Tools available'));
1804
+ });
1805
+ it('should handle agents without tools', async () => {
1806
+ const configPath = getTestConfigPath(tmpDir);
1807
+ const configContent = createTestConfigContent();
1808
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
1809
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
1810
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('no tools'));
1811
+ });
1812
+ it('should handle clarification prompt path', async () => {
1813
+ const clarificationPromptFile = path_1.default.join(tmpDir, 'clarification-prompt.txt');
1814
+ fs_1.default.writeFileSync(clarificationPromptFile, 'Custom clarification prompt');
1815
+ const configPath = getTestConfigPath(tmpDir);
1816
+ const configContent = {
1817
+ agents: [
1818
+ createTestAgentConfig({
1819
+ clarificationPromptPath: clarificationPromptFile
1820
+ })
1821
+ ],
1822
+ debate: createTestDebateConfig(),
1823
+ judge: {
1824
+ id: 'test-judge',
1825
+ name: 'Test Judge',
1826
+ role: 'generalist',
1827
+ model: 'gpt-4',
1828
+ provider: 'openai',
1829
+ temperature: 0.3,
1830
+ },
1831
+ };
1832
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
1833
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
1834
+ expect(stdoutSpy).toHaveBeenCalled();
1835
+ });
1836
+ });
1837
+ describe('buildAgents with tracing context', () => {
1838
+ it('should wrap agents with tracing when tracing context is provided', async () => {
1839
+ process.env.LANGFUSE_SECRET_KEY = 'test-secret-key';
1840
+ process.env.LANGFUSE_PUBLIC_KEY = 'test-public-key';
1841
+ const configPath = getTestConfigPath(tmpDir);
1842
+ const configContent = createTestConfigContent(undefined, {
1843
+ trace: 'langfuse',
1844
+ });
1845
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
1846
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
1847
+ expect(stdoutSpy).toHaveBeenCalled();
1848
+ });
1849
+ });
1850
+ describe('validateExactlyOneProblemSource branches', () => {
1851
+ it('should error when both problem string and --problemDescription are provided', async () => {
1852
+ const problemFile = path_1.default.join(tmpDir, 'problem.txt');
1853
+ fs_1.default.writeFileSync(problemFile, 'Problem from file');
1854
+ await expect((0, index_1.runCli)(['debate', 'Problem from string', '--problemDescription', problemFile]))
1855
+ .rejects.toHaveProperty('code', dialectic_core_1.EXIT_INVALID_ARGS);
1856
+ expect(stderrWriteSpy).toHaveBeenCalledWith(expect.stringContaining('provide exactly one of'));
1857
+ });
1858
+ it('should error when neither problem string nor --problemDescription are provided', async () => {
1859
+ await expect((0, index_1.runCli)(['debate']))
1860
+ .rejects.toHaveProperty('code', dialectic_core_1.EXIT_INVALID_ARGS);
1861
+ expect(stderrWriteSpy).toHaveBeenCalledWith(expect.stringContaining('problem is required'));
1862
+ });
1863
+ });
1864
+ describe('outputRoundSummary branches', () => {
1865
+ it('should output summaries when round has summaries', async () => {
1866
+ const configPath = getTestConfigPath(tmpDir);
1867
+ const configContent = createTestConfigContent(undefined, {
1868
+ summarization: {
1869
+ enabled: true,
1870
+ threshold: 100,
1871
+ maxLength: 500,
1872
+ method: 'length-based',
1873
+ },
1874
+ });
1875
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
1876
+ const capturedStderr = [];
1877
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation((chunk) => {
1878
+ capturedStderr.push(String(chunk));
1879
+ return true;
1880
+ });
1881
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--verbose', '--rounds', '2']);
1882
+ const stderr = capturedStderr.join('');
1883
+ expect(stderr).toMatch(/Round\s+\d+/);
1884
+ stderrWriteSpy.mockRestore();
1885
+ });
1886
+ it('should output contributions when round has contributions', async () => {
1887
+ const capturedStderr = [];
1888
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation((chunk) => {
1889
+ capturedStderr.push(String(chunk));
1890
+ return true;
1891
+ });
1892
+ await (0, index_1.runCli)(['debate', 'Design a system', '--verbose', '--rounds', '1']);
1893
+ const stderr = capturedStderr.join('');
1894
+ expect(stderr.length).toBeGreaterThan(0);
1895
+ stderrWriteSpy.mockRestore();
1896
+ });
1897
+ });
1898
+ describe('outputResults branches', () => {
1899
+ it('should write JSON output when output path ends with .json', async () => {
1900
+ const outputFile = path_1.default.join(tmpDir, 'result.json');
1901
+ await (0, index_1.runCli)(['debate', 'Design a system', '--output', outputFile, '--rounds', '1']);
1902
+ expect(fs_1.default.existsSync(outputFile)).toBe(true);
1903
+ const content = JSON.parse(fs_1.default.readFileSync(outputFile, 'utf-8'));
1904
+ expect(content).toHaveProperty('id');
1905
+ });
1906
+ it('should write text output when output path does not end with .json', async () => {
1907
+ const outputFile = path_1.default.join(tmpDir, 'result.txt');
1908
+ await (0, index_1.runCli)(['debate', 'Design a system', '--output', outputFile, '--rounds', '1']);
1909
+ expect(fs_1.default.existsSync(outputFile)).toBe(true);
1910
+ const content = fs_1.default.readFileSync(outputFile, 'utf-8');
1911
+ expect(content).toContain(MOCK_SOLUTION_TEXT);
1912
+ });
1913
+ it('should write to stdout when no output path is provided', async () => {
1914
+ const capturedStdout = [];
1915
+ const stdoutWriteSpy = jest.spyOn(process.stdout, 'write').mockImplementation((chunk) => {
1916
+ capturedStdout.push(String(chunk));
1917
+ return true;
1918
+ });
1919
+ await (0, index_1.runCli)(['debate', 'Design a system', '--rounds', '1']);
1920
+ const stdout = capturedStdout.join('');
1921
+ expect(stdout).toContain(MOCK_SOLUTION_TEXT);
1922
+ stdoutWriteSpy.mockRestore();
1923
+ });
1924
+ it('should show verbose summary when no output path and verbose is true', async () => {
1925
+ const capturedStderr = [];
1926
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation((chunk) => {
1927
+ capturedStderr.push(String(chunk));
1928
+ return true;
1929
+ });
1930
+ await (0, index_1.runCli)(['debate', 'Design a system', '--verbose', '--rounds', '1']);
1931
+ const stderr = capturedStderr.join('');
1932
+ expect(stderr).toContain('Summary (verbose)');
1933
+ stderrWriteSpy.mockRestore();
1934
+ });
1935
+ it('should not show verbose summary when output path is provided', async () => {
1936
+ const outputFile = path_1.default.join(tmpDir, 'result.txt');
1937
+ const capturedStderr = [];
1938
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation((chunk) => {
1939
+ capturedStderr.push(String(chunk));
1940
+ return true;
1941
+ });
1942
+ await (0, index_1.runCli)(['debate', 'Design a system', '--verbose', '--output', outputFile, '--rounds', '1']);
1943
+ const stderr = capturedStderr.join('');
1944
+ expect(stderr).not.toContain('Summary (verbose)');
1945
+ stderrWriteSpy.mockRestore();
1946
+ });
1947
+ });
1948
+ describe('generateReport error handling', () => {
1949
+ let originalGetDebate;
1950
+ beforeEach(() => {
1951
+ originalGetDebate = dialectic_core_1.StateManager.prototype.getDebate;
1952
+ });
1953
+ afterEach(() => {
1954
+ dialectic_core_1.StateManager.prototype.getDebate = originalGetDebate;
1955
+ });
1956
+ it('should handle debate state not found error', async () => {
1957
+ const reportPath = path_1.default.join(tmpDir, 'report.md');
1958
+ await (0, index_1.runCli)(['debate', 'Design a system', '--report', reportPath, '--rounds', '1']);
1959
+ expect(stdoutSpy).toHaveBeenCalled();
1960
+ });
1961
+ it('should handle report generation failure gracefully', async () => {
1962
+ const reportPath = path_1.default.join(tmpDir, 'report.md');
1963
+ mockedGenerateDebateReport.mockImplementationOnce(() => {
1964
+ throw new Error('Report generation failed');
1965
+ });
1966
+ await (0, index_1.runCli)(['debate', 'Design a system', '--report', reportPath, '--rounds', '1']);
1967
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('Failed to generate report'));
1968
+ mockedGenerateDebateReport.mockImplementation(jest.requireActual('dialectic-core').generateDebateReport);
1969
+ });
1970
+ });
1971
+ describe('extractProblemFileName and extractContextFileName', () => {
1972
+ it('should extract problem file name when provided', async () => {
1973
+ const problemFile = path_1.default.join(tmpDir, 'problem.txt');
1974
+ fs_1.default.writeFileSync(problemFile, 'Problem content');
1975
+ await (0, index_1.runCli)(['debate', '--problemDescription', problemFile, '--rounds', '1']);
1976
+ expect(stdoutSpy).toHaveBeenCalled();
1977
+ });
1978
+ it('should return undefined when problem file name is not provided', async () => {
1979
+ await (0, index_1.runCli)(['debate', 'Design a system', '--rounds', '1']);
1980
+ expect(stdoutSpy).toHaveBeenCalled();
1981
+ });
1982
+ it('should extract context directory name when provided', async () => {
1983
+ const contextDir = path_1.default.join(tmpDir, 'context');
1984
+ fs_1.default.mkdirSync(contextDir, { recursive: true });
1985
+ process.env.LANGFUSE_SECRET_KEY = 'test-secret-key';
1986
+ process.env.LANGFUSE_PUBLIC_KEY = 'test-public-key';
1987
+ const configPath = getTestConfigPath(tmpDir);
1988
+ const configContent = createTestConfigContent(undefined, {
1989
+ trace: 'langfuse',
1990
+ });
1991
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
1992
+ await (0, index_1.runCli)(['debate', 'Design a system', '--context', contextDir, '--config', configPath, '--rounds', '1']);
1993
+ expect(stdoutSpy).toHaveBeenCalled();
1994
+ });
1995
+ it('should return undefined when context file name is not provided', async () => {
1996
+ await (0, index_1.runCli)(['debate', 'Design a system', '--rounds', '1']);
1997
+ expect(stdoutSpy).toHaveBeenCalled();
1998
+ });
1999
+ });
2000
+ describe('initializeTracingContext branches', () => {
2001
+ it('should return undefined when trace is not LANGFUSE', async () => {
2002
+ const configPath = getTestConfigPath(tmpDir);
2003
+ const configContent = createTestConfigContent(undefined, {
2004
+ trace: 'none',
2005
+ });
2006
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2007
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
2008
+ expect(stdoutSpy).toHaveBeenCalled();
2009
+ });
2010
+ it('should include problemFileName in metadata when provided', async () => {
2011
+ process.env.LANGFUSE_SECRET_KEY = 'test-secret-key';
2012
+ process.env.LANGFUSE_PUBLIC_KEY = 'test-public-key';
2013
+ const problemFile = path_1.default.join(tmpDir, 'problem.txt');
2014
+ fs_1.default.writeFileSync(problemFile, 'Design a system');
2015
+ const configPath = getTestConfigPath(tmpDir);
2016
+ const configContent = createTestConfigContent(undefined, {
2017
+ trace: 'langfuse',
2018
+ });
2019
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2020
+ await (0, index_1.runCli)(['debate', '--problemDescription', problemFile, '--config', configPath, '--rounds', '1']);
2021
+ expect(stdoutSpy).toHaveBeenCalled();
2022
+ });
2023
+ it('should not include problemFileName in metadata when not provided', async () => {
2024
+ process.env.LANGFUSE_SECRET_KEY = 'test-secret-key';
2025
+ process.env.LANGFUSE_PUBLIC_KEY = 'test-public-key';
2026
+ const configPath = getTestConfigPath(tmpDir);
2027
+ const configContent = createTestConfigContent(undefined, {
2028
+ trace: 'langfuse',
2029
+ });
2030
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2031
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
2032
+ expect(stdoutSpy).toHaveBeenCalled();
2033
+ });
2034
+ it('should include contextFileName in metadata when provided', async () => {
2035
+ process.env.LANGFUSE_SECRET_KEY = 'test-secret-key';
2036
+ process.env.LANGFUSE_PUBLIC_KEY = 'test-public-key';
2037
+ const contextDir = path_1.default.join(tmpDir, 'context');
2038
+ fs_1.default.mkdirSync(contextDir, { recursive: true });
2039
+ const configPath = getTestConfigPath(tmpDir);
2040
+ const configContent = createTestConfigContent(undefined, {
2041
+ trace: 'langfuse',
2042
+ });
2043
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2044
+ await (0, index_1.runCli)(['debate', 'Design a system', '--context', contextDir, '--config', configPath, '--rounds', '1']);
2045
+ expect(stdoutSpy).toHaveBeenCalled();
2046
+ });
2047
+ it('should not include contextFileName in metadata when not provided', async () => {
2048
+ process.env.LANGFUSE_SECRET_KEY = 'test-secret-key';
2049
+ process.env.LANGFUSE_PUBLIC_KEY = 'test-public-key';
2050
+ const configPath = getTestConfigPath(tmpDir);
2051
+ const configContent = createTestConfigContent(undefined, {
2052
+ trace: 'langfuse',
2053
+ });
2054
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2055
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
2056
+ expect(stdoutSpy).toHaveBeenCalled();
2057
+ });
2058
+ it('should include judgeConfig in metadata when judge exists', async () => {
2059
+ process.env.LANGFUSE_SECRET_KEY = 'test-secret-key';
2060
+ process.env.LANGFUSE_PUBLIC_KEY = 'test-public-key';
2061
+ const configPath = getTestConfigPath(tmpDir);
2062
+ const configContent = createTestConfigContent(undefined, {
2063
+ trace: 'langfuse',
2064
+ });
2065
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2066
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
2067
+ expect(stdoutSpy).toHaveBeenCalled();
2068
+ });
2069
+ it('should not include judgeConfig in metadata when judge does not exist', async () => {
2070
+ process.env.LANGFUSE_SECRET_KEY = 'test-secret-key';
2071
+ process.env.LANGFUSE_PUBLIC_KEY = 'test-public-key';
2072
+ const configPath = getTestConfigPath(tmpDir);
2073
+ const configContent = {
2074
+ agents: [createTestAgentConfig()],
2075
+ debate: createTestDebateConfig(),
2076
+ };
2077
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2078
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
2079
+ expect(stdoutSpy).toHaveBeenCalled();
2080
+ });
2081
+ });
2082
+ describe('outputVerboseDebateInfo branches', () => {
2083
+ it('should return early when verbose is false', async () => {
2084
+ const capturedStderr = [];
2085
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation((chunk) => {
2086
+ capturedStderr.push(String(chunk));
2087
+ return true;
2088
+ });
2089
+ await (0, index_1.runCli)(['debate', 'Design a system', '--rounds', '1']);
2090
+ const stderr = capturedStderr.join('');
2091
+ expect(stderr).not.toContain('Running debate (verbose)');
2092
+ stderrWriteSpy.mockRestore();
2093
+ });
2094
+ it('should output verbose info when verbose is true', async () => {
2095
+ const capturedStderr = [];
2096
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation((chunk) => {
2097
+ capturedStderr.push(String(chunk));
2098
+ return true;
2099
+ });
2100
+ await (0, index_1.runCli)(['debate', 'Design a system', '--verbose', '--rounds', '1']);
2101
+ const stderr = capturedStderr.join('');
2102
+ expect(stderr).toContain('Running debate (verbose)');
2103
+ stderrWriteSpy.mockRestore();
2104
+ });
2105
+ });
2106
+ describe('flushTracingContext error handling', () => {
2107
+ it('should handle flush errors gracefully', async () => {
2108
+ process.env.LANGFUSE_SECRET_KEY = 'test-secret-key';
2109
+ process.env.LANGFUSE_PUBLIC_KEY = 'test-public-key';
2110
+ const configPath = getTestConfigPath(tmpDir);
2111
+ const configContent = createTestConfigContent(undefined, {
2112
+ trace: 'langfuse',
2113
+ });
2114
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2115
+ const actualCreateTracingContext = jest.requireActual('dialectic-core').createTracingContext;
2116
+ dialecticCore.createTracingContext.mockImplementation((...args) => {
2117
+ const context = actualCreateTracingContext(...args);
2118
+ if (context) {
2119
+ context.langfuse.flushAsync = jest.fn().mockRejectedValue(new Error('Flush failed'));
2120
+ }
2121
+ return context;
2122
+ });
2123
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
2124
+ expect(dialecticCore.logWarning).toHaveBeenCalledWith(expect.stringContaining('Failed to flush Langfuse trace'));
2125
+ expect(stdoutSpy).toHaveBeenCalled();
2126
+ dialecticCore.createTracingContext.mockReset();
2127
+ dialecticCore.createTracingContext.mockImplementation(jest.requireActual('dialectic-core').createTracingContext);
2128
+ });
2129
+ });
2130
+ describe('generateReportIfRequested', () => {
2131
+ it('should return early when report is not requested', async () => {
2132
+ await (0, index_1.runCli)(['debate', 'Design a system', '--rounds', '1']);
2133
+ expect(stdoutSpy).toHaveBeenCalled();
2134
+ });
2135
+ });
2136
+ describe('getSystemSummaryConfig branches', () => {
2137
+ it('should use default summarization config when not provided', async () => {
2138
+ const configPath = getTestConfigPath(tmpDir);
2139
+ const configContent = {
2140
+ agents: [createTestAgentConfig()],
2141
+ debate: {
2142
+ rounds: 3,
2143
+ terminationCondition: { type: 'fixed' },
2144
+ synthesisMethod: 'judge',
2145
+ includeFullHistory: true,
2146
+ timeoutPerRound: 300000,
2147
+ },
2148
+ judge: {
2149
+ id: 'test-judge',
2150
+ name: 'Test Judge',
2151
+ role: 'generalist',
2152
+ model: 'gpt-4',
2153
+ provider: 'openai',
2154
+ temperature: 0.3,
2155
+ },
2156
+ };
2157
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2158
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
2159
+ expect(stdoutSpy).toHaveBeenCalled();
2160
+ });
2161
+ it('should use provided summarization config when available', async () => {
2162
+ const configPath = getTestConfigPath(tmpDir);
2163
+ const configContent = createTestConfigContent(undefined, {
2164
+ summarization: {
2165
+ enabled: false,
2166
+ threshold: 3000,
2167
+ maxLength: 1500,
2168
+ method: 'length-based',
2169
+ },
2170
+ });
2171
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2172
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
2173
+ expect(stdoutSpy).toHaveBeenCalled();
2174
+ });
2175
+ });
2176
+ describe('isClarificationRequested branches', () => {
2177
+ it('should return true when options.clarify is true', async () => {
2178
+ const configPath = getTestConfigPath(tmpDir);
2179
+ const configContent = createTestConfigContent(undefined, { orchestratorType: 'classic' });
2180
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2181
+ mockReadlineWithAnswers([]);
2182
+ mockedCollectClarifications.mockResolvedValueOnce([]);
2183
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--clarify', '--rounds', '1']);
2184
+ expect(mockedCollectClarifications).toHaveBeenCalled();
2185
+ });
2186
+ it('should return true when sysConfig.debate.interactiveClarifications is true', async () => {
2187
+ const configPath = getTestConfigPath(tmpDir);
2188
+ const configContent = createTestConfigContent(undefined, {
2189
+ interactiveClarifications: true,
2190
+ });
2191
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2192
+ mockReadlineWithAnswers([]);
2193
+ mockedCollectClarifications.mockResolvedValueOnce([]);
2194
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
2195
+ expect(stdoutSpy).toHaveBeenCalled();
2196
+ });
2197
+ it('should return false when neither option is set', async () => {
2198
+ await (0, index_1.runCli)(['debate', 'Design a system', '--rounds', '1']);
2199
+ expect(stdoutSpy).toHaveBeenCalled();
2200
+ });
2201
+ });
2202
+ describe('collectFinalClarifications', () => {
2203
+ it('should return undefined when clarification is not requested', async () => {
2204
+ await (0, index_1.runCli)(['debate', 'Design a system', '--rounds', '1']);
2205
+ expect(stdoutSpy).toHaveBeenCalled();
2206
+ });
2207
+ it('should use custom maxPerAgent when provided (classic orchestrator path)', async () => {
2208
+ const configPath = getTestConfigPath(tmpDir);
2209
+ const configContent = createTestConfigContent(undefined, {
2210
+ orchestratorType: 'classic',
2211
+ clarificationsMaxPerAgent: 3,
2212
+ });
2213
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2214
+ mockReadlineWithAnswers([]);
2215
+ mockedCollectClarifications.mockResolvedValueOnce([]);
2216
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--clarify', '--rounds', '1']);
2217
+ expect(mockedCollectClarifications).toHaveBeenCalledWith(expect.any(String), expect.any(Array), 3, expect.any(Function));
2218
+ });
2219
+ it('should use default maxPerAgent when not provided', async () => {
2220
+ const configPath = getTestConfigPath(tmpDir);
2221
+ const configContent = createTestConfigContent(undefined, { orchestratorType: 'classic' });
2222
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2223
+ mockReadlineWithAnswers([]);
2224
+ mockedCollectClarifications.mockResolvedValueOnce([]);
2225
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--clarify', '--rounds', '1']);
2226
+ expect(mockedCollectClarifications).toHaveBeenCalledWith(expect.any(String), expect.any(Array), 5, expect.any(Function));
2227
+ });
2228
+ });
2229
+ function mockReadlineWithAnswers(answers) {
2230
+ const readlineModule = require('readline');
2231
+ if (readlineModule.__setMockAnswers) {
2232
+ readlineModule.__setMockAnswers(answers);
2233
+ }
2234
+ else {
2235
+ mockAnswers = [...answers];
2236
+ currentIndex = 0;
2237
+ }
2238
+ }
2239
+ describe('rethrowIfErrorCode edge cases', () => {
2240
+ it('should not rethrow when error code does not match expected code', async () => {
2241
+ const problemFile = path_1.default.join(tmpDir, 'problem.txt');
2242
+ fs_1.default.writeFileSync(problemFile, 'Some content');
2243
+ const errorWithCode = Object.assign(new Error('Read error'), { code: dialectic_core_1.EXIT_GENERAL_ERROR });
2244
+ jest.spyOn(fs_1.default.promises, 'readFile').mockRejectedValueOnce(errorWithCode);
2245
+ await expect((0, index_1.runCli)(['debate', '--problemDescription', problemFile]))
2246
+ .rejects.toHaveProperty('code', dialectic_core_1.EXIT_GENERAL_ERROR);
2247
+ jest.spyOn(fs_1.default.promises, 'readFile').mockRestore();
2248
+ });
2249
+ it('should handle error without code property', async () => {
2250
+ const problemFile = path_1.default.join(tmpDir, 'problem.txt');
2251
+ fs_1.default.writeFileSync(problemFile, 'Some content');
2252
+ const errorWithoutCode = new Error('Read error');
2253
+ jest.spyOn(fs_1.default.promises, 'readFile').mockRejectedValueOnce(errorWithoutCode);
2254
+ await expect((0, index_1.runCli)(['debate', '--problemDescription', problemFile]))
2255
+ .rejects.toHaveProperty('code', dialectic_core_1.EXIT_GENERAL_ERROR);
2256
+ jest.spyOn(fs_1.default.promises, 'readFile').mockRestore();
2257
+ });
2258
+ it('should handle non-object error', async () => {
2259
+ const problemFile = path_1.default.join(tmpDir, 'problem.txt');
2260
+ fs_1.default.writeFileSync(problemFile, 'Some content');
2261
+ jest.spyOn(fs_1.default.promises, 'readFile').mockRejectedValueOnce('String error');
2262
+ await expect((0, index_1.runCli)(['debate', '--problemDescription', problemFile]))
2263
+ .rejects.toHaveProperty('code', dialectic_core_1.EXIT_GENERAL_ERROR);
2264
+ jest.spyOn(fs_1.default.promises, 'readFile').mockRestore();
2265
+ });
2266
+ });
2267
+ describe('collectAndAnswerClarifications empty groups', () => {
2268
+ it('should skip empty clarification groups', async () => {
2269
+ const configPath = getTestConfigPath(tmpDir);
2270
+ const configContent = createTestConfigContent(undefined, { orchestratorType: 'classic' });
2271
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2272
+ mockedCollectClarifications.mockResolvedValueOnce([
2273
+ { agentName: 'Test Agent', agentId: 'test-agent', role: 'architect', items: [] },
2274
+ { agentName: 'Test Agent 2', agentId: 'test-agent-2', role: 'performance', items: [] },
2275
+ ]);
2276
+ mockReadlineWithAnswers([]);
2277
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--clarify', '--rounds', '1']);
2278
+ expect(stdoutSpy).toHaveBeenCalled();
2279
+ });
2280
+ });
2281
+ describe('promptUserForAnswers already answered questions', () => {
2282
+ it('should handle state machine orchestrator with clarifications', async () => {
2283
+ const configPath = getTestConfigPath(tmpDir);
2284
+ const configContent = createTestConfigContent(undefined, {
2285
+ orchestratorType: 'state-machine',
2286
+ interactiveClarifications: true,
2287
+ });
2288
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2289
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
2290
+ expect(stdoutSpy).toHaveBeenCalled();
2291
+ });
2292
+ });
2293
+ describe('outputResults branches', () => {
2294
+ it('should write JSON output when output path ends with .json', async () => {
2295
+ const outputPath = path_1.default.join(tmpDir, 'output.json');
2296
+ await (0, index_1.runCli)(['debate', 'Design a system', '--output', outputPath, '--rounds', '1']);
2297
+ expect(fs_1.default.existsSync(outputPath)).toBe(true);
2298
+ const content = fs_1.default.readFileSync(outputPath, 'utf-8');
2299
+ const parsed = JSON.parse(content);
2300
+ expect(parsed).toHaveProperty('id');
2301
+ });
2302
+ it('should write text output when output path does not end with .json', async () => {
2303
+ const outputPath = path_1.default.join(tmpDir, 'output.txt');
2304
+ await (0, index_1.runCli)(['debate', 'Design a system', '--output', outputPath, '--rounds', '1']);
2305
+ expect(fs_1.default.existsSync(outputPath)).toBe(true);
2306
+ const content = fs_1.default.readFileSync(outputPath, 'utf-8');
2307
+ expect(content).toContain('Solution text');
2308
+ });
2309
+ it('should not output verbose summary when output path is provided', async () => {
2310
+ const outputPath = path_1.default.join(tmpDir, 'output.txt');
2311
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
2312
+ await (0, index_1.runCli)(['debate', 'Design a system', '--output', outputPath, '--verbose', '--rounds', '1']);
2313
+ const calls = stderrWriteSpy.mock.calls.map(args => String(args[0]));
2314
+ const stderrContent = calls.join('');
2315
+ expect(stderrContent).not.toContain('Summary (verbose)');
2316
+ stderrWriteSpy.mockRestore();
2317
+ });
2318
+ it('should output verbose summary to stderr when no output path and verbose is true', async () => {
2319
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
2320
+ await (0, index_1.runCli)(['debate', 'Design a system', '--verbose', '--rounds', '1']);
2321
+ expect(stderrWriteSpy).toHaveBeenCalled();
2322
+ stderrWriteSpy.mockRestore();
2323
+ });
2324
+ });
2325
+ describe('generateReport error handling', () => {
2326
+ it('should handle missing debate state gracefully', async () => {
2327
+ const configPath = getTestConfigPath(tmpDir);
2328
+ const configContent = createTestConfigContent();
2329
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2330
+ const reportPath = path_1.default.join(tmpDir, 'report.md');
2331
+ const getDebateSpy = jest.spyOn(dialectic_core_1.StateManager.prototype, 'getDebate').mockResolvedValueOnce(null);
2332
+ const warnUserSpy = jest.spyOn(indexModule, 'warnUser').mockImplementation(() => { });
2333
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--report', reportPath, '--rounds', '1']);
2334
+ expect(warnUserSpy).toHaveBeenCalledWith(expect.stringContaining('Failed to generate report'));
2335
+ getDebateSpy.mockRestore();
2336
+ warnUserSpy.mockRestore();
2337
+ });
2338
+ it('should handle report generation errors gracefully', async () => {
2339
+ const configPath = getTestConfigPath(tmpDir);
2340
+ const configContent = createTestConfigContent();
2341
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2342
+ const reportPath = path_1.default.join(tmpDir, 'report.md');
2343
+ mockedGenerateDebateReport.mockImplementationOnce(() => {
2344
+ throw new Error('Report generation failed');
2345
+ });
2346
+ const warnUserSpy = jest.spyOn(indexModule, 'warnUser').mockImplementation(() => { });
2347
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--report', reportPath, '--rounds', '1']);
2348
+ expect(warnUserSpy).toHaveBeenCalledWith(expect.stringContaining('Failed to generate report'));
2349
+ expect(stdoutSpy).toHaveBeenCalled();
2350
+ warnUserSpy.mockRestore();
2351
+ });
2352
+ });
2353
+ describe('outputVerboseDebateInfo prompt sources', () => {
2354
+ it('should display file-based prompt sources when prompts come from files', async () => {
2355
+ const configPath = getTestConfigPath(tmpDir);
2356
+ const promptDir = path_1.default.join(tmpDir, 'prompts');
2357
+ fs_1.default.mkdirSync(promptDir, { recursive: true });
2358
+ const agentPromptFile = path_1.default.join(promptDir, 'agent-prompt.txt');
2359
+ const judgePromptFile = path_1.default.join(promptDir, 'judge-prompt.txt');
2360
+ fs_1.default.writeFileSync(agentPromptFile, 'Agent system prompt from file');
2361
+ fs_1.default.writeFileSync(judgePromptFile, 'Judge system prompt from file');
2362
+ const configContent = {
2363
+ agents: [{
2364
+ ...createTestAgentConfig(),
2365
+ systemPromptPath: agentPromptFile,
2366
+ }],
2367
+ judge: {
2368
+ id: 'test-judge',
2369
+ name: 'Test Judge',
2370
+ role: 'generalist',
2371
+ model: 'gpt-4',
2372
+ provider: 'openai',
2373
+ temperature: 0.3,
2374
+ systemPromptPath: judgePromptFile,
2375
+ },
2376
+ debate: createTestDebateConfig(),
2377
+ };
2378
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2379
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
2380
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--verbose', '--rounds', '1']);
2381
+ const calls = stderrWriteSpy.mock.calls.map(args => String(args[0]));
2382
+ const stderrContent = calls.join('');
2383
+ expect(stderrWriteSpy).toHaveBeenCalled();
2384
+ expect(stderrContent.length).toBeGreaterThan(0);
2385
+ stderrWriteSpy.mockRestore();
2386
+ });
2387
+ });
2388
+ describe('readAndValidateFileContent error handling', () => {
2389
+ it('should handle general read errors (non-EXIT_INVALID_ARGS)', async () => {
2390
+ const problemFile = path_1.default.join(tmpDir, 'problem.txt');
2391
+ fs_1.default.writeFileSync(problemFile, 'Some content');
2392
+ const generalError = new Error('Permission denied');
2393
+ jest.spyOn(fs_1.default.promises, 'readFile').mockRejectedValueOnce(generalError);
2394
+ await expect((0, index_1.runCli)(['debate', '--problemDescription', problemFile]))
2395
+ .rejects.toHaveProperty('code', dialectic_core_1.EXIT_GENERAL_ERROR);
2396
+ expect(stderrWriteSpy).toHaveBeenCalledWith(expect.stringContaining('Failed to read problem description file'));
2397
+ jest.spyOn(fs_1.default.promises, 'readFile').mockRestore();
2398
+ });
2399
+ });
2400
+ describe('runDebateWithClarifications state machine edge cases', () => {
2401
+ it('should handle state machine orchestrator suspend/resume flow', async () => {
2402
+ const configPath = getTestConfigPath(tmpDir);
2403
+ const configContent = createTestConfigContent(undefined, {
2404
+ orchestratorType: 'state-machine',
2405
+ interactiveClarifications: true,
2406
+ });
2407
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2408
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
2409
+ expect(stdoutSpy).toHaveBeenCalled();
2410
+ });
2411
+ it('should call promptUserForAnswers and resume twice when orchestrator suspends twice', async () => {
2412
+ const configPath = getTestConfigPath(tmpDir);
2413
+ const configContent = createTestConfigContent(undefined, {
2414
+ orchestratorType: 'state-machine',
2415
+ interactiveClarifications: true,
2416
+ });
2417
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2418
+ const coreModule = require('dialectic-core');
2419
+ const { EXECUTION_STATUS, SUSPEND_REASON } = coreModule;
2420
+ const questions1 = [
2421
+ { agentId: 'a1', agentName: 'A', role: 'architect', items: [{ id: 'q1', question: 'Q1?', answer: '' }] },
2422
+ ];
2423
+ const questions2 = [
2424
+ { agentId: 'a1', agentName: 'A', role: 'architect', items: [{ id: 'q1', question: 'Q1?', answer: '' }, { id: 'q2', question: 'Q2?', answer: '' }] },
2425
+ ];
2426
+ const completedResult = {
2427
+ debateId: 'debate-1',
2428
+ solution: { description: 'S', tradeoffs: [], recommendations: [], confidence: 90, synthesizedBy: 'judge' },
2429
+ rounds: [],
2430
+ metadata: { totalRounds: 0, durationMs: 100 },
2431
+ };
2432
+ const resumeMock = jest
2433
+ .fn()
2434
+ .mockResolvedValueOnce({
2435
+ status: EXECUTION_STATUS.SUSPENDED,
2436
+ suspendReason: SUSPEND_REASON.WAITING_FOR_INPUT,
2437
+ suspendPayload: { debateId: 'debate-1', questions: questions2, iteration: 2 },
2438
+ })
2439
+ .mockResolvedValueOnce({
2440
+ status: EXECUTION_STATUS.COMPLETED,
2441
+ result: completedResult,
2442
+ });
2443
+ const fakeOrchestrator = {
2444
+ runDebate: jest.fn().mockResolvedValue({
2445
+ status: EXECUTION_STATUS.SUSPENDED,
2446
+ suspendReason: SUSPEND_REASON.WAITING_FOR_INPUT,
2447
+ suspendPayload: { debateId: 'debate-1', questions: questions1, iteration: 1 },
2448
+ }),
2449
+ resume: resumeMock,
2450
+ };
2451
+ const createOrchestratorSpy = jest
2452
+ .spyOn(require('dialectic-core'), 'createOrchestrator')
2453
+ .mockReturnValue(fakeOrchestrator);
2454
+ const isStateMachineSpy = jest
2455
+ .spyOn(require('dialectic-core'), 'isStateMachineOrchestrator')
2456
+ .mockReturnValue(true);
2457
+ const readlineModule = require('readline');
2458
+ if (readlineModule.__setMockAnswers) {
2459
+ readlineModule.__setMockAnswers(['ans1', 'ans2', 'ans3']);
2460
+ }
2461
+ const mockDebateState = {
2462
+ id: 'debate-1',
2463
+ problem: 'Design a system',
2464
+ status: 'completed',
2465
+ rounds: [],
2466
+ finalSolution: { description: 'S', tradeoffs: [], recommendations: [], confidence: 90, synthesizedBy: 'judge' },
2467
+ };
2468
+ const getDebateSpy = jest
2469
+ .spyOn(dialectic_core_1.StateManager.prototype, 'getDebate')
2470
+ .mockImplementation(() => Promise.resolve(mockDebateState));
2471
+ const setPromptSourcesSpy = jest
2472
+ .spyOn(dialectic_core_1.StateManager.prototype, 'setPromptSources')
2473
+ .mockResolvedValue(undefined);
2474
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
2475
+ getDebateSpy.mockRestore();
2476
+ setPromptSourcesSpy.mockRestore();
2477
+ expect(resumeMock).toHaveBeenCalledTimes(2);
2478
+ expect(resumeMock).toHaveBeenNthCalledWith(1, 'debate-1', expect.any(Array));
2479
+ expect(resumeMock).toHaveBeenNthCalledWith(2, 'debate-1', expect.any(Array));
2480
+ expect(stdoutSpy).toHaveBeenCalled();
2481
+ createOrchestratorSpy.mockRestore();
2482
+ isStateMachineSpy.mockRestore();
2483
+ });
2484
+ });
2485
+ describe('extractProblemFileName and extractContextDirectoryName', () => {
2486
+ it('should extract problem file name from options', async () => {
2487
+ const problemFile = path_1.default.join(tmpDir, 'problem.txt');
2488
+ fs_1.default.writeFileSync(problemFile, 'Problem description');
2489
+ await (0, index_1.runCli)(['debate', '--problemDescription', problemFile, '--rounds', '1']);
2490
+ expect(stdoutSpy).toHaveBeenCalled();
2491
+ });
2492
+ it('should extract context directory name from options', async () => {
2493
+ const contextDir = path_1.default.join(tmpDir, 'context');
2494
+ fs_1.default.mkdirSync(contextDir, { recursive: true });
2495
+ await (0, index_1.runCli)(['debate', 'Design a system', '--context', contextDir, '--rounds', '1']);
2496
+ expect(stdoutSpy).toHaveBeenCalled();
2497
+ });
2498
+ });
2499
+ describe('outputRoundSummary branches', () => {
2500
+ it('should output verbose summary with rounds containing summaries and contributions', async () => {
2501
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
2502
+ await (0, index_1.runCli)(['debate', 'Design a system', '--verbose', '--rounds', '1']);
2503
+ expect(stderrWriteSpy).toHaveBeenCalled();
2504
+ stderrWriteSpy.mockRestore();
2505
+ });
2506
+ });
2507
+ describe('outputResults debate null branch', () => {
2508
+ it('should handle null debate state gracefully in verbose mode', async () => {
2509
+ const configPath = getTestConfigPath(tmpDir);
2510
+ const configContent = createTestConfigContent();
2511
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2512
+ const getDebateSpy = jest.spyOn(dialectic_core_1.StateManager.prototype, 'getDebate')
2513
+ .mockResolvedValueOnce(null)
2514
+ .mockResolvedValueOnce(null);
2515
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
2516
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--verbose', '--rounds', '1']);
2517
+ expect(stdoutSpy).toHaveBeenCalled();
2518
+ getDebateSpy.mockRestore();
2519
+ stderrWriteSpy.mockRestore();
2520
+ });
2521
+ });
2522
+ describe('generateReport .md extension branch', () => {
2523
+ it('should not append .md when report path already ends with .md', async () => {
2524
+ const configPath = getTestConfigPath(tmpDir);
2525
+ const configContent = createTestConfigContent();
2526
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2527
+ const reportPath = path_1.default.join(tmpDir, 'report.md');
2528
+ const warnUserSpy = jest.spyOn(indexModule, 'warnUser').mockImplementation(() => { });
2529
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--report', reportPath, '--rounds', '1']);
2530
+ expect(warnUserSpy).not.toHaveBeenCalledWith(expect.stringContaining('appending .md extension'));
2531
+ expect(fs_1.default.existsSync(reportPath)).toBe(true);
2532
+ warnUserSpy.mockRestore();
2533
+ });
2534
+ });
2535
+ describe('resolveJudgeSystemPromptWithDefault and resolveJudgeSummaryPromptWithDefault', () => {
2536
+ it('should resolve judge prompts from files when provided', async () => {
2537
+ const configPath = getTestConfigPath(tmpDir);
2538
+ const promptDir = path_1.default.join(tmpDir, 'prompts');
2539
+ fs_1.default.mkdirSync(promptDir, { recursive: true });
2540
+ const judgePromptFile = path_1.default.join(promptDir, 'judge-prompt.txt');
2541
+ const judgeSummaryPromptFile = path_1.default.join(promptDir, 'judge-summary-prompt.txt');
2542
+ fs_1.default.writeFileSync(judgePromptFile, 'Judge system prompt from file');
2543
+ fs_1.default.writeFileSync(judgeSummaryPromptFile, 'Judge summary prompt from file');
2544
+ const configContent = {
2545
+ agents: [createTestAgentConfig()],
2546
+ judge: {
2547
+ id: 'test-judge',
2548
+ name: 'Test Judge',
2549
+ role: 'generalist',
2550
+ model: 'gpt-4',
2551
+ provider: 'openai',
2552
+ temperature: 0.3,
2553
+ systemPromptPath: judgePromptFile,
2554
+ summaryPromptPath: judgeSummaryPromptFile,
2555
+ },
2556
+ debate: createTestDebateConfig(),
2557
+ };
2558
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2559
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
2560
+ expect(stdoutSpy).toHaveBeenCalled();
2561
+ });
2562
+ });
2563
+ describe('createAgentWithPromptResolution prompt path branches', () => {
2564
+ it('should handle agent with systemPromptPath', async () => {
2565
+ const configPath = getTestConfigPath(tmpDir);
2566
+ const promptDir = path_1.default.join(tmpDir, 'prompts');
2567
+ fs_1.default.mkdirSync(promptDir, { recursive: true });
2568
+ const agentPromptFile = path_1.default.join(promptDir, 'agent-prompt.txt');
2569
+ fs_1.default.writeFileSync(agentPromptFile, 'Agent system prompt from file');
2570
+ const configContent = {
2571
+ agents: [{
2572
+ ...createTestAgentConfig(),
2573
+ systemPromptPath: agentPromptFile,
2574
+ }],
2575
+ judge: {
2576
+ id: 'test-judge',
2577
+ name: 'Test Judge',
2578
+ role: 'generalist',
2579
+ model: 'gpt-4',
2580
+ provider: 'openai',
2581
+ temperature: 0.3,
2582
+ },
2583
+ debate: createTestDebateConfig(),
2584
+ };
2585
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2586
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
2587
+ expect(stdoutSpy).toHaveBeenCalled();
2588
+ });
2589
+ it('should handle agent with summaryPromptPath', async () => {
2590
+ const configPath = getTestConfigPath(tmpDir);
2591
+ const promptDir = path_1.default.join(tmpDir, 'prompts');
2592
+ fs_1.default.mkdirSync(promptDir, { recursive: true });
2593
+ const summaryPromptFile = path_1.default.join(promptDir, 'summary-prompt.txt');
2594
+ fs_1.default.writeFileSync(summaryPromptFile, 'Summary prompt from file');
2595
+ const configContent = {
2596
+ agents: [{
2597
+ ...createTestAgentConfig(),
2598
+ summaryPromptPath: summaryPromptFile,
2599
+ }],
2600
+ judge: {
2601
+ id: 'test-judge',
2602
+ name: 'Test Judge',
2603
+ role: 'generalist',
2604
+ model: 'gpt-4',
2605
+ provider: 'openai',
2606
+ temperature: 0.3,
2607
+ },
2608
+ debate: createTestDebateConfig(),
2609
+ };
2610
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2611
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
2612
+ expect(stdoutSpy).toHaveBeenCalled();
2613
+ });
2614
+ it('should handle agent with clarificationPromptPath', async () => {
2615
+ const configPath = getTestConfigPath(tmpDir);
2616
+ const promptDir = path_1.default.join(tmpDir, 'prompts');
2617
+ fs_1.default.mkdirSync(promptDir, { recursive: true });
2618
+ const clarificationPromptFile = path_1.default.join(promptDir, 'clarification-prompt.txt');
2619
+ fs_1.default.writeFileSync(clarificationPromptFile, 'Clarification prompt from file');
2620
+ const configContent = {
2621
+ agents: [{
2622
+ ...createTestAgentConfig(),
2623
+ clarificationPromptPath: clarificationPromptFile,
2624
+ }],
2625
+ judge: {
2626
+ id: 'test-judge',
2627
+ name: 'Test Judge',
2628
+ role: 'generalist',
2629
+ model: 'gpt-4',
2630
+ provider: 'openai',
2631
+ temperature: 0.3,
2632
+ },
2633
+ debate: createTestDebateConfig(),
2634
+ };
2635
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2636
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
2637
+ expect(stdoutSpy).toHaveBeenCalled();
2638
+ });
2639
+ });
2640
+ describe('createJudgeWithPromptResolution absPath branches', () => {
2641
+ it('should handle judge prompt resolution with absPath', async () => {
2642
+ const configPath = getTestConfigPath(tmpDir);
2643
+ const promptDir = path_1.default.join(tmpDir, 'prompts');
2644
+ fs_1.default.mkdirSync(promptDir, { recursive: true });
2645
+ const judgePromptFile = path_1.default.join(promptDir, 'judge-prompt.txt');
2646
+ fs_1.default.writeFileSync(judgePromptFile, 'Judge prompt');
2647
+ const configContent = {
2648
+ agents: [createTestAgentConfig()],
2649
+ judge: {
2650
+ id: 'test-judge',
2651
+ name: 'Test Judge',
2652
+ role: 'generalist',
2653
+ model: 'gpt-4',
2654
+ provider: 'openai',
2655
+ temperature: 0.3,
2656
+ systemPromptPath: judgePromptFile,
2657
+ },
2658
+ debate: createTestDebateConfig(),
2659
+ };
2660
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2661
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
2662
+ expect(stdoutSpy).toHaveBeenCalled();
2663
+ });
2664
+ });
2665
+ describe('outputVerboseDebateInfo file source branches', () => {
2666
+ it('should display file path when prompt source is file', async () => {
2667
+ const configPath = getTestConfigPath(tmpDir);
2668
+ const promptDir = path_1.default.join(tmpDir, 'prompts');
2669
+ fs_1.default.mkdirSync(promptDir, { recursive: true });
2670
+ const agentPromptFile = path_1.default.join(promptDir, 'agent-prompt.txt');
2671
+ const judgePromptFile = path_1.default.join(promptDir, 'judge-prompt.txt');
2672
+ fs_1.default.writeFileSync(agentPromptFile, 'Agent prompt');
2673
+ fs_1.default.writeFileSync(judgePromptFile, 'Judge prompt');
2674
+ const configContent = {
2675
+ agents: [{
2676
+ ...createTestAgentConfig(),
2677
+ systemPromptPath: agentPromptFile,
2678
+ }],
2679
+ judge: {
2680
+ id: 'test-judge',
2681
+ name: 'Test Judge',
2682
+ role: 'generalist',
2683
+ model: 'gpt-4',
2684
+ provider: 'openai',
2685
+ temperature: 0.3,
2686
+ systemPromptPath: judgePromptFile,
2687
+ },
2688
+ debate: createTestDebateConfig(),
2689
+ };
2690
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2691
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
2692
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--verbose', '--rounds', '1']);
2693
+ const calls = stderrWriteSpy.mock.calls.map(args => String(args[0]));
2694
+ const stderrContent = calls.join('');
2695
+ expect(stderrContent.length).toBeGreaterThan(0);
2696
+ stderrWriteSpy.mockRestore();
2697
+ });
2698
+ });
2699
+ describe('initializeTracingContext trace option branches', () => {
2700
+ it('should return undefined when trace is not LANGFUSE', async () => {
2701
+ const configPath = getTestConfigPath(tmpDir);
2702
+ const configContent = createTestConfigContent(undefined, {
2703
+ trace: 'none',
2704
+ });
2705
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2706
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
2707
+ expect(stdoutSpy).toHaveBeenCalled();
2708
+ });
2709
+ it('should handle tracing context creation failure gracefully', async () => {
2710
+ const configPath = getTestConfigPath(tmpDir);
2711
+ const configContent = createTestConfigContent(undefined, {
2712
+ trace: 'langfuse',
2713
+ });
2714
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2715
+ dialecticCore.validateLangfuseConfig.mockImplementationOnce(() => {
2716
+ throw new Error('Langfuse config invalid');
2717
+ });
2718
+ const warnUserSpy = jest.spyOn(indexModule, 'warnUser').mockImplementation(() => { });
2719
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
2720
+ expect(warnUserSpy).toHaveBeenCalledWith(expect.stringContaining('Langfuse tracing initialization failed'));
2721
+ expect(stdoutSpy).toHaveBeenCalled();
2722
+ dialecticCore.validateLangfuseConfig.mockReset();
2723
+ dialecticCore.validateLangfuseConfig.mockImplementation(jest.requireActual('dialectic-core').validateLangfuseConfig);
2724
+ warnUserSpy.mockRestore();
2725
+ });
2726
+ });
2727
+ describe('debateConfigFromSysConfig rounds branches', () => {
2728
+ it('should use sysConfig.debate.rounds when options.rounds is not provided', async () => {
2729
+ const configPath = getTestConfigPath(tmpDir);
2730
+ const configContent = createTestConfigContent(undefined, {
2731
+ rounds: 5,
2732
+ });
2733
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2734
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath]);
2735
+ expect(stdoutSpy).toHaveBeenCalled();
2736
+ });
2737
+ });
2738
+ describe('agentConfigsFromSysConfig filtering branches', () => {
2739
+ it('should filter agents by role when --agents option is provided', async () => {
2740
+ const configPath = getTestConfigPath(tmpDir);
2741
+ const configContent = {
2742
+ agents: [
2743
+ createTestAgentConfig({ id: 'agent1', role: 'architect' }),
2744
+ createTestAgentConfig({ id: 'agent2', role: 'performance' }),
2745
+ createTestAgentConfig({ id: 'agent3', role: 'security' }),
2746
+ ],
2747
+ judge: {
2748
+ id: 'test-judge',
2749
+ name: 'Test Judge',
2750
+ role: 'generalist',
2751
+ model: 'gpt-4',
2752
+ provider: 'openai',
2753
+ temperature: 0.3,
2754
+ },
2755
+ debate: createTestDebateConfig(),
2756
+ };
2757
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2758
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--agents', 'architect,performance', '--rounds', '1']);
2759
+ expect(stdoutSpy).toHaveBeenCalled();
2760
+ });
2761
+ it('should filter out disabled agents', async () => {
2762
+ const configPath = getTestConfigPath(tmpDir);
2763
+ const configContent = {
2764
+ agents: [
2765
+ createTestAgentConfig({ id: 'agent1', enabled: true }),
2766
+ createTestAgentConfig({ id: 'agent2', enabled: false }),
2767
+ createTestAgentConfig({ id: 'agent3', enabled: true }),
2768
+ ],
2769
+ judge: {
2770
+ id: 'test-judge',
2771
+ name: 'Test Judge',
2772
+ role: 'generalist',
2773
+ model: 'gpt-4',
2774
+ provider: 'openai',
2775
+ temperature: 0.3,
2776
+ },
2777
+ debate: createTestDebateConfig(),
2778
+ };
2779
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2780
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
2781
+ expect(stdoutSpy).toHaveBeenCalled();
2782
+ });
2783
+ });
2784
+ describe('outputVerboseDebateInfo path branches', () => {
2785
+ it('should handle undefined path when source is file', async () => {
2786
+ const configPath = getTestConfigPath(tmpDir);
2787
+ const promptDir = path_1.default.join(tmpDir, 'prompts');
2788
+ fs_1.default.mkdirSync(promptDir, { recursive: true });
2789
+ const agentPromptFile = path_1.default.join(promptDir, 'agent-prompt.txt');
2790
+ fs_1.default.writeFileSync(agentPromptFile, 'Agent prompt');
2791
+ const configContent = {
2792
+ agents: [{
2793
+ ...createTestAgentConfig(),
2794
+ systemPromptPath: agentPromptFile,
2795
+ }],
2796
+ judge: {
2797
+ id: 'test-judge',
2798
+ name: 'Test Judge',
2799
+ role: 'generalist',
2800
+ model: 'gpt-4',
2801
+ provider: 'openai',
2802
+ temperature: 0.3,
2803
+ },
2804
+ debate: createTestDebateConfig(),
2805
+ };
2806
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2807
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
2808
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--verbose', '--rounds', '1']);
2809
+ expect(stderrWriteSpy).toHaveBeenCalled();
2810
+ stderrWriteSpy.mockRestore();
2811
+ });
2812
+ it('should handle agent not found in promptSources', async () => {
2813
+ const configPath = getTestConfigPath(tmpDir);
2814
+ const configContent = createTestConfigContent();
2815
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2816
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
2817
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--verbose', '--rounds', '1']);
2818
+ expect(stderrWriteSpy).toHaveBeenCalled();
2819
+ stderrWriteSpy.mockRestore();
2820
+ });
2821
+ });
2822
+ describe('outputRoundSummary metadata branches', () => {
2823
+ it('should handle contributions without metadata', async () => {
2824
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
2825
+ await (0, index_1.runCli)(['debate', 'Design a system', '--verbose', '--rounds', '1']);
2826
+ expect(stderrWriteSpy).toHaveBeenCalled();
2827
+ stderrWriteSpy.mockRestore();
2828
+ });
2829
+ it('should handle summaries with null tokensUsed and latencyMs', async () => {
2830
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
2831
+ await (0, index_1.runCli)(['debate', 'Design a system', '--verbose', '--rounds', '1']);
2832
+ expect(stderrWriteSpy).toHaveBeenCalled();
2833
+ stderrWriteSpy.mockRestore();
2834
+ });
2835
+ });
2836
+ describe('prompt resolution absPath branches', () => {
2837
+ it('should handle prompt resolution when absPath might be undefined', async () => {
2838
+ const configPath = getTestConfigPath(tmpDir);
2839
+ const promptDir = path_1.default.join(tmpDir, 'prompts');
2840
+ fs_1.default.mkdirSync(promptDir, { recursive: true });
2841
+ const agentPromptFile = path_1.default.join(promptDir, 'agent.txt');
2842
+ const judgePromptFile = path_1.default.join(promptDir, 'judge.txt');
2843
+ fs_1.default.writeFileSync(agentPromptFile, 'Agent prompt');
2844
+ fs_1.default.writeFileSync(judgePromptFile, 'Judge prompt');
2845
+ const configContent = {
2846
+ agents: [{
2847
+ ...createTestAgentConfig(),
2848
+ systemPromptPath: 'prompts/agent.txt',
2849
+ }],
2850
+ judge: {
2851
+ id: 'test-judge',
2852
+ name: 'Test Judge',
2853
+ role: 'generalist',
2854
+ model: 'gpt-4',
2855
+ provider: 'openai',
2856
+ temperature: 0.3,
2857
+ systemPromptPath: 'prompts/judge.txt',
2858
+ },
2859
+ debate: createTestDebateConfig(),
2860
+ };
2861
+ const configParentDir = path_1.default.dirname(configPath);
2862
+ const promptsDir = path_1.default.join(configParentDir, 'prompts');
2863
+ fs_1.default.mkdirSync(promptsDir, { recursive: true });
2864
+ fs_1.default.writeFileSync(path_1.default.join(promptsDir, 'agent.txt'), 'Agent prompt');
2865
+ fs_1.default.writeFileSync(path_1.default.join(promptsDir, 'judge.txt'), 'Judge prompt');
2866
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2867
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
2868
+ expect(stdoutSpy).toHaveBeenCalled();
2869
+ });
2870
+ });
2871
+ describe('createAgentLogger onlyVerbose undefined branch', () => {
2872
+ it('should handle onlyVerbose being undefined', async () => {
2873
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
2874
+ await (0, index_1.runCli)(['debate', 'Design a system', '--rounds', '1']);
2875
+ expect(stdoutSpy).toHaveBeenCalled();
2876
+ stderrWriteSpy.mockRestore();
2877
+ });
2878
+ });
2879
+ describe('onSummarizationComplete verbose branch', () => {
2880
+ it('should log summarization details when verbose is true', async () => {
2881
+ const configPath = getTestConfigPath(tmpDir);
2882
+ const configContent = createTestConfigContent(undefined, {
2883
+ summarization: {
2884
+ enabled: true,
2885
+ threshold: 100,
2886
+ maxLength: 500,
2887
+ method: 'length-based',
2888
+ },
2889
+ });
2890
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2891
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
2892
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--verbose', '--rounds', '1']);
2893
+ expect(stderrWriteSpy).toHaveBeenCalled();
2894
+ stderrWriteSpy.mockRestore();
2895
+ });
2896
+ });
2897
+ describe('initializeTracingContext optional metadata branches', () => {
2898
+ it('should handle undefined problemFileName and contextDirectoryName', async () => {
2899
+ const configPath = getTestConfigPath(tmpDir);
2900
+ const configContent = createTestConfigContent(undefined, {
2901
+ trace: 'langfuse',
2902
+ });
2903
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2904
+ process.env.LANGFUSE_PUBLIC_KEY = 'test-key';
2905
+ process.env.LANGFUSE_SECRET_KEY = 'test-secret';
2906
+ process.env.LANGFUSE_HOST = 'https://cloud.langfuse.com';
2907
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
2908
+ expect(stdoutSpy).toHaveBeenCalled();
2909
+ delete process.env.LANGFUSE_PUBLIC_KEY;
2910
+ delete process.env.LANGFUSE_SECRET_KEY;
2911
+ delete process.env.LANGFUSE_HOST;
2912
+ });
2913
+ it('should handle undefined judgeConfig in trace metadata', async () => {
2914
+ const configPath = getTestConfigPath(tmpDir);
2915
+ const configContent = {
2916
+ agents: [createTestAgentConfig()],
2917
+ debate: createTestDebateConfig({ trace: 'langfuse' }),
2918
+ };
2919
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2920
+ process.env.LANGFUSE_PUBLIC_KEY = 'test-key';
2921
+ process.env.LANGFUSE_SECRET_KEY = 'test-secret';
2922
+ process.env.LANGFUSE_HOST = 'https://cloud.langfuse.com';
2923
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
2924
+ expect(stdoutSpy).toHaveBeenCalled();
2925
+ delete process.env.LANGFUSE_PUBLIC_KEY;
2926
+ delete process.env.LANGFUSE_SECRET_KEY;
2927
+ delete process.env.LANGFUSE_HOST;
2928
+ });
2929
+ });
2930
+ describe('createTracingContext falsy return branch', () => {
2931
+ it('should handle when createTracingContext returns undefined', async () => {
2932
+ const configPath = getTestConfigPath(tmpDir);
2933
+ const configContent = createTestConfigContent(undefined, {
2934
+ trace: 'langfuse',
2935
+ });
2936
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2937
+ dialecticCore.createTracingContext.mockReturnValueOnce(undefined);
2938
+ process.env.LANGFUSE_PUBLIC_KEY = 'test-key';
2939
+ process.env.LANGFUSE_SECRET_KEY = 'test-secret';
2940
+ process.env.LANGFUSE_HOST = 'https://cloud.langfuse.com';
2941
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
2942
+ expect(stdoutSpy).toHaveBeenCalled();
2943
+ dialecticCore.createTracingContext.mockReset();
2944
+ dialecticCore.createTracingContext.mockImplementation(jest.requireActual('dialectic-core').createTracingContext);
2945
+ delete process.env.LANGFUSE_PUBLIC_KEY;
2946
+ delete process.env.LANGFUSE_SECRET_KEY;
2947
+ delete process.env.LANGFUSE_HOST;
2948
+ });
2949
+ });
2950
+ describe('runDebateWithClarifications state machine error branch', () => {
2951
+ it('should throw for unknown suspend reason in state machine orchestrator', async () => {
2952
+ const configPath = getTestConfigPath(tmpDir);
2953
+ const configContent = createTestConfigContent(undefined, {
2954
+ orchestratorType: 'state-machine',
2955
+ interactiveClarifications: true,
2956
+ });
2957
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2958
+ const coreModule = require('dialectic-core');
2959
+ const { EXECUTION_STATUS } = coreModule;
2960
+ const suspendResultWithUnknownReason = {
2961
+ status: EXECUTION_STATUS.SUSPENDED,
2962
+ suspendReason: 'UNKNOWN_REASON',
2963
+ suspendPayload: {
2964
+ debateId: 'debate-2',
2965
+ questions: [],
2966
+ },
2967
+ };
2968
+ const fakeOrchestrator = {
2969
+ runDebate: jest.fn().mockResolvedValue(suspendResultWithUnknownReason),
2970
+ resume: jest.fn(),
2971
+ };
2972
+ const createOrchestratorSpy = jest
2973
+ .spyOn(require('dialectic-core'), 'createOrchestrator')
2974
+ .mockReturnValue(fakeOrchestrator);
2975
+ const isStateMachineSpy = jest
2976
+ .spyOn(require('dialectic-core'), 'isStateMachineOrchestrator')
2977
+ .mockReturnValue(true);
2978
+ await expect((0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1'])).rejects.toThrow(/Unknown suspend reason/);
2979
+ createOrchestratorSpy.mockRestore();
2980
+ isStateMachineSpy.mockRestore();
2981
+ });
2982
+ });
2983
+ describe('outputRoundSummary summaries formatting branches', () => {
2984
+ it('should format summaries with tokens and latency metadata', async () => {
2985
+ const configPath = getTestConfigPath(tmpDir);
2986
+ const configContent = createTestConfigContent();
2987
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
2988
+ const debateState = {
2989
+ id: 'debate-3',
2990
+ rounds: [
2991
+ {
2992
+ roundNumber: 1,
2993
+ summaries: {
2994
+ architect: {
2995
+ agentRole: 'architect',
2996
+ metadata: {
2997
+ beforeChars: 120,
2998
+ afterChars: 60,
2999
+ tokensUsed: 42,
3000
+ latencyMs: 150,
3001
+ method: 'length-based',
3002
+ },
3003
+ },
3004
+ },
3005
+ contributions: [],
3006
+ },
3007
+ ],
3008
+ };
3009
+ const getDebateSpy = jest
3010
+ .spyOn(dialectic_core_1.StateManager.prototype, 'getDebate')
3011
+ .mockResolvedValueOnce(debateState);
3012
+ const stderrChunks = [];
3013
+ const stderrSpy = jest
3014
+ .spyOn(process.stderr, 'write')
3015
+ .mockImplementation((chunk) => {
3016
+ stderrChunks.push(String(chunk));
3017
+ return true;
3018
+ });
3019
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--verbose', '--rounds', '1']);
3020
+ const stderrOutput = stderrChunks.join('');
3021
+ expect(stderrOutput).toContain('summaries:');
3022
+ expect(stderrOutput).toContain('[architect]');
3023
+ expect(stderrOutput).toContain('latency=150ms, tokens=42, method=length-based');
3024
+ getDebateSpy.mockRestore();
3025
+ stderrSpy.mockRestore();
3026
+ });
3027
+ });
3028
+ describe('Targeted branch coverage tests', () => {
3029
+ it('should handle empty items in promptUserForAnswers', async () => {
3030
+ const configPath = getTestConfigPath(tmpDir);
3031
+ const configContent = createTestConfigContent(undefined, {
3032
+ orchestratorType: 'state-machine',
3033
+ interactiveClarifications: true,
3034
+ });
3035
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
3036
+ mockedCollectClarifications.mockResolvedValueOnce([
3037
+ { agentName: 'Test Agent', agentId: 'test-agent', role: 'architect', items: [] },
3038
+ ]);
3039
+ mockReadlineWithAnswers([]);
3040
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
3041
+ expect(stdoutSpy).toHaveBeenCalled();
3042
+ });
3043
+ it('should handle empty answer input (NA branch)', async () => {
3044
+ const configPath = getTestConfigPath(tmpDir);
3045
+ const configContent = createTestConfigContent(undefined, { orchestratorType: 'classic' });
3046
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
3047
+ mockedCollectClarifications.mockResolvedValueOnce([
3048
+ {
3049
+ agentName: 'Test Agent',
3050
+ agentId: 'test-agent',
3051
+ role: 'architect',
3052
+ items: [{ id: 'q1', question: 'Question 1', answer: '' }],
3053
+ },
3054
+ ]);
3055
+ mockReadlineWithAnswers(['']);
3056
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--clarify', '--rounds', '1']);
3057
+ expect(stdoutSpy).toHaveBeenCalled();
3058
+ });
3059
+ it('should handle createAgentLogger with onlyVerbose explicitly false', async () => {
3060
+ await (0, index_1.runCli)(['debate', 'Design a system', '--rounds', '1']);
3061
+ expect(stdoutSpy).toHaveBeenCalled();
3062
+ });
3063
+ it('should handle onSummarizationComplete verbose branch', async () => {
3064
+ const configPath = getTestConfigPath(tmpDir);
3065
+ const configContent = createTestConfigContent(undefined, {
3066
+ summarization: {
3067
+ enabled: true,
3068
+ threshold: 100,
3069
+ maxLength: 500,
3070
+ method: 'length-based',
3071
+ },
3072
+ });
3073
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
3074
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
3075
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--verbose', '--rounds', '1']);
3076
+ expect(stderrWriteSpy).toHaveBeenCalled();
3077
+ stderrWriteSpy.mockRestore();
3078
+ });
3079
+ it('should handle options.rounds branch in debateConfigFromSysConfig', async () => {
3080
+ await (0, index_1.runCli)(['debate', 'Design a system', '--rounds', '2']);
3081
+ expect(stdoutSpy).toHaveBeenCalled();
3082
+ });
3083
+ it('should handle rounds with summaries in outputRoundSummary', async () => {
3084
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
3085
+ await (0, index_1.runCli)(['debate', 'Design a system', '--verbose', '--rounds', '1']);
3086
+ expect(stderrWriteSpy).toHaveBeenCalled();
3087
+ stderrWriteSpy.mockRestore();
3088
+ });
3089
+ it('should handle contributions without metadata in outputRoundSummary', async () => {
3090
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
3091
+ await (0, index_1.runCli)(['debate', 'Design a system', '--verbose', '--rounds', '1']);
3092
+ expect(stderrWriteSpy).toHaveBeenCalled();
3093
+ stderrWriteSpy.mockRestore();
3094
+ });
3095
+ it('should handle classic orchestrator branch in outputVerboseDebateInfo', async () => {
3096
+ const configPath = getTestConfigPath(tmpDir);
3097
+ const configContent = createTestConfigContent(undefined, {
3098
+ orchestratorType: 'classic',
3099
+ });
3100
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
3101
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
3102
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--verbose', '--rounds', '1']);
3103
+ const calls = stderrWriteSpy.mock.calls.map(args => String(args[0]));
3104
+ const stderrContent = calls.join('');
3105
+ expect(stderrContent).toContain('classic');
3106
+ stderrWriteSpy.mockRestore();
3107
+ });
3108
+ it('should handle clarificationRequested false branch', async () => {
3109
+ await (0, index_1.runCli)(['debate', 'Design a system', '--rounds', '1']);
3110
+ expect(stdoutSpy).toHaveBeenCalled();
3111
+ });
3112
+ it('should handle no context directory branch', async () => {
3113
+ await (0, index_1.runCli)(['debate', 'Design a system', '--rounds', '1']);
3114
+ expect(stdoutSpy).toHaveBeenCalled();
3115
+ });
3116
+ it('should handle path undefined in outputVerboseDebateInfo for agents', async () => {
3117
+ const configPath = getTestConfigPath(tmpDir);
3118
+ const configContent = createTestConfigContent();
3119
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
3120
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
3121
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--verbose', '--rounds', '1']);
3122
+ expect(stderrWriteSpy).toHaveBeenCalled();
3123
+ stderrWriteSpy.mockRestore();
3124
+ });
3125
+ it('should handle path undefined in outputVerboseDebateInfo for judge', async () => {
3126
+ const configPath = getTestConfigPath(tmpDir);
3127
+ const configContent = createTestConfigContent();
3128
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
3129
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
3130
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--verbose', '--rounds', '1']);
3131
+ expect(stderrWriteSpy).toHaveBeenCalled();
3132
+ stderrWriteSpy.mockRestore();
3133
+ });
3134
+ it('should handle totalTokens null branch in outputResults', async () => {
3135
+ const stderrWriteSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
3136
+ await (0, index_1.runCli)(['debate', 'Design a system', '--verbose', '--rounds', '1']);
3137
+ expect(stderrWriteSpy).toHaveBeenCalled();
3138
+ stderrWriteSpy.mockRestore();
3139
+ });
3140
+ it('should handle undefined configDir in resolveJudgeSystemPromptWithDefault', async () => {
3141
+ const configPath = getTestConfigPath(tmpDir);
3142
+ const configContent = {
3143
+ agents: [createTestAgentConfig()],
3144
+ judge: {
3145
+ id: 'test-judge',
3146
+ name: 'Test Judge',
3147
+ role: 'generalist',
3148
+ model: 'gpt-4',
3149
+ provider: 'openai',
3150
+ temperature: 0.3,
3151
+ },
3152
+ debate: createTestDebateConfig(),
3153
+ };
3154
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
3155
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
3156
+ expect(stdoutSpy).toHaveBeenCalled();
3157
+ });
3158
+ it('should handle undefined configDir in resolveJudgeSummaryPromptWithDefault', async () => {
3159
+ const configPath = getTestConfigPath(tmpDir);
3160
+ const configContent = {
3161
+ agents: [createTestAgentConfig()],
3162
+ judge: {
3163
+ id: 'test-judge',
3164
+ name: 'Test Judge',
3165
+ role: 'generalist',
3166
+ model: 'gpt-4',
3167
+ provider: 'openai',
3168
+ temperature: 0.3,
3169
+ summaryPromptPath: 'nonexistent.txt',
3170
+ },
3171
+ debate: createTestDebateConfig(),
3172
+ };
3173
+ fs_1.default.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
3174
+ await (0, index_1.runCli)(['debate', 'Design a system', '--config', configPath, '--rounds', '1']);
3175
+ expect(stdoutSpy).toHaveBeenCalled();
3176
+ });
1482
3177
  });
1483
3178
  });
1484
3179
  });