@todoist/doistbot-cli 0.0.0-stage → 1.0.16

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 (185) hide show
  1. package/dist/actions/auth.js +225 -0
  2. package/dist/actions/doctor.js +39 -0
  3. package/dist/actions/review-usage.js +25 -0
  4. package/dist/actions/review.js +311 -0
  5. package/dist/actions/setup.js +14 -0
  6. package/dist/auth.js +202 -0
  7. package/dist/config.js +145 -0
  8. package/dist/env.js +10 -0
  9. package/dist/file-store.js +112 -0
  10. package/dist/index.js +164 -0
  11. package/dist/input.js +40 -0
  12. package/dist/runtime.js +105 -0
  13. package/dist/secure-store.js +148 -0
  14. package/dist/terminal.js +185 -0
  15. package/dist/test-utils/secure-store-mocks.js +29 -0
  16. package/dist/update-cache-entrypoint.js +16 -0
  17. package/dist/update-check.js +235 -0
  18. package/package.json +65 -5
  19. package/sandbox/_node_modules/doistbot-logging/dist/index.d.ts +23 -0
  20. package/sandbox/_node_modules/doistbot-logging/dist/index.js +51 -0
  21. package/sandbox/_node_modules/doistbot-logging/package.json +12 -0
  22. package/sandbox/_node_modules/doistbot-repo-config/dist/index.d.ts +93 -0
  23. package/sandbox/_node_modules/doistbot-repo-config/dist/index.js +197 -0
  24. package/sandbox/_node_modules/doistbot-repo-config/package.json +12 -0
  25. package/sandbox/dist/core/autofix-resources.js +58 -0
  26. package/sandbox/dist/core/autofix-service-query.js +69 -0
  27. package/sandbox/dist/core/autofix-test-policy.js +11 -0
  28. package/sandbox/dist/core/autofix-verification.js +355 -0
  29. package/sandbox/dist/core/bot-logins.js +14 -0
  30. package/sandbox/dist/core/check-run.js +158 -0
  31. package/sandbox/dist/core/datadog-metrics.js +113 -0
  32. package/sandbox/dist/core/environment.js +49 -0
  33. package/sandbox/dist/core/git-auth.js +17 -0
  34. package/sandbox/dist/core/github-history.js +4 -0
  35. package/sandbox/dist/core/github-issues.js +18 -0
  36. package/sandbox/dist/core/label-parsing.js +1 -0
  37. package/sandbox/dist/core/langfuse-observation.js +33 -0
  38. package/sandbox/dist/core/langfuse-session.js +39 -0
  39. package/sandbox/dist/core/logger-span-processor.js +90 -0
  40. package/sandbox/dist/core/logging.js +18 -0
  41. package/sandbox/dist/core/pi-autofix-extension.js +188 -0
  42. package/sandbox/dist/core/pi-execution-limits-extension.js +79 -0
  43. package/sandbox/dist/core/pi-progress-logging.js +51 -0
  44. package/sandbox/dist/core/pi-tool-output-filter-extension.js +65 -0
  45. package/sandbox/dist/core/pi.js +870 -0
  46. package/sandbox/dist/core/pii-scrubber.js +31 -0
  47. package/sandbox/dist/core/prompt-template.js +63 -0
  48. package/sandbox/dist/core/reactions.js +36 -0
  49. package/sandbox/dist/core/regexp.js +3 -0
  50. package/sandbox/dist/core/repository-map.js +1 -0
  51. package/sandbox/dist/core/repository-resolver.js +1 -0
  52. package/sandbox/dist/core/retry.js +29 -0
  53. package/sandbox/dist/core/s3-utils.js +42 -0
  54. package/sandbox/dist/core/shared.js +124 -0
  55. package/sandbox/dist/core/snapshot-helpers.js +17 -0
  56. package/sandbox/dist/core/span-test-helpers.js +13 -0
  57. package/sandbox/dist/core/tool-environment.js +83 -0
  58. package/sandbox/dist/core/tool-output.js +18 -0
  59. package/sandbox/dist/core/tracing-setup.js +92 -0
  60. package/sandbox/dist/core/tracing.js +81 -0
  61. package/sandbox/dist/main.js +51 -0
  62. package/sandbox/dist/providers/config.js +23 -0
  63. package/sandbox/dist/providers/credentials.js +40 -0
  64. package/sandbox/dist/providers/gemini.js +28 -0
  65. package/sandbox/dist/providers/helpers.js +253 -0
  66. package/sandbox/dist/providers/index.js +27 -0
  67. package/sandbox/dist/providers/openai.js +23 -0
  68. package/sandbox/dist/providers/openrouter-pi.js +25 -0
  69. package/sandbox/dist/providers/pi-review.js +301 -0
  70. package/sandbox/dist/providers/process.js +34 -0
  71. package/sandbox/dist/providers/schemas.js +61 -0
  72. package/sandbox/dist/providers/types.js +4 -0
  73. package/sandbox/dist/tasks/chat/chat.js +440 -0
  74. package/sandbox/dist/tasks/chat/prompt.js +20 -0
  75. package/sandbox/dist/tasks/issue-fix-retry/fix-retry.js +304 -0
  76. package/sandbox/dist/tasks/issue-summarize/context.js +21 -0
  77. package/sandbox/dist/tasks/issue-summarize/media.js +335 -0
  78. package/sandbox/dist/tasks/issue-summarize/model.js +230 -0
  79. package/sandbox/dist/tasks/issue-summarize/output.js +45 -0
  80. package/sandbox/dist/tasks/issue-summarize/prompt.js +11 -0
  81. package/sandbox/dist/tasks/issue-summarize/summarize.js +238 -0
  82. package/sandbox/dist/tasks/issue-summarize/thread.js +123 -0
  83. package/sandbox/dist/tasks/issue-summarize/types.js +1 -0
  84. package/sandbox/dist/tasks/issue-triage/assignee-review.js +114 -0
  85. package/sandbox/dist/tasks/issue-triage/autofix-capabilities.js +22 -0
  86. package/sandbox/dist/tasks/issue-triage/autofix-checkpoint.js +122 -0
  87. package/sandbox/dist/tasks/issue-triage/autofix-policy.js +36 -0
  88. package/sandbox/dist/tasks/issue-triage/clone.js +40 -0
  89. package/sandbox/dist/tasks/issue-triage/context.js +57 -0
  90. package/sandbox/dist/tasks/issue-triage/diff-validator.js +102 -0
  91. package/sandbox/dist/tasks/issue-triage/fix-attempt.js +617 -0
  92. package/sandbox/dist/tasks/issue-triage/fix-context.js +5 -0
  93. package/sandbox/dist/tasks/issue-triage/fix-dispatch.js +263 -0
  94. package/sandbox/dist/tasks/issue-triage/fix-failure-reasons.js +2 -0
  95. package/sandbox/dist/tasks/issue-triage/fix-loop/effects.js +526 -0
  96. package/sandbox/dist/tasks/issue-triage/fix-loop/effects.types.js +1 -0
  97. package/sandbox/dist/tasks/issue-triage/fix-loop/fake-effects.js +65 -0
  98. package/sandbox/dist/tasks/issue-triage/fix-loop/promotion-task.js +108 -0
  99. package/sandbox/dist/tasks/issue-triage/fix-loop/recovery.js +512 -0
  100. package/sandbox/dist/tasks/issue-triage/fix-loop/runners.js +233 -0
  101. package/sandbox/dist/tasks/issue-triage/fix-loop/state-machine.js +171 -0
  102. package/sandbox/dist/tasks/issue-triage/github-request.js +2 -0
  103. package/sandbox/dist/tasks/issue-triage/hero-group-map.js +206 -0
  104. package/sandbox/dist/tasks/issue-triage/markers.js +6 -0
  105. package/sandbox/dist/tasks/issue-triage/model.js +124 -0
  106. package/sandbox/dist/tasks/issue-triage/output.js +164 -0
  107. package/sandbox/dist/tasks/issue-triage/pr-creator.js +585 -0
  108. package/sandbox/dist/tasks/issue-triage/pr-readiness.js +25 -0
  109. package/sandbox/dist/tasks/issue-triage/prompt.js +63 -0
  110. package/sandbox/dist/tasks/issue-triage/repo-config.js +47 -0
  111. package/sandbox/dist/tasks/issue-triage/repo-prompts.js +77 -0
  112. package/sandbox/dist/tasks/issue-triage/report-fixture.js +53 -0
  113. package/sandbox/dist/tasks/issue-triage/report-history.js +113 -0
  114. package/sandbox/dist/tasks/issue-triage/report-schema.js +63 -0
  115. package/sandbox/dist/tasks/issue-triage/report.js +111 -0
  116. package/sandbox/dist/tasks/issue-triage/resolution.js +30 -0
  117. package/sandbox/dist/tasks/issue-triage/routing.js +169 -0
  118. package/sandbox/dist/tasks/issue-triage/sandbox-environment.js +59 -0
  119. package/sandbox/dist/tasks/issue-triage/shared.js +86 -0
  120. package/sandbox/dist/tasks/issue-triage/side-effects.js +102 -0
  121. package/sandbox/dist/tasks/issue-triage/triage.js +442 -0
  122. package/sandbox/dist/tasks/issue-triage/types.js +1 -0
  123. package/sandbox/dist/tasks/persist-logs/context.js +19 -0
  124. package/sandbox/dist/tasks/persist-logs/dd-proxy.js +116 -0
  125. package/sandbox/dist/tasks/persist-logs/llm-query-planner.js +156 -0
  126. package/sandbox/dist/tasks/persist-logs/persist-logs.js +491 -0
  127. package/sandbox/dist/tasks/refresh-s3-urls/refresh-s3-urls.js +214 -0
  128. package/sandbox/dist/tasks/review/check-run.js +326 -0
  129. package/sandbox/dist/tasks/review/comment-mapper.js +46 -0
  130. package/sandbox/dist/tasks/review/conversation-context.js +477 -0
  131. package/sandbox/dist/tasks/review/diff.js +95 -0
  132. package/sandbox/dist/tasks/review/engines/dedupe.js +291 -0
  133. package/sandbox/dist/tasks/review/engines/multi-focus.js +419 -0
  134. package/sandbox/dist/tasks/review/engines/shared.js +87 -0
  135. package/sandbox/dist/tasks/review/engines/types.js +1 -0
  136. package/sandbox/dist/tasks/review/finding-output.js +18 -0
  137. package/sandbox/dist/tasks/review/format.js +17 -0
  138. package/sandbox/dist/tasks/review/github-comment-format.js +94 -0
  139. package/sandbox/dist/tasks/review/local-git.js +374 -0
  140. package/sandbox/dist/tasks/review/local.js +118 -0
  141. package/sandbox/dist/tasks/review/multi-focus-prompt.js +59 -0
  142. package/sandbox/dist/tasks/review/prompt.js +81 -0
  143. package/sandbox/dist/tasks/review/review-feedback.js +48 -0
  144. package/sandbox/dist/tasks/review/review-mode-github.js +58 -0
  145. package/sandbox/dist/tasks/review/review-mode-policy.js +232 -0
  146. package/sandbox/dist/tasks/review/review-mode-types.js +1 -0
  147. package/sandbox/dist/tasks/review/review-mode.js +296 -0
  148. package/sandbox/dist/tasks/review/review-summary.js +123 -0
  149. package/sandbox/dist/tasks/review/review.js +391 -0
  150. package/sandbox/dist/tasks/review/runner.js +255 -0
  151. package/sandbox/dist/tasks/review/severity.js +5 -0
  152. package/sandbox/dist/tasks/review/summary-model.js +271 -0
  153. package/sandbox/dist/tasks/review/thinking-level.js +14 -0
  154. package/sandbox/dist/tasks/review/translation-review-policy.js +37 -0
  155. package/sandbox/dist/tasks/review/types.js +1 -0
  156. package/sandbox/dist/tasks/review-slice-plan/context.js +66 -0
  157. package/sandbox/dist/tasks/review-slice-plan/plan.js +210 -0
  158. package/sandbox/dist/tasks/review-slice-plan/prompt.js +59 -0
  159. package/sandbox/dist/tasks/review-slice-plan/review-slice-plan.js +367 -0
  160. package/sandbox/dist/tasks/sentry-triage/store.js +133 -0
  161. package/sandbox/dist/tasks/sentry-triage/triage.js +217 -0
  162. package/sandbox/node_modules/doistbot-logging/dist/index.d.ts +23 -0
  163. package/sandbox/node_modules/doistbot-logging/dist/index.js +51 -0
  164. package/sandbox/node_modules/doistbot-logging/package.json +12 -0
  165. package/sandbox/node_modules/doistbot-repo-config/dist/index.d.ts +93 -0
  166. package/sandbox/node_modules/doistbot-repo-config/dist/index.js +197 -0
  167. package/sandbox/node_modules/doistbot-repo-config/package.json +12 -0
  168. package/sandbox/src/review/prompts/ai-internal-tools.md +254 -0
  169. package/sandbox/src/review/prompts/backend-repository-standards.md +102 -0
  170. package/sandbox/src/review/prompts/observability-standard.md +169 -0
  171. package/sandbox/src/review/prompts/outline-map.json +26 -0
  172. package/sandbox/src/review/prompts/secrets-management.md +286 -0
  173. package/sandbox/src/review/prompts/service-production-readiness.md +122 -0
  174. package/sandbox/src/review/prompts/workload-placement.md +345 -0
  175. package/sandbox/src/tasks/review/prompts/review-focus-prompts/efficiency.md +16 -0
  176. package/sandbox/src/tasks/review/prompts/review-focus-prompts/general.md +32 -0
  177. package/sandbox/src/tasks/review/prompts/review-focus-prompts/quality.md +20 -0
  178. package/sandbox/src/tasks/review/prompts/review-focus-prompts/reuse.md +13 -0
  179. package/sandbox/src/tasks/review/prompts/review-focus-prompts/security.md +18 -0
  180. package/sandbox/src/tasks/review/prompts/review-focus-prompts/standards.md +21 -0
  181. package/sandbox/src/tasks/review/prompts/review-focus-prompts/tests.md +19 -0
  182. package/sandbox/src/tasks/review/prompts/review-mode.md +40 -0
  183. package/sandbox/src/tasks/review/prompts/review-multi-focus-base-prompt.md +142 -0
  184. package/scripts/restore-runtime-assets.mjs +21 -0
  185. package/README.md +0 -3
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ import { optionalPositiveInt, requiredEnv, requiredPositiveInt } from '../../core/environment.js';
2
+ export function parsePersistLogsContext(env = process.env) {
3
+ return {
4
+ owner: requiredEnv('REPO_OWNER', env),
5
+ repo: requiredEnv('REPO_NAME', env),
6
+ issueNumber: requiredPositiveInt('ISSUE_NUMBER', env),
7
+ installationId: requiredPositiveInt('INSTALLATION_ID', env),
8
+ appId: requiredEnv('APP_ID', env),
9
+ privateKey: requiredEnv('PRIVATE_KEY', env),
10
+ datadogApiKey: requiredEnv('DATADOG_API_KEY', env),
11
+ datadogAppKey: requiredEnv('DATADOG_APP_KEY', env),
12
+ openaiApiKey: requiredEnv('OPENAI_KEY', env),
13
+ logSnapshotBucket: env.LOG_SNAPSHOT_BUCKET?.trim() || '',
14
+ dryRun: env.DRY_RUN?.toLowerCase() === 'true',
15
+ commentId: optionalPositiveInt(env.COMMENT_ID),
16
+ reactionId: optionalPositiveInt(env.REACTION_ID),
17
+ deliveryId: env.DELIVERY_ID?.trim() || 'unknown',
18
+ };
19
+ }
@@ -0,0 +1,116 @@
1
+ import http from 'http';
2
+ import https from 'https';
3
+ import { logger } from '../../core/logging.js';
4
+ import { scrubPii, scrubPiiFromObject } from '../../core/pii-scrubber.js';
5
+ const DD_HOST = 'api.datadoghq.com';
6
+ /**
7
+ * Starts a localhost HTTP proxy that forwards requests to the Datadog API,
8
+ * scrubs PII from responses, and logs each query for auditability.
9
+ *
10
+ * The agent hits http://localhost:{port}/api/v2/... instead of the DD API directly.
11
+ * The proxy injects DD auth headers so the agent never sees the API keys.
12
+ */
13
+ export function startDdProxy({ port, datadogApiKey, datadogAppKey }) {
14
+ return new Promise((resolve, reject) => {
15
+ const server = http.createServer((req, res) => {
16
+ const body = [];
17
+ req.on('data', (chunk) => body.push(chunk));
18
+ req.on('end', () => {
19
+ const requestBody = Buffer.concat(body).toString();
20
+ const urlPath = req.url ?? '/';
21
+ // Log the query for audit trail (queries contain no PII)
22
+ let queryString = '';
23
+ try {
24
+ const parsed = JSON.parse(requestBody);
25
+ queryString = parsed?.filter?.query ?? '';
26
+ }
27
+ catch {
28
+ // Not JSON or no query field — that's fine
29
+ }
30
+ logger.info('DD proxy: forwarding query', {
31
+ task: 'persist-logs',
32
+ path: urlPath,
33
+ method: req.method,
34
+ query: scrubPii(queryString),
35
+ });
36
+ const options = {
37
+ hostname: DD_HOST,
38
+ port: 443,
39
+ path: urlPath,
40
+ method: req.method ?? 'GET',
41
+ headers: {
42
+ 'Content-Type': 'application/json',
43
+ 'DD-API-KEY': datadogApiKey,
44
+ 'DD-APPLICATION-KEY': datadogAppKey,
45
+ },
46
+ };
47
+ const proxyReq = https.request(options, (proxyRes) => {
48
+ const chunks = [];
49
+ proxyRes.on('error', (err) => {
50
+ logger.error('DD proxy: upstream response error', {
51
+ task: 'persist-logs',
52
+ error: err.message,
53
+ path: urlPath,
54
+ });
55
+ if (!res.headersSent) {
56
+ res.writeHead(502);
57
+ res.end(JSON.stringify({ error: 'DD proxy upstream response error' }));
58
+ }
59
+ });
60
+ proxyRes.on('data', (chunk) => chunks.push(chunk));
61
+ proxyRes.on('end', () => {
62
+ const rawResponse = Buffer.concat(chunks).toString();
63
+ let scrubbedResponse;
64
+ try {
65
+ const parsed = JSON.parse(rawResponse);
66
+ const scrubbed = scrubPiiFromObject(parsed);
67
+ scrubbedResponse = JSON.stringify(scrubbed);
68
+ }
69
+ catch {
70
+ // Non-JSON response — scrub as plain text
71
+ scrubbedResponse = scrubPii(rawResponse);
72
+ }
73
+ logger.info('DD proxy: response scrubbed', {
74
+ task: 'persist-logs',
75
+ path: urlPath,
76
+ statusCode: proxyRes.statusCode,
77
+ originalBytes: rawResponse.length,
78
+ scrubbedBytes: scrubbedResponse.length,
79
+ });
80
+ res.writeHead(proxyRes.statusCode ?? 200, {
81
+ 'Content-Type': proxyRes.headers['content-type'] ?? 'application/json',
82
+ });
83
+ res.end(scrubbedResponse);
84
+ });
85
+ });
86
+ proxyReq.on('error', (err) => {
87
+ logger.error('DD proxy: upstream request failed', {
88
+ task: 'persist-logs',
89
+ error: err.message,
90
+ path: urlPath,
91
+ });
92
+ res.writeHead(502);
93
+ res.end(JSON.stringify({ error: 'DD proxy upstream error' }));
94
+ });
95
+ if (requestBody) {
96
+ proxyReq.write(requestBody);
97
+ }
98
+ proxyReq.end();
99
+ });
100
+ });
101
+ server.on('error', reject);
102
+ server.listen(port, '127.0.0.1', () => {
103
+ logger.info('DD proxy started', {
104
+ task: 'persist-logs',
105
+ port,
106
+ target: DD_HOST,
107
+ });
108
+ resolve({
109
+ server,
110
+ stop: () => new Promise((resolveStop) => {
111
+ server.close(() => resolveStop());
112
+ }),
113
+ });
114
+ });
115
+ });
116
+ }
@@ -0,0 +1,156 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { logger } from '../../core/logging.js';
4
+ import { invokePiPrompt } from '../../core/pi.js';
5
+ import { scrubPii } from '../../core/pii-scrubber.js';
6
+ import { loadPromptTemplateFile, resolvePromptFile } from '../../core/prompt-template.js';
7
+ import { startDdProxy } from './dd-proxy.js';
8
+ export const PERSIST_LOGS_PROVIDER = 'openai';
9
+ const TIMEOUT_MS = 20 * 60 * 1000; // 20 minutes — investigation can take a while
10
+ function buildPrompt(params, proxyPort) {
11
+ const ddApiReference = loadPromptTemplateFile({
12
+ fileName: resolvePromptFile(import.meta.url, 'persist-logs-dd-api-reference.md'),
13
+ promptLabel: 'persist-logs DD API reference',
14
+ });
15
+ const template = loadPromptTemplateFile({
16
+ fileName: resolvePromptFile(import.meta.url, 'persist-logs-investigation-prompt.md'),
17
+ promptLabel: 'persist-logs investigation prompt',
18
+ });
19
+ const scrubbedTitle = scrubPii(params.issueTitle);
20
+ const scrubbedBody = scrubPii(params.issueBody);
21
+ const scrubbedComments = params.comments
22
+ .map((c) => `### Comment by ${c.author}\n${scrubPii(c.body)}`)
23
+ .join('\n\n');
24
+ const proxyUrl = `http://127.0.0.1:${proxyPort}`;
25
+ const replacements = {
26
+ '{{PROXY_URL}}': proxyUrl,
27
+ '{{DD_API_REFERENCE}}': ddApiReference,
28
+ '{{REPO_DIR}}': params.repoDir,
29
+ '{{OUTPUT_DIR}}': params.outputDir,
30
+ '{{ISSUE_NUMBER}}': String(params.issueNumber),
31
+ '{{ISSUE_TITLE}}': scrubbedTitle,
32
+ '{{ISSUE_TITLE_ESCAPED}}': scrubbedTitle.replace(/"/g, '\\"'),
33
+ '{{ISSUE_BODY}}': scrubbedBody,
34
+ '{{COMMENTS_SECTION}}': scrubbedComments ? `## Comments\n\n${scrubbedComments}` : '',
35
+ };
36
+ let prompt = template;
37
+ for (const [placeholder, value] of Object.entries(replacements)) {
38
+ prompt = prompt.replaceAll(placeholder, value);
39
+ }
40
+ return prompt;
41
+ }
42
+ export async function runInvestigation(params) {
43
+ const DD_PROXY_PORT = 3100;
44
+ logger.info('Starting LLM investigation', {
45
+ task: 'persist-logs',
46
+ issueNumber: params.issueNumber,
47
+ repoDir: params.repoDir,
48
+ model: PERSIST_LOGS_PROVIDER,
49
+ });
50
+ // Ensure output directory exists
51
+ fs.mkdirSync(params.outputDir, { recursive: true });
52
+ // Start the DD proxy so the agent queries localhost; the proxy injects auth and scrubs PII.
53
+ const proxy = await startDdProxy({
54
+ port: DD_PROXY_PORT,
55
+ datadogApiKey: params.datadogApiKey,
56
+ datadogAppKey: params.datadogAppKey,
57
+ });
58
+ const prompt = buildPrompt(params, DD_PROXY_PORT);
59
+ let result;
60
+ try {
61
+ result = await invokePiPrompt({
62
+ provider: PERSIST_LOGS_PROVIDER,
63
+ apiKey: params.openaiApiKey,
64
+ prompt,
65
+ cwd: params.repoDir,
66
+ timeoutMs: TIMEOUT_MS,
67
+ capabilityPreset: 'shell-focused',
68
+ thinkingLevel: 'high',
69
+ onProgress: (event) => {
70
+ if (event.type === 'tool_start') {
71
+ logger.info('Investigation tool call', {
72
+ task: 'persist-logs',
73
+ tool: event.toolName,
74
+ });
75
+ }
76
+ },
77
+ });
78
+ }
79
+ finally {
80
+ await proxy.stop();
81
+ logger.info('DD proxy stopped', { task: 'persist-logs' });
82
+ }
83
+ logger.info('Persist logs invocation result', {
84
+ task: 'persist-logs',
85
+ ok: result.ok,
86
+ toolUsage: result.toolUsage,
87
+ ...(result.ok
88
+ ? { outputLength: result.output.length }
89
+ : { error: result.error, timedOut: result.timedOut }),
90
+ });
91
+ if (!result.ok) {
92
+ if (result.timedOut) {
93
+ logger.error('LLM investigation timed out', {
94
+ task: 'persist-logs',
95
+ timeoutMs: TIMEOUT_MS,
96
+ });
97
+ return {
98
+ success: false,
99
+ totalLogs: 0,
100
+ error: `Investigation timed out after ${TIMEOUT_MS / 1000}s`,
101
+ };
102
+ }
103
+ logger.error('LLM investigation failed', {
104
+ task: 'persist-logs',
105
+ error: result.error,
106
+ });
107
+ return { success: false, totalLogs: 0, error: result.error };
108
+ }
109
+ // Check for output files
110
+ const rawLogsPath = path.join(params.outputDir, `raw-logs-${params.issueNumber}.json`);
111
+ const analysisPath = path.join(params.outputDir, `analysis-${params.issueNumber}.md`);
112
+ const csvPath = path.join(params.outputDir, `logs-${params.issueNumber}.csv`);
113
+ const worklogPath = path.join(params.outputDir, `worklog-${params.issueNumber}.md`);
114
+ const hasRawLogs = fs.existsSync(rawLogsPath);
115
+ const hasAnalysis = fs.existsSync(analysisPath);
116
+ const hasCsv = fs.existsSync(csvPath);
117
+ const hasWorklog = fs.existsSync(worklogPath);
118
+ let totalLogs = 0;
119
+ if (hasRawLogs) {
120
+ try {
121
+ const raw = JSON.parse(fs.readFileSync(rawLogsPath, 'utf-8'));
122
+ totalLogs = raw.total_log_entries ?? 0;
123
+ }
124
+ catch {
125
+ logger.warn('Could not parse raw logs file', {
126
+ task: 'persist-logs',
127
+ path: rawLogsPath,
128
+ });
129
+ }
130
+ }
131
+ if (!hasRawLogs) {
132
+ logger.warn('LLM investigation completed but raw-logs JSON not generated', {
133
+ task: 'persist-logs',
134
+ outputDir: params.outputDir,
135
+ hasAnalysis,
136
+ });
137
+ return { success: false, totalLogs: 0, error: 'raw-logs JSON not generated' };
138
+ }
139
+ logger.info('LLM investigation completed', {
140
+ task: 'persist-logs',
141
+ totalLogs,
142
+ hasRawLogs,
143
+ hasAnalysis,
144
+ hasCsv,
145
+ hasWorklog,
146
+ toolUsage: result.toolUsage,
147
+ });
148
+ return {
149
+ success: true,
150
+ rawLogsPath: hasRawLogs ? rawLogsPath : undefined,
151
+ analysisPath: hasAnalysis ? analysisPath : undefined,
152
+ csvPath: hasCsv ? csvPath : undefined,
153
+ worklogPath: hasWorklog ? worklogPath : undefined,
154
+ totalLogs,
155
+ };
156
+ }