@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.
Files changed (35) hide show
  1. package/dist/actions/auth.js +29 -9
  2. package/dist/actions/doctor.js +1 -1
  3. package/dist/actions/review.js +2 -10
  4. package/dist/config.js +49 -6
  5. package/package.json +1 -1
  6. package/sandbox/_node_modules/doistbot-repo-config/dist/index.d.ts +1 -1
  7. package/sandbox/_node_modules/doistbot-repo-config/dist/index.js +1 -1
  8. package/sandbox/dist/core/pi.js +31 -1
  9. package/sandbox/dist/core/prompt-template.js +45 -3
  10. package/sandbox/dist/core/repository-map.js +24 -0
  11. package/sandbox/dist/core/tracing.js +9 -0
  12. package/sandbox/dist/providers/helpers.js +51 -0
  13. package/sandbox/dist/providers/pi-review.js +7 -1
  14. package/sandbox/dist/tasks/chat/chat.js +13 -2
  15. package/sandbox/dist/tasks/issue-summarize/model.js +119 -48
  16. package/sandbox/dist/tasks/issue-summarize/prompt.js +2 -2
  17. package/sandbox/dist/tasks/issue-summarize/summarize.js +104 -88
  18. package/sandbox/dist/tasks/issue-triage/fix-attempt.js +2 -2
  19. package/sandbox/dist/tasks/issue-triage/hero-group-map.js +2 -0
  20. package/sandbox/dist/tasks/issue-triage/pr-creator.js +2 -2
  21. package/sandbox/dist/tasks/issue-triage/prompt.js +2 -2
  22. package/sandbox/dist/tasks/issue-triage/triage.js +2 -2
  23. package/sandbox/dist/tasks/persist-logs/llm-query-planner.js +3 -3
  24. package/sandbox/dist/tasks/review/engines/dedupe.js +107 -42
  25. package/sandbox/dist/tasks/review/engines/multi-focus.js +229 -119
  26. package/sandbox/dist/tasks/review/engines/shared.js +36 -1
  27. package/sandbox/dist/tasks/review/github-comment-format.js +12 -4
  28. package/sandbox/dist/tasks/review/local.js +6 -1
  29. package/sandbox/dist/tasks/review/review-summary.js +3 -0
  30. package/sandbox/dist/tasks/review/review.js +253 -186
  31. package/sandbox/dist/tasks/review/runner.js +10 -5
  32. package/sandbox/dist/tasks/review/summary-model.js +40 -18
  33. package/sandbox/node_modules/doistbot-repo-config/dist/index.d.ts +1 -1
  34. package/sandbox/node_modules/doistbot-repo-config/dist/index.js +1 -1
  35. 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
- const result = await model.invoke({
16
- prompt,
17
- timeoutMs: ISSUE_SUMMARIZE_TIMEOUT_MS,
18
- });
19
- if (!result.ok) {
20
- logger.warn('Issue summarize model request failed', {
21
- error: result.error,
22
- timedOut: result.timedOut,
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
- return null;
25
- }
26
- return result.output.trim() || null;
27
- }
28
- export async function invokeIssueSummarizeModel({ model, prompt, mediaImages, }) {
29
- if (mediaImages.length === 0) {
30
- logger.info('Using text-only summarize model request');
31
- return await invokeIssueSummarizeTextModel({ model, prompt });
32
- }
33
- logger.info('Using multimodal summarize model request', {
34
- mediaImages: mediaImages.length,
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
- try {
47
- const result = await model.invoke({
48
- modelName: ISSUE_SUMMARIZE_MULTIMODAL_MODEL,
49
- prompt: `${prompt}\n\n<media_context>\n${mediaContext}\n</media_context>`,
50
- timeoutMs: ISSUE_SUMMARIZE_TIMEOUT_MS,
51
- images: mediaImages.map((mediaImage) => buildPiImageContent({
52
- dataBase64: mediaImage.dataBase64,
53
- contentType: mediaImage.contentType,
54
- })),
55
- });
56
- if (!result.ok) {
57
- logger.warn('Issue summarize multimodal request failed', {
58
- error: result.error,
59
- timedOut: result.timedOut,
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
- return await invokeIssueSummarizeTextModel({ model, prompt });
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
- const text = result.output.trim();
65
- if (!text) {
66
- logger.warn('Issue summarize multimodal request returned empty output', {
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 await invokeIssueSummarizeTextModel({ model, prompt });
114
+ return null;
70
115
  }
71
- return text;
72
- }
73
- catch (error) {
74
- logger.warn('Issue summarize multimodal request errored', {
75
- error: error instanceof Error ? error.message : String(error),
76
- mediaImages: mediaImages.length,
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
- logger.info('Issue summarize task started', {
113
- dryRun: ctx.dryRun,
114
- issueNumber: ctx.issueNumber,
115
- });
116
- const { readOctokit, writeOctokit } = await createGitHubClients(ctx);
117
- try {
118
- const issueParams = {
119
- owner: ctx.owner,
120
- repo: ctx.repo,
121
- issueNumber: ctx.issueNumber,
122
- mediaType: { format: 'full' },
123
- };
124
- const [issue, comments] = await Promise.all([
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
- catch (error) {
183
- logger.error('Issue summarize task failed', {
184
- issueNumber: ctx.issueNumber,
185
- error: error instanceof Error ? error.message : String(error),
186
- });
187
- if (!ctx.dryRun) {
188
- try {
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: 'Sorry, I encountered an error while summarizing this issue. Please try again or contact the team maintaining the bot.',
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
- error: replyError instanceof Error ? replyError.message : String(replyError),
184
+ mediaReferences: mediaReferences.length,
200
185
  });
201
186
  }
202
187
  }
203
- process.exit(1);
204
- }
205
- finally {
206
- await removeIssueCommentReactionIfPresent({
207
- octokit: writeOctokit,
208
- owner: ctx.owner,
209
- repo: ctx.repo,
210
- commentId: ctx.commentId,
211
- reactionId: ctx.reactionId,
212
- warnMessage: 'Failed to remove summarize reaction',
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);