@doist/doistbot-cli 1.0.4 → 1.0.6
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 +62 -28
- package/dist/actions/doctor.js +16 -11
- package/dist/actions/review.js +77 -11
- package/dist/auth.js +53 -8
- package/dist/config.js +40 -6
- package/dist/env.js +1 -1
- package/dist/file-store.js +1 -1
- package/dist/runtime.js +1 -1
- package/dist/terminal.js +2 -7
- package/package.json +7 -7
- package/sandbox/_node_modules/doistbot-repo-config/dist/index.d.ts +5 -23
- package/sandbox/_node_modules/doistbot-repo-config/dist/index.js +9 -85
- package/sandbox/dist/core/bot-logins.js +0 -13
- package/sandbox/dist/core/check-run.js +16 -0
- package/sandbox/dist/core/datadog-metrics.js +6 -54
- package/sandbox/dist/core/logger-span-processor.js +78 -0
- package/sandbox/dist/core/pi.js +270 -186
- package/sandbox/dist/core/tracing-setup.js +15 -0
- package/sandbox/dist/core/tracing.js +71 -0
- package/sandbox/dist/main.js +4 -1
- package/sandbox/dist/providers/config.js +1 -1
- package/sandbox/dist/providers/credentials.js +40 -0
- package/sandbox/dist/providers/gemini.js +8 -10
- package/sandbox/dist/providers/helpers.js +18 -38
- package/sandbox/dist/providers/index.js +4 -3
- package/sandbox/dist/providers/openai.js +1 -8
- package/sandbox/dist/providers/openrouter-pi.js +25 -0
- package/sandbox/dist/providers/pi-review.js +28 -55
- package/sandbox/dist/providers/schemas.js +0 -39
- package/sandbox/dist/tasks/chat/chat.js +162 -101
- package/sandbox/dist/tasks/issue-fix-retry/fix-retry.js +210 -185
- package/sandbox/dist/tasks/issue-summarize/context.js +1 -1
- package/sandbox/dist/tasks/issue-summarize/model.js +4 -2
- package/sandbox/dist/tasks/issue-summarize/summarize.js +1 -1
- package/sandbox/dist/tasks/issue-triage/clone.js +40 -0
- package/sandbox/dist/tasks/issue-triage/context.js +25 -2
- package/sandbox/dist/tasks/issue-triage/fix-attempt.js +31 -8
- 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/model.js +120 -36
- package/sandbox/dist/tasks/issue-triage/output.js +27 -0
- package/sandbox/dist/tasks/issue-triage/pr-creator.js +3 -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 +288 -418
- package/sandbox/dist/tasks/persist-logs/dd-proxy.js +2 -2
- package/sandbox/dist/tasks/persist-logs/llm-query-planner.js +1 -1
- package/sandbox/dist/tasks/persist-logs/persist-logs.js +1 -1
- package/sandbox/dist/tasks/review/check-run.js +2 -1
- package/sandbox/dist/tasks/review/conversation-context.js +15 -8
- package/sandbox/dist/tasks/review/engines/dedupe.js +19 -9
- package/sandbox/dist/tasks/review/engines/multi-focus.js +62 -36
- package/sandbox/dist/tasks/review/engines/shared.js +15 -61
- package/sandbox/dist/tasks/review/github-comment-format.js +64 -10
- package/sandbox/dist/tasks/review/local.js +2 -29
- package/sandbox/dist/tasks/review/prompt.js +2 -49
- package/sandbox/dist/tasks/review/review-summary.js +120 -0
- package/sandbox/dist/tasks/review/review.js +49 -69
- package/sandbox/dist/tasks/review/runner.js +57 -52
- package/sandbox/dist/tasks/review/summary-model.js +172 -16
- package/sandbox/dist/tasks/review/thinking-level.js +4 -2
- package/sandbox/node_modules/doistbot-repo-config/dist/index.d.ts +5 -23
- package/sandbox/node_modules/doistbot-repo-config/dist/index.js +9 -85
- package/sandbox/_node_modules/doistbot-repo-config/dist/review-engine.d.ts +0 -11
- package/sandbox/_node_modules/doistbot-repo-config/dist/review-engine.js +0 -41
- package/sandbox/dist/providers/anthropic.js +0 -30
- package/sandbox/dist/tasks/review/author-format.js +0 -14
- package/sandbox/dist/tasks/review/author-profile.js +0 -67
- package/sandbox/dist/tasks/review/council/normalizer.js +0 -104
- package/sandbox/dist/tasks/review/council/orchestrator.js +0 -194
- package/sandbox/dist/tasks/review/council/provider-health.js +0 -31
- package/sandbox/dist/tasks/review/council/summary.js +0 -192
- package/sandbox/dist/tasks/review/council/types.js +0 -4
- package/sandbox/dist/tasks/review/council/voter.js +0 -384
- package/sandbox/dist/tasks/review/engines/council.js +0 -126
- package/sandbox/dist/tasks/review/engines/index.js +0 -12
- package/sandbox/dist/tasks/review/review-engine.js +0 -5
- package/sandbox/node_modules/doistbot-repo-config/dist/review-engine.d.ts +0 -11
- package/sandbox/node_modules/doistbot-repo-config/dist/review-engine.js +0 -41
- package/sandbox/review-prompt.md +0 -209
- package/sandbox/vote-prompt.md +0 -69
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
import { performance } from 'node:perf_hooks';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { SpanStatusCode } from '@opentelemetry/api';
|
|
3
4
|
import { recordTaskMetrics } from '../../core/datadog-metrics.js';
|
|
4
5
|
import { logger } from '../../core/logging.js';
|
|
5
|
-
import { logPiProgressEvent } from '../../core/pi-progress-logging.js';
|
|
6
6
|
import { loadPromptTemplateFile } from '../../core/prompt-template.js';
|
|
7
7
|
import { removeIssueCommentReactionIfPresent } from '../../core/reactions.js';
|
|
8
|
-
import { resolveIssueRepositories } from '../../core/repository-resolver.js';
|
|
9
8
|
import { REPOS_SUBDIR, WORKSPACE_DIR } from '../../core/shared.js';
|
|
10
|
-
import {
|
|
9
|
+
import { withPhase } from '../../core/tracing.js';
|
|
10
|
+
import { cloneTriageRepositories } from './clone.js';
|
|
11
|
+
import { loadTriageContext, parseIssueTriageContext } from './context.js';
|
|
11
12
|
import { postAutoFixFailureComment, shouldPostAutoFixFailureComment } from './fix-attempt.js';
|
|
12
13
|
import { runConfiguredFix } from './fix-dispatch.js';
|
|
13
14
|
import { invokeTriageModel } from './model.js';
|
|
14
|
-
import { buildTriageFailureMessage, buildUnresolvedLabelsOutput,
|
|
15
|
+
import { buildTriageFailureMessage, buildUnresolvedLabelsOutput, REQUIRED_HEADINGS, validateTriageOutput, } from './output.js';
|
|
15
16
|
import { fillTemplate, loadPromptTemplate } from './prompt.js';
|
|
16
17
|
import { allRepositoriesAllow, createRepoConfigLoader, isAutoFixEnabled, isAutoTriageEnabled, } from './repo-config.js';
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
18
|
+
import { resolveTriageScope } from './resolution.js';
|
|
19
|
+
import { buildHeroRoutingComment, postTriageRouting, shouldPostHeroRoutingComment, } from './routing.js';
|
|
20
|
+
import { createGitHubClients, getIssueUrl, prepareWorkspaceReposDirectory } from './shared.js';
|
|
21
|
+
import { deleteAutoTriagePlaceholder, postTriageOutput } from './side-effects.js';
|
|
20
22
|
const MAX_ISSUE_BODY_CHARS = 8_000;
|
|
21
23
|
const MAX_COMMENT_BODY_CHARS = 2_000;
|
|
22
24
|
const MAX_PROMPT_COMMENTS = 120;
|
|
23
|
-
const VERBOSE_PI_PROGRESS_LOGS = process.env.DOISTBOT_VERBOSE_PI_PROGRESS === 'true';
|
|
24
25
|
const TRIAGE_PROVIDER = 'openai';
|
|
25
26
|
function truncateText(text, maxChars) {
|
|
26
27
|
if (text.length <= maxChars) {
|
|
@@ -58,27 +59,6 @@ function formatRepositoriesForPrompt(repositories) {
|
|
|
58
59
|
.map(({ repository, localPath }) => `- ${repository.fullName} (local path: ${localPath})`)
|
|
59
60
|
.join('\n');
|
|
60
61
|
}
|
|
61
|
-
function logTriageModelProgress(ctx, event) {
|
|
62
|
-
const meta = {
|
|
63
|
-
task: 'issue-triage',
|
|
64
|
-
issueNumber: ctx.issueNumber,
|
|
65
|
-
};
|
|
66
|
-
logPiProgressEvent({
|
|
67
|
-
event,
|
|
68
|
-
messages: {
|
|
69
|
-
agentStart: 'Issue triage model agent started',
|
|
70
|
-
agentEnd: 'Issue triage model agent finished',
|
|
71
|
-
thinking: 'Issue triage model still thinking',
|
|
72
|
-
responding: 'Issue triage model streaming response',
|
|
73
|
-
toolStart: 'Issue triage model tool started',
|
|
74
|
-
toolUpdate: 'Issue triage model tool still running',
|
|
75
|
-
toolEnd: 'Issue triage model tool finished',
|
|
76
|
-
},
|
|
77
|
-
meta,
|
|
78
|
-
logResponding: VERBOSE_PI_PROGRESS_LOGS,
|
|
79
|
-
logTools: VERBOSE_PI_PROGRESS_LOGS,
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
62
|
function buildPrompt({ ctx, issue, comments, labelNames, clonedRepositories, decisionPath, enableAutoFix, }) {
|
|
83
63
|
const template = loadPromptTemplate();
|
|
84
64
|
const issueBody = truncateText(formatCommentBody(issue.body ?? '(No issue description provided.)'), MAX_ISSUE_BODY_CHARS);
|
|
@@ -105,22 +85,6 @@ function buildPrompt({ ctx, issue, comments, labelNames, clonedRepositories, dec
|
|
|
105
85
|
: '',
|
|
106
86
|
});
|
|
107
87
|
}
|
|
108
|
-
async function postTriageComment({ octokit, ctx, body, }) {
|
|
109
|
-
logger.info('Issue triage comment post starting', {
|
|
110
|
-
task: 'issue-triage',
|
|
111
|
-
issueNumber: ctx.issueNumber,
|
|
112
|
-
bodyChars: body.length,
|
|
113
|
-
editingPlaceholder: Boolean(ctx.placeholderCommentId),
|
|
114
|
-
});
|
|
115
|
-
await postIssueComment(ctx, octokit, body, {
|
|
116
|
-
marker: TRIAGE_MARKER,
|
|
117
|
-
placeholderCommentId: ctx.placeholderCommentId,
|
|
118
|
-
});
|
|
119
|
-
logger.info('Issue triage comment post succeeded', {
|
|
120
|
-
task: 'issue-triage',
|
|
121
|
-
issueNumber: ctx.issueNumber,
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
88
|
export async function runIssueTriage(ctx) {
|
|
125
89
|
if (ctx.triggerType === 'auto') {
|
|
126
90
|
return runAutoIssueTriage(ctx);
|
|
@@ -152,425 +116,331 @@ async function runIssueTriagePipeline(ctx, mode) {
|
|
|
152
116
|
repository: `${ctx.owner}/${ctx.repo}`,
|
|
153
117
|
issueNumber: ctx.issueNumber,
|
|
154
118
|
});
|
|
155
|
-
|
|
119
|
+
// The root withPhase wraps the entire run. OTel sets this span active, so
|
|
120
|
+
// every nested withPhase / startToolSpan parents under it automatically.
|
|
121
|
+
// withPhase handles the span lifecycle + ERROR status; the inner try/catch
|
|
122
|
+
// keeps the existing failure log and cleanup side effects.
|
|
123
|
+
return withPhase('triage', {
|
|
156
124
|
task: 'issue-triage',
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
125
|
+
'gen_ai.operation.name': 'invoke_agent',
|
|
126
|
+
'gen_ai.agent.name': 'doistbot.triage',
|
|
127
|
+
dry_run: ctx.dryRun,
|
|
128
|
+
'issue.number': ctx.issueNumber,
|
|
129
|
+
'comment.id': ctx.commentId,
|
|
160
130
|
trigger: ctx.triggerType,
|
|
161
|
-
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
ctx,
|
|
168
|
-
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
logger.info('Fetching issue triage context from GitHub', {
|
|
174
|
-
task: 'issue-triage',
|
|
175
|
-
issueNumber: ctx.issueNumber,
|
|
176
|
-
});
|
|
177
|
-
const [issue, comments] = await Promise.all([
|
|
178
|
-
fetchIssue(readOctokit, {
|
|
179
|
-
owner: ctx.owner,
|
|
180
|
-
repo: ctx.repo,
|
|
181
|
-
issueNumber: ctx.issueNumber,
|
|
182
|
-
}),
|
|
183
|
-
fetchIssueComments(readOctokit, {
|
|
131
|
+
enable_auto_fix_override: ctx.enableAutoFixOverride,
|
|
132
|
+
}, async (rootSpan) => {
|
|
133
|
+
const { app, readOctokit, writeOctokit } = await createGitHubClients(ctx);
|
|
134
|
+
// postTriageOutput adds TRIAGE_MARKER (and edits the auto-triage
|
|
135
|
+
// placeholder) internally — callers pass marker-free bodies.
|
|
136
|
+
async function postFinalTriageComment(body) {
|
|
137
|
+
await postTriageOutput({ octokit: writeOctokit, ctx, body });
|
|
138
|
+
triageCommentPosted = true;
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
const { issue, comments, labelNames } = await loadTriageContext({
|
|
142
|
+
octokit: readOctokit,
|
|
184
143
|
owner: ctx.owner,
|
|
185
144
|
repo: ctx.repo,
|
|
186
145
|
issueNumber: ctx.issueNumber,
|
|
187
|
-
}),
|
|
188
|
-
]);
|
|
189
|
-
const labelNames = extractLabelNames(issue.labels);
|
|
190
|
-
logger.info('Issue triage context loaded', {
|
|
191
|
-
task: 'issue-triage',
|
|
192
|
-
issueNumber: ctx.issueNumber,
|
|
193
|
-
labelsCount: labelNames.length,
|
|
194
|
-
commentsCount: comments.length,
|
|
195
|
-
});
|
|
196
|
-
const resolution = resolveIssueRepositories(labelNames);
|
|
197
|
-
logger.info('Issue triage label resolution completed', {
|
|
198
|
-
task: 'issue-triage',
|
|
199
|
-
issueNumber: ctx.issueNumber,
|
|
200
|
-
status: resolution.status,
|
|
201
|
-
reason: resolution.reason,
|
|
202
|
-
confidence: resolution.confidence,
|
|
203
|
-
decisionPath: resolution.decisionPath,
|
|
204
|
-
parsedProducts: resolution.parsedLabels.productLabels,
|
|
205
|
-
parsedApps: resolution.parsedLabels.appLabels,
|
|
206
|
-
parsedTeams: resolution.parsedLabels.teamLabels,
|
|
207
|
-
unknownProductLabels: resolution.parsedLabels.unknownProductLabels,
|
|
208
|
-
unknownAppLabels: resolution.parsedLabels.unknownAppLabels,
|
|
209
|
-
unknownTeamLabels: resolution.parsedLabels.unknownTeamLabels,
|
|
210
|
-
});
|
|
211
|
-
logger.info('triage.repo_resolution', {
|
|
212
|
-
task: 'issue-triage',
|
|
213
|
-
trigger: ctx.triggerType,
|
|
214
|
-
status: resolution.status,
|
|
215
|
-
confidence: resolution.confidence,
|
|
216
|
-
});
|
|
217
|
-
if (resolution.status === 'unresolved') {
|
|
218
|
-
const unresolvedBody = buildUnresolvedLabelsOutput({
|
|
219
|
-
reason: resolution.reason,
|
|
220
|
-
productLabels: resolution.parsedLabels.productLabels,
|
|
221
|
-
appLabels: resolution.parsedLabels.appLabels,
|
|
222
|
-
teamLabels: resolution.parsedLabels.teamLabels,
|
|
223
146
|
});
|
|
224
|
-
await
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
147
|
+
const resolution = await resolveTriageScope({
|
|
148
|
+
triggerType: ctx.triggerType,
|
|
149
|
+
labelNames,
|
|
150
|
+
});
|
|
151
|
+
if (resolution.status === 'unresolved') {
|
|
152
|
+
const unresolvedBody = buildUnresolvedLabelsOutput({
|
|
153
|
+
reason: resolution.reason,
|
|
154
|
+
productLabels: resolution.parsedLabels.productLabels,
|
|
155
|
+
appLabels: resolution.parsedLabels.appLabels,
|
|
156
|
+
teamLabels: resolution.parsedLabels.teamLabels,
|
|
233
157
|
});
|
|
158
|
+
await postFinalTriageComment(unresolvedBody);
|
|
159
|
+
if (mode.routeUnresolvedLabels) {
|
|
160
|
+
await postTriageRouting({
|
|
161
|
+
octokit: writeOctokit,
|
|
162
|
+
ctx,
|
|
163
|
+
body: buildHeroRoutingComment({
|
|
164
|
+
labels: labelNames,
|
|
165
|
+
triageOutput: unresolvedBody,
|
|
166
|
+
}),
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
triageDurationMs = performance.now() - startedAt;
|
|
170
|
+
return;
|
|
234
171
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
logger.info('Issue triage repository resolution succeeded', {
|
|
239
|
-
task: 'issue-triage',
|
|
240
|
-
issueNumber: ctx.issueNumber,
|
|
241
|
-
resolvedReposCount: resolution.repositories.length,
|
|
242
|
-
resolvedRepos: resolution.repositories.map((repository) => repository.fullName),
|
|
243
|
-
confidence: resolution.confidence,
|
|
244
|
-
});
|
|
245
|
-
const getCachedRepoConfig = createRepoConfigLoader({
|
|
246
|
-
octokit: readOctokit,
|
|
247
|
-
logger,
|
|
248
|
-
});
|
|
249
|
-
if (ctx.triggerType === 'auto') {
|
|
250
|
-
const enableAutoTriage = await allRepositoriesAllow({
|
|
251
|
-
repositories: resolution.repositories,
|
|
252
|
-
loadRepoConfig: getCachedRepoConfig,
|
|
253
|
-
isEnabled: isAutoTriageEnabled,
|
|
254
|
-
feature: 'auto_triage',
|
|
172
|
+
const getCachedRepoConfig = createRepoConfigLoader({
|
|
173
|
+
octokit: readOctokit,
|
|
255
174
|
logger,
|
|
256
175
|
});
|
|
257
|
-
if (
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
176
|
+
if (ctx.triggerType === 'auto') {
|
|
177
|
+
const enableAutoTriage = await allRepositoriesAllow({
|
|
178
|
+
repositories: resolution.repositories,
|
|
179
|
+
loadRepoConfig: getCachedRepoConfig,
|
|
180
|
+
isEnabled: isAutoTriageEnabled,
|
|
181
|
+
feature: 'auto_triage',
|
|
182
|
+
logger,
|
|
263
183
|
});
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
184
|
+
if (!enableAutoTriage) {
|
|
185
|
+
shouldRecordTriageMetric = false;
|
|
186
|
+
logger.info('Auto issue triage skipped: a resolved repository disabled auto triage', {
|
|
187
|
+
task: 'issue-triage',
|
|
188
|
+
issueNumber: ctx.issueNumber,
|
|
189
|
+
resolvedRepos: resolution.repositories.map((repository) => repository.fullName),
|
|
190
|
+
});
|
|
191
|
+
await deleteAutoTriagePlaceholder({
|
|
192
|
+
octokit: writeOctokit,
|
|
193
|
+
ctx,
|
|
194
|
+
reason: 'auto_triage_disabled',
|
|
195
|
+
});
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
let enableAutoFix = ctx.enableAutoFixOverride ?? true;
|
|
200
|
+
if (ctx.enableAutoFixOverride === undefined) {
|
|
201
|
+
enableAutoFix = await allRepositoriesAllow({
|
|
202
|
+
repositories: resolution.repositories,
|
|
203
|
+
loadRepoConfig: getCachedRepoConfig,
|
|
204
|
+
isEnabled: isAutoFixEnabled,
|
|
205
|
+
feature: 'auto_fix',
|
|
206
|
+
logger,
|
|
268
207
|
});
|
|
269
|
-
return;
|
|
270
208
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
209
|
+
const reposDir = prepareWorkspaceReposDirectory();
|
|
210
|
+
const { clonedRepositories, failedRepositories } = await cloneTriageRepositories({
|
|
211
|
+
app,
|
|
212
|
+
reposDir,
|
|
275
213
|
repositories: resolution.repositories,
|
|
276
|
-
|
|
277
|
-
isEnabled: isAutoFixEnabled,
|
|
278
|
-
feature: 'auto_fix',
|
|
279
|
-
logger,
|
|
214
|
+
fallbackInstallationId: ctx.installationId,
|
|
280
215
|
});
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
for (const repository of resolution.repositories) {
|
|
290
|
-
try {
|
|
291
|
-
const cloneResult = await cloneRepository({
|
|
292
|
-
app,
|
|
293
|
-
repository,
|
|
294
|
-
reposDir,
|
|
295
|
-
fallbackInstallationId: ctx.installationId,
|
|
216
|
+
if (clonedRepositories.length === 0) {
|
|
217
|
+
await postFinalTriageComment(buildTriageFailureMessage('Repository cloning failed for all resolved repositories.'));
|
|
218
|
+
// Non-throwing failure: mark the root span so the run is
|
|
219
|
+
// not reported as ok in Datadog. withPhase never forces OK,
|
|
220
|
+
// so this survives to the root phase.end.
|
|
221
|
+
rootSpan.setStatus({
|
|
222
|
+
code: SpanStatusCode.ERROR,
|
|
223
|
+
message: 'Repository cloning failed for all resolved repositories.',
|
|
296
224
|
});
|
|
297
|
-
|
|
225
|
+
triageDurationMs = performance.now() - startedAt;
|
|
226
|
+
return;
|
|
298
227
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
repository: repository.fullName,
|
|
302
|
-
error: error instanceof Error ? error.message : String(error),
|
|
303
|
-
});
|
|
304
|
-
logger.warn('Issue triage clone failed', {
|
|
228
|
+
if (failedRepositories.length > 0) {
|
|
229
|
+
logger.warn('Issue triage running in degraded mode after partial clone failures', {
|
|
305
230
|
task: 'issue-triage',
|
|
306
|
-
|
|
307
|
-
|
|
231
|
+
issueNumber: ctx.issueNumber,
|
|
232
|
+
cloneSucceeded: clonedRepositories.length,
|
|
233
|
+
cloneFailed: failedRepositories.length,
|
|
234
|
+
failedRepositories,
|
|
308
235
|
});
|
|
309
236
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
if (failedRepositories.length > 0) {
|
|
325
|
-
logger.warn('Issue triage running in degraded mode after partial clone failures', {
|
|
326
|
-
task: 'issue-triage',
|
|
327
|
-
issueNumber: ctx.issueNumber,
|
|
328
|
-
cloneSucceeded: clonedRepositories.length,
|
|
329
|
-
cloneFailed: failedRepositories.length,
|
|
237
|
+
const issueInstallationAuth = (await app.octokit.auth({
|
|
238
|
+
type: 'installation',
|
|
239
|
+
installationId: ctx.installationId,
|
|
240
|
+
}));
|
|
241
|
+
const prompt = buildPrompt({
|
|
242
|
+
ctx,
|
|
243
|
+
issue,
|
|
244
|
+
comments,
|
|
245
|
+
labelNames,
|
|
246
|
+
clonedRepositories,
|
|
247
|
+
decisionPath: resolution.decisionPath,
|
|
248
|
+
enableAutoFix,
|
|
330
249
|
});
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
comments,
|
|
340
|
-
labelNames,
|
|
341
|
-
clonedRepositories,
|
|
342
|
-
decisionPath: resolution.decisionPath,
|
|
343
|
-
enableAutoFix,
|
|
344
|
-
});
|
|
345
|
-
logger.info('Issue triage model invocation starting', {
|
|
346
|
-
task: 'issue-triage',
|
|
347
|
-
issueNumber: ctx.issueNumber,
|
|
348
|
-
provider: TRIAGE_PROVIDER,
|
|
349
|
-
promptChars: prompt.length,
|
|
350
|
-
repositoriesAnalyzed: clonedRepositories.length,
|
|
351
|
-
});
|
|
352
|
-
const triageResult = await invokeTriageModel({
|
|
353
|
-
prompt,
|
|
354
|
-
apiKey: ctx.openaiApiKey,
|
|
355
|
-
cwd: WORKSPACE_DIR,
|
|
356
|
-
extraEnv: {
|
|
357
|
-
GH_TOKEN: issueInstallationAuth.token,
|
|
358
|
-
},
|
|
359
|
-
thinkingLevel: 'high',
|
|
360
|
-
onProgress: (event) => {
|
|
361
|
-
logTriageModelProgress(ctx, event);
|
|
362
|
-
},
|
|
363
|
-
});
|
|
364
|
-
if (!triageResult.ok) {
|
|
365
|
-
logger.error('Issue triage model invocation failed', {
|
|
366
|
-
task: 'issue-triage',
|
|
367
|
-
issueNumber: ctx.issueNumber,
|
|
368
|
-
provider: TRIAGE_PROVIDER,
|
|
369
|
-
timedOut: triageResult.timedOut,
|
|
370
|
-
durationMs: triageResult.durationMs,
|
|
371
|
-
error: triageResult.error,
|
|
250
|
+
const triageResult = await invokeTriageModel({
|
|
251
|
+
prompt,
|
|
252
|
+
apiKey: ctx.openaiApiKey,
|
|
253
|
+
cwd: WORKSPACE_DIR,
|
|
254
|
+
extraEnv: {
|
|
255
|
+
GH_TOKEN: issueInstallationAuth.token,
|
|
256
|
+
},
|
|
257
|
+
thinkingLevel: 'high',
|
|
372
258
|
});
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
issueNumber: ctx.issueNumber,
|
|
389
|
-
missingSections: normalized.missingSections,
|
|
390
|
-
missingSectionsCount: normalized.missingSections.length,
|
|
391
|
-
});
|
|
392
|
-
if (normalized.missingSections.length > 0) {
|
|
393
|
-
const failureComment = `${TRIAGE_MARKER}\n\n${buildTriageFailureMessage(`Triage model returned incomplete output. Missing sections: ${normalized.missingSections.join(', ')}`)}`;
|
|
394
|
-
await postFinalTriageComment(failureComment);
|
|
259
|
+
if (!triageResult.ok) {
|
|
260
|
+
await postFinalTriageComment(buildTriageFailureMessage(triageResult.error));
|
|
261
|
+
rootSpan.setStatus({ code: SpanStatusCode.ERROR, message: triageResult.error });
|
|
262
|
+
triageDurationMs = performance.now() - startedAt;
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
const validated = await validateTriageOutput({ rawOutput: triageResult.output });
|
|
266
|
+
if (!validated.ok) {
|
|
267
|
+
await postFinalTriageComment(buildTriageFailureMessage(validated.error));
|
|
268
|
+
rootSpan.setStatus({ code: SpanStatusCode.ERROR, message: validated.error });
|
|
269
|
+
triageDurationMs = performance.now() - startedAt;
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
await postFinalTriageComment(validated.commentOutput);
|
|
273
|
+
triageMetricOutcome = 'success';
|
|
395
274
|
triageDurationMs = performance.now() - startedAt;
|
|
396
|
-
|
|
275
|
+
const fixAssessment = validated.assessment;
|
|
276
|
+
if (fixAssessment) {
|
|
277
|
+
logger.info('Fix assessment parsed from triage output', {
|
|
278
|
+
task: 'issue-triage',
|
|
279
|
+
issueNumber: ctx.issueNumber,
|
|
280
|
+
classification: fixAssessment.classification,
|
|
281
|
+
confidence: fixAssessment.confidence,
|
|
282
|
+
scope: fixAssessment.scope,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
if (shouldPostHeroRoutingComment(fixAssessment)) {
|
|
286
|
+
const routingComment = buildHeroRoutingComment({
|
|
287
|
+
labels: labelNames,
|
|
288
|
+
primaryRepo: resolution.repositories.length === 1
|
|
289
|
+
? resolution.repositories[0]
|
|
290
|
+
: undefined,
|
|
291
|
+
triageOutput: validated.output,
|
|
292
|
+
});
|
|
293
|
+
await postTriageRouting({
|
|
294
|
+
octokit: writeOctokit,
|
|
295
|
+
ctx,
|
|
296
|
+
body: routingComment,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
if (enableAutoFix && fixAssessment) {
|
|
300
|
+
const fixMetricRepository = fixAssessment.targetRepo ??
|
|
301
|
+
(clonedRepositories.length === 1
|
|
302
|
+
? clonedRepositories[0].repository.fullName
|
|
303
|
+
: `${ctx.owner}/${ctx.repo}`);
|
|
304
|
+
pendingFixMetric = {
|
|
305
|
+
repository: fixMetricRepository,
|
|
306
|
+
startedAt: performance.now(),
|
|
307
|
+
outcome: 'failure',
|
|
308
|
+
};
|
|
309
|
+
const fixResult = await runConfiguredFix({
|
|
310
|
+
ctx,
|
|
311
|
+
octokit: writeOctokit,
|
|
312
|
+
triageOutput: validated.output,
|
|
313
|
+
fixAssessment,
|
|
314
|
+
clonedRepos: clonedRepositories,
|
|
315
|
+
issueTitle: issue.title ?? 'Untitled issue',
|
|
316
|
+
issueUrl: issue.html_url ?? getIssueUrl(ctx.owner, ctx.repo, ctx.issueNumber),
|
|
317
|
+
ghToken: issueInstallationAuth.token,
|
|
318
|
+
loadRepoConfig: getCachedRepoConfig,
|
|
319
|
+
});
|
|
320
|
+
logger.info('triage.fix_attempted', {
|
|
321
|
+
task: 'issue-triage',
|
|
322
|
+
trigger: ctx.triggerType,
|
|
323
|
+
classification: fixAssessment.classification,
|
|
324
|
+
attempted: fixResult.attempted,
|
|
325
|
+
});
|
|
326
|
+
logger.info('Fix attempt result', {
|
|
327
|
+
task: 'issue-triage',
|
|
328
|
+
issueNumber: ctx.issueNumber,
|
|
329
|
+
attempted: fixResult.attempted,
|
|
330
|
+
prCreated: fixResult.attempted && fixResult.prCreated,
|
|
331
|
+
reason: 'reason' in fixResult ? fixResult.reason : undefined,
|
|
332
|
+
prUrl: 'prUrl' in fixResult ? fixResult.prUrl : undefined,
|
|
333
|
+
pr_number: 'prNumber' in fixResult ? fixResult.prNumber : undefined,
|
|
334
|
+
durationMs: performance.now() - startedAt,
|
|
335
|
+
});
|
|
336
|
+
pendingFixMetric = {
|
|
337
|
+
repository: fixResult.repository,
|
|
338
|
+
startedAt: pendingFixMetric.startedAt,
|
|
339
|
+
outcome: fixResult.attempted && fixResult.prCreated ? 'success' : 'failure',
|
|
340
|
+
};
|
|
341
|
+
const shouldPostFailureComment = await shouldPostAutoFixFailureComment(fixResult, {
|
|
342
|
+
dryRun: ctx.dryRun,
|
|
343
|
+
octokit: writeOctokit,
|
|
344
|
+
ctx,
|
|
345
|
+
});
|
|
346
|
+
if (shouldPostFailureComment) {
|
|
347
|
+
try {
|
|
348
|
+
await postAutoFixFailureComment({
|
|
349
|
+
octokit: writeOctokit,
|
|
350
|
+
ctx,
|
|
351
|
+
reason: 'reason' in fixResult ? fixResult.reason : 'Unknown reason',
|
|
352
|
+
targetRepoFullName: fixResult.repository,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
catch (commentError) {
|
|
356
|
+
logger.warn('Failed to post auto-fix failure comment', {
|
|
357
|
+
task: 'issue-triage',
|
|
358
|
+
issue_number: ctx.issueNumber,
|
|
359
|
+
error: commentError instanceof Error
|
|
360
|
+
? commentError.message
|
|
361
|
+
: String(commentError),
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
logger.info('triage.fix_attempted', {
|
|
368
|
+
task: 'issue-triage',
|
|
369
|
+
trigger: ctx.triggerType,
|
|
370
|
+
classification: fixAssessment?.classification ?? 'none',
|
|
371
|
+
attempted: false,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
397
374
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
resolvedReposCount: clonedRepositories.length,
|
|
404
|
-
durationMs: performance.now() - startedAt,
|
|
405
|
-
});
|
|
406
|
-
triageMetricOutcome = 'success';
|
|
407
|
-
triageDurationMs = performance.now() - startedAt;
|
|
408
|
-
const fixAssessment = parseFixAssessment(normalized.output);
|
|
409
|
-
if (fixAssessment) {
|
|
410
|
-
logger.info('Fix assessment parsed from triage output', {
|
|
375
|
+
catch (error) {
|
|
376
|
+
// withPhase records the exception + sets the root span ERROR status
|
|
377
|
+
// when we re-throw. This block keeps the existing failure log and
|
|
378
|
+
// user-visible cleanup side effects.
|
|
379
|
+
logger.error('Issue triage task failed', {
|
|
411
380
|
task: 'issue-triage',
|
|
412
381
|
issueNumber: ctx.issueNumber,
|
|
413
|
-
classification: fixAssessment.classification,
|
|
414
|
-
confidence: fixAssessment.confidence,
|
|
415
|
-
scope: fixAssessment.scope,
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
|
-
if (shouldPostHeroRoutingComment(fixAssessment)) {
|
|
419
|
-
const routingComment = buildHeroRoutingComment({
|
|
420
|
-
labels: labelNames,
|
|
421
|
-
primaryRepo: resolution.repositories.length === 1 ? resolution.repositories[0] : undefined,
|
|
422
|
-
triageOutput: normalized.output,
|
|
423
|
-
});
|
|
424
|
-
await postHeroRoutingComment({
|
|
425
|
-
octokit: writeOctokit,
|
|
426
|
-
ctx,
|
|
427
|
-
body: routingComment,
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
|
-
if (enableAutoFix && fixAssessment) {
|
|
431
|
-
const fixMetricRepository = fixAssessment.targetRepo ??
|
|
432
|
-
(clonedRepositories.length === 1
|
|
433
|
-
? clonedRepositories[0].repository.fullName
|
|
434
|
-
: `${ctx.owner}/${ctx.repo}`);
|
|
435
|
-
pendingFixMetric = {
|
|
436
|
-
repository: fixMetricRepository,
|
|
437
|
-
startedAt: performance.now(),
|
|
438
|
-
outcome: 'failure',
|
|
439
|
-
};
|
|
440
|
-
const fixResult = await runConfiguredFix({
|
|
441
|
-
ctx,
|
|
442
|
-
octokit: writeOctokit,
|
|
443
|
-
triageOutput: normalized.output,
|
|
444
|
-
fixAssessment,
|
|
445
|
-
clonedRepos: clonedRepositories,
|
|
446
|
-
issueTitle: issue.title ?? 'Untitled issue',
|
|
447
|
-
issueUrl: issue.html_url ?? getIssueUrl(ctx.owner, ctx.repo, ctx.issueNumber),
|
|
448
|
-
ghToken: issueInstallationAuth.token,
|
|
449
|
-
loadRepoConfig: getCachedRepoConfig,
|
|
450
|
-
});
|
|
451
|
-
logger.info('triage.fix_attempted', {
|
|
452
|
-
task: 'issue-triage',
|
|
453
382
|
trigger: ctx.triggerType,
|
|
454
|
-
|
|
455
|
-
attempted: fixResult.attempted,
|
|
456
|
-
});
|
|
457
|
-
logger.info('Fix attempt result', {
|
|
458
|
-
task: 'issue-triage',
|
|
459
|
-
issueNumber: ctx.issueNumber,
|
|
460
|
-
attempted: fixResult.attempted,
|
|
461
|
-
prCreated: fixResult.attempted && fixResult.prCreated,
|
|
462
|
-
reason: 'reason' in fixResult ? fixResult.reason : undefined,
|
|
463
|
-
prUrl: 'prUrl' in fixResult ? fixResult.prUrl : undefined,
|
|
464
|
-
pr_number: 'prNumber' in fixResult ? fixResult.prNumber : undefined,
|
|
383
|
+
error: error instanceof Error ? error.message : String(error),
|
|
465
384
|
durationMs: performance.now() - startedAt,
|
|
466
385
|
});
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
ctx,
|
|
476
|
-
});
|
|
477
|
-
if (shouldPostFailureComment) {
|
|
386
|
+
if (ctx.triggerType === 'auto' && !triageCommentPosted) {
|
|
387
|
+
await deleteAutoTriagePlaceholder({
|
|
388
|
+
octokit: writeOctokit,
|
|
389
|
+
ctx,
|
|
390
|
+
reason: 'auto_triage_failed_before_final_comment',
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
if (!ctx.dryRun && ctx.triggerType !== 'auto') {
|
|
478
394
|
try {
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
ctx,
|
|
482
|
-
reason: 'reason' in fixResult ? fixResult.reason : 'Unknown reason',
|
|
483
|
-
targetRepoFullName: fixResult.repository,
|
|
484
|
-
});
|
|
395
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
396
|
+
await postFinalTriageComment(buildTriageFailureMessage(reason));
|
|
485
397
|
}
|
|
486
398
|
catch (commentError) {
|
|
487
|
-
logger.warn('Failed to post
|
|
399
|
+
logger.warn('Failed to post issue triage failure response', {
|
|
488
400
|
task: 'issue-triage',
|
|
489
|
-
|
|
401
|
+
issueNumber: ctx.issueNumber,
|
|
490
402
|
error: commentError instanceof Error
|
|
491
403
|
? commentError.message
|
|
492
404
|
: String(commentError),
|
|
493
405
|
});
|
|
494
406
|
}
|
|
495
407
|
}
|
|
408
|
+
throw error;
|
|
496
409
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
task: 'issue-triage',
|
|
500
|
-
trigger: ctx.triggerType,
|
|
501
|
-
classification: fixAssessment?.classification ?? 'none',
|
|
502
|
-
attempted: false,
|
|
503
|
-
});
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
catch (error) {
|
|
507
|
-
logger.error('Issue triage task failed', {
|
|
508
|
-
task: 'issue-triage',
|
|
509
|
-
issueNumber: ctx.issueNumber,
|
|
510
|
-
trigger: ctx.triggerType,
|
|
511
|
-
error: error instanceof Error ? error.message : String(error),
|
|
512
|
-
durationMs: performance.now() - startedAt,
|
|
513
|
-
});
|
|
514
|
-
if (ctx.triggerType === 'auto' && !triageCommentPosted) {
|
|
515
|
-
await deleteAutoTriagePlaceholder({
|
|
410
|
+
finally {
|
|
411
|
+
await removeIssueCommentReactionIfPresent({
|
|
516
412
|
octokit: writeOctokit,
|
|
517
|
-
ctx,
|
|
518
|
-
|
|
413
|
+
owner: ctx.owner,
|
|
414
|
+
repo: ctx.repo,
|
|
415
|
+
commentId: ctx.commentId,
|
|
416
|
+
reactionId: ctx.reactionId,
|
|
417
|
+
warnMessage: 'Failed to remove issue triage reaction',
|
|
418
|
+
successMessage: 'Issue triage reaction removed',
|
|
419
|
+
logMeta: {
|
|
420
|
+
task: 'issue-triage',
|
|
421
|
+
reactionId: ctx.reactionId,
|
|
422
|
+
},
|
|
519
423
|
});
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
424
|
+
if (pendingFixMetric) {
|
|
425
|
+
await recordTaskMetrics({
|
|
426
|
+
type: 'fix',
|
|
427
|
+
repository: pendingFixMetric.repository,
|
|
428
|
+
model: TRIAGE_PROVIDER,
|
|
429
|
+
outcome: pendingFixMetric.outcome,
|
|
430
|
+
durationMs: performance.now() - pendingFixMetric.startedAt,
|
|
431
|
+
});
|
|
525
432
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
433
|
+
if (shouldRecordTriageMetric) {
|
|
434
|
+
await recordTaskMetrics({
|
|
435
|
+
type: 'triage',
|
|
436
|
+
repository: triageMetricRepository,
|
|
437
|
+
model: TRIAGE_PROVIDER,
|
|
438
|
+
outcome: triageMetricOutcome,
|
|
439
|
+
durationMs: triageDurationMs ?? performance.now() - startedAt,
|
|
531
440
|
});
|
|
532
441
|
}
|
|
533
442
|
}
|
|
534
|
-
|
|
535
|
-
}
|
|
536
|
-
finally {
|
|
537
|
-
await removeIssueCommentReactionIfPresent({
|
|
538
|
-
octokit: writeOctokit,
|
|
539
|
-
owner: ctx.owner,
|
|
540
|
-
repo: ctx.repo,
|
|
541
|
-
commentId: ctx.commentId,
|
|
542
|
-
reactionId: ctx.reactionId,
|
|
543
|
-
warnMessage: 'Failed to remove issue triage reaction',
|
|
544
|
-
successMessage: 'Issue triage reaction removed',
|
|
545
|
-
logMeta: {
|
|
546
|
-
task: 'issue-triage',
|
|
547
|
-
reactionId: ctx.reactionId,
|
|
548
|
-
},
|
|
549
|
-
});
|
|
550
|
-
logger.info('Issue triage task finalized', {
|
|
551
|
-
task: 'issue-triage',
|
|
552
|
-
issueNumber: ctx.issueNumber,
|
|
553
|
-
durationMs: performance.now() - startedAt,
|
|
554
|
-
});
|
|
555
|
-
if (pendingFixMetric) {
|
|
556
|
-
await recordTaskMetrics({
|
|
557
|
-
type: 'fix',
|
|
558
|
-
repository: pendingFixMetric.repository,
|
|
559
|
-
model: TRIAGE_PROVIDER,
|
|
560
|
-
outcome: pendingFixMetric.outcome,
|
|
561
|
-
durationMs: performance.now() - pendingFixMetric.startedAt,
|
|
562
|
-
});
|
|
563
|
-
}
|
|
564
|
-
if (shouldRecordTriageMetric) {
|
|
565
|
-
await recordTaskMetrics({
|
|
566
|
-
type: 'triage',
|
|
567
|
-
repository: triageMetricRepository,
|
|
568
|
-
model: TRIAGE_PROVIDER,
|
|
569
|
-
outcome: triageMetricOutcome,
|
|
570
|
-
durationMs: triageDurationMs ?? performance.now() - startedAt,
|
|
571
|
-
});
|
|
572
|
-
}
|
|
573
|
-
}
|
|
443
|
+
});
|
|
574
444
|
}
|
|
575
445
|
async function main() {
|
|
576
446
|
try {
|