@doist/doistbot-cli 1.0.6 → 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 +29 -9
- package/dist/actions/doctor.js +1 -1
- package/dist/actions/review.js +2 -10
- package/dist/config.js +49 -6
- package/package.json +1 -1
- package/sandbox/_node_modules/doistbot-repo-config/dist/index.d.ts +1 -1
- package/sandbox/_node_modules/doistbot-repo-config/dist/index.js +1 -1
- package/sandbox/dist/core/pi.js +31 -1
- package/sandbox/dist/core/prompt-template.js +45 -3
- package/sandbox/dist/core/repository-map.js +24 -0
- package/sandbox/dist/core/tracing.js +9 -0
- package/sandbox/dist/providers/helpers.js +51 -0
- package/sandbox/dist/providers/pi-review.js +7 -1
- package/sandbox/dist/tasks/chat/chat.js +13 -2
- package/sandbox/dist/tasks/issue-summarize/model.js +119 -48
- 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/fix-attempt.js +2 -2
- package/sandbox/dist/tasks/issue-triage/hero-group-map.js +2 -0
- package/sandbox/dist/tasks/issue-triage/pr-creator.js +2 -2
- package/sandbox/dist/tasks/issue-triage/prompt.js +2 -2
- package/sandbox/dist/tasks/issue-triage/triage.js +2 -2
- package/sandbox/dist/tasks/persist-logs/llm-query-planner.js +3 -3
- package/sandbox/dist/tasks/review/engines/dedupe.js +107 -42
- package/sandbox/dist/tasks/review/engines/multi-focus.js +229 -119
- package/sandbox/dist/tasks/review/engines/shared.js +36 -1
- package/sandbox/dist/tasks/review/github-comment-format.js +12 -4
- package/sandbox/dist/tasks/review/local.js +6 -1
- package/sandbox/dist/tasks/review/review-summary.js +3 -0
- package/sandbox/dist/tasks/review/review.js +253 -186
- package/sandbox/dist/tasks/review/runner.js +10 -5
- package/sandbox/dist/tasks/review/summary-model.js +40 -18
- package/sandbox/node_modules/doistbot-repo-config/dist/index.d.ts +1 -1
- package/sandbox/node_modules/doistbot-repo-config/dist/index.js +1 -1
- package/sandbox/src/tasks/review/prompts/review-multi-focus-base-prompt.md +1 -0
|
@@ -1,7 +1,19 @@
|
|
|
1
|
+
import { SpanStatusCode } from '@opentelemetry/api';
|
|
1
2
|
import { logger } from '../../core/logging.js';
|
|
2
|
-
import { buildPiImageContent, createPiInvoker } from '../../core/pi.js';
|
|
3
|
+
import { buildPiImageContent, createPiInvoker, DEFAULT_OPENROUTER_MODELS } from '../../core/pi.js';
|
|
4
|
+
import { withPhase } from '../../core/tracing.js';
|
|
3
5
|
const ISSUE_SUMMARIZE_TIMEOUT_MS = 5 * 60 * 1000;
|
|
4
6
|
const ISSUE_SUMMARIZE_MULTIMODAL_MODEL = 'google/gemini-2.5-pro';
|
|
7
|
+
const SUMMARIZE_TEXT_PROVIDER = 'zai';
|
|
8
|
+
// Single source of truth: inherit the repo-wide ZAI default so a default-model
|
|
9
|
+
// bump in core/pi.ts moves summarize too, and the executed model matches the
|
|
10
|
+
// recorded gen_ai.request.model.
|
|
11
|
+
const SUMMARIZE_TEXT_MODEL = DEFAULT_OPENROUTER_MODELS.zai;
|
|
12
|
+
const SUMMARIZE_MULTIMODAL_PROVIDER = 'gcp.gemini';
|
|
13
|
+
// One model span per actual model.invoke. The final summary uses
|
|
14
|
+
// `issue-summarize.model`; each chunk summary uses `issue-summarize.chunk`.
|
|
15
|
+
const SUMMARIZE_MODEL_PHASE = 'issue-summarize.model';
|
|
16
|
+
const SUMMARIZE_CHUNK_PHASE = 'issue-summarize.chunk';
|
|
5
17
|
export function createIssueSummarizeModel(apiKey) {
|
|
6
18
|
return createPiInvoker({
|
|
7
19
|
provider: 'zai',
|
|
@@ -11,28 +23,40 @@ export function createIssueSummarizeModel(apiKey) {
|
|
|
11
23
|
capabilityPreset: 'read-only',
|
|
12
24
|
});
|
|
13
25
|
}
|
|
14
|
-
async function invokeIssueSummarizeTextModel({ model, prompt, }) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
async function invokeIssueSummarizeTextModel({ model, prompt, phaseName, }) {
|
|
27
|
+
return withPhase(phaseName, {
|
|
28
|
+
'gen_ai.operation.name': 'chat',
|
|
29
|
+
'gen_ai.provider.name': SUMMARIZE_TEXT_PROVIDER,
|
|
30
|
+
'gen_ai.request.model': SUMMARIZE_TEXT_MODEL,
|
|
31
|
+
}, async (span) => {
|
|
32
|
+
const result = await model.invoke({
|
|
33
|
+
prompt,
|
|
34
|
+
modelName: SUMMARIZE_TEXT_MODEL,
|
|
35
|
+
timeoutMs: ISSUE_SUMMARIZE_TIMEOUT_MS,
|
|
23
36
|
});
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
37
|
+
if (!result.ok) {
|
|
38
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: result.error });
|
|
39
|
+
logger.warn('Issue summarize model request failed', {
|
|
40
|
+
error: result.error,
|
|
41
|
+
timedOut: result.timedOut,
|
|
42
|
+
});
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
const output = result.output.trim() || null;
|
|
46
|
+
if (!output) {
|
|
47
|
+
span.setStatus({
|
|
48
|
+
code: SpanStatusCode.ERROR,
|
|
49
|
+
message: 'summarize model produced no output',
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return output;
|
|
35
53
|
});
|
|
54
|
+
}
|
|
55
|
+
// One model span for the multimodal model.invoke (provider/model known at
|
|
56
|
+
// start). On failure it returns null and the caller falls back to a separate
|
|
57
|
+
// text request — which emits its own model span, so a fallback shows two real
|
|
58
|
+
// model calls rather than one.
|
|
59
|
+
async function invokeMultimodalSummarizeModel({ model, prompt, mediaImages, }) {
|
|
36
60
|
const mediaContext = mediaImages
|
|
37
61
|
.map((mediaImage, index) => [
|
|
38
62
|
`Media item ${index + 1} of ${mediaImages.length}`,
|
|
@@ -43,40 +67,72 @@ export async function invokeIssueSummarizeModel({ model, prompt, mediaImages, })
|
|
|
43
67
|
mediaImage.sourceContext,
|
|
44
68
|
].join('\n'))
|
|
45
69
|
.join('\n\n');
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
mediaImages: mediaImages.length,
|
|
70
|
+
return withPhase(SUMMARIZE_MODEL_PHASE, {
|
|
71
|
+
'gen_ai.operation.name': 'chat',
|
|
72
|
+
'gen_ai.provider.name': SUMMARIZE_MULTIMODAL_PROVIDER,
|
|
73
|
+
'gen_ai.request.model': ISSUE_SUMMARIZE_MULTIMODAL_MODEL,
|
|
74
|
+
}, async (span) => {
|
|
75
|
+
try {
|
|
76
|
+
const result = await model.invoke({
|
|
77
|
+
modelName: ISSUE_SUMMARIZE_MULTIMODAL_MODEL,
|
|
78
|
+
prompt: `${prompt}\n\n<media_context>\n${mediaContext}\n</media_context>`,
|
|
79
|
+
timeoutMs: ISSUE_SUMMARIZE_TIMEOUT_MS,
|
|
80
|
+
images: mediaImages.map((mediaImage) => buildPiImageContent({
|
|
81
|
+
dataBase64: mediaImage.dataBase64,
|
|
82
|
+
contentType: mediaImage.contentType,
|
|
83
|
+
})),
|
|
61
84
|
});
|
|
62
|
-
|
|
85
|
+
if (!result.ok) {
|
|
86
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: result.error });
|
|
87
|
+
logger.warn('Issue summarize multimodal request failed', {
|
|
88
|
+
error: result.error,
|
|
89
|
+
timedOut: result.timedOut,
|
|
90
|
+
mediaImages: mediaImages.length,
|
|
91
|
+
});
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
const text = result.output.trim();
|
|
95
|
+
if (!text) {
|
|
96
|
+
span.setStatus({
|
|
97
|
+
code: SpanStatusCode.ERROR,
|
|
98
|
+
message: 'multimodal summarize produced no output',
|
|
99
|
+
});
|
|
100
|
+
logger.warn('Issue summarize multimodal request returned empty output', {
|
|
101
|
+
mediaImages: mediaImages.length,
|
|
102
|
+
});
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
return text;
|
|
63
106
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
107
|
+
catch (error) {
|
|
108
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
109
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message });
|
|
110
|
+
logger.warn('Issue summarize multimodal request errored', {
|
|
111
|
+
error: message,
|
|
67
112
|
mediaImages: mediaImages.length,
|
|
68
113
|
});
|
|
69
|
-
return
|
|
114
|
+
return null;
|
|
70
115
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
export async function invokeIssueSummarizeModel({ model, prompt, mediaImages, }) {
|
|
119
|
+
if (mediaImages.length === 0) {
|
|
120
|
+
logger.info('Using text-only summarize model request');
|
|
121
|
+
return await invokeIssueSummarizeTextModel({
|
|
122
|
+
model,
|
|
123
|
+
prompt,
|
|
124
|
+
phaseName: SUMMARIZE_MODEL_PHASE,
|
|
77
125
|
});
|
|
78
|
-
return await invokeIssueSummarizeTextModel({ model, prompt });
|
|
79
126
|
}
|
|
127
|
+
logger.info('Using multimodal summarize model request', {
|
|
128
|
+
mediaImages: mediaImages.length,
|
|
129
|
+
});
|
|
130
|
+
const multimodal = await invokeMultimodalSummarizeModel({ model, prompt, mediaImages });
|
|
131
|
+
if (multimodal) {
|
|
132
|
+
return multimodal;
|
|
133
|
+
}
|
|
134
|
+
// Multimodal failed — fall back to a separate text request (own model span).
|
|
135
|
+
return await invokeIssueSummarizeTextModel({ model, prompt, phaseName: SUMMARIZE_MODEL_PHASE });
|
|
80
136
|
}
|
|
81
137
|
function buildChunkSummaryPrompt({ chunk, chunkIndex, selectedChunkCount, totalChunks, }) {
|
|
82
138
|
return `You are preparing an intermediate summary for a GitHub issue discussion.
|
|
@@ -97,6 +153,14 @@ ${chunk}
|
|
|
97
153
|
</chunk_content>`;
|
|
98
154
|
}
|
|
99
155
|
export async function summarizeChunks({ model, chunks, totalChunks, }) {
|
|
156
|
+
// Workflow phase: it orchestrates one model request PER chunk, so it is not
|
|
157
|
+
// itself a single gen-AI operation and carries no gen_ai.* attrs.
|
|
158
|
+
return withPhase('issue-summarize.chunks', {
|
|
159
|
+
'chunks.selected': chunks.length,
|
|
160
|
+
'chunks.total': totalChunks,
|
|
161
|
+
}, async (span) => summarizeChunksInner({ model, chunks, totalChunks }, span));
|
|
162
|
+
}
|
|
163
|
+
async function summarizeChunksInner({ model, chunks, totalChunks, }, span) {
|
|
100
164
|
const summaries = [];
|
|
101
165
|
let missingChunkSummaries = 0;
|
|
102
166
|
logger.info('Summarizing issue thread in chunks', {
|
|
@@ -113,6 +177,7 @@ export async function summarizeChunks({ model, chunks, totalChunks, }) {
|
|
|
113
177
|
const chunkSummary = await invokeIssueSummarizeTextModel({
|
|
114
178
|
model,
|
|
115
179
|
prompt: chunkPrompt,
|
|
180
|
+
phaseName: SUMMARIZE_CHUNK_PHASE,
|
|
116
181
|
});
|
|
117
182
|
if (chunkSummary?.trim()) {
|
|
118
183
|
summaries.push(`### Chunk ${index + 1}\n${chunkSummary.trim()}`);
|
|
@@ -121,7 +186,13 @@ export async function summarizeChunks({ model, chunks, totalChunks, }) {
|
|
|
121
186
|
missingChunkSummaries += 1;
|
|
122
187
|
}
|
|
123
188
|
}
|
|
189
|
+
span.setAttributes({ 'chunks.summarized': summaries.length });
|
|
124
190
|
if (summaries.length === 0) {
|
|
191
|
+
// No chunk produced a model summary — we fall back to raw chunk content.
|
|
192
|
+
span.setStatus({
|
|
193
|
+
code: SpanStatusCode.ERROR,
|
|
194
|
+
message: 'chunk summarization produced no model output',
|
|
195
|
+
});
|
|
125
196
|
logger.warn('Chunk summarization returned no model output, using raw chunk content', {
|
|
126
197
|
selectedChunks: chunks.length,
|
|
127
198
|
totalChunks,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { fillPromptTemplate, loadPromptTemplateFile } from '../../core/prompt-template.js';
|
|
1
|
+
import { fillPromptTemplate, loadPromptTemplateFile, resolvePromptFile, } from '../../core/prompt-template.js';
|
|
2
2
|
const PROMPT_FILE = 'issue-summarize-prompt.md';
|
|
3
3
|
export function loadPromptTemplate() {
|
|
4
4
|
return loadPromptTemplateFile({
|
|
5
|
-
fileName: PROMPT_FILE,
|
|
5
|
+
fileName: resolvePromptFile(import.meta.url, PROMPT_FILE),
|
|
6
6
|
promptLabel: 'issue summarize',
|
|
7
7
|
});
|
|
8
8
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { SpanStatusCode } from '@opentelemetry/api';
|
|
1
2
|
import { App } from 'octokit';
|
|
2
3
|
import { fetchIssue, fetchIssueComments } from '../../core/github-issues.js';
|
|
3
4
|
import { logger } from '../../core/logging.js';
|
|
4
5
|
import { removeIssueCommentReactionIfPresent } from '../../core/reactions.js';
|
|
5
6
|
import { resolveWriteOctokit } from '../../core/shared.js';
|
|
7
|
+
import { withPhase } from '../../core/tracing.js';
|
|
6
8
|
import { parseSummarizeContext } from './context.js';
|
|
7
9
|
import { buildMediaPromptImages, extractMediaReferences, formatMediaContext, inspectMediaReferences, } from './media.js';
|
|
8
10
|
import { createIssueSummarizeModel, invokeIssueSummarizeModel, summarizeChunks } from './model.js';
|
|
@@ -109,108 +111,122 @@ async function main() {
|
|
|
109
111
|
repository: `${ctx.owner}/${ctx.repo}`,
|
|
110
112
|
issueNumber: ctx.issueNumber,
|
|
111
113
|
});
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
fetchIssue(readOctokit, issueParams),
|
|
126
|
-
fetchIssueComments(readOctokit, issueParams),
|
|
127
|
-
]);
|
|
128
|
-
logger.info('Loaded issue summarize context', {
|
|
129
|
-
issueState: issue.state ?? 'unknown',
|
|
130
|
-
commentsCount: comments.length,
|
|
131
|
-
});
|
|
132
|
-
const entries = buildThreadEntries({
|
|
133
|
-
issue,
|
|
134
|
-
comments,
|
|
135
|
-
triggerCommentId: ctx.commentId,
|
|
136
|
-
});
|
|
137
|
-
logger.info('Prepared issue summarize thread entries', {
|
|
138
|
-
issueNumber: issue.number,
|
|
139
|
-
entryCount: entries.length,
|
|
140
|
-
});
|
|
141
|
-
const mediaReferences = extractMediaReferences(entries);
|
|
142
|
-
const mediaInspections = await inspectMediaReferences(mediaReferences);
|
|
143
|
-
const mediaInspectionStats = summarizeMediaInspectionStats(mediaInspections);
|
|
144
|
-
logger.info('Inspected issue summarize media references', {
|
|
145
|
-
mediaReferences: mediaReferences.length,
|
|
146
|
-
mediaInspections: mediaInspections.length,
|
|
147
|
-
mediaFetched: mediaInspectionStats.fetched,
|
|
148
|
-
mediaFetchFailed: mediaInspectionStats.fetchFailed,
|
|
149
|
-
mediaNotImage: mediaInspectionStats.notMedia,
|
|
150
|
-
});
|
|
151
|
-
const summary = await summarizeIssue({
|
|
152
|
-
ctx,
|
|
153
|
-
issue,
|
|
154
|
-
entries,
|
|
155
|
-
mediaReferences,
|
|
156
|
-
mediaInspections,
|
|
157
|
-
});
|
|
158
|
-
logger.info('Generated issue summary', {
|
|
159
|
-
issueNumber: ctx.issueNumber,
|
|
160
|
-
summaryChars: summary.length,
|
|
161
|
-
});
|
|
162
|
-
const summaryComment = `${SUMMARY_MARKER}\n\n${summary}`;
|
|
163
|
-
if (ctx.dryRun) {
|
|
164
|
-
logger.info('Dry run enabled, printing summarize output instead of posting comment', {
|
|
165
|
-
issueNumber: ctx.issueNumber,
|
|
166
|
-
});
|
|
167
|
-
process.stdout.write(`${summaryComment}\n`);
|
|
168
|
-
}
|
|
169
|
-
else {
|
|
170
|
-
await writeOctokit.rest.issues.createComment({
|
|
114
|
+
let exitCode = 0;
|
|
115
|
+
await withPhase('issue-summarize', {
|
|
116
|
+
task: 'issue-summarize',
|
|
117
|
+
'gen_ai.operation.name': 'invoke_agent',
|
|
118
|
+
'gen_ai.agent.name': 'doistbot.issue-summarize',
|
|
119
|
+
'issue.number': ctx.issueNumber,
|
|
120
|
+
'comment.id': ctx.commentId,
|
|
121
|
+
dry_run: ctx.dryRun,
|
|
122
|
+
}, async (span) => {
|
|
123
|
+
// Inside the span so auth/setup failures are captured by the root span.
|
|
124
|
+
const { readOctokit, writeOctokit } = await createGitHubClients(ctx);
|
|
125
|
+
try {
|
|
126
|
+
const issueParams = {
|
|
171
127
|
owner: ctx.owner,
|
|
172
128
|
repo: ctx.repo,
|
|
173
|
-
issue_number: ctx.issueNumber,
|
|
174
|
-
body: summaryComment,
|
|
175
|
-
});
|
|
176
|
-
logger.info('Posted issue summary comment', {
|
|
177
129
|
issueNumber: ctx.issueNumber,
|
|
130
|
+
mediaType: { format: 'full' },
|
|
131
|
+
};
|
|
132
|
+
const [issue, comments] = await Promise.all([
|
|
133
|
+
fetchIssue(readOctokit, issueParams),
|
|
134
|
+
fetchIssueComments(readOctokit, issueParams),
|
|
135
|
+
]);
|
|
136
|
+
logger.info('Loaded issue summarize context', {
|
|
137
|
+
issueState: issue.state ?? 'unknown',
|
|
138
|
+
commentsCount: comments.length,
|
|
139
|
+
});
|
|
140
|
+
const entries = buildThreadEntries({
|
|
141
|
+
issue,
|
|
142
|
+
comments,
|
|
143
|
+
triggerCommentId: ctx.commentId,
|
|
144
|
+
});
|
|
145
|
+
logger.info('Prepared issue summarize thread entries', {
|
|
146
|
+
issueNumber: issue.number,
|
|
147
|
+
entryCount: entries.length,
|
|
148
|
+
});
|
|
149
|
+
const mediaReferences = extractMediaReferences(entries);
|
|
150
|
+
const mediaInspections = await inspectMediaReferences(mediaReferences);
|
|
151
|
+
const mediaInspectionStats = summarizeMediaInspectionStats(mediaInspections);
|
|
152
|
+
logger.info('Inspected issue summarize media references', {
|
|
178
153
|
mediaReferences: mediaReferences.length,
|
|
154
|
+
mediaInspections: mediaInspections.length,
|
|
155
|
+
mediaFetched: mediaInspectionStats.fetched,
|
|
156
|
+
mediaFetchFailed: mediaInspectionStats.fetchFailed,
|
|
157
|
+
mediaNotImage: mediaInspectionStats.notMedia,
|
|
179
158
|
});
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
159
|
+
const summary = await summarizeIssue({
|
|
160
|
+
ctx,
|
|
161
|
+
issue,
|
|
162
|
+
entries,
|
|
163
|
+
mediaReferences,
|
|
164
|
+
mediaInspections,
|
|
165
|
+
});
|
|
166
|
+
logger.info('Generated issue summary', {
|
|
167
|
+
issueNumber: ctx.issueNumber,
|
|
168
|
+
summaryChars: summary.length,
|
|
169
|
+
});
|
|
170
|
+
const summaryComment = `${SUMMARY_MARKER}\n\n${summary}`;
|
|
171
|
+
if (ctx.dryRun) {
|
|
172
|
+
logger.info('Dry run enabled, printing summarize output instead of posting comment', { issueNumber: ctx.issueNumber });
|
|
173
|
+
process.stdout.write(`${summaryComment}\n`);
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
189
176
|
await writeOctokit.rest.issues.createComment({
|
|
190
177
|
owner: ctx.owner,
|
|
191
178
|
repo: ctx.repo,
|
|
192
179
|
issue_number: ctx.issueNumber,
|
|
193
|
-
body:
|
|
180
|
+
body: summaryComment,
|
|
194
181
|
});
|
|
195
|
-
|
|
196
|
-
catch (replyError) {
|
|
197
|
-
logger.warn('Failed to post summarize error response', {
|
|
182
|
+
logger.info('Posted issue summary comment', {
|
|
198
183
|
issueNumber: ctx.issueNumber,
|
|
199
|
-
|
|
184
|
+
mediaReferences: mediaReferences.length,
|
|
200
185
|
});
|
|
201
186
|
}
|
|
202
187
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
188
|
+
catch (error) {
|
|
189
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
190
|
+
logger.error('Issue summarize task failed', {
|
|
191
|
+
issueNumber: ctx.issueNumber,
|
|
192
|
+
error: message,
|
|
193
|
+
});
|
|
194
|
+
// Set the span status before the run exits; deferring process.exit
|
|
195
|
+
// until after withPhase lets phase.end flush.
|
|
196
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message });
|
|
197
|
+
if (!ctx.dryRun) {
|
|
198
|
+
try {
|
|
199
|
+
await writeOctokit.rest.issues.createComment({
|
|
200
|
+
owner: ctx.owner,
|
|
201
|
+
repo: ctx.repo,
|
|
202
|
+
issue_number: ctx.issueNumber,
|
|
203
|
+
body: 'Sorry, I encountered an error while summarizing this issue. Please try again or contact the team maintaining the bot.',
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
catch (replyError) {
|
|
207
|
+
logger.warn('Failed to post summarize error response', {
|
|
208
|
+
issueNumber: ctx.issueNumber,
|
|
209
|
+
error: replyError instanceof Error
|
|
210
|
+
? replyError.message
|
|
211
|
+
: String(replyError),
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
exitCode = 1;
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
await removeIssueCommentReactionIfPresent({
|
|
219
|
+
octokit: writeOctokit,
|
|
220
|
+
owner: ctx.owner,
|
|
221
|
+
repo: ctx.repo,
|
|
222
|
+
commentId: ctx.commentId,
|
|
223
|
+
reactionId: ctx.reactionId,
|
|
224
|
+
warnMessage: 'Failed to remove summarize reaction',
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
if (exitCode !== 0) {
|
|
229
|
+
process.exit(exitCode);
|
|
214
230
|
}
|
|
215
231
|
}
|
|
216
232
|
if (process.env.VITEST !== 'true') {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execFileSync } from 'child_process';
|
|
2
2
|
import { logger } from '../../core/logging.js';
|
|
3
|
-
import { fillPromptTemplate, loadPromptTemplateFile } from '../../core/prompt-template.js';
|
|
3
|
+
import { fillPromptTemplate, loadPromptTemplateFile, resolvePromptFile, } from '../../core/prompt-template.js';
|
|
4
4
|
import { validateDiff } from './diff-validator.js';
|
|
5
5
|
import { CODEX_CANNOT_FIX_REASON, FIX_LOOP_FAILED_REASON_PREFIX } from './fix-failure-reasons.js';
|
|
6
6
|
import { CX_HERO_MANUAL_HANDLE, resolveHeroGroup } from './hero-group-map.js';
|
|
@@ -24,7 +24,7 @@ export function buildFixAssessmentSkipReason(assessment) {
|
|
|
24
24
|
}
|
|
25
25
|
function loadFixPromptTemplate() {
|
|
26
26
|
return loadPromptTemplateFile({
|
|
27
|
-
fileName: FIX_PROMPT_FILE,
|
|
27
|
+
fileName: resolvePromptFile(import.meta.url, FIX_PROMPT_FILE),
|
|
28
28
|
promptLabel: 'issue triage fix',
|
|
29
29
|
});
|
|
30
30
|
}
|
|
@@ -42,8 +42,10 @@ const TEAM_LABEL_TO_HERO = {
|
|
|
42
42
|
const SURFACE_TO_HERO = {
|
|
43
43
|
android: 'android-hero',
|
|
44
44
|
backend: 'backend-hero',
|
|
45
|
+
cli: 'frontend-hero',
|
|
45
46
|
desktop: 'desktop-hero',
|
|
46
47
|
ios: 'apple-hero',
|
|
48
|
+
mcp: 'frontend-hero',
|
|
47
49
|
web: 'frontend-hero',
|
|
48
50
|
};
|
|
49
51
|
export const CX_HERO_MANUAL_HANDLE = HERO_GROUPS['cx-hero'].mention;
|
|
@@ -3,7 +3,7 @@ import fs from 'fs';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { DOISTBOT_AUTO_FIX_LABEL, DOISTBOT_SPECULATIVE_FIX_LABEL } from 'doistbot-shared-contracts';
|
|
5
5
|
import { logger } from '../../core/logging.js';
|
|
6
|
-
import { fillPromptTemplate, loadPromptTemplateFile } from '../../core/prompt-template.js';
|
|
6
|
+
import { fillPromptTemplate, loadPromptTemplateFile, resolvePromptFile, } from '../../core/prompt-template.js';
|
|
7
7
|
const PR_TEMPLATE_PATHS = [
|
|
8
8
|
'.github/pull_request_template.md',
|
|
9
9
|
'.github/PULL_REQUEST_TEMPLATE.md',
|
|
@@ -196,7 +196,7 @@ function getConfidenceQualifier(classification, confidence) {
|
|
|
196
196
|
const PR_BODY_TEMPLATE_FILE = 'auto-fix-pr-body-template.md';
|
|
197
197
|
function buildPRBody({ issueNumber, issueUrl, triageAnalysis, fixDescription, fixAssessment, }) {
|
|
198
198
|
const template = loadPromptTemplateFile({
|
|
199
|
-
fileName: PR_BODY_TEMPLATE_FILE,
|
|
199
|
+
fileName: resolvePromptFile(import.meta.url, PR_BODY_TEMPLATE_FILE),
|
|
200
200
|
promptLabel: 'auto-fix PR body',
|
|
201
201
|
});
|
|
202
202
|
let confidenceLine = '';
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { fillPromptTemplate, loadPromptTemplateFile } from '../../core/prompt-template.js';
|
|
1
|
+
import { fillPromptTemplate, loadPromptTemplateFile, resolvePromptFile, } from '../../core/prompt-template.js';
|
|
2
2
|
const PROMPT_FILE = 'issue-triage-prompt.md';
|
|
3
3
|
export function loadPromptTemplate() {
|
|
4
4
|
return loadPromptTemplateFile({
|
|
5
|
-
fileName: PROMPT_FILE,
|
|
5
|
+
fileName: resolvePromptFile(import.meta.url, PROMPT_FILE),
|
|
6
6
|
promptLabel: 'issue triage',
|
|
7
7
|
});
|
|
8
8
|
}
|
|
@@ -3,7 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import { SpanStatusCode } from '@opentelemetry/api';
|
|
4
4
|
import { recordTaskMetrics } from '../../core/datadog-metrics.js';
|
|
5
5
|
import { logger } from '../../core/logging.js';
|
|
6
|
-
import { loadPromptTemplateFile } from '../../core/prompt-template.js';
|
|
6
|
+
import { loadPromptTemplateFile, resolvePromptFile } from '../../core/prompt-template.js';
|
|
7
7
|
import { removeIssueCommentReactionIfPresent } from '../../core/reactions.js';
|
|
8
8
|
import { REPOS_SUBDIR, WORKSPACE_DIR } from '../../core/shared.js';
|
|
9
9
|
import { withPhase } from '../../core/tracing.js';
|
|
@@ -79,7 +79,7 @@ function buildPrompt({ ctx, issue, comments, labelNames, clonedRepositories, dec
|
|
|
79
79
|
OUTPUT_CONTRACT: outputContract,
|
|
80
80
|
FIX_ASSESSMENT_INSTRUCTIONS: enableAutoFix
|
|
81
81
|
? loadPromptTemplateFile({
|
|
82
|
-
fileName: 'fix-assessment-instructions.md',
|
|
82
|
+
fileName: resolvePromptFile(import.meta.url, 'fix-assessment-instructions.md'),
|
|
83
83
|
promptLabel: 'fix-assessment-instructions',
|
|
84
84
|
})
|
|
85
85
|
: '',
|
|
@@ -3,17 +3,17 @@ import path from 'path';
|
|
|
3
3
|
import { logger } from '../../core/logging.js';
|
|
4
4
|
import { invokePiPrompt } from '../../core/pi.js';
|
|
5
5
|
import { scrubPii } from '../../core/pii-scrubber.js';
|
|
6
|
-
import { loadPromptTemplateFile } from '../../core/prompt-template.js';
|
|
6
|
+
import { loadPromptTemplateFile, resolvePromptFile } from '../../core/prompt-template.js';
|
|
7
7
|
import { startDdProxy } from './dd-proxy.js';
|
|
8
8
|
export const PERSIST_LOGS_PROVIDER = 'openai';
|
|
9
9
|
const TIMEOUT_MS = 20 * 60 * 1000; // 20 minutes — investigation can take a while
|
|
10
10
|
function buildPrompt(params, proxyPort) {
|
|
11
11
|
const ddApiReference = loadPromptTemplateFile({
|
|
12
|
-
fileName: 'persist-logs-dd-api-reference.md',
|
|
12
|
+
fileName: resolvePromptFile(import.meta.url, 'persist-logs-dd-api-reference.md'),
|
|
13
13
|
promptLabel: 'persist-logs DD API reference',
|
|
14
14
|
});
|
|
15
15
|
const template = loadPromptTemplateFile({
|
|
16
|
-
fileName: 'persist-logs-investigation-prompt.md',
|
|
16
|
+
fileName: resolvePromptFile(import.meta.url, 'persist-logs-investigation-prompt.md'),
|
|
17
17
|
promptLabel: 'persist-logs investigation prompt',
|
|
18
18
|
});
|
|
19
19
|
const scrubbedTitle = scrubPii(params.issueTitle);
|