@doist/doistbot-cli 1.0.4 → 1.0.5

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 (62) hide show
  1. package/dist/actions/auth.js +23 -23
  2. package/dist/actions/doctor.js +3 -5
  3. package/dist/actions/review.js +5 -4
  4. package/dist/auth.js +17 -17
  5. package/dist/config.js +14 -9
  6. package/dist/env.js +1 -2
  7. package/dist/file-store.js +1 -1
  8. package/dist/terminal.js +0 -7
  9. package/package.json +5 -5
  10. package/sandbox/_node_modules/doistbot-repo-config/dist/index.d.ts +7 -21
  11. package/sandbox/_node_modules/doistbot-repo-config/dist/index.js +8 -72
  12. package/sandbox/dist/core/bot-logins.js +0 -13
  13. package/sandbox/dist/core/datadog-metrics.js +0 -2
  14. package/sandbox/dist/core/pi.js +16 -13
  15. package/sandbox/dist/providers/config.js +1 -1
  16. package/sandbox/dist/providers/credentials.js +30 -0
  17. package/sandbox/dist/providers/gemini.js +4 -10
  18. package/sandbox/dist/providers/helpers.js +6 -21
  19. package/sandbox/dist/providers/index.js +0 -3
  20. package/sandbox/dist/providers/openai.js +1 -8
  21. package/sandbox/dist/providers/pi-review.js +16 -54
  22. package/sandbox/dist/providers/schemas.js +0 -39
  23. package/sandbox/dist/tasks/chat/chat.js +2 -1
  24. package/sandbox/dist/tasks/issue-summarize/context.js +1 -1
  25. package/sandbox/dist/tasks/issue-summarize/model.js +3 -1
  26. package/sandbox/dist/tasks/issue-summarize/summarize.js +1 -1
  27. package/sandbox/dist/tasks/issue-triage/context.js +1 -2
  28. package/sandbox/dist/tasks/issue-triage/fix-dispatch.js +3 -3
  29. package/sandbox/dist/tasks/persist-logs/dd-proxy.js +2 -2
  30. package/sandbox/dist/tasks/persist-logs/llm-query-planner.js +1 -1
  31. package/sandbox/dist/tasks/persist-logs/persist-logs.js +1 -1
  32. package/sandbox/dist/tasks/review/conversation-context.js +13 -7
  33. package/sandbox/dist/tasks/review/engines/dedupe.js +18 -9
  34. package/sandbox/dist/tasks/review/engines/multi-focus.js +26 -31
  35. package/sandbox/dist/tasks/review/engines/shared.js +11 -59
  36. package/sandbox/dist/tasks/review/local.js +4 -29
  37. package/sandbox/dist/tasks/review/prompt.js +2 -49
  38. package/sandbox/dist/tasks/review/review-summary.js +82 -0
  39. package/sandbox/dist/tasks/review/review.js +23 -57
  40. package/sandbox/dist/tasks/review/runner.js +35 -51
  41. package/sandbox/dist/tasks/review/summary-model.js +11 -2
  42. package/sandbox/dist/tasks/review/thinking-level.js +0 -2
  43. package/sandbox/node_modules/doistbot-repo-config/dist/index.d.ts +7 -21
  44. package/sandbox/node_modules/doistbot-repo-config/dist/index.js +8 -72
  45. package/sandbox/_node_modules/doistbot-repo-config/dist/review-engine.d.ts +0 -11
  46. package/sandbox/_node_modules/doistbot-repo-config/dist/review-engine.js +0 -41
  47. package/sandbox/dist/providers/anthropic.js +0 -30
  48. package/sandbox/dist/tasks/review/author-format.js +0 -14
  49. package/sandbox/dist/tasks/review/author-profile.js +0 -67
  50. package/sandbox/dist/tasks/review/council/normalizer.js +0 -104
  51. package/sandbox/dist/tasks/review/council/orchestrator.js +0 -194
  52. package/sandbox/dist/tasks/review/council/provider-health.js +0 -31
  53. package/sandbox/dist/tasks/review/council/summary.js +0 -192
  54. package/sandbox/dist/tasks/review/council/types.js +0 -4
  55. package/sandbox/dist/tasks/review/council/voter.js +0 -384
  56. package/sandbox/dist/tasks/review/engines/council.js +0 -126
  57. package/sandbox/dist/tasks/review/engines/index.js +0 -12
  58. package/sandbox/dist/tasks/review/review-engine.js +0 -5
  59. package/sandbox/node_modules/doistbot-repo-config/dist/review-engine.d.ts +0 -11
  60. package/sandbox/node_modules/doistbot-repo-config/dist/review-engine.js +0 -41
  61. package/sandbox/review-prompt.md +0 -209
  62. package/sandbox/vote-prompt.md +0 -69
@@ -1,384 +0,0 @@
1
- /**
2
- * Voter module for achieving consensus on findings across multiple models.
3
- * Each model votes on findings from OTHER models (no self-voting).
4
- */
5
- import fs from 'fs';
6
- import path from 'path';
7
- import { fileURLToPath } from 'url';
8
- import { logger } from '../../../core/logging.js';
9
- import { WORKSPACE_DIR } from '../../../core/shared.js';
10
- import { getProvider } from '../../../providers/index.js';
11
- import { BATCH_VOTE_SCHEMA, isBatchVoteResponse } from '../../../providers/schemas.js';
12
- import { loadStandardsSection } from '../prompt.js';
13
- import { getReviewThinkingLevel } from '../thinking-level.js';
14
- import { DeliveryProviderHealthTracker } from './provider-health.js';
15
- const CONSENSUS_THRESHOLD = 0.67; // 2/3 approval required
16
- const REVIEW_TASK_DIR = path.dirname(fileURLToPath(import.meta.url));
17
- const VOTE_PROMPT_FILE_CANDIDATES = [
18
- path.resolve(REVIEW_TASK_DIR, '../../../vote-prompt.md'),
19
- path.resolve(REVIEW_TASK_DIR, '../../../../vote-prompt.md'),
20
- ];
21
- function resolveVotePromptFile() {
22
- const promptFile = VOTE_PROMPT_FILE_CANDIDATES.find((candidate) => fs.existsSync(candidate));
23
- if (!promptFile) {
24
- throw new Error(`Unable to find vote prompt template. Checked: ${VOTE_PROMPT_FILE_CANDIDATES.join(', ')}`);
25
- }
26
- return promptFile;
27
- }
28
- /**
29
- * Loads the voting prompt template.
30
- */
31
- function loadVotePromptTemplate() {
32
- const votePromptFile = resolveVotePromptFile();
33
- try {
34
- return fs.readFileSync(votePromptFile, 'utf8');
35
- }
36
- catch (error) {
37
- logger.error('Failed to load vote prompt template', {
38
- path: votePromptFile,
39
- error: error instanceof Error ? error.message : String(error),
40
- });
41
- throw error;
42
- }
43
- }
44
- /**
45
- * Builds the serialized block for a finding in a batched vote prompt.
46
- */
47
- function buildFindingVoteBlock(finding, codeContext) {
48
- return [
49
- '<finding>',
50
- `<finding_id>${finding.id}</finding_id>`,
51
- `<file_path>${finding.path}</file_path>`,
52
- `<line_number>${finding.line}</line_number>`,
53
- `<side>${finding.side}</side>`,
54
- '<finding_body>',
55
- finding.body,
56
- '</finding_body>',
57
- '<code_context>',
58
- '```',
59
- codeContext,
60
- '```',
61
- '</code_context>',
62
- '</finding>',
63
- ].join('\n');
64
- }
65
- /**
66
- * Builds a voting prompt for a batch of findings assigned to one voter.
67
- */
68
- function buildVotePrompt(groups, workspaceDir) {
69
- const template = loadVotePromptTemplate();
70
- const findingsBlock = groups
71
- .map((group) => buildFindingVoteBlock(group.primaryFinding, getCodeContext(group.primaryFinding, workspaceDir)))
72
- .join('\n\n');
73
- return template
74
- .replace('$FINDINGS', () => findingsBlock)
75
- .replace('$STANDARDS_SECTION', () => loadStandardsSection());
76
- }
77
- /**
78
- * Gets code context around a finding from the workspace.
79
- */
80
- function getWorkspaceDir(workspaceDir) {
81
- return workspaceDir ?? WORKSPACE_DIR;
82
- }
83
- function getCodeContext(finding, workspaceDir, contextLines = 10) {
84
- try {
85
- const filePath = path.join(getWorkspaceDir(workspaceDir), finding.path);
86
- if (!fs.existsSync(filePath)) {
87
- return '[File not found in workspace]';
88
- }
89
- const content = fs.readFileSync(filePath, 'utf8');
90
- const lines = content.split('\n');
91
- const startLine = Math.max(0, finding.line - contextLines - 1);
92
- const endLine = Math.min(lines.length, finding.line + contextLines);
93
- const contextWithLineNumbers = lines
94
- .slice(startLine, endLine)
95
- .map((line, idx) => {
96
- const lineNum = startLine + idx + 1;
97
- const marker = lineNum === finding.line ? '>>> ' : ' ';
98
- return `${marker}${lineNum}: ${line}`;
99
- })
100
- .join('\n');
101
- return contextWithLineNumbers;
102
- }
103
- catch (error) {
104
- logger.warn('Failed to get code context for voting', {
105
- finding: finding.id,
106
- path: finding.path,
107
- error: error instanceof Error ? error.message : String(error),
108
- });
109
- return '[Error reading file]';
110
- }
111
- }
112
- /**
113
- * Has a single model vote on a batch of findings.
114
- */
115
- async function getVotesForVoter(voter, groups, apiKeys, workspaceDir, providerHealth) {
116
- const apiKey = apiKeys[voter];
117
- if (!apiKey) {
118
- logger.warn('Council vote batch skipped - no API key', {
119
- voter,
120
- findingsCount: groups.length,
121
- });
122
- return [];
123
- }
124
- const prompt = buildVotePrompt(groups, workspaceDir);
125
- const provider = getProvider(voter);
126
- const config = {
127
- apiKey,
128
- workspaceDir,
129
- jsonSchema: BATCH_VOTE_SCHEMA,
130
- thinkingLevel: getReviewThinkingLevel(voter),
131
- };
132
- const expectedFindingIds = new Set(groups.map((group) => group.primaryFinding.id));
133
- logger.info('Council vote batch requested', {
134
- voter,
135
- findingsCount: groups.length,
136
- findingIds: groups.map((group) => group.primaryFinding.id),
137
- });
138
- try {
139
- const result = await provider.review(prompt, config);
140
- if (result.error) {
141
- providerHealth.recordFailure({
142
- provider: voter,
143
- stage: 'vote',
144
- error: result.error,
145
- timedOut: result.timedOut,
146
- });
147
- logger.error('Council vote batch failed', {
148
- voter,
149
- findingsCount: groups.length,
150
- error: result.error,
151
- });
152
- return [];
153
- }
154
- if (!isBatchVoteResponse(result.structuredOutput)) {
155
- throw new Error('Could not parse batch vote response from structured output');
156
- }
157
- const batchVoteResponse = result.structuredOutput;
158
- const parseMethod = 'structured_output';
159
- const votesByFindingId = new Map();
160
- const unexpectedFindingIds = [];
161
- for (const decision of batchVoteResponse.votes) {
162
- if (!expectedFindingIds.has(decision.findingId)) {
163
- unexpectedFindingIds.push(decision.findingId);
164
- continue;
165
- }
166
- if (votesByFindingId.has(decision.findingId)) {
167
- continue;
168
- }
169
- const vote = {
170
- voter,
171
- findingId: decision.findingId,
172
- approve: Boolean(decision.approve),
173
- confidence: Math.max(0, Math.min(1, decision.confidence ?? 0.5)),
174
- reasoning: typeof decision.reasoning === 'string' ? decision.reasoning : '',
175
- };
176
- votesByFindingId.set(decision.findingId, vote);
177
- logger.info('Council vote received', {
178
- voter,
179
- findingId: vote.findingId,
180
- vote: vote.approve,
181
- confidence: vote.confidence,
182
- parseMethod,
183
- reasoning: vote.reasoning,
184
- });
185
- }
186
- if (unexpectedFindingIds.length > 0) {
187
- logger.error('Council vote batch included unexpected finding ids', {
188
- voter,
189
- unexpectedFindingIds,
190
- });
191
- return [];
192
- }
193
- const missingFindingIds = groups
194
- .map((group) => group.primaryFinding.id)
195
- .filter((findingId) => !votesByFindingId.has(findingId));
196
- if (missingFindingIds.length > 0) {
197
- logger.error('Council vote batch missing decisions', {
198
- voter,
199
- missingFindingIds,
200
- });
201
- }
202
- logger.info('Council vote batch received', {
203
- voter,
204
- findingsCount: groups.length,
205
- votesCount: votesByFindingId.size,
206
- parseMethod,
207
- });
208
- return [...votesByFindingId.values()];
209
- }
210
- catch (error) {
211
- logger.error('Council vote batch failed', {
212
- voter,
213
- findingsCount: groups.length,
214
- error: error instanceof Error ? error.message : String(error),
215
- });
216
- return [];
217
- }
218
- }
219
- /**
220
- * Batches voting so each eligible voter reviews all of its assigned findings in one call.
221
- */
222
- async function collectVotesForGroups(groups, availableModels, apiKeys, workspaceDir, providerHealth) {
223
- const votesByFindingId = new Map();
224
- const voters = providerHealth.getHealthyProviders(availableModels, {
225
- stage: 'vote',
226
- });
227
- const votePromises = voters.map((voter) => {
228
- const eligibleGroups = groups.filter((group) => !group.sourceModels.includes(voter));
229
- if (eligibleGroups.length === 0) {
230
- return Promise.resolve([]);
231
- }
232
- return getVotesForVoter(voter, eligibleGroups, apiKeys, workspaceDir, providerHealth);
233
- });
234
- const voteResults = await Promise.allSettled(votePromises);
235
- for (const result of voteResults) {
236
- if (result.status !== 'fulfilled') {
237
- continue;
238
- }
239
- for (const vote of result.value) {
240
- const findingVotes = votesByFindingId.get(vote.findingId) ?? [];
241
- findingVotes.push(vote);
242
- votesByFindingId.set(vote.findingId, findingVotes);
243
- }
244
- }
245
- return votesByFindingId;
246
- }
247
- function isAutoApprovedGroup(group, availableModels) {
248
- return (availableModels.length > 0 &&
249
- availableModels.every((model) => group.sourceModels.includes(model)));
250
- }
251
- function getEligibleVoters(group, availableModels) {
252
- return availableModels.filter((model) => !group.sourceModels.includes(model));
253
- }
254
- /**
255
- * Calculates consensus for a finding group.
256
- * Uses the primary finding for voting, applies result to the group.
257
- * All approvals must be explicit - no implicit approvals based on who found the issue.
258
- */
259
- function calculateConsensusForGroup(group, votes, threshold) {
260
- const finding = group.primaryFinding;
261
- const approvals = votes.filter((v) => v.approve).length;
262
- const approvalRatio = votes.length > 0 ? approvals / votes.length : 0;
263
- const isApproved = approvalRatio >= threshold;
264
- const consensusResult = {
265
- findingId: finding.id,
266
- finding,
267
- approvalRatio,
268
- isApproved,
269
- votes,
270
- };
271
- logger.info('Council consensus result', {
272
- findingId: finding.id,
273
- approvalRatio,
274
- isApproved,
275
- votes: votes.map((v) => ({
276
- voter: v.voter,
277
- approve: v.approve,
278
- confidence: v.confidence,
279
- })),
280
- sourceModels: group.sourceModels,
281
- });
282
- return consensusResult;
283
- }
284
- function buildAutoApprovedConsensusResult(group) {
285
- const consensusResult = {
286
- findingId: group.primaryFinding.id,
287
- finding: group.primaryFinding,
288
- approvalRatio: 1,
289
- isApproved: true,
290
- votes: [],
291
- };
292
- logger.info('Council consensus result', {
293
- findingId: group.primaryFinding.id,
294
- approvalRatio: 1,
295
- isApproved: true,
296
- votes: [],
297
- sourceModels: group.sourceModels,
298
- });
299
- return consensusResult;
300
- }
301
- function buildSingleVoterFailOpenConsensusResult(group, eligibleVoter) {
302
- const consensusResult = {
303
- findingId: group.primaryFinding.id,
304
- finding: group.primaryFinding,
305
- approvalRatio: 1,
306
- isApproved: true,
307
- votes: [],
308
- };
309
- logger.warn('Failing open for finding after sole voter failure', {
310
- findingId: group.primaryFinding.id,
311
- sourceModels: group.sourceModels,
312
- eligibleVoter,
313
- });
314
- logger.info('Council consensus result', {
315
- findingId: group.primaryFinding.id,
316
- approvalRatio: 1,
317
- isApproved: true,
318
- votes: [],
319
- sourceModels: group.sourceModels,
320
- failOpen: true,
321
- eligibleVoter,
322
- });
323
- return consensusResult;
324
- }
325
- export async function runVoting({ findingGroups, availableModels, apiKeys, workspaceDir, providerHealth = new DeliveryProviderHealthTracker(), }) {
326
- if (findingGroups.length === 0) {
327
- return [];
328
- }
329
- if (availableModels.length < 2) {
330
- logger.warn('Less than 2 models available for voting, skipping consensus', {
331
- availableModels,
332
- });
333
- // If we can't achieve meaningful consensus, approve all findings
334
- return findingGroups.map((group) => ({
335
- findingId: group.primaryFinding.id,
336
- finding: group.primaryFinding,
337
- approvalRatio: 1.0,
338
- isApproved: true,
339
- votes: [],
340
- }));
341
- }
342
- logger.info('Council voting started', {
343
- findingGroupsCount: findingGroups.length,
344
- availableModels,
345
- threshold: CONSENSUS_THRESHOLD,
346
- });
347
- const pendingGroups = findingGroups.filter((group) => !isAutoApprovedGroup(group, availableModels));
348
- const votesByFindingId = await collectVotesForGroups(pendingGroups, availableModels, apiKeys, workspaceDir, providerHealth);
349
- const consensusResults = findingGroups.map((group) => {
350
- if (isAutoApprovedGroup(group, availableModels)) {
351
- return buildAutoApprovedConsensusResult(group);
352
- }
353
- const votes = votesByFindingId.get(group.primaryFinding.id) ?? [];
354
- const eligibleVoters = getEligibleVoters(group, availableModels);
355
- const onlyEligibleVoter = eligibleVoters[0];
356
- if (votes.length === 0 && eligibleVoters.length === 1 && onlyEligibleVoter) {
357
- return buildSingleVoterFailOpenConsensusResult(group, onlyEligibleVoter);
358
- }
359
- return calculateConsensusForGroup(group, votes, CONSENSUS_THRESHOLD);
360
- });
361
- for (const group of pendingGroups) {
362
- const votes = votesByFindingId.get(group.primaryFinding.id) ?? [];
363
- const eligibleVoters = getEligibleVoters(group, availableModels);
364
- if (votes.length > 0 || eligibleVoters.length === 1) {
365
- continue;
366
- }
367
- logger.warn('No voters available for finding', {
368
- findingId: group.primaryFinding.id,
369
- sourceModels: group.sourceModels,
370
- });
371
- }
372
- logger.info('Council voting completed', {
373
- totalGroups: findingGroups.length,
374
- approved: consensusResults.filter((r) => r.isApproved).length,
375
- rejected: consensusResults.filter((r) => !r.isApproved).length,
376
- });
377
- return consensusResults;
378
- }
379
- /**
380
- * Filters consensus results to only return approved findings.
381
- */
382
- export function getApprovedFindings(consensusResults) {
383
- return consensusResults.filter((r) => r.isApproved).map((r) => r.finding);
384
- }
@@ -1,126 +0,0 @@
1
- import { runCouncilReview } from '../council/orchestrator.js';
2
- import { buildReviewOutputFromFindings, getApiKeys, getAvailableModels, getProviderFailures, requestModelReview, } from './shared.js';
3
- async function runSingleModelReview({ prompt, lineLookup, prTitle, prBody, prAuthor, prAuthorGreetingName, resolvePrAuthorGreetingName, excludePrAuthorFromSummary, workspaceDir, apiKeys, onProgress, }) {
4
- const availableModels = getAvailableModels(apiKeys);
5
- const model = availableModels[0];
6
- if (!model) {
7
- return { review: null, providerFailures: [] };
8
- }
9
- const reviewResult = await requestModelReview({
10
- model,
11
- prompt,
12
- apiKeys,
13
- workspaceDir,
14
- onProgress,
15
- });
16
- if (!reviewResult || reviewResult.error) {
17
- return {
18
- review: null,
19
- providerFailures: reviewResult
20
- ? getProviderFailures([reviewResult])
21
- : [{ model, error: `No review result returned for ${model}` }],
22
- };
23
- }
24
- return {
25
- review: await buildReviewOutputFromFindings({
26
- commentsSource: reviewResult.findings,
27
- lineLookup,
28
- prTitle,
29
- prBody,
30
- prAuthor,
31
- prAuthorGreetingName,
32
- resolvePrAuthorGreetingName,
33
- excludePrAuthorFromSummary,
34
- summaryApiKey: apiKeys.gemini,
35
- workspaceDir,
36
- preferredSummary: reviewResult.summary,
37
- }),
38
- providerFailures: [],
39
- };
40
- }
41
- async function runMultiModelReview({ ctx, prompt, lineLookup, prTitle, prBody, prAuthor, prAuthorGreetingName, resolvePrAuthorGreetingName, excludePrAuthorFromSummary, apiKeys, onProgress, }) {
42
- const result = await runCouncilReview({
43
- prompt,
44
- apiKeys,
45
- repository: `${ctx.owner}/${ctx.repo}`,
46
- prNumber: ctx.prNumber,
47
- workspaceDir: ctx.workspaceDir,
48
- onProgress,
49
- });
50
- if (result.errors.length > 0 && result.modelsUsed.length === 0) {
51
- return { review: null, providerFailures: result.errors };
52
- }
53
- return {
54
- review: await buildReviewOutputFromFindings({
55
- commentsSource: result.approvedFindings,
56
- lineLookup,
57
- prTitle,
58
- prBody,
59
- prAuthor,
60
- prAuthorGreetingName,
61
- resolvePrAuthorGreetingName,
62
- excludePrAuthorFromSummary,
63
- summaryApiKey: apiKeys.gemini,
64
- workspaceDir: ctx.workspaceDir,
65
- }),
66
- providerFailures: result.errors,
67
- };
68
- }
69
- export async function runCouncilReviewEngine({ ctx, prompt, lineLookup, prTitle, prBody, prAuthor, prAuthorGreetingName, resolvePrAuthorGreetingName, excludePrAuthorFromSummary, onProgress, standardsSection: _standardsSection, }) {
70
- const apiKeys = getApiKeys(ctx);
71
- const availableModels = getAvailableModels(apiKeys);
72
- let metricModels = [];
73
- if (availableModels.length > 0) {
74
- metricModels =
75
- ctx.councilEnabled && availableModels.length >= 2
76
- ? [...availableModels]
77
- : [availableModels[0]];
78
- }
79
- else if (ctx.councilModelsEnabled.length > 0) {
80
- metricModels = [...ctx.councilModelsEnabled];
81
- }
82
- if (availableModels.length === 0) {
83
- return {
84
- review: null,
85
- availableModels,
86
- metricModels,
87
- mode: 'single',
88
- providerFailures: [],
89
- };
90
- }
91
- const useCouncil = ctx.councilEnabled && availableModels.length >= 2;
92
- const { review, providerFailures } = useCouncil
93
- ? await runMultiModelReview({
94
- ctx,
95
- prompt,
96
- lineLookup,
97
- prTitle,
98
- prBody,
99
- prAuthor,
100
- prAuthorGreetingName,
101
- resolvePrAuthorGreetingName,
102
- excludePrAuthorFromSummary,
103
- apiKeys,
104
- onProgress,
105
- })
106
- : await runSingleModelReview({
107
- prompt,
108
- lineLookup,
109
- prTitle,
110
- prBody,
111
- prAuthor,
112
- prAuthorGreetingName,
113
- resolvePrAuthorGreetingName,
114
- excludePrAuthorFromSummary,
115
- workspaceDir: ctx.workspaceDir,
116
- apiKeys,
117
- onProgress,
118
- });
119
- return {
120
- review,
121
- availableModels,
122
- metricModels,
123
- mode: useCouncil ? 'council' : 'single',
124
- providerFailures,
125
- };
126
- }
@@ -1,12 +0,0 @@
1
- import { runCouncilReviewEngine } from './council.js';
2
- import { runMultiFocusReviewEngine } from './multi-focus.js';
3
- export function getReviewEngineRunner(engine) {
4
- switch (engine) {
5
- case 'council':
6
- return runCouncilReviewEngine;
7
- case 'multi_focus':
8
- return runMultiFocusReviewEngine;
9
- default:
10
- throw new Error(`Unsupported review engine: ${engine}`);
11
- }
12
- }
@@ -1,5 +0,0 @@
1
- import { DEFAULT_REVIEW_ENGINE, parseReviewEngine as parseSharedReviewEngine, } from 'doistbot-repo-config';
2
- export { DEFAULT_REVIEW_ENGINE };
3
- export function parseReviewEngine(raw) {
4
- return parseSharedReviewEngine(raw) ?? DEFAULT_REVIEW_ENGINE;
5
- }
@@ -1,11 +0,0 @@
1
- export declare const DEFAULT_REVIEW_ENGINE: "multi_focus";
2
- export type ReviewEngine = 'council' | 'multi_focus';
3
- export declare function parseReviewEngine(raw: unknown): ReviewEngine | null;
4
- export declare function resolveReviewEngine({ configuredEngine, envEngine, logger, repository, }: {
5
- configuredEngine?: unknown;
6
- envEngine?: unknown;
7
- logger?: {
8
- warn: (message: string, context?: Record<string, unknown>) => void;
9
- };
10
- repository?: string;
11
- }): ReviewEngine;
@@ -1,41 +0,0 @@
1
- export const DEFAULT_REVIEW_ENGINE = 'multi_focus';
2
- function isReviewEngine(value) {
3
- return value === 'council' || value === 'multi_focus';
4
- }
5
- export function parseReviewEngine(raw) {
6
- if (typeof raw !== 'string') {
7
- return null;
8
- }
9
- const normalized = raw.trim().toLowerCase();
10
- if (!normalized) {
11
- return null;
12
- }
13
- return isReviewEngine(normalized) ? normalized : null;
14
- }
15
- export function resolveReviewEngine({ configuredEngine, envEngine, logger, repository, }) {
16
- const configured = parseReviewEngine(configuredEngine);
17
- if (configured) {
18
- return configured;
19
- }
20
- if (configuredEngine != null) {
21
- logger?.warn('Invalid configured review engine, checking environment override next', {
22
- component: 'review-engine',
23
- repository,
24
- configured_engine: configuredEngine,
25
- next_source: 'env',
26
- });
27
- }
28
- const fromEnv = parseReviewEngine(envEngine);
29
- if (fromEnv) {
30
- return fromEnv;
31
- }
32
- if (envEngine != null) {
33
- logger?.warn('Invalid env review engine, falling back', {
34
- component: 'review-engine',
35
- repository,
36
- env_engine: envEngine,
37
- fallback: DEFAULT_REVIEW_ENGINE,
38
- });
39
- }
40
- return DEFAULT_REVIEW_ENGINE;
41
- }