@doist/doistbot-cli 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/auth.js +78 -24
- package/dist/actions/doctor.js +14 -7
- package/dist/actions/review.js +67 -10
- package/dist/auth.js +51 -6
- package/dist/config.js +77 -5
- package/dist/env.js +1 -0
- package/dist/runtime.js +1 -1
- package/dist/terminal.js +2 -0
- package/package.json +4 -4
- package/sandbox/_node_modules/doistbot-repo-config/dist/index.d.ts +4 -8
- package/sandbox/_node_modules/doistbot-repo-config/dist/index.js +8 -20
- package/sandbox/dist/core/check-run.js +16 -0
- package/sandbox/dist/core/datadog-metrics.js +6 -52
- package/sandbox/dist/core/logger-span-processor.js +78 -0
- package/sandbox/dist/core/pi.js +285 -174
- package/sandbox/dist/core/prompt-template.js +45 -3
- package/sandbox/dist/core/repository-map.js +24 -0
- package/sandbox/dist/core/tracing-setup.js +15 -0
- package/sandbox/dist/core/tracing.js +80 -0
- package/sandbox/dist/main.js +4 -1
- package/sandbox/dist/providers/credentials.js +12 -2
- package/sandbox/dist/providers/gemini.js +5 -1
- package/sandbox/dist/providers/helpers.js +63 -17
- package/sandbox/dist/providers/index.js +4 -0
- package/sandbox/dist/providers/openrouter-pi.js +25 -0
- package/sandbox/dist/providers/pi-review.js +19 -2
- package/sandbox/dist/tasks/chat/chat.js +173 -102
- package/sandbox/dist/tasks/issue-fix-retry/fix-retry.js +210 -185
- package/sandbox/dist/tasks/issue-summarize/model.js +120 -49
- package/sandbox/dist/tasks/issue-summarize/prompt.js +2 -2
- package/sandbox/dist/tasks/issue-summarize/summarize.js +104 -88
- package/sandbox/dist/tasks/issue-triage/clone.js +40 -0
- package/sandbox/dist/tasks/issue-triage/context.js +25 -1
- package/sandbox/dist/tasks/issue-triage/fix-attempt.js +33 -10
- package/sandbox/dist/tasks/issue-triage/fix-dispatch.js +154 -101
- package/sandbox/dist/tasks/issue-triage/fix-loop/runners.js +15 -6
- package/sandbox/dist/tasks/issue-triage/hero-group-map.js +2 -0
- package/sandbox/dist/tasks/issue-triage/model.js +120 -36
- package/sandbox/dist/tasks/issue-triage/output.js +27 -0
- package/sandbox/dist/tasks/issue-triage/pr-creator.js +5 -4
- package/sandbox/dist/tasks/issue-triage/prompt.js +2 -2
- package/sandbox/dist/tasks/issue-triage/resolution.js +26 -0
- package/sandbox/dist/tasks/issue-triage/routing.js +9 -9
- package/sandbox/dist/tasks/issue-triage/side-effects.js +9 -0
- package/sandbox/dist/tasks/issue-triage/triage.js +290 -420
- package/sandbox/dist/tasks/persist-logs/llm-query-planner.js +3 -3
- package/sandbox/dist/tasks/review/check-run.js +2 -1
- package/sandbox/dist/tasks/review/conversation-context.js +2 -1
- package/sandbox/dist/tasks/review/engines/dedupe.js +110 -44
- package/sandbox/dist/tasks/review/engines/multi-focus.js +254 -113
- package/sandbox/dist/tasks/review/engines/shared.js +43 -6
- package/sandbox/dist/tasks/review/github-comment-format.js +72 -10
- package/sandbox/dist/tasks/review/local.js +7 -4
- package/sandbox/dist/tasks/review/review-summary.js +48 -7
- package/sandbox/dist/tasks/review/review.js +267 -186
- package/sandbox/dist/tasks/review/runner.js +33 -7
- package/sandbox/dist/tasks/review/summary-model.js +189 -20
- package/sandbox/dist/tasks/review/thinking-level.js +4 -0
- package/sandbox/node_modules/doistbot-repo-config/dist/index.d.ts +4 -8
- package/sandbox/node_modules/doistbot-repo-config/dist/index.js +8 -20
- package/sandbox/src/tasks/review/prompts/review-multi-focus-base-prompt.md +1 -0
|
@@ -1,13 +1,15 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
1
2
|
import fs from 'fs';
|
|
2
3
|
import { logger } from '../../core/logging.js';
|
|
3
|
-
import { hasReviewProviderCredential } from '../../providers/credentials.js';
|
|
4
4
|
import { runMultiFocusReviewEngine } from './engines/multi-focus.js';
|
|
5
|
+
import { getEnabledReviewModels } from './engines/shared.js';
|
|
5
6
|
import {} from './engines/types.js';
|
|
6
7
|
import { buildAnnotatedDiff, buildDiffSection } from './diff.js';
|
|
7
8
|
import { buildMultiFocusSharedPrompt } from './multi-focus-prompt.js';
|
|
8
9
|
import { loadStandardsSection } from './prompt.js';
|
|
9
10
|
const REVIEW_GUIDANCE_FILES = ['AGENTS.md', 'CLAUDE.md', 'GEMINI.md'];
|
|
10
11
|
const MASKED_PR_AUTHOR_LABEL = 'PR author';
|
|
12
|
+
const OPENROUTER_SESSION_REPO_SEGMENT_MAX_LENGTH = 96;
|
|
11
13
|
function getGuidanceFilePresence(workspaceDir) {
|
|
12
14
|
return Object.fromEntries(REVIEW_GUIDANCE_FILES.map((fileName) => [
|
|
13
15
|
fileName,
|
|
@@ -47,12 +49,23 @@ function buildEnginePrompt({ input, diffSection, truncated, standardsSection, pr
|
|
|
47
49
|
prompt: buildMultiFocusSharedPrompt(sharedPromptArgs),
|
|
48
50
|
};
|
|
49
51
|
}
|
|
50
|
-
function getEnabledReviewModels({ apiKeys, reviewModelsEnabled, }) {
|
|
51
|
-
return reviewModelsEnabled.filter((model) => hasReviewProviderCredential(apiKeys[model]));
|
|
52
|
-
}
|
|
53
52
|
function getMissingRequiredReviewModels(availableModels, requiredReviewModels = []) {
|
|
54
53
|
return requiredReviewModels.filter((model) => !availableModels.includes(model));
|
|
55
54
|
}
|
|
55
|
+
function toOpenRouterSessionSegment(value, fallback) {
|
|
56
|
+
const normalized = value
|
|
57
|
+
.trim()
|
|
58
|
+
.toLowerCase()
|
|
59
|
+
.replace(/[^a-z0-9._-]+/g, '-')
|
|
60
|
+
.replace(/^-+|-+$/g, '')
|
|
61
|
+
.slice(0, OPENROUTER_SESSION_REPO_SEGMENT_MAX_LENGTH);
|
|
62
|
+
return normalized || fallback;
|
|
63
|
+
}
|
|
64
|
+
export function buildOpenRouterReviewSessionId({ owner, repo, prNumber, prompt, }) {
|
|
65
|
+
const repoSegment = toOpenRouterSessionSegment(`${owner}-${repo}`, 'repo');
|
|
66
|
+
const promptHash = createHash('sha256').update(prompt).digest('hex').slice(0, 32);
|
|
67
|
+
return `doistbot-review-${repoSegment}-pr-${prNumber}-${promptHash}`;
|
|
68
|
+
}
|
|
56
69
|
export async function runSharedReview(input) {
|
|
57
70
|
const { annotatedFiles, lineLookup } = buildAnnotatedDiff(input.files);
|
|
58
71
|
if (annotatedFiles.length === 0) {
|
|
@@ -76,10 +89,11 @@ export async function runSharedReview(input) {
|
|
|
76
89
|
providerFailures: [],
|
|
77
90
|
};
|
|
78
91
|
}
|
|
79
|
-
const configuredAvailableModels = getEnabledReviewModels(input);
|
|
92
|
+
const configuredAvailableModels = getEnabledReviewModels(input.apiKeys, input.reviewModelsEnabled);
|
|
80
93
|
if (configuredAvailableModels.length === 0) {
|
|
81
94
|
logger.error('No enabled review models with API keys are available, skipping review', {
|
|
82
95
|
review_models_enabled: input.reviewModelsEnabled,
|
|
96
|
+
review_focuses_enabled: input.reviewFocusesEnabled,
|
|
83
97
|
});
|
|
84
98
|
return {
|
|
85
99
|
status: 'skipped',
|
|
@@ -94,6 +108,7 @@ export async function runSharedReview(input) {
|
|
|
94
108
|
if (missingRequiredModels.length > 0) {
|
|
95
109
|
logger.error('Required review models with API keys are unavailable, skipping review', {
|
|
96
110
|
review_models_enabled: input.reviewModelsEnabled,
|
|
111
|
+
review_focuses_enabled: input.reviewFocusesEnabled,
|
|
97
112
|
available_models: configuredAvailableModels,
|
|
98
113
|
missing_required_models: missingRequiredModels,
|
|
99
114
|
});
|
|
@@ -134,9 +149,19 @@ export async function runSharedReview(input) {
|
|
|
134
149
|
repo: input.repo,
|
|
135
150
|
prNumber: input.prNumber,
|
|
136
151
|
workspaceDir: input.workspaceDir,
|
|
137
|
-
|
|
138
|
-
openaiApiKey: input.apiKeys.openai,
|
|
152
|
+
apiKeys: input.apiKeys,
|
|
139
153
|
reviewModelsEnabled: input.reviewModelsEnabled,
|
|
154
|
+
...(input.reviewModelsByFocus ? { reviewModelsByFocus: input.reviewModelsByFocus } : {}),
|
|
155
|
+
...(input.geminiFallbackFocuses
|
|
156
|
+
? { geminiFallbackFocuses: input.geminiFallbackFocuses }
|
|
157
|
+
: {}),
|
|
158
|
+
...(input.reviewSummaryModel ? { reviewSummaryModel: input.reviewSummaryModel } : {}),
|
|
159
|
+
openRouterSessionId: buildOpenRouterReviewSessionId({
|
|
160
|
+
owner: input.owner,
|
|
161
|
+
repo: input.repo,
|
|
162
|
+
prNumber: input.prNumber,
|
|
163
|
+
prompt,
|
|
164
|
+
}),
|
|
140
165
|
};
|
|
141
166
|
const { review, availableModels, metricModels, mode, providerFailures } = await runMultiFocusReviewEngine({
|
|
142
167
|
ctx: engineContext,
|
|
@@ -146,6 +171,7 @@ export async function runSharedReview(input) {
|
|
|
146
171
|
prTitle: input.prTitle,
|
|
147
172
|
prBody: input.prBody,
|
|
148
173
|
lowPriorityFindingPlacement: input.lowPriorityFindingPlacement,
|
|
174
|
+
enabledFocuses: input.reviewFocusesEnabled,
|
|
149
175
|
onProgress: input.onProgress,
|
|
150
176
|
});
|
|
151
177
|
if (availableModels.length === 0) {
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
import { SpanStatusCode } from '@opentelemetry/api';
|
|
1
2
|
import { logger } from '../../core/logging.js';
|
|
2
|
-
import { invokePiPrompt } from '../../core/pi.js';
|
|
3
|
+
import { DEFAULT_GEMINI_FLASH_MODEL, DEFAULT_OPENROUTER_MODELS, DEFAULT_PI_MODELS, invokePiPrompt, PI_PROVIDER_BY_DOISTBOT_PROVIDER, } from '../../core/pi.js';
|
|
3
4
|
import { WORKSPACE_DIR } from '../../core/shared.js';
|
|
5
|
+
import { genAiProviderName, withPhase } from '../../core/tracing.js';
|
|
4
6
|
import { resolveReviewProviderCredential } from '../../providers/credentials.js';
|
|
7
|
+
import { extractJsonCandidatesFromResponse } from '../../providers/helpers.js';
|
|
5
8
|
const REVIEW_SUMMARY_TIMEOUT_MS = 5 * 60 * 1000;
|
|
9
|
+
const REVIEW_SUMMARY_THINKING_LEVEL = 'medium';
|
|
10
|
+
// One model span per summary attempt; a fallback chain emits one span per model.
|
|
11
|
+
const SUMMARY_PHASE = 'review.summary';
|
|
6
12
|
const DIFF_SIDE = {
|
|
7
13
|
LEFT: 'LEFT',
|
|
8
14
|
RIGHT: 'RIGHT',
|
|
@@ -10,53 +16,216 @@ const DIFF_SIDE = {
|
|
|
10
16
|
function getWorkspaceDir() {
|
|
11
17
|
return WORKSPACE_DIR;
|
|
12
18
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
function getReviewSummaryModelNameForLog({ provider, piProviderId, modelName, }) {
|
|
20
|
+
if (modelName) {
|
|
21
|
+
return modelName;
|
|
22
|
+
}
|
|
23
|
+
const resolvedPiProviderId = piProviderId ?? PI_PROVIDER_BY_DOISTBOT_PROVIDER[provider];
|
|
24
|
+
return ((resolvedPiProviderId === 'openrouter' ? DEFAULT_OPENROUTER_MODELS[provider] : undefined) ??
|
|
25
|
+
DEFAULT_PI_MODELS[provider]);
|
|
26
|
+
}
|
|
27
|
+
export function buildGeminiFlashReviewSummaryModel(credential) {
|
|
28
|
+
if (credential === undefined) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
const resolvedCredential = resolveReviewProviderCredential(credential);
|
|
32
|
+
if (!resolvedCredential) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
provider: 'gemini',
|
|
37
|
+
credential,
|
|
38
|
+
modelName: DEFAULT_GEMINI_FLASH_MODEL,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function failedReviewSummaryRequest({ reason, error, timedOut, durationMs, outputChars, }) {
|
|
42
|
+
return {
|
|
43
|
+
status: 'failed',
|
|
44
|
+
reason,
|
|
45
|
+
...(error ? { error } : {}),
|
|
46
|
+
...(timedOut !== undefined ? { timedOut } : {}),
|
|
47
|
+
...(durationMs !== undefined ? { durationMs } : {}),
|
|
48
|
+
...(outputChars !== undefined ? { outputChars } : {}),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function getRequestFailureReason(result) {
|
|
52
|
+
if (result.timedOut) {
|
|
53
|
+
return 'timeout';
|
|
54
|
+
}
|
|
55
|
+
return result.error.toLowerCase().includes('did not produce an assistant response')
|
|
56
|
+
? 'no_assistant_response'
|
|
57
|
+
: 'request_failed';
|
|
58
|
+
}
|
|
59
|
+
export async function requestReviewSummary({ credential, provider = 'zai', modelName, prompt, workspaceDir, }) {
|
|
60
|
+
const resolvedCredential = resolveReviewProviderCredential(credential);
|
|
61
|
+
if (!resolvedCredential) {
|
|
62
|
+
return failedReviewSummaryRequest({ reason: 'missing_credential' });
|
|
63
|
+
}
|
|
64
|
+
const resolvedModelName = modelName ?? resolvedCredential.modelName;
|
|
65
|
+
const logModelName = getReviewSummaryModelNameForLog({
|
|
66
|
+
provider,
|
|
67
|
+
piProviderId: resolvedCredential.piProviderId,
|
|
68
|
+
modelName: resolvedModelName,
|
|
69
|
+
});
|
|
70
|
+
// Span wraps the actual model invoke (after the credential check), so a
|
|
71
|
+
// missing-credential config error returns before any span and never shows a fake model attempt.
|
|
72
|
+
return withPhase(SUMMARY_PHASE, {
|
|
73
|
+
'gen_ai.operation.name': 'chat',
|
|
74
|
+
'gen_ai.provider.name': genAiProviderName(provider),
|
|
75
|
+
'gen_ai.request.model': logModelName,
|
|
76
|
+
}, async (span) => {
|
|
77
|
+
const result = await runReviewSummaryRequest({
|
|
78
|
+
provider,
|
|
79
|
+
resolvedCredential,
|
|
80
|
+
resolvedModelName,
|
|
81
|
+
logModelName,
|
|
82
|
+
prompt,
|
|
83
|
+
workspaceDir,
|
|
84
|
+
});
|
|
85
|
+
if (result.status === 'failed') {
|
|
86
|
+
span.setStatus({
|
|
87
|
+
code: SpanStatusCode.ERROR,
|
|
88
|
+
message: result.error ?? result.reason,
|
|
89
|
+
});
|
|
18
90
|
}
|
|
91
|
+
return result;
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
async function runReviewSummaryRequest({ provider, resolvedCredential, resolvedModelName, logModelName, prompt, workspaceDir, }) {
|
|
95
|
+
try {
|
|
96
|
+
logger.info('Review summary model request', {
|
|
97
|
+
provider,
|
|
98
|
+
modelName: logModelName,
|
|
99
|
+
promptLength: prompt.length,
|
|
100
|
+
timeoutMs: REVIEW_SUMMARY_TIMEOUT_MS,
|
|
101
|
+
thinkingLevel: REVIEW_SUMMARY_THINKING_LEVEL,
|
|
102
|
+
});
|
|
19
103
|
const result = await invokePiPrompt({
|
|
20
|
-
provider
|
|
104
|
+
provider,
|
|
21
105
|
apiKey: resolvedCredential.apiKey,
|
|
22
106
|
...(resolvedCredential.piProviderId
|
|
23
107
|
? { piProviderId: resolvedCredential.piProviderId }
|
|
24
108
|
: {}),
|
|
25
|
-
...(
|
|
109
|
+
...(resolvedModelName ? { modelName: resolvedModelName } : {}),
|
|
26
110
|
prompt,
|
|
27
111
|
cwd: workspaceDir ?? getWorkspaceDir(),
|
|
28
112
|
timeoutMs: REVIEW_SUMMARY_TIMEOUT_MS,
|
|
29
113
|
capabilityPreset: 'read-only',
|
|
114
|
+
thinkingLevel: REVIEW_SUMMARY_THINKING_LEVEL,
|
|
30
115
|
});
|
|
31
116
|
if (!result.ok) {
|
|
117
|
+
const reason = getRequestFailureReason(result);
|
|
32
118
|
logger.warn('Review summary model request failed', {
|
|
119
|
+
reason,
|
|
120
|
+
provider,
|
|
121
|
+
modelName: logModelName,
|
|
122
|
+
error: result.error,
|
|
123
|
+
timedOut: result.timedOut,
|
|
124
|
+
durationMs: result.durationMs,
|
|
125
|
+
promptLength: prompt.length,
|
|
126
|
+
});
|
|
127
|
+
return failedReviewSummaryRequest({
|
|
128
|
+
reason,
|
|
33
129
|
error: result.error,
|
|
34
130
|
timedOut: result.timedOut,
|
|
131
|
+
durationMs: result.durationMs,
|
|
35
132
|
});
|
|
36
|
-
return null;
|
|
37
133
|
}
|
|
38
134
|
const output = result.output;
|
|
39
135
|
if (!output?.trim()) {
|
|
40
|
-
return
|
|
136
|
+
return failedReviewSummaryRequest({
|
|
137
|
+
reason: 'empty_output',
|
|
138
|
+
durationMs: result.durationMs,
|
|
139
|
+
outputChars: output?.length ?? 0,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
const jsonCandidates = extractJsonCandidatesFromResponse(output);
|
|
143
|
+
if (jsonCandidates.length === 0) {
|
|
144
|
+
logger.warn('Review summary model output missing JSON', {
|
|
145
|
+
provider,
|
|
146
|
+
modelName: logModelName,
|
|
147
|
+
durationMs: result.durationMs,
|
|
148
|
+
outputChars: output.length,
|
|
149
|
+
});
|
|
150
|
+
return failedReviewSummaryRequest({
|
|
151
|
+
reason: 'missing_json',
|
|
152
|
+
durationMs: result.durationMs,
|
|
153
|
+
outputChars: output.length,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
let parsed;
|
|
157
|
+
let parsedSuccessfully = false;
|
|
158
|
+
let parseError;
|
|
159
|
+
for (const jsonCandidate of jsonCandidates) {
|
|
160
|
+
try {
|
|
161
|
+
parsed = JSON.parse(jsonCandidate);
|
|
162
|
+
parsedSuccessfully = true;
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
parseError = error;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (!parsedSuccessfully) {
|
|
170
|
+
logger.warn('Review summary model output contained invalid JSON', {
|
|
171
|
+
provider,
|
|
172
|
+
modelName: logModelName,
|
|
173
|
+
error: parseError instanceof Error ? parseError.message : String(parseError),
|
|
174
|
+
durationMs: result.durationMs,
|
|
175
|
+
outputChars: output.length,
|
|
176
|
+
});
|
|
177
|
+
return failedReviewSummaryRequest({
|
|
178
|
+
reason: 'invalid_json',
|
|
179
|
+
error: parseError instanceof Error ? parseError.message : String(parseError),
|
|
180
|
+
durationMs: result.durationMs,
|
|
181
|
+
outputChars: output.length,
|
|
182
|
+
});
|
|
41
183
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
184
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
185
|
+
return failedReviewSummaryRequest({
|
|
186
|
+
reason: 'invalid_schema',
|
|
187
|
+
durationMs: result.durationMs,
|
|
188
|
+
outputChars: output.length,
|
|
189
|
+
});
|
|
47
190
|
}
|
|
48
|
-
const
|
|
49
|
-
|
|
191
|
+
const summaryResult = parsed;
|
|
192
|
+
if (typeof summaryResult.summary !== 'string') {
|
|
193
|
+
return failedReviewSummaryRequest({
|
|
194
|
+
reason: 'invalid_schema',
|
|
195
|
+
durationMs: result.durationMs,
|
|
196
|
+
outputChars: output.length,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
if (!summaryResult.summary.trim()) {
|
|
200
|
+
return failedReviewSummaryRequest({
|
|
201
|
+
reason: 'empty_summary',
|
|
202
|
+
durationMs: result.durationMs,
|
|
203
|
+
outputChars: output.length,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
logger.info('Review summary model response', {
|
|
207
|
+
provider,
|
|
208
|
+
modelName: logModelName,
|
|
209
|
+
durationMs: result.durationMs,
|
|
210
|
+
outputChars: output.length,
|
|
211
|
+
summaryChars: summaryResult.summary.trim().length,
|
|
212
|
+
});
|
|
50
213
|
return {
|
|
51
|
-
|
|
52
|
-
|
|
214
|
+
status: 'succeeded',
|
|
215
|
+
summary: summaryResult.summary,
|
|
216
|
+
comments: Array.isArray(summaryResult.comments) ? summaryResult.comments : [],
|
|
217
|
+
durationMs: result.durationMs,
|
|
218
|
+
outputChars: output.length,
|
|
53
219
|
};
|
|
54
220
|
}
|
|
55
221
|
catch (error) {
|
|
56
222
|
logger.error('Review summary model request or parsing failed', {
|
|
57
223
|
error: error instanceof Error ? error.message : String(error),
|
|
58
224
|
});
|
|
59
|
-
return
|
|
225
|
+
return failedReviewSummaryRequest({
|
|
226
|
+
reason: 'request_failed',
|
|
227
|
+
error: error instanceof Error ? error.message : String(error),
|
|
228
|
+
});
|
|
60
229
|
}
|
|
61
230
|
}
|
|
62
231
|
export function sanitizeInlineComments(comments, lineLookup) {
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import type { Logger } from 'doistbot-logging';
|
|
2
2
|
import type { Octokit } from 'octokit';
|
|
3
3
|
export declare const CONFIG_FILE_PATH = ".github/doistbot.yml";
|
|
4
|
-
export declare const DEFAULT_REVIEW_MODELS: readonly ["
|
|
5
|
-
export
|
|
6
|
-
type ParseReviewModelsEnabledOptions = {
|
|
7
|
-
defaultModels?: readonly string[];
|
|
8
|
-
requiredModels?: readonly string[];
|
|
9
|
-
};
|
|
4
|
+
export declare const DEFAULT_REVIEW_MODELS: readonly ["openai", "deepseek", "zai"];
|
|
5
|
+
export type DefaultReviewModel = (typeof DEFAULT_REVIEW_MODELS)[number];
|
|
10
6
|
export type RepoConfig = {
|
|
11
7
|
review?: {
|
|
12
8
|
skip_forks?: boolean;
|
|
@@ -50,6 +46,6 @@ export declare function getLocalRepoConfig({ workspaceDir, logger, }: {
|
|
|
50
46
|
workspaceDir: string;
|
|
51
47
|
logger: Logger;
|
|
52
48
|
}): RepoConfig | null;
|
|
53
|
-
export declare function
|
|
54
|
-
export declare function
|
|
49
|
+
export declare function getDefaultReviewModels(): DefaultReviewModel[];
|
|
50
|
+
export declare function parseReviewModelsEnabled(value: string | undefined): string[];
|
|
55
51
|
export {};
|
|
@@ -2,8 +2,7 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { load as yamlLoad } from 'js-yaml';
|
|
4
4
|
export const CONFIG_FILE_PATH = '.github/doistbot.yml';
|
|
5
|
-
export const DEFAULT_REVIEW_MODELS = ['
|
|
6
|
-
export const REQUIRED_REVIEW_MODELS = DEFAULT_REVIEW_MODELS;
|
|
5
|
+
export const DEFAULT_REVIEW_MODELS = ['openai', 'deepseek', 'zai'];
|
|
7
6
|
function parseRepoConfigContent(content) {
|
|
8
7
|
return yamlLoad(content);
|
|
9
8
|
}
|
|
@@ -91,24 +90,13 @@ export function getLocalRepoConfig({ workspaceDir, logger, }) {
|
|
|
91
90
|
return null;
|
|
92
91
|
}
|
|
93
92
|
}
|
|
94
|
-
export function parseReviewModelsEnabled(raw, { defaultModels = DEFAULT_REVIEW_MODELS, requiredModels = REQUIRED_REVIEW_MODELS, } = {}) {
|
|
95
|
-
if (!raw?.trim()) {
|
|
96
|
-
return [...defaultModels];
|
|
97
|
-
}
|
|
98
|
-
const allowedModels = new Set(DEFAULT_REVIEW_MODELS);
|
|
99
|
-
const models = [];
|
|
100
|
-
for (const token of raw.split(',')) {
|
|
101
|
-
const model = token.trim().toLowerCase();
|
|
102
|
-
if (!model || !allowedModels.has(model)) {
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
if (!models.includes(model)) {
|
|
106
|
-
models.push(model);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
const hasRequiredModels = requiredModels.every((model) => models.includes(model));
|
|
110
|
-
return models.length > 0 && hasRequiredModels ? models : [...defaultModels];
|
|
111
|
-
}
|
|
112
93
|
export function getDefaultReviewModels() {
|
|
113
94
|
return [...DEFAULT_REVIEW_MODELS];
|
|
114
95
|
}
|
|
96
|
+
export function parseReviewModelsEnabled(value) {
|
|
97
|
+
const models = value
|
|
98
|
+
?.split(',')
|
|
99
|
+
.map((model) => model.trim())
|
|
100
|
+
.filter(Boolean) ?? [];
|
|
101
|
+
return models.length > 0 ? [...new Set(models)] : getDefaultReviewModels();
|
|
102
|
+
}
|
|
@@ -71,6 +71,7 @@ $DIFF_SECTION
|
|
|
71
71
|
- Use one comment per distinct issue.
|
|
72
72
|
- Ignore trivial style unless it obscures meaning or violates documented standards.
|
|
73
73
|
- Keep code ranges tight and comments easy to understand without close reading.
|
|
74
|
+
- **Do not flag dependency or tooling versions as nonexistent, outdated, or "too new."** Your training data has a cutoff, so you can't know which versions of GitHub Actions, npm/PyPI/etc. packages, or their APIs are currently published. Never tell the author a version doesn't exist, is wrong, or should be downgraded based on your own knowledge — and never claim a library feature or API is unavailable because you assume the project uses an older major version. Raise a version-related finding only when the diff or checked-out repo contains concrete contradicting evidence (e.g., the manifest/lockfile pins a different version, or the referenced symbol is absent from the dependency's actual source on disk).
|
|
74
75
|
|
|
75
76
|
## Writing Style
|
|
76
77
|
|