@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.
- package/dist/actions/auth.js +225 -0
- package/dist/actions/doctor.js +39 -0
- package/dist/actions/review-usage.js +25 -0
- package/dist/actions/review.js +311 -0
- package/dist/actions/setup.js +14 -0
- package/dist/auth.js +202 -0
- package/dist/config.js +145 -0
- package/dist/env.js +10 -0
- package/dist/file-store.js +112 -0
- package/dist/index.js +164 -0
- package/dist/input.js +40 -0
- package/dist/runtime.js +105 -0
- package/dist/secure-store.js +148 -0
- package/dist/terminal.js +185 -0
- package/dist/test-utils/secure-store-mocks.js +29 -0
- package/dist/update-cache-entrypoint.js +16 -0
- package/dist/update-check.js +235 -0
- package/package.json +65 -5
- package/sandbox/_node_modules/doistbot-logging/dist/index.d.ts +23 -0
- package/sandbox/_node_modules/doistbot-logging/dist/index.js +51 -0
- package/sandbox/_node_modules/doistbot-logging/package.json +12 -0
- package/sandbox/_node_modules/doistbot-repo-config/dist/index.d.ts +93 -0
- package/sandbox/_node_modules/doistbot-repo-config/dist/index.js +197 -0
- package/sandbox/_node_modules/doistbot-repo-config/package.json +12 -0
- package/sandbox/dist/core/autofix-resources.js +58 -0
- package/sandbox/dist/core/autofix-service-query.js +69 -0
- package/sandbox/dist/core/autofix-test-policy.js +11 -0
- package/sandbox/dist/core/autofix-verification.js +355 -0
- package/sandbox/dist/core/bot-logins.js +14 -0
- package/sandbox/dist/core/check-run.js +158 -0
- package/sandbox/dist/core/datadog-metrics.js +113 -0
- package/sandbox/dist/core/environment.js +49 -0
- package/sandbox/dist/core/git-auth.js +17 -0
- package/sandbox/dist/core/github-history.js +4 -0
- package/sandbox/dist/core/github-issues.js +18 -0
- package/sandbox/dist/core/label-parsing.js +1 -0
- package/sandbox/dist/core/langfuse-observation.js +33 -0
- package/sandbox/dist/core/langfuse-session.js +39 -0
- package/sandbox/dist/core/logger-span-processor.js +90 -0
- package/sandbox/dist/core/logging.js +18 -0
- package/sandbox/dist/core/pi-autofix-extension.js +188 -0
- package/sandbox/dist/core/pi-execution-limits-extension.js +79 -0
- package/sandbox/dist/core/pi-progress-logging.js +51 -0
- package/sandbox/dist/core/pi-tool-output-filter-extension.js +65 -0
- package/sandbox/dist/core/pi.js +870 -0
- package/sandbox/dist/core/pii-scrubber.js +31 -0
- package/sandbox/dist/core/prompt-template.js +63 -0
- package/sandbox/dist/core/reactions.js +36 -0
- package/sandbox/dist/core/regexp.js +3 -0
- package/sandbox/dist/core/repository-map.js +1 -0
- package/sandbox/dist/core/repository-resolver.js +1 -0
- package/sandbox/dist/core/retry.js +29 -0
- package/sandbox/dist/core/s3-utils.js +42 -0
- package/sandbox/dist/core/shared.js +124 -0
- package/sandbox/dist/core/snapshot-helpers.js +17 -0
- package/sandbox/dist/core/span-test-helpers.js +13 -0
- package/sandbox/dist/core/tool-environment.js +83 -0
- package/sandbox/dist/core/tool-output.js +18 -0
- package/sandbox/dist/core/tracing-setup.js +92 -0
- package/sandbox/dist/core/tracing.js +81 -0
- package/sandbox/dist/main.js +51 -0
- package/sandbox/dist/providers/config.js +23 -0
- package/sandbox/dist/providers/credentials.js +40 -0
- package/sandbox/dist/providers/gemini.js +28 -0
- package/sandbox/dist/providers/helpers.js +253 -0
- package/sandbox/dist/providers/index.js +27 -0
- package/sandbox/dist/providers/openai.js +23 -0
- package/sandbox/dist/providers/openrouter-pi.js +25 -0
- package/sandbox/dist/providers/pi-review.js +301 -0
- package/sandbox/dist/providers/process.js +34 -0
- package/sandbox/dist/providers/schemas.js +61 -0
- package/sandbox/dist/providers/types.js +4 -0
- package/sandbox/dist/tasks/chat/chat.js +440 -0
- package/sandbox/dist/tasks/chat/prompt.js +20 -0
- package/sandbox/dist/tasks/issue-fix-retry/fix-retry.js +304 -0
- package/sandbox/dist/tasks/issue-summarize/context.js +21 -0
- package/sandbox/dist/tasks/issue-summarize/media.js +335 -0
- package/sandbox/dist/tasks/issue-summarize/model.js +230 -0
- package/sandbox/dist/tasks/issue-summarize/output.js +45 -0
- package/sandbox/dist/tasks/issue-summarize/prompt.js +11 -0
- package/sandbox/dist/tasks/issue-summarize/summarize.js +238 -0
- package/sandbox/dist/tasks/issue-summarize/thread.js +123 -0
- package/sandbox/dist/tasks/issue-summarize/types.js +1 -0
- package/sandbox/dist/tasks/issue-triage/assignee-review.js +114 -0
- package/sandbox/dist/tasks/issue-triage/autofix-capabilities.js +22 -0
- package/sandbox/dist/tasks/issue-triage/autofix-checkpoint.js +122 -0
- package/sandbox/dist/tasks/issue-triage/autofix-policy.js +36 -0
- package/sandbox/dist/tasks/issue-triage/clone.js +40 -0
- package/sandbox/dist/tasks/issue-triage/context.js +57 -0
- package/sandbox/dist/tasks/issue-triage/diff-validator.js +102 -0
- package/sandbox/dist/tasks/issue-triage/fix-attempt.js +617 -0
- package/sandbox/dist/tasks/issue-triage/fix-context.js +5 -0
- package/sandbox/dist/tasks/issue-triage/fix-dispatch.js +263 -0
- package/sandbox/dist/tasks/issue-triage/fix-failure-reasons.js +2 -0
- package/sandbox/dist/tasks/issue-triage/fix-loop/effects.js +526 -0
- package/sandbox/dist/tasks/issue-triage/fix-loop/effects.types.js +1 -0
- package/sandbox/dist/tasks/issue-triage/fix-loop/fake-effects.js +65 -0
- package/sandbox/dist/tasks/issue-triage/fix-loop/promotion-task.js +108 -0
- package/sandbox/dist/tasks/issue-triage/fix-loop/recovery.js +512 -0
- package/sandbox/dist/tasks/issue-triage/fix-loop/runners.js +233 -0
- package/sandbox/dist/tasks/issue-triage/fix-loop/state-machine.js +171 -0
- package/sandbox/dist/tasks/issue-triage/github-request.js +2 -0
- package/sandbox/dist/tasks/issue-triage/hero-group-map.js +206 -0
- package/sandbox/dist/tasks/issue-triage/markers.js +6 -0
- package/sandbox/dist/tasks/issue-triage/model.js +124 -0
- package/sandbox/dist/tasks/issue-triage/output.js +164 -0
- package/sandbox/dist/tasks/issue-triage/pr-creator.js +585 -0
- package/sandbox/dist/tasks/issue-triage/pr-readiness.js +25 -0
- package/sandbox/dist/tasks/issue-triage/prompt.js +63 -0
- package/sandbox/dist/tasks/issue-triage/repo-config.js +47 -0
- package/sandbox/dist/tasks/issue-triage/repo-prompts.js +77 -0
- package/sandbox/dist/tasks/issue-triage/report-fixture.js +53 -0
- package/sandbox/dist/tasks/issue-triage/report-history.js +113 -0
- package/sandbox/dist/tasks/issue-triage/report-schema.js +63 -0
- package/sandbox/dist/tasks/issue-triage/report.js +111 -0
- package/sandbox/dist/tasks/issue-triage/resolution.js +30 -0
- package/sandbox/dist/tasks/issue-triage/routing.js +169 -0
- package/sandbox/dist/tasks/issue-triage/sandbox-environment.js +59 -0
- package/sandbox/dist/tasks/issue-triage/shared.js +86 -0
- package/sandbox/dist/tasks/issue-triage/side-effects.js +102 -0
- package/sandbox/dist/tasks/issue-triage/triage.js +442 -0
- package/sandbox/dist/tasks/issue-triage/types.js +1 -0
- package/sandbox/dist/tasks/persist-logs/context.js +19 -0
- package/sandbox/dist/tasks/persist-logs/dd-proxy.js +116 -0
- package/sandbox/dist/tasks/persist-logs/llm-query-planner.js +156 -0
- package/sandbox/dist/tasks/persist-logs/persist-logs.js +491 -0
- package/sandbox/dist/tasks/refresh-s3-urls/refresh-s3-urls.js +214 -0
- package/sandbox/dist/tasks/review/check-run.js +326 -0
- package/sandbox/dist/tasks/review/comment-mapper.js +46 -0
- package/sandbox/dist/tasks/review/conversation-context.js +477 -0
- package/sandbox/dist/tasks/review/diff.js +95 -0
- package/sandbox/dist/tasks/review/engines/dedupe.js +291 -0
- package/sandbox/dist/tasks/review/engines/multi-focus.js +419 -0
- package/sandbox/dist/tasks/review/engines/shared.js +87 -0
- package/sandbox/dist/tasks/review/engines/types.js +1 -0
- package/sandbox/dist/tasks/review/finding-output.js +18 -0
- package/sandbox/dist/tasks/review/format.js +17 -0
- package/sandbox/dist/tasks/review/github-comment-format.js +94 -0
- package/sandbox/dist/tasks/review/local-git.js +374 -0
- package/sandbox/dist/tasks/review/local.js +118 -0
- package/sandbox/dist/tasks/review/multi-focus-prompt.js +59 -0
- package/sandbox/dist/tasks/review/prompt.js +81 -0
- package/sandbox/dist/tasks/review/review-feedback.js +48 -0
- package/sandbox/dist/tasks/review/review-mode-github.js +58 -0
- package/sandbox/dist/tasks/review/review-mode-policy.js +232 -0
- package/sandbox/dist/tasks/review/review-mode-types.js +1 -0
- package/sandbox/dist/tasks/review/review-mode.js +296 -0
- package/sandbox/dist/tasks/review/review-summary.js +123 -0
- package/sandbox/dist/tasks/review/review.js +391 -0
- package/sandbox/dist/tasks/review/runner.js +255 -0
- package/sandbox/dist/tasks/review/severity.js +5 -0
- package/sandbox/dist/tasks/review/summary-model.js +271 -0
- package/sandbox/dist/tasks/review/thinking-level.js +14 -0
- package/sandbox/dist/tasks/review/translation-review-policy.js +37 -0
- package/sandbox/dist/tasks/review/types.js +1 -0
- package/sandbox/dist/tasks/review-slice-plan/context.js +66 -0
- package/sandbox/dist/tasks/review-slice-plan/plan.js +210 -0
- package/sandbox/dist/tasks/review-slice-plan/prompt.js +59 -0
- package/sandbox/dist/tasks/review-slice-plan/review-slice-plan.js +367 -0
- package/sandbox/dist/tasks/sentry-triage/store.js +133 -0
- package/sandbox/dist/tasks/sentry-triage/triage.js +217 -0
- package/sandbox/node_modules/doistbot-logging/dist/index.d.ts +23 -0
- package/sandbox/node_modules/doistbot-logging/dist/index.js +51 -0
- package/sandbox/node_modules/doistbot-logging/package.json +12 -0
- package/sandbox/node_modules/doistbot-repo-config/dist/index.d.ts +93 -0
- package/sandbox/node_modules/doistbot-repo-config/dist/index.js +197 -0
- package/sandbox/node_modules/doistbot-repo-config/package.json +12 -0
- package/sandbox/src/review/prompts/ai-internal-tools.md +254 -0
- package/sandbox/src/review/prompts/backend-repository-standards.md +102 -0
- package/sandbox/src/review/prompts/observability-standard.md +169 -0
- package/sandbox/src/review/prompts/outline-map.json +26 -0
- package/sandbox/src/review/prompts/secrets-management.md +286 -0
- package/sandbox/src/review/prompts/service-production-readiness.md +122 -0
- package/sandbox/src/review/prompts/workload-placement.md +345 -0
- package/sandbox/src/tasks/review/prompts/review-focus-prompts/efficiency.md +16 -0
- package/sandbox/src/tasks/review/prompts/review-focus-prompts/general.md +32 -0
- package/sandbox/src/tasks/review/prompts/review-focus-prompts/quality.md +20 -0
- package/sandbox/src/tasks/review/prompts/review-focus-prompts/reuse.md +13 -0
- package/sandbox/src/tasks/review/prompts/review-focus-prompts/security.md +18 -0
- package/sandbox/src/tasks/review/prompts/review-focus-prompts/standards.md +21 -0
- package/sandbox/src/tasks/review/prompts/review-focus-prompts/tests.md +19 -0
- package/sandbox/src/tasks/review/prompts/review-mode.md +40 -0
- package/sandbox/src/tasks/review/prompts/review-multi-focus-base-prompt.md +142 -0
- package/scripts/restore-runtime-assets.mjs +21 -0
- package/README.md +0 -3
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { WORKSPACE_DIR } from '../core/environment.js';
|
|
2
|
+
import { logger } from '../core/logging.js';
|
|
3
|
+
import { createPiInvoker, parsePiAuthProviderId, PI_PROVIDER_BY_DOISTBOT_PROVIDER, toPiInvocationTelemetryFields, } from '../core/pi.js';
|
|
4
|
+
import { logPiProgressEvent } from '../core/pi-progress-logging.js';
|
|
5
|
+
import { DEFAULT_PROVIDER_TIMEOUT_MS } from './config.js';
|
|
6
|
+
import { logRawModelOutput, normalizeParsedModelOutput } from './helpers.js';
|
|
7
|
+
const VERBOSE_PI_PROGRESS_LOGS = process.env.DOISTBOT_VERBOSE_PI_PROGRESS === 'true';
|
|
8
|
+
const VERBOSE_PI_RAW_OUTPUT_LOGS = process.env.DOISTBOT_VERBOSE_PI_RAW_OUTPUT === 'true';
|
|
9
|
+
const REVIEW_LOG_ENGINE = 'multi_focus';
|
|
10
|
+
export const REVIEW_PI_EXECUTION_LIMITS = {
|
|
11
|
+
softTimeMs: 5 * 60 * 1000,
|
|
12
|
+
softToolCalls: 40,
|
|
13
|
+
hardTimeMs: 8 * 60 * 1000,
|
|
14
|
+
hardToolCalls: 65,
|
|
15
|
+
};
|
|
16
|
+
export const REVIEW_PI_PROCESS_KILL_MS = DEFAULT_PROVIDER_TIMEOUT_MS;
|
|
17
|
+
const REVIEW_PI_SYSTEM_PROMPT = 'You are running inside doistbot in a non-interactive environment. Stay tightly focused on the review task in the user prompt. Read only the files that are directly relevant to the changed code, the explicitly named guidance files, and any immediately connected definitions needed to verify a concrete finding. Do not broadly explore the repository, inspect unrelated modules, or search history unless it is necessary to validate a specific review concern. Stop gathering context once you have enough evidence to answer the review task accurately.';
|
|
18
|
+
function buildModelFailureResult({ model, error, durationMs, timedOut = false, telemetry, }) {
|
|
19
|
+
return {
|
|
20
|
+
model,
|
|
21
|
+
summary: '',
|
|
22
|
+
findings: [],
|
|
23
|
+
error,
|
|
24
|
+
timedOut,
|
|
25
|
+
durationMs,
|
|
26
|
+
...(telemetry ? { telemetry } : {}),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function logReviewLatency({ provider, modelName, attempt, attemptsTotal, result, }) {
|
|
30
|
+
const telemetry = result.telemetry;
|
|
31
|
+
logger.info('Review model latency', {
|
|
32
|
+
model: provider,
|
|
33
|
+
engine: REVIEW_LOG_ENGINE,
|
|
34
|
+
model_name: modelName,
|
|
35
|
+
attempt,
|
|
36
|
+
attempts_total: attemptsTotal,
|
|
37
|
+
success: result.ok,
|
|
38
|
+
timed_out: !result.ok && result.timedOut,
|
|
39
|
+
stop_reason: result.stopReason,
|
|
40
|
+
duration_ms: result.durationMs,
|
|
41
|
+
...(telemetry ? toPiInvocationTelemetryFields(telemetry) : {}),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function logReviewModelProgress(model, event) {
|
|
45
|
+
if (event.type === 'limit_reached') {
|
|
46
|
+
logger.warn('Review model execution limit reached', {
|
|
47
|
+
model,
|
|
48
|
+
engine: REVIEW_LOG_ENGINE,
|
|
49
|
+
level: event.level,
|
|
50
|
+
trigger: event.trigger,
|
|
51
|
+
elapsed_ms: event.elapsedMs,
|
|
52
|
+
tool_call_count: event.toolCallCount,
|
|
53
|
+
});
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
logPiProgressEvent({
|
|
57
|
+
event,
|
|
58
|
+
messages: {
|
|
59
|
+
agentStart: 'Review model agent started',
|
|
60
|
+
agentEnd: 'Review model agent finished',
|
|
61
|
+
thinking: 'Review model still thinking',
|
|
62
|
+
responding: 'Review model streaming response',
|
|
63
|
+
toolStart: 'Review model tool started',
|
|
64
|
+
toolUpdate: 'Review model tool still running',
|
|
65
|
+
toolEnd: 'Review model tool finished',
|
|
66
|
+
},
|
|
67
|
+
meta: { model, engine: REVIEW_LOG_ENGINE },
|
|
68
|
+
logResponding: VERBOSE_PI_PROGRESS_LOGS,
|
|
69
|
+
logTools: VERBOSE_PI_PROGRESS_LOGS,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function emitReviewModelProgress(provider, event, config) {
|
|
73
|
+
config.onProgress?.({
|
|
74
|
+
...event,
|
|
75
|
+
provider,
|
|
76
|
+
});
|
|
77
|
+
logReviewModelProgress(provider, event);
|
|
78
|
+
}
|
|
79
|
+
function logReviewRawModelOutput(args) {
|
|
80
|
+
if (!VERBOSE_PI_RAW_OUTPUT_LOGS) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
logRawModelOutput(args);
|
|
84
|
+
}
|
|
85
|
+
function isOpenRouterBackedReviewRequest(provider, config) {
|
|
86
|
+
const resolvedPiProviderId = parsePiAuthProviderId(config.apiKey) ??
|
|
87
|
+
config.piProviderId ??
|
|
88
|
+
PI_PROVIDER_BY_DOISTBOT_PROVIDER[provider];
|
|
89
|
+
return resolvedPiProviderId === 'openrouter';
|
|
90
|
+
}
|
|
91
|
+
export async function runPiReviewProvider(params) {
|
|
92
|
+
// Stamp the resolved model spec on every result (success or failure) so
|
|
93
|
+
// telemetry has the real model without re-deriving each provider's default.
|
|
94
|
+
const result = await runPiReviewProviderCore(params);
|
|
95
|
+
return { ...result, requestModel: params.modelName };
|
|
96
|
+
}
|
|
97
|
+
async function runPiReviewProviderCore({ provider, modelName, prompt, config, prompts = [prompt], parseOutput, }) {
|
|
98
|
+
const startTime = Date.now();
|
|
99
|
+
const timeoutMs = Math.min(config.timeout ?? REVIEW_PI_PROCESS_KILL_MS, REVIEW_PI_PROCESS_KILL_MS);
|
|
100
|
+
let stdout = '';
|
|
101
|
+
let stderr = '';
|
|
102
|
+
let status = null;
|
|
103
|
+
let latestTelemetry;
|
|
104
|
+
logger.info('Review model request', {
|
|
105
|
+
model: provider,
|
|
106
|
+
engine: REVIEW_LOG_ENGINE,
|
|
107
|
+
promptLength: prompt.length,
|
|
108
|
+
modelName,
|
|
109
|
+
timeoutMs,
|
|
110
|
+
...(isOpenRouterBackedReviewRequest(provider, config) && config.openRouterSessionId
|
|
111
|
+
? { openRouterSessionId: config.openRouterSessionId }
|
|
112
|
+
: {}),
|
|
113
|
+
});
|
|
114
|
+
try {
|
|
115
|
+
const invoker = createPiInvoker({
|
|
116
|
+
provider,
|
|
117
|
+
apiKey: config.apiKey,
|
|
118
|
+
piProviderId: config.piProviderId,
|
|
119
|
+
cwd: config.workspaceDir ?? WORKSPACE_DIR,
|
|
120
|
+
onProgress: (event) => {
|
|
121
|
+
emitReviewModelProgress(provider, event, config);
|
|
122
|
+
},
|
|
123
|
+
capabilityPreset: 'read-only',
|
|
124
|
+
thinkingLevel: config.thinkingLevel,
|
|
125
|
+
systemPrompt: REVIEW_PI_SYSTEM_PROMPT,
|
|
126
|
+
noContextFiles: true,
|
|
127
|
+
openRouterSessionId: config.openRouterSessionId,
|
|
128
|
+
executionLimits: REVIEW_PI_EXECUTION_LIMITS,
|
|
129
|
+
});
|
|
130
|
+
for (let attempt = 0; attempt < prompts.length; attempt += 1) {
|
|
131
|
+
const result = await invoker.invoke({
|
|
132
|
+
prompt: prompts[attempt] ?? prompt,
|
|
133
|
+
modelName,
|
|
134
|
+
timeoutMs,
|
|
135
|
+
});
|
|
136
|
+
const durationMs = Date.now() - startTime;
|
|
137
|
+
latestTelemetry = result.telemetry;
|
|
138
|
+
stdout = result.ok ? result.output : '';
|
|
139
|
+
stderr = result.ok ? '' : result.error;
|
|
140
|
+
status = result.ok ? 0 : null;
|
|
141
|
+
logger.info('Review model tool usage', {
|
|
142
|
+
model: provider,
|
|
143
|
+
engine: REVIEW_LOG_ENGINE,
|
|
144
|
+
attempt: attempt + 1,
|
|
145
|
+
attemptsTotal: prompts.length,
|
|
146
|
+
attemptDurationMs: result.durationMs,
|
|
147
|
+
toolUsage: result.toolUsage,
|
|
148
|
+
});
|
|
149
|
+
logReviewLatency({
|
|
150
|
+
provider,
|
|
151
|
+
modelName,
|
|
152
|
+
attempt: attempt + 1,
|
|
153
|
+
attemptsTotal: prompts.length,
|
|
154
|
+
result,
|
|
155
|
+
});
|
|
156
|
+
if (!result.ok && result.timedOut) {
|
|
157
|
+
logReviewRawModelOutput({
|
|
158
|
+
model: provider,
|
|
159
|
+
stage: 'timeout',
|
|
160
|
+
success: false,
|
|
161
|
+
status,
|
|
162
|
+
durationMs,
|
|
163
|
+
stdout,
|
|
164
|
+
stderr,
|
|
165
|
+
});
|
|
166
|
+
const error = result.error || `Pi ${provider} request timed out after ${timeoutMs}ms`;
|
|
167
|
+
logger.error('Review model failed', {
|
|
168
|
+
model: provider,
|
|
169
|
+
engine: REVIEW_LOG_ENGINE,
|
|
170
|
+
error,
|
|
171
|
+
durationMs,
|
|
172
|
+
});
|
|
173
|
+
return buildModelFailureResult({
|
|
174
|
+
model: provider,
|
|
175
|
+
error,
|
|
176
|
+
durationMs,
|
|
177
|
+
timedOut: true,
|
|
178
|
+
telemetry: result.telemetry,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
if (!result.ok) {
|
|
182
|
+
logReviewRawModelOutput({
|
|
183
|
+
model: provider,
|
|
184
|
+
stage: 'non-zero-exit',
|
|
185
|
+
success: false,
|
|
186
|
+
status,
|
|
187
|
+
durationMs,
|
|
188
|
+
stdout,
|
|
189
|
+
stderr,
|
|
190
|
+
});
|
|
191
|
+
const error = `Pi ${provider} request failed: ${result.error.slice(0, 500)}`;
|
|
192
|
+
logger.error('Review model failed', {
|
|
193
|
+
model: provider,
|
|
194
|
+
engine: REVIEW_LOG_ENGINE,
|
|
195
|
+
error,
|
|
196
|
+
durationMs,
|
|
197
|
+
});
|
|
198
|
+
return buildModelFailureResult({
|
|
199
|
+
model: provider,
|
|
200
|
+
error,
|
|
201
|
+
durationMs,
|
|
202
|
+
telemetry: result.telemetry,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
const output = result.output;
|
|
206
|
+
if (!output.trim()) {
|
|
207
|
+
logReviewRawModelOutput({
|
|
208
|
+
model: provider,
|
|
209
|
+
stage: 'empty-output',
|
|
210
|
+
success: false,
|
|
211
|
+
status,
|
|
212
|
+
durationMs,
|
|
213
|
+
stdout,
|
|
214
|
+
stderr,
|
|
215
|
+
});
|
|
216
|
+
return buildModelFailureResult({
|
|
217
|
+
model: provider,
|
|
218
|
+
error: `Empty response from Pi ${provider} request`,
|
|
219
|
+
durationMs,
|
|
220
|
+
telemetry: result.telemetry,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
const normalized = normalizeParsedModelOutput(parseOutput(output), provider);
|
|
225
|
+
logger.info('Review model response', {
|
|
226
|
+
model: provider,
|
|
227
|
+
engine: REVIEW_LOG_ENGINE,
|
|
228
|
+
findingsCount: normalized.findings.length,
|
|
229
|
+
durationMs,
|
|
230
|
+
parseMethod: 'extract_json',
|
|
231
|
+
});
|
|
232
|
+
if (VERBOSE_PI_RAW_OUTPUT_LOGS) {
|
|
233
|
+
logger.info('Review model findings', {
|
|
234
|
+
model: provider,
|
|
235
|
+
engine: REVIEW_LOG_ENGINE,
|
|
236
|
+
findings: normalized.findings,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
logReviewRawModelOutput({
|
|
240
|
+
model: provider,
|
|
241
|
+
stage: 'success',
|
|
242
|
+
success: true,
|
|
243
|
+
status,
|
|
244
|
+
durationMs,
|
|
245
|
+
stdout,
|
|
246
|
+
stderr,
|
|
247
|
+
});
|
|
248
|
+
return {
|
|
249
|
+
model: provider,
|
|
250
|
+
summary: normalized.summary,
|
|
251
|
+
findings: normalized.findings,
|
|
252
|
+
durationMs,
|
|
253
|
+
telemetry: result.telemetry,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
logReviewRawModelOutput({
|
|
258
|
+
model: provider,
|
|
259
|
+
stage: 'parse-error',
|
|
260
|
+
success: false,
|
|
261
|
+
status,
|
|
262
|
+
durationMs,
|
|
263
|
+
stdout,
|
|
264
|
+
stderr,
|
|
265
|
+
});
|
|
266
|
+
throw error;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return buildModelFailureResult({
|
|
270
|
+
model: provider,
|
|
271
|
+
error: `Pi ${provider} request returned no usable output`,
|
|
272
|
+
durationMs: Date.now() - startTime,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
const durationMs = Date.now() - startTime;
|
|
277
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
278
|
+
logReviewRawModelOutput({
|
|
279
|
+
model: provider,
|
|
280
|
+
stage: 'exception',
|
|
281
|
+
success: false,
|
|
282
|
+
status,
|
|
283
|
+
durationMs,
|
|
284
|
+
stdout,
|
|
285
|
+
stderr,
|
|
286
|
+
});
|
|
287
|
+
logger.error('Review model failed', {
|
|
288
|
+
model: provider,
|
|
289
|
+
engine: REVIEW_LOG_ENGINE,
|
|
290
|
+
error: errorMessage,
|
|
291
|
+
durationMs,
|
|
292
|
+
});
|
|
293
|
+
return buildModelFailureResult({
|
|
294
|
+
model: provider,
|
|
295
|
+
error: errorMessage,
|
|
296
|
+
durationMs,
|
|
297
|
+
timedOut: errorMessage.toLowerCase().includes('timed out'),
|
|
298
|
+
telemetry: latestTelemetry,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { execFile } from 'child_process';
|
|
2
|
+
const DEFAULT_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
|
|
3
|
+
function isTimeoutError(error) {
|
|
4
|
+
if (!error) {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
return (error.code === 'ETIMEDOUT' ||
|
|
8
|
+
error.code === 143 ||
|
|
9
|
+
error.killed === true ||
|
|
10
|
+
error.signal === 'SIGTERM' ||
|
|
11
|
+
error.message.toLowerCase().includes('timed out') ||
|
|
12
|
+
error.message.toLowerCase().includes('etimedout'));
|
|
13
|
+
}
|
|
14
|
+
export function runProcess({ command, args, cwd, env, timeoutMs, maxBufferBytes = DEFAULT_MAX_BUFFER_BYTES, }) {
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
execFile(command, args, {
|
|
17
|
+
cwd,
|
|
18
|
+
env,
|
|
19
|
+
encoding: 'utf8',
|
|
20
|
+
maxBuffer: maxBufferBytes,
|
|
21
|
+
timeout: timeoutMs,
|
|
22
|
+
}, (error, stdout, stderr) => {
|
|
23
|
+
const timedOut = isTimeoutError(error);
|
|
24
|
+
const status = typeof error?.code === 'number' ? error.code : error == null ? 0 : null;
|
|
25
|
+
resolve({
|
|
26
|
+
stdout: stdout ?? '',
|
|
27
|
+
stderr: stderr ?? '',
|
|
28
|
+
status,
|
|
29
|
+
timedOut,
|
|
30
|
+
errorMessage: error?.message,
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Ajv } from 'ajv';
|
|
2
|
+
import { TRANSLATION_RESOLUTIONS } from './types.js';
|
|
3
|
+
export const REVIEW_SCHEMA = {
|
|
4
|
+
type: 'object',
|
|
5
|
+
additionalProperties: false,
|
|
6
|
+
properties: {
|
|
7
|
+
summary: { type: 'string' },
|
|
8
|
+
comments: {
|
|
9
|
+
type: 'array',
|
|
10
|
+
items: {
|
|
11
|
+
type: 'object',
|
|
12
|
+
additionalProperties: false,
|
|
13
|
+
properties: {
|
|
14
|
+
path: { type: 'string', minLength: 1 },
|
|
15
|
+
line: { type: 'integer', minimum: 1 },
|
|
16
|
+
side: { type: 'string', enum: ['LEFT', 'RIGHT'] },
|
|
17
|
+
body: { type: 'string', minLength: 1 },
|
|
18
|
+
translationResolution: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
enum: TRANSLATION_RESOLUTIONS,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
// Classification stays optional so omission keeps the finding instead of failing
|
|
24
|
+
// the entire review pass.
|
|
25
|
+
required: ['path', 'line', 'side', 'body'],
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
required: ['summary', 'comments'],
|
|
30
|
+
};
|
|
31
|
+
export const DEDUPE_SCHEMA = {
|
|
32
|
+
type: 'object',
|
|
33
|
+
additionalProperties: false,
|
|
34
|
+
properties: {
|
|
35
|
+
groups: {
|
|
36
|
+
type: 'array',
|
|
37
|
+
items: {
|
|
38
|
+
type: 'object',
|
|
39
|
+
additionalProperties: false,
|
|
40
|
+
properties: {
|
|
41
|
+
ids: {
|
|
42
|
+
type: 'array',
|
|
43
|
+
items: { type: 'string', minLength: 1 },
|
|
44
|
+
},
|
|
45
|
+
reason: { type: 'string' },
|
|
46
|
+
},
|
|
47
|
+
required: ['ids'],
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
required: ['groups'],
|
|
52
|
+
};
|
|
53
|
+
const ajv = new Ajv();
|
|
54
|
+
const validateRawReviewResult = ajv.compile(REVIEW_SCHEMA);
|
|
55
|
+
const validateDedupeResponse = ajv.compile(DEDUPE_SCHEMA);
|
|
56
|
+
export function isRawReviewResult(value) {
|
|
57
|
+
return validateRawReviewResult(value);
|
|
58
|
+
}
|
|
59
|
+
export function isDedupeResponse(value) {
|
|
60
|
+
return validateDedupeResponse(value);
|
|
61
|
+
}
|