edsger 0.42.1 → 0.44.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.
Files changed (98) hide show
  1. package/.claude/settings.local.json +23 -3
  2. package/.env.local +12 -0
  3. package/dist/api/release-test-cases.d.ts +7 -0
  4. package/dist/api/release-test-cases.js +21 -0
  5. package/dist/api/releases.d.ts +41 -0
  6. package/dist/api/releases.js +31 -0
  7. package/dist/api/web-deploy.d.ts +8 -1
  8. package/dist/api/web-deploy.js +2 -1
  9. package/dist/commands/release-sync/index.d.ts +5 -0
  10. package/dist/commands/release-sync/index.js +38 -0
  11. package/dist/commands/smoke-test/index.d.ts +5 -0
  12. package/dist/commands/smoke-test/index.js +40 -0
  13. package/dist/commands/workflow/phase-orchestrator.js +3 -1
  14. package/dist/index.js +40 -0
  15. package/dist/phases/app-store-generation/__tests__/screenshot-composer.test.js +1 -0
  16. package/dist/phases/app-store-generation/index.js +3 -1
  17. package/dist/phases/app-store-generation/screenshot-composer.js +34 -10
  18. package/dist/phases/branch-planning/index.js +3 -1
  19. package/dist/phases/bug-fixing/analyzer.js +3 -1
  20. package/dist/phases/code-implementation/index.js +3 -1
  21. package/dist/phases/code-refine/index.js +3 -1
  22. package/dist/phases/code-review/__tests__/diff-utils.test.js +11 -11
  23. package/dist/phases/code-review/index.js +3 -1
  24. package/dist/phases/code-testing/analyzer.js +3 -1
  25. package/dist/phases/feature-analysis/index.js +3 -1
  26. package/dist/phases/functional-testing/analyzer.js +3 -1
  27. package/dist/phases/growth-analysis/index.js +3 -1
  28. package/dist/phases/intelligence-analysis/__tests__/orchestration.test.js +12 -12
  29. package/dist/phases/intelligence-analysis/agent.js +2 -0
  30. package/dist/phases/intelligence-analysis/index.js +1 -0
  31. package/dist/phases/intelligence-analysis/prompts.js +11 -1
  32. package/dist/phases/output-contracts.js +1 -0
  33. package/dist/phases/pr-execution/__tests__/file-assigner.test.js +22 -13
  34. package/dist/phases/pr-execution/context.js +4 -2
  35. package/dist/phases/pr-execution/file-assigner.js +1 -0
  36. package/dist/phases/pr-resolve/__tests__/checklist-learner.test.js +11 -11
  37. package/dist/phases/pr-resolve/__tests__/prompts.test.js +12 -12
  38. package/dist/phases/pr-resolve/__tests__/resolve-mapping.test.js +6 -6
  39. package/dist/phases/pr-resolve/__tests__/types.test.js +11 -11
  40. package/dist/phases/pr-resolve/__tests__/workspace.test.js +13 -13
  41. package/dist/phases/pr-resolve/checklist-learner.js +34 -9
  42. package/dist/phases/pr-resolve/index.js +29 -13
  43. package/dist/phases/pr-resolve/prompts.js +2 -1
  44. package/dist/phases/pr-resolve/workspace.d.ts +12 -2
  45. package/dist/phases/pr-resolve/workspace.js +6 -4
  46. package/dist/phases/pr-review/__tests__/prompts.test.js +9 -9
  47. package/dist/phases/pr-review/__tests__/review-comments.test.js +6 -6
  48. package/dist/phases/pr-review/index.js +1 -0
  49. package/dist/phases/pr-shared/__tests__/agent-utils.test.js +17 -17
  50. package/dist/phases/pr-shared/__tests__/context.test.js +12 -12
  51. package/dist/phases/pr-splitting/import-dep-validator.js +14 -6
  52. package/dist/phases/pr-splitting/index.js +3 -1
  53. package/dist/phases/release-sync/__tests__/github.test.d.ts +9 -0
  54. package/dist/phases/release-sync/__tests__/github.test.js +123 -0
  55. package/dist/phases/release-sync/__tests__/snapshot.test.d.ts +8 -0
  56. package/dist/phases/release-sync/__tests__/snapshot.test.js +93 -0
  57. package/dist/phases/release-sync/github.d.ts +54 -0
  58. package/dist/phases/release-sync/github.js +101 -0
  59. package/dist/phases/release-sync/index.d.ts +24 -0
  60. package/dist/phases/release-sync/index.js +147 -0
  61. package/dist/phases/release-sync/snapshot.d.ts +27 -0
  62. package/dist/phases/release-sync/snapshot.js +159 -0
  63. package/dist/phases/smoke-test/__tests__/agent.test.d.ts +4 -0
  64. package/dist/phases/smoke-test/__tests__/agent.test.js +85 -0
  65. package/dist/phases/smoke-test/agent.d.ts +12 -0
  66. package/dist/phases/smoke-test/agent.js +94 -0
  67. package/dist/phases/smoke-test/index.d.ts +22 -0
  68. package/dist/phases/smoke-test/index.js +233 -0
  69. package/dist/phases/smoke-test/prompts.d.ts +15 -0
  70. package/dist/phases/smoke-test/prompts.js +35 -0
  71. package/dist/phases/technical-design/index.js +3 -1
  72. package/dist/phases/test-cases-analysis/index.js +3 -1
  73. package/dist/phases/user-stories-analysis/index.js +3 -1
  74. package/dist/services/phase-hooks/__tests__/hook-executor.test.js +7 -4
  75. package/dist/services/phase-hooks/__tests__/hook-runner.test.js +22 -21
  76. package/dist/services/phase-hooks/hook-executor.js +1 -0
  77. package/dist/services/phase-hooks/plugin-loader.js +3 -0
  78. package/dist/services/video/screenshot-generator.js +8 -2
  79. package/dist/skills/phase/smoke-test/SKILL.md +80 -0
  80. package/dist/utils/json-extract.d.ts +6 -0
  81. package/dist/utils/json-extract.js +44 -0
  82. package/dist/workspace/__tests__/workspace-manager.test.d.ts +7 -0
  83. package/dist/workspace/__tests__/workspace-manager.test.js +52 -0
  84. package/dist/workspace/workspace-manager.d.ts +31 -0
  85. package/dist/workspace/workspace-manager.js +96 -10
  86. package/package.json +1 -1
  87. package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.d.ts +0 -4
  88. package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.js +0 -133
  89. package/dist/services/lifecycle-agent/__tests__/transition-rules.test.d.ts +0 -4
  90. package/dist/services/lifecycle-agent/__tests__/transition-rules.test.js +0 -336
  91. package/dist/services/lifecycle-agent/index.d.ts +0 -24
  92. package/dist/services/lifecycle-agent/index.js +0 -25
  93. package/dist/services/lifecycle-agent/phase-criteria.d.ts +0 -57
  94. package/dist/services/lifecycle-agent/phase-criteria.js +0 -335
  95. package/dist/services/lifecycle-agent/transition-rules.d.ts +0 -60
  96. package/dist/services/lifecycle-agent/transition-rules.js +0 -184
  97. package/dist/services/lifecycle-agent/types.d.ts +0 -190
  98. package/dist/services/lifecycle-agent/types.js +0 -12
@@ -110,26 +110,26 @@ function makeDeps(aiResult, contextOverride) {
110
110
  saveSnapCalls,
111
111
  saveRptCalls,
112
112
  updateRptCalls,
113
- prepareContext: async () => ({
113
+ prepareContext: () => Promise.resolve({
114
114
  context: contextOverride || makeContext(),
115
115
  analysisPrompt: 'fake prompt',
116
116
  }),
117
- executeQuery: async () => aiResult,
118
- batchCreate: async (_productId, competitors) => {
117
+ executeQuery: () => Promise.resolve(aiResult),
118
+ batchCreate: (_productId, competitors) => {
119
119
  batchCreateCalls.push([_productId, competitors]);
120
- return competitors.map((c) => makeCompetitor(c.name, `discovered-${batchCreateCounter++}`, 'suggested'));
120
+ return Promise.resolve(competitors.map((c) => makeCompetitor(c.name, `discovered-${batchCreateCounter++}`, 'suggested')));
121
121
  },
122
- saveSnap: async (snap) => {
122
+ saveSnap: (snap) => {
123
123
  saveSnapCalls.push([snap]);
124
- return makeSnapshot(snap.competitor_id, `snap-${saveSnapCalls.length}`);
124
+ return Promise.resolve(makeSnapshot(snap.competitor_id, `snap-${saveSnapCalls.length}`));
125
125
  },
126
- saveRpt: async (report) => {
126
+ saveRpt: (report) => {
127
127
  saveRptCalls.push([report]);
128
- return makeReport(`rpt-${saveRptCalls.length}`);
128
+ return Promise.resolve(makeReport(`rpt-${saveRptCalls.length}`));
129
129
  },
130
- updateRpt: async (_id, updates) => {
130
+ updateRpt: (_id, updates) => {
131
131
  updateRptCalls.push([_id, updates]);
132
- return makeReport(_id);
132
+ return Promise.resolve(makeReport(_id));
133
133
  },
134
134
  };
135
135
  }
@@ -368,8 +368,8 @@ void describe('analyseIntelligence — guidance', () => {
368
368
  void describe('analyseIntelligence — exception in deps', () => {
369
369
  void it('should return error when context fetch throws', async () => {
370
370
  const deps = makeDeps(null);
371
- deps.prepareContext = async () => {
372
- throw new Error('Network error');
371
+ deps.prepareContext = () => {
372
+ return Promise.reject(new Error('Network error'));
373
373
  };
374
374
  const result = await analyseIntelligence({ productId: 'prod-001' }, fakeConfig, deps);
375
375
  assert.strictEqual(result.status, 'error');
@@ -261,12 +261,14 @@ function parseIntelligenceResult(responseText) {
261
261
  error: 'JSON parsing failed: could not extract structured data from response',
262
262
  };
263
263
  }
264
+ // eslint-disable-next-line @typescript-eslint/require-await
264
265
  async function* prompt(analysisPrompt) {
265
266
  yield {
266
267
  type: 'user',
267
268
  message: { role: 'user', content: analysisPrompt },
268
269
  };
269
270
  }
271
+ // eslint-disable-next-line complexity
270
272
  export async function executeIntelligenceQuery(currentPrompt, systemPrompt, _config, verbose, cwd) {
271
273
  let lastAssistantResponse = '';
272
274
  let structuredResult = null;
@@ -74,6 +74,7 @@ const defaultDeps = {
74
74
  * 2. AI agent researches competitors and market via web search
75
75
  * 3. Save discovered competitors, snapshots, and report
76
76
  */
77
+ // eslint-disable-next-line complexity
77
78
  export async function analyseIntelligence(options, config, deps = defaultDeps) {
78
79
  const { productId, reportType = 'competitive', verbose, guidance, reportId, } = options;
79
80
  try {
@@ -29,9 +29,19 @@ export const createIntelligencePromptWithContext = (productId, contextInfo, repo
29
29
  **Competitor Discovery**: While analyzing confirmed competitors, also look for new entrants or products that should be tracked. Include any new finds in the "discovered_competitors" array.`;
30
30
  }
31
31
  else {
32
+ let focusLabel;
33
+ if (reportType === 'research') {
34
+ focusLabel = 'papers and reports';
35
+ }
36
+ else if (reportType === 'market') {
37
+ focusLabel = 'market signals and trends';
38
+ }
39
+ else {
40
+ focusLabel = 'product-market fit metrics';
41
+ }
32
42
  discoveryInstructions = `
33
43
 
34
- **Note**: This is a ${reportType} analysis — focus on ${reportType === 'research' ? 'papers and reports' : reportType === 'market' ? 'market signals and trends' : 'product-market fit metrics'}. Do NOT discover new competitors. Set "discovered_competitors" to an empty array.`;
44
+ **Note**: This is a ${reportType} analysis — focus on ${focusLabel}. Do NOT discover new competitors. Set "discovered_competitors" to an empty array.`;
35
45
  }
36
46
  const researchInstructions = reportType === 'research'
37
47
  ? `
@@ -1,3 +1,4 @@
1
+ /* eslint-disable max-lines */
1
2
  /**
2
3
  * Output format contracts for each phase.
3
4
  * These define the JSON structure that LLM responses must follow.
@@ -20,8 +20,8 @@ function makePR(overrides) {
20
20
  ...overrides,
21
21
  };
22
22
  }
23
- function cf(path, change_type = 'modified') {
24
- return { path, change_type };
23
+ function cf(path, changeType = 'modified') {
24
+ return { path, change_type: changeType };
25
25
  }
26
26
  // ============================================================
27
27
  // findUnassignedFiles
@@ -133,8 +133,9 @@ void describe('applyAssignments', () => {
133
133
  ];
134
134
  const unassigned = [cf('b.ts', 'added')];
135
135
  const updates = [];
136
- const mockUpdater = async (prId, u) => {
136
+ const mockUpdater = (prId, u) => {
137
137
  updates.push({ prId, files: u.files });
138
+ return Promise.resolve();
138
139
  };
139
140
  const count = await applyAssignments([{ file: 'b.ts', pr_id: 'pr-1' }], unassigned, prs, mockUpdater);
140
141
  assert.strictEqual(count, 1);
@@ -155,8 +156,9 @@ void describe('applyAssignments', () => {
155
156
  ];
156
157
  const unassigned = [cf('a.ts')];
157
158
  const updates = [];
158
- const mockUpdater = async (_prId, u) => {
159
+ const mockUpdater = (_prId, u) => {
159
160
  updates.push({ files: u.files });
161
+ return Promise.resolve();
160
162
  };
161
163
  await applyAssignments([{ file: 'a.ts', pr_id: 'pr-1' }], unassigned, prs, mockUpdater);
162
164
  // File list should not have duplicates
@@ -168,8 +170,9 @@ void describe('applyAssignments', () => {
168
170
  const prs = [makePR({ id: 'pr-1', sequence: 1, files: [] })];
169
171
  const unassigned = [cf('x.ts', 'added')];
170
172
  let called = false;
171
- const mockUpdater = async () => {
173
+ const mockUpdater = () => {
172
174
  called = true;
175
+ return Promise.resolve();
173
176
  };
174
177
  const count = await applyAssignments([{ file: 'x.ts', pr_id: 'unknown-id' }], unassigned, prs, mockUpdater);
175
178
  assert.strictEqual(count, 0);
@@ -179,8 +182,9 @@ void describe('applyAssignments', () => {
179
182
  const prs = [makePR({ id: 'pr-1', sequence: 1, files: [] })];
180
183
  const unassigned = [cf('new.ts', 'added')];
181
184
  const updates = [];
182
- const mockUpdater = async (_prId, u) => {
185
+ const mockUpdater = (_prId, u) => {
183
186
  updates.push({ files: u.files });
187
+ return Promise.resolve();
184
188
  };
185
189
  await applyAssignments([{ file: 'new.ts', pr_id: 'pr-1' }], unassigned, prs, mockUpdater);
186
190
  assert.strictEqual(updates[0].files[0].change_type, 'added');
@@ -195,8 +199,9 @@ void describe('applyAssignments', () => {
195
199
  cf('b.ts', 'modified'),
196
200
  ];
197
201
  const updates = [];
198
- const mockUpdater = async (prId) => {
202
+ const mockUpdater = (prId) => {
199
203
  updates.push({ prId });
204
+ return Promise.resolve();
200
205
  };
201
206
  const count = await applyAssignments([
202
207
  { file: 'a.ts', pr_id: 'pr-1' },
@@ -208,8 +213,8 @@ void describe('applyAssignments', () => {
208
213
  void it('handles updater failure gracefully', async () => {
209
214
  const prs = [makePR({ id: 'pr-1', sequence: 1, files: [] })];
210
215
  const unassigned = [cf('a.ts')];
211
- const mockUpdater = async () => {
212
- throw new Error('DB error');
216
+ const mockUpdater = () => {
217
+ return Promise.reject(new Error('DB error'));
213
218
  };
214
219
  const count = await applyAssignments([{ file: 'a.ts', pr_id: 'pr-1' }], unassigned, prs, mockUpdater);
215
220
  assert.strictEqual(count, 0);
@@ -235,8 +240,9 @@ void describe('removeDeletedFilesFromPRs', () => {
235
240
  cf('gone.ts', 'deleted'),
236
241
  ];
237
242
  const updates = [];
238
- const mockUpdater = async (prId, u) => {
243
+ const mockUpdater = (prId, u) => {
239
244
  updates.push({ prId, files: u.files });
245
+ return Promise.resolve();
240
246
  };
241
247
  const count = await removeDeletedFilesFromPRs(changedFiles, prs, mockUpdater);
242
248
  assert.strictEqual(count, 1);
@@ -253,8 +259,9 @@ void describe('removeDeletedFilesFromPRs', () => {
253
259
  }),
254
260
  ];
255
261
  let called = false;
256
- const mockUpdater = async () => {
262
+ const mockUpdater = () => {
257
263
  called = true;
264
+ return Promise.resolve();
258
265
  };
259
266
  const count = await removeDeletedFilesFromPRs([cf('a.ts')], prs, mockUpdater);
260
267
  assert.strictEqual(count, 0);
@@ -274,8 +281,9 @@ void describe('removeDeletedFilesFromPRs', () => {
274
281
  }),
275
282
  ];
276
283
  const updates = [];
277
- const mockUpdater = async (prId) => {
284
+ const mockUpdater = (prId) => {
278
285
  updates.push(prId);
286
+ return Promise.resolve();
279
287
  };
280
288
  await removeDeletedFilesFromPRs([cf('b.ts', 'deleted')], prs, mockUpdater);
281
289
  // Only pr-2 should be updated
@@ -284,8 +292,9 @@ void describe('removeDeletedFilesFromPRs', () => {
284
292
  void it('handles PRs with null files', async () => {
285
293
  const prs = [makePR({ id: 'pr-1', sequence: 1, files: null })];
286
294
  let called = false;
287
- const mockUpdater = async () => {
295
+ const mockUpdater = () => {
288
296
  called = true;
297
+ return Promise.resolve();
289
298
  };
290
299
  const count = await removeDeletedFilesFromPRs([cf('a.ts', 'deleted')], prs, mockUpdater);
291
300
  assert.strictEqual(count, 0);
@@ -193,7 +193,9 @@ export async function fetchPRExecutionContext(featureId, verbose) {
193
193
  // If the branch was rebased, lastSyncedCommit is no longer in the history.
194
194
  // Treat this as a full re-execution so downstream logic uses the correct
195
195
  // prompt and skips incremental file-assignment against an invalid base.
196
- if (isIncrementalSync && !isAncestor(lastSyncedCommit, devRef)) {
196
+ if (isIncrementalSync &&
197
+ lastSyncedCommit &&
198
+ !isAncestor(lastSyncedCommit, devRef)) {
197
199
  if (verbose) {
198
200
  logInfo(`⚠️ Last synced commit ${lastSyncedCommit} is no longer in branch history (likely rebased). Treating as full re-execution.`);
199
201
  }
@@ -201,7 +203,7 @@ export async function fetchPRExecutionContext(featureId, verbose) {
201
203
  isIncrementalSync = false;
202
204
  }
203
205
  // Get diff info: for incremental, diff from last sync; for first run, diff from main
204
- const diffBase = isIncrementalSync ? lastSyncedCommit : 'main';
206
+ const diffBase = isIncrementalSync && lastSyncedCommit ? lastSyncedCommit : 'main';
205
207
  const diffStat = getDiffStat(diffBase, devRef);
206
208
  const changedFiles = getChangedFiles(diffBase, devRef);
207
209
  if (verbose) {
@@ -97,6 +97,7 @@ async function callLLMForAssignment(prompt, verbose) {
97
97
  })) {
98
98
  if (message.type === 'assistant' && message.message?.content) {
99
99
  for (const item of message.message.content) {
100
+ // eslint-disable-next-line max-depth
100
101
  if (item.type === 'text') {
101
102
  responseText += `${item.text}\n`;
102
103
  }
@@ -26,8 +26,8 @@ function makeMap(entries) {
26
26
  return new Map(entries);
27
27
  }
28
28
  // ── buildLearnerPrompt ──────────────────────────────────────
29
- describe('buildLearnerPrompt', () => {
30
- it('includes addressed comment count in header', () => {
29
+ void describe('buildLearnerPrompt', () => {
30
+ void it('includes addressed comment count in header', () => {
31
31
  const comments = [
32
32
  { comment_id: 'comment_1', action: 'changed', reply: 'Fixed' },
33
33
  { comment_id: 'comment_2', action: 'changed', reply: 'Done' },
@@ -40,7 +40,7 @@ describe('buildLearnerPrompt', () => {
40
40
  const prompt = buildLearnerPrompt(comments, threads, map);
41
41
  assert.ok(prompt.includes('2 review comment(s)'));
42
42
  });
43
- it('includes reviewer, file path, line, and comment body', () => {
43
+ void it('includes reviewer, file path, line, and comment body', () => {
44
44
  const comments = [
45
45
  { comment_id: 'comment_1', action: 'changed', reply: 'Added null check' },
46
46
  ];
@@ -60,7 +60,7 @@ describe('buildLearnerPrompt', () => {
60
60
  assert.ok(prompt.includes('Missing null check here'));
61
61
  assert.ok(prompt.includes('Added null check'));
62
62
  });
63
- it('includes resolution text for each comment', () => {
63
+ void it('includes resolution text for each comment', () => {
64
64
  const comments = [
65
65
  {
66
66
  comment_id: 'comment_1',
@@ -73,7 +73,7 @@ describe('buildLearnerPrompt', () => {
73
73
  const prompt = buildLearnerPrompt(comments, threads, map);
74
74
  assert.ok(prompt.includes('**Resolution**: Refactored to use try/catch'));
75
75
  });
76
- it('includes summary when provided', () => {
76
+ void it('includes summary when provided', () => {
77
77
  const comments = [
78
78
  { comment_id: 'comment_1', action: 'changed', reply: 'Fixed' },
79
79
  ];
@@ -83,7 +83,7 @@ describe('buildLearnerPrompt', () => {
83
83
  assert.ok(prompt.includes('## Overall Summary'));
84
84
  assert.ok(prompt.includes('Improved error handling across 3 files'));
85
85
  });
86
- it('omits summary section when not provided', () => {
86
+ void it('omits summary section when not provided', () => {
87
87
  const comments = [
88
88
  { comment_id: 'comment_1', action: 'changed', reply: 'Fixed' },
89
89
  ];
@@ -92,7 +92,7 @@ describe('buildLearnerPrompt', () => {
92
92
  const prompt = buildLearnerPrompt(comments, threads, map);
93
93
  assert.ok(!prompt.includes('## Overall Summary'));
94
94
  });
95
- it('handles comment with no matching thread gracefully', () => {
95
+ void it('handles comment with no matching thread gracefully', () => {
96
96
  const comments = [
97
97
  { comment_id: 'comment_1', action: 'changed', reply: 'Fixed' },
98
98
  ];
@@ -105,7 +105,7 @@ describe('buildLearnerPrompt', () => {
105
105
  // Should NOT include reviewer info since thread was not found
106
106
  assert.ok(!prompt.includes('**Reviewer**'));
107
107
  });
108
- it('handles comment_id not in map gracefully', () => {
108
+ void it('handles comment_id not in map gracefully', () => {
109
109
  const comments = [
110
110
  { comment_id: 'comment_99', action: 'changed', reply: 'Fixed' },
111
111
  ];
@@ -113,7 +113,7 @@ describe('buildLearnerPrompt', () => {
113
113
  assert.ok(prompt.includes('## comment_99'));
114
114
  assert.ok(prompt.includes('**Resolution**: Fixed'));
115
115
  });
116
- it('omits line when null', () => {
116
+ void it('omits line when null', () => {
117
117
  const comments = [
118
118
  { comment_id: 'comment_1', action: 'changed', reply: 'Fixed' },
119
119
  ];
@@ -123,7 +123,7 @@ describe('buildLearnerPrompt', () => {
123
123
  assert.ok(prompt.includes('**File**: src/index.ts'));
124
124
  assert.ok(!prompt.includes('**Line**'));
125
125
  });
126
- it('uses threadById map correctly for multiple comments', () => {
126
+ void it('uses threadById map correctly for multiple comments', () => {
127
127
  const comments = [
128
128
  { comment_id: 'comment_1', action: 'changed', reply: 'Fix A' },
129
129
  { comment_id: 'comment_3', action: 'changed', reply: 'Fix C' },
@@ -146,7 +146,7 @@ describe('buildLearnerPrompt', () => {
146
146
  // comment_2 was not in addressedComments, should not appear
147
147
  assert.ok(!prompt.includes('Issue B'));
148
148
  });
149
- it('includes instructions section', () => {
149
+ void it('includes instructions section', () => {
150
150
  const comments = [
151
151
  { comment_id: 'comment_1', action: 'changed', reply: 'Fixed' },
152
152
  ];
@@ -34,26 +34,26 @@ function makeThread(id, overrides) {
34
34
  },
35
35
  };
36
36
  }
37
- describe('createResolveSystemPrompt', () => {
38
- it('includes decision criteria', () => {
37
+ void describe('createResolveSystemPrompt', () => {
38
+ void it('includes decision criteria', () => {
39
39
  const prompt = createResolveSystemPrompt();
40
40
  assert.ok(prompt.includes('Make the change when'));
41
41
  assert.ok(prompt.includes('Skip the change when'));
42
42
  });
43
- it('specifies comment_id format', () => {
43
+ void it('specifies comment_id format', () => {
44
44
  const prompt = createResolveSystemPrompt();
45
45
  assert.ok(prompt.includes('comment_id'));
46
46
  assert.ok(prompt.includes('comment_1'));
47
47
  });
48
- it('includes result format', () => {
48
+ void it('includes result format', () => {
49
49
  const prompt = createResolveSystemPrompt();
50
50
  assert.ok(prompt.includes('resolve_result'));
51
51
  assert.ok(prompt.includes('"action"'));
52
52
  assert.ok(prompt.includes('"reply"'));
53
53
  });
54
54
  });
55
- describe('createResolveUserPrompt', () => {
56
- it('uses sequential comment IDs not thread IDs', () => {
55
+ void describe('createResolveUserPrompt', () => {
56
+ void it('uses sequential comment IDs not thread IDs', () => {
57
57
  const threads = [makeThread('PRRT_kwDOxx_1'), makeThread('PRRT_kwDOxx_2')];
58
58
  const { prompt, commentIdToThreadId } = createResolveUserPrompt(threads);
59
59
  // Should use comment_1, comment_2 in the prompt
@@ -67,20 +67,20 @@ describe('createResolveUserPrompt', () => {
67
67
  assert.strictEqual(commentIdToThreadId.get('comment_2'), 'PRRT_kwDOxx_2');
68
68
  assert.strictEqual(commentIdToThreadId.size, 2);
69
69
  });
70
- it('includes file path and line number', () => {
70
+ void it('includes file path and line number', () => {
71
71
  const threads = [makeThread('t1', { path: 'src/auth.ts', line: 42 })];
72
72
  const { prompt } = createResolveUserPrompt(threads);
73
73
  assert.ok(prompt.includes('src/auth.ts'));
74
74
  assert.ok(prompt.includes('42'));
75
75
  });
76
- it('includes comment body', () => {
76
+ void it('includes comment body', () => {
77
77
  const threads = [
78
78
  makeThread('t1', { body: 'This should use a const instead of let' }),
79
79
  ];
80
80
  const { prompt } = createResolveUserPrompt(threads);
81
81
  assert.ok(prompt.includes('This should use a const instead of let'));
82
82
  });
83
- it('includes follow-up comments', () => {
83
+ void it('includes follow-up comments', () => {
84
84
  const threads = [
85
85
  makeThread('t1', {
86
86
  body: 'Main comment',
@@ -92,12 +92,12 @@ describe('createResolveUserPrompt', () => {
92
92
  assert.ok(prompt.includes('I disagree because...'));
93
93
  assert.ok(prompt.includes('@dev'));
94
94
  });
95
- it('includes instruction to use exact comment IDs', () => {
95
+ void it('includes instruction to use exact comment IDs', () => {
96
96
  const threads = [makeThread('t1'), makeThread('t2'), makeThread('t3')];
97
97
  const { prompt } = createResolveUserPrompt(threads);
98
98
  assert.ok(prompt.includes('comment_1, comment_2, comment_3'));
99
99
  });
100
- it('handles threads with no comments gracefully', () => {
100
+ void it('handles threads with no comments gracefully', () => {
101
101
  const emptyThread = {
102
102
  id: 'empty',
103
103
  isResolved: false,
@@ -108,7 +108,7 @@ describe('createResolveUserPrompt', () => {
108
108
  // Empty thread should be skipped (no comment nodes to index)
109
109
  assert.strictEqual(commentIdToThreadId.size, 0);
110
110
  });
111
- it('returns correct count in header', () => {
111
+ void it('returns correct count in header', () => {
112
112
  const threads = [makeThread('t1'), makeThread('t2')];
113
113
  const { prompt } = createResolveUserPrompt(threads);
114
114
  assert.ok(prompt.includes('2 unresolved review comment(s)'));
@@ -45,8 +45,8 @@ function makeThread(id, body) {
45
45
  },
46
46
  };
47
47
  }
48
- describe('resolve comment→thread mapping (integration)', () => {
49
- it('correctly maps comment_ids to thread IDs', () => {
48
+ void describe('resolve comment→thread mapping (integration)', () => {
49
+ void it('correctly maps comment_ids to thread IDs', () => {
50
50
  const threads = [
51
51
  makeThread('PRRT_aaa', 'Use const'),
52
52
  makeThread('PRRT_bbb', 'Add error handling'),
@@ -75,7 +75,7 @@ describe('resolve comment→thread mapping (integration)', () => {
75
75
  assert.strictEqual(result.addressed[1].threadId, 'PRRT_bbb');
76
76
  assert.strictEqual(result.skipped[0].threadId, 'PRRT_ccc');
77
77
  });
78
- it('reports errors for unknown comment_ids', () => {
78
+ void it('reports errors for unknown comment_ids', () => {
79
79
  const threads = [makeThread('PRRT_aaa', 'Fix bug')];
80
80
  const { commentIdToThreadId } = createResolveUserPrompt(threads);
81
81
  const agentResult = [
@@ -87,7 +87,7 @@ describe('resolve comment→thread mapping (integration)', () => {
87
87
  assert.strictEqual(result.errors.length, 1);
88
88
  assert.ok(result.errors[0].includes('comment_99'));
89
89
  });
90
- it('handles agent returning partial results', () => {
90
+ void it('handles agent returning partial results', () => {
91
91
  const threads = [
92
92
  makeThread('PRRT_aaa', 'Fix A'),
93
93
  makeThread('PRRT_bbb', 'Fix B'),
@@ -105,7 +105,7 @@ describe('resolve comment→thread mapping (integration)', () => {
105
105
  assert.strictEqual(result.errors.length, 0);
106
106
  // comment_2 was not mentioned - no error, just not processed
107
107
  });
108
- it('handles empty agent result', () => {
108
+ void it('handles empty agent result', () => {
109
109
  const threads = [makeThread('PRRT_aaa', 'Fix')];
110
110
  const { commentIdToThreadId } = createResolveUserPrompt(threads);
111
111
  const result = processResolveResult([], commentIdToThreadId);
@@ -113,7 +113,7 @@ describe('resolve comment→thread mapping (integration)', () => {
113
113
  assert.strictEqual(result.skipped.length, 0);
114
114
  assert.strictEqual(result.errors.length, 0);
115
115
  });
116
- it('preserves reply text in all cases', () => {
116
+ void it('preserves reply text in all cases', () => {
117
117
  const threads = [
118
118
  makeThread('PRRT_aaa', 'Fix this'),
119
119
  makeThread('PRRT_bbb', 'Change that'),
@@ -1,43 +1,43 @@
1
1
  import assert from 'node:assert';
2
2
  import { describe, it } from 'node:test';
3
3
  import { isResolveResult } from '../types.js';
4
- describe('isResolveResult', () => {
5
- it('returns true for valid ResolveResult', () => {
4
+ void describe('isResolveResult', () => {
5
+ void it('returns true for valid ResolveResult', () => {
6
6
  assert.ok(isResolveResult({
7
7
  comments: [
8
8
  { comment_id: 'comment_1', action: 'changed', reply: 'Fixed' },
9
9
  ],
10
10
  }));
11
11
  });
12
- it('returns true when optional fields are present', () => {
12
+ void it('returns true when optional fields are present', () => {
13
13
  assert.ok(isResolveResult({
14
14
  comments: [],
15
15
  files_modified: ['a.ts'],
16
16
  summary: 'Done',
17
17
  }));
18
18
  });
19
- it('returns true for empty comments array', () => {
19
+ void it('returns true for empty comments array', () => {
20
20
  assert.ok(isResolveResult({ comments: [] }));
21
21
  });
22
- it('returns false for null', () => {
22
+ void it('returns false for null', () => {
23
23
  assert.ok(!isResolveResult(null));
24
24
  });
25
- it('returns false for undefined', () => {
25
+ void it('returns false for undefined', () => {
26
26
  assert.ok(!isResolveResult(undefined));
27
27
  });
28
- it('returns false for string', () => {
28
+ void it('returns false for string', () => {
29
29
  assert.ok(!isResolveResult('not an object'));
30
30
  });
31
- it('returns false for number', () => {
31
+ void it('returns false for number', () => {
32
32
  assert.ok(!isResolveResult(42));
33
33
  });
34
- it('returns false when comments is missing', () => {
34
+ void it('returns false when comments is missing', () => {
35
35
  assert.ok(!isResolveResult({ files_modified: ['a.ts'] }));
36
36
  });
37
- it('returns false when comments is not an array', () => {
37
+ void it('returns false when comments is not an array', () => {
38
38
  assert.ok(!isResolveResult({ comments: 'not-array' }));
39
39
  });
40
- it('returns false for empty object', () => {
40
+ void it('returns false for empty object', () => {
41
41
  assert.ok(!isResolveResult({}));
42
42
  });
43
43
  });
@@ -16,8 +16,8 @@ function createTempRepo() {
16
16
  execSync('git commit -m "init"', { cwd: dir, stdio: 'pipe' });
17
17
  return dir;
18
18
  }
19
- describe('buildCredentialArgs', () => {
20
- it('returns 4 args with credential helper config', () => {
19
+ void describe('buildCredentialArgs', () => {
20
+ void it('returns 4 args with credential helper config', () => {
21
21
  const args = buildCredentialArgs('my-token');
22
22
  assert.strictEqual(args.length, 4);
23
23
  assert.strictEqual(args[0], '-c');
@@ -26,12 +26,12 @@ describe('buildCredentialArgs', () => {
26
26
  assert.ok(args[3].includes('my-token'));
27
27
  assert.ok(args[3].includes('x-access-token'));
28
28
  });
29
- it('escapes token in credential helper', () => {
29
+ void it('escapes token in credential helper', () => {
30
30
  const args = buildCredentialArgs('token-with-special-chars!@#');
31
31
  assert.ok(args[3].includes('token-with-special-chars!@#'));
32
32
  });
33
33
  });
34
- describe('hasUncommittedChanges', () => {
34
+ void describe('hasUncommittedChanges', () => {
35
35
  let repoPath;
36
36
  beforeEach(() => {
37
37
  repoPath = createTempRepo();
@@ -39,18 +39,18 @@ describe('hasUncommittedChanges', () => {
39
39
  afterEach(() => {
40
40
  rmSync(repoPath, { recursive: true, force: true });
41
41
  });
42
- it('returns false for clean repo', () => {
42
+ void it('returns false for clean repo', () => {
43
43
  assert.strictEqual(hasUncommittedChanges(repoPath), false);
44
44
  });
45
- it('returns true after modifying a file', () => {
45
+ void it('returns true after modifying a file', () => {
46
46
  writeFileSync(join(repoPath, 'README.md'), '# Modified');
47
47
  assert.strictEqual(hasUncommittedChanges(repoPath), true);
48
48
  });
49
- it('returns true for new untracked file', () => {
49
+ void it('returns true for new untracked file', () => {
50
50
  writeFileSync(join(repoPath, 'new-file.txt'), 'content');
51
51
  assert.strictEqual(hasUncommittedChanges(repoPath), true);
52
52
  });
53
- it('returns false after staging and committing', () => {
53
+ void it('returns false after staging and committing', () => {
54
54
  writeFileSync(join(repoPath, 'new.txt'), 'x');
55
55
  execSync('git add . && git commit -m "add"', {
56
56
  cwd: repoPath,
@@ -58,11 +58,11 @@ describe('hasUncommittedChanges', () => {
58
58
  });
59
59
  assert.strictEqual(hasUncommittedChanges(repoPath), false);
60
60
  });
61
- it('returns false for non-existent path', () => {
61
+ void it('returns false for non-existent path', () => {
62
62
  assert.strictEqual(hasUncommittedChanges('/tmp/nonexistent-repo-xyz'), false);
63
63
  });
64
64
  });
65
- describe('hasNewCommits', () => {
65
+ void describe('hasNewCommits', () => {
66
66
  let repoPath;
67
67
  let bareRemote;
68
68
  beforeEach(() => {
@@ -89,10 +89,10 @@ describe('hasNewCommits', () => {
89
89
  rmSync(repoPath, { recursive: true, force: true });
90
90
  rmSync(bareRemote, { recursive: true, force: true });
91
91
  });
92
- it('returns false when HEAD matches remote', () => {
92
+ void it('returns false when HEAD matches remote', () => {
93
93
  assert.strictEqual(hasNewCommits(repoPath, 'main'), false);
94
94
  });
95
- it('returns true after a local commit not pushed', () => {
95
+ void it('returns true after a local commit not pushed', () => {
96
96
  writeFileSync(join(repoPath, 'new.txt'), 'hello');
97
97
  execSync('git add . && git commit -m "local"', {
98
98
  cwd: repoPath,
@@ -100,7 +100,7 @@ describe('hasNewCommits', () => {
100
100
  });
101
101
  assert.strictEqual(hasNewCommits(repoPath, 'main'), true);
102
102
  });
103
- it('returns false after pushing local commit', () => {
103
+ void it('returns false after pushing local commit', () => {
104
104
  writeFileSync(join(repoPath, 'new.txt'), 'hello');
105
105
  execSync('git add . && git commit -m "local" && git push origin main', {
106
106
  cwd: repoPath,