@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,124 @@
|
|
|
1
|
+
import { SpanStatusCode } from '@opentelemetry/api';
|
|
2
|
+
import { DEFAULT_PI_MODELS, invokePiPrompt, } from '../../core/pi.js';
|
|
3
|
+
import { recordEvent, startToolSpan, withPhase } from '../../core/tracing.js';
|
|
4
|
+
const ISSUE_TRIAGE_TIMEOUT_MS = 90 * 60 * 1000;
|
|
5
|
+
const TRIAGE_PROVIDER = 'openai';
|
|
6
|
+
export async function invokeTriageModel({ prompt, apiKey, cwd, timeoutMs = ISSUE_TRIAGE_TIMEOUT_MS, extraEnv, thinkingLevel, modelName = DEFAULT_PI_MODELS[TRIAGE_PROVIDER], capabilityPreset = 'shell-focused',
|
|
7
|
+
// Callers on other paths (e.g. fix generation) pass their own phase name
|
|
8
|
+
// so model telemetry stays separable per flow.
|
|
9
|
+
phaseName = 'triage.model', }) {
|
|
10
|
+
return withPhase(phaseName, {
|
|
11
|
+
'gen_ai.operation.name': 'chat',
|
|
12
|
+
'gen_ai.provider.name': TRIAGE_PROVIDER,
|
|
13
|
+
'gen_ai.request.model': modelName,
|
|
14
|
+
}, async (span) => {
|
|
15
|
+
// pi emits tool start/end across separate onProgress callbacks, so
|
|
16
|
+
// tool spans are managed manually (the documented exception to
|
|
17
|
+
// "withPhase everywhere"). startToolSpan captures context.active()
|
|
18
|
+
// at call time, parenting each tool span under THIS model span.
|
|
19
|
+
const inFlightTools = new Map();
|
|
20
|
+
function toolKey(event) {
|
|
21
|
+
return event.toolCallId ?? event.toolName ?? '';
|
|
22
|
+
}
|
|
23
|
+
function onProgress(event) {
|
|
24
|
+
switch (event.type) {
|
|
25
|
+
case 'tool_start': {
|
|
26
|
+
const toolCallId = event.toolCallId;
|
|
27
|
+
const key = toolKey(event);
|
|
28
|
+
// Overlapping same-name tools without a toolCallId
|
|
29
|
+
// collide on the key; close the displaced span so it
|
|
30
|
+
// is never leaked un-ended.
|
|
31
|
+
const displaced = inFlightTools.get(key);
|
|
32
|
+
if (displaced) {
|
|
33
|
+
displaced.setStatus({
|
|
34
|
+
code: SpanStatusCode.ERROR,
|
|
35
|
+
message: 'replaced by overlapping same-name tool without toolCallId',
|
|
36
|
+
});
|
|
37
|
+
displaced.end();
|
|
38
|
+
}
|
|
39
|
+
const toolSpan = startToolSpan(event.toolName, {
|
|
40
|
+
'gen_ai.operation.name': 'execute_tool',
|
|
41
|
+
'gen_ai.tool.name': event.toolName,
|
|
42
|
+
...(toolCallId ? { 'gen_ai.tool.call.id': toolCallId } : {}),
|
|
43
|
+
});
|
|
44
|
+
inFlightTools.set(key, toolSpan);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
case 'tool_end': {
|
|
48
|
+
const key = toolKey(event);
|
|
49
|
+
const toolSpan = inFlightTools.get(key);
|
|
50
|
+
if (toolSpan) {
|
|
51
|
+
if (event.isError) {
|
|
52
|
+
toolSpan.setStatus({
|
|
53
|
+
code: SpanStatusCode.ERROR,
|
|
54
|
+
message: 'tool reported error',
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
toolSpan.end();
|
|
58
|
+
inFlightTools.delete(key);
|
|
59
|
+
}
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
case 'thinking':
|
|
63
|
+
case 'responding': {
|
|
64
|
+
// Liveness signal for long model phases: pi already
|
|
65
|
+
// throttles these to one per 30s. Emitted as point-in-time
|
|
66
|
+
// events (correlated to this model span), NOT spans —
|
|
67
|
+
// they are heartbeats, not boundaries.
|
|
68
|
+
recordEvent('model.heartbeat', { state: event.type });
|
|
69
|
+
}
|
|
70
|
+
// agent_start/agent_end duplicate this phase span's own
|
|
71
|
+
// boundaries and tool_update is covered by the open tool
|
|
72
|
+
// span — intentionally no emission for those.
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const startedAt = Date.now();
|
|
76
|
+
const result = await invokePiPrompt({
|
|
77
|
+
provider: TRIAGE_PROVIDER,
|
|
78
|
+
apiKey,
|
|
79
|
+
prompt,
|
|
80
|
+
cwd,
|
|
81
|
+
timeoutMs,
|
|
82
|
+
extraEnv,
|
|
83
|
+
capabilityPreset,
|
|
84
|
+
...(thinkingLevel ? { thinkingLevel } : {}),
|
|
85
|
+
...(modelName ? { modelName } : {}),
|
|
86
|
+
onProgress,
|
|
87
|
+
});
|
|
88
|
+
// Force-end any tool spans pi never closed (defensive — pi's stream
|
|
89
|
+
// is ordered, but if a tool_end was lost we don't want hanging spans).
|
|
90
|
+
for (const [, toolSpan] of inFlightTools) {
|
|
91
|
+
toolSpan.setStatus({
|
|
92
|
+
code: SpanStatusCode.ERROR,
|
|
93
|
+
message: 'tool span did not receive matching end',
|
|
94
|
+
});
|
|
95
|
+
toolSpan.end();
|
|
96
|
+
}
|
|
97
|
+
inFlightTools.clear();
|
|
98
|
+
const durationMs = Date.now() - startedAt;
|
|
99
|
+
if (!result.ok) {
|
|
100
|
+
// Mark the model phase as failed. withPhase does NOT force OK on
|
|
101
|
+
// success, so this ERROR status survives to the phase.end line.
|
|
102
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: result.error });
|
|
103
|
+
return {
|
|
104
|
+
ok: false,
|
|
105
|
+
error: result.error,
|
|
106
|
+
durationMs,
|
|
107
|
+
timedOut: result.timedOut,
|
|
108
|
+
telemetry: result.telemetry,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const output = result.output.trim();
|
|
112
|
+
if (!output) {
|
|
113
|
+
const message = 'Pi triage model did not produce an assistant response';
|
|
114
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message });
|
|
115
|
+
return {
|
|
116
|
+
ok: false,
|
|
117
|
+
error: message,
|
|
118
|
+
durationMs,
|
|
119
|
+
timedOut: false,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return { ok: true, output, durationMs, telemetry: result.telemetry };
|
|
123
|
+
});
|
|
124
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
2
|
+
import { SpanStatusCode } from '@opentelemetry/api';
|
|
3
|
+
import { fillPromptTemplate } from '../../core/prompt-template.js';
|
|
4
|
+
import { withPhase } from '../../core/tracing.js';
|
|
5
|
+
import { MAX_TRIAGE_COMMENT_CHARS, TRIAGE_MARKER } from './markers.js';
|
|
6
|
+
import { escapeReportText, parseTriageAnalysis, readTriageReport, renderTriageReport, reportAssessment, reportForFix, TRIAGE_METADATA_MARKER, TRIAGE_REJECTED_MARKER, TRIAGE_REPORT_MARKER, } from './report.js';
|
|
7
|
+
import { createTriageReport } from './report-history.js';
|
|
8
|
+
const UNRESOLVED_LABELS_TEMPLATE = `I couldn’t resolve triage repositories from the current issue labels.
|
|
9
|
+
|
|
10
|
+
Reason: $REASON
|
|
11
|
+
|
|
12
|
+
Detected labels: Product=[$PRODUCTS] App=[$APPS] Team=[$TEAMS]
|
|
13
|
+
|
|
14
|
+
Please update labels and run \`@doistbot /triage\` again.
|
|
15
|
+
Expected examples:
|
|
16
|
+
- \`Product:Todoist\` + \`App:Android\`
|
|
17
|
+
- \`Product:Comms\` + \`App:Android\`
|
|
18
|
+
- \`Product:Todoist\` + \`App:All\``;
|
|
19
|
+
export function buildTriageFailureMessage(reason) {
|
|
20
|
+
const normalizedReason = reason.trim().replace(/\s+/g, ' ');
|
|
21
|
+
return `Triage failed: ${normalizedReason || 'Unknown error'}`;
|
|
22
|
+
}
|
|
23
|
+
export function buildUnresolvedLabelsOutput({ reason, productLabels, appLabels, teamLabels, }) {
|
|
24
|
+
const products = productLabels.length > 0 ? productLabels.join(', ') : '(none)';
|
|
25
|
+
const apps = appLabels.length > 0 ? appLabels.join(', ') : '(none)';
|
|
26
|
+
const teams = teamLabels.length > 0 ? teamLabels.join(', ') : '(none)';
|
|
27
|
+
return fillPromptTemplate(UNRESOLVED_LABELS_TEMPLATE, {
|
|
28
|
+
REASON: reason,
|
|
29
|
+
PRODUCTS: products,
|
|
30
|
+
APPS: apps,
|
|
31
|
+
TEAMS: teams,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
const VALID_CLASSIFICATIONS = [
|
|
35
|
+
'auto-fixable',
|
|
36
|
+
'speculative',
|
|
37
|
+
'logs-needed',
|
|
38
|
+
'needs-human',
|
|
39
|
+
'insufficient-context',
|
|
40
|
+
];
|
|
41
|
+
const VALID_CONFIDENCES = ['high', 'medium', 'low'];
|
|
42
|
+
const FIX_ASSESSMENT_HEADING = '## Automated Fix Assessment';
|
|
43
|
+
export function parseFixAssessment(triageOutput) {
|
|
44
|
+
if (triageOutput.includes(TRIAGE_REJECTED_MARKER))
|
|
45
|
+
return null;
|
|
46
|
+
if (triageOutput.includes(TRIAGE_REPORT_MARKER) ||
|
|
47
|
+
triageOutput.includes(TRIAGE_METADATA_MARKER)) {
|
|
48
|
+
try {
|
|
49
|
+
const report = readTriageReport(triageOutput);
|
|
50
|
+
return report ? reportAssessment(report) : null;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const headingPattern = new RegExp(`^${FIX_ASSESSMENT_HEADING}\\s*$`, 'm');
|
|
57
|
+
const headingMatch = triageOutput.match(headingPattern);
|
|
58
|
+
if (headingMatch?.index === undefined) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const afterHeading = triageOutput.slice(headingMatch.index + headingMatch[0].length);
|
|
62
|
+
const nextHeadingIndex = afterHeading.search(/^##\s/m);
|
|
63
|
+
const sectionBody = nextHeadingIndex >= 0 ? afterHeading.slice(0, nextHeadingIndex) : afterHeading;
|
|
64
|
+
const jsonMatch = sectionBody.match(/```json\s*\n([\s\S]*?)\n\s*```/);
|
|
65
|
+
if (!jsonMatch?.[1]) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
let parsed;
|
|
69
|
+
try {
|
|
70
|
+
parsed = JSON.parse(jsonMatch[1]);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
const { classification, confidence, scope, targetRepo } = parsed;
|
|
79
|
+
if (typeof classification !== 'string' ||
|
|
80
|
+
typeof confidence !== 'string' ||
|
|
81
|
+
typeof scope !== 'string' ||
|
|
82
|
+
!VALID_CLASSIFICATIONS.includes(classification) ||
|
|
83
|
+
!VALID_CONFIDENCES.includes(confidence)) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
classification: classification,
|
|
88
|
+
confidence: confidence,
|
|
89
|
+
scope,
|
|
90
|
+
...(typeof targetRepo === 'string' && targetRepo ? { targetRepo } : {}),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function rejectedOutput(reason, rawOutput) {
|
|
94
|
+
const start = `${escapeReportText(buildTriageFailureMessage(reason).slice(0, 1000))}\n\n${TRIAGE_REJECTED_MARKER}\n`;
|
|
95
|
+
const end = '\n-->';
|
|
96
|
+
const budget = MAX_TRIAGE_COMMENT_CHARS - TRIAGE_MARKER.length - 2 - start.length - end.length;
|
|
97
|
+
const escaped = escapeReportText(rawOutput);
|
|
98
|
+
if (escaped.length <= budget)
|
|
99
|
+
return start + escaped + end;
|
|
100
|
+
const notice = '\n\n[Output shortened to fit the comment limit.]';
|
|
101
|
+
let excerpt = '';
|
|
102
|
+
for (const character of rawOutput) {
|
|
103
|
+
const escapedCharacter = escapeReportText(character);
|
|
104
|
+
if (excerpt.length + escapedCharacter.length > budget - notice.length)
|
|
105
|
+
break;
|
|
106
|
+
excerpt += escapedCharacter;
|
|
107
|
+
}
|
|
108
|
+
return start + excerpt + notice + end;
|
|
109
|
+
}
|
|
110
|
+
export function validateTriageOutput(params) {
|
|
111
|
+
return withPhase('triage.output', { 'output.length': params.rawOutput.length }, async (span) => {
|
|
112
|
+
function reject(error) {
|
|
113
|
+
span.setAttributes({ 'output.quality_check.passed': false });
|
|
114
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: error });
|
|
115
|
+
return {
|
|
116
|
+
ok: false,
|
|
117
|
+
error,
|
|
118
|
+
commentOutput: rejectedOutput(error, params.rawOutput),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
const analysis = parseTriageAnalysis(params.rawOutput);
|
|
123
|
+
const { decision, reinvestigation } = analysis;
|
|
124
|
+
if (params.expectedMetadata &&
|
|
125
|
+
!isDeepStrictEqual({ decision, reinvestigation }, params.expectedMetadata)) {
|
|
126
|
+
throw new Error('Shortening the report changed its workflow metadata.');
|
|
127
|
+
}
|
|
128
|
+
const report = createTriageReport({
|
|
129
|
+
analysis,
|
|
130
|
+
issueUrl: params.issueUrl,
|
|
131
|
+
repositories: params.repositories,
|
|
132
|
+
history: params.history,
|
|
133
|
+
});
|
|
134
|
+
const commentOutput = renderTriageReport(report);
|
|
135
|
+
if (params.enforceCommentLimit !== false &&
|
|
136
|
+
commentOutput.length + TRIAGE_MARKER.length + 2 > MAX_TRIAGE_COMMENT_CHARS) {
|
|
137
|
+
return {
|
|
138
|
+
...reject(`Triage report exceeds ${MAX_TRIAGE_COMMENT_CHARS} characters.`),
|
|
139
|
+
reason: 'too-long',
|
|
140
|
+
analysis,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
span.setAttributes({
|
|
144
|
+
'output.quality_check.passed': true,
|
|
145
|
+
'output.classification': report.analysis.decision.classification,
|
|
146
|
+
'output.completed_logging_rounds': report.completedLoggingRounds,
|
|
147
|
+
});
|
|
148
|
+
return {
|
|
149
|
+
ok: true,
|
|
150
|
+
report,
|
|
151
|
+
commentOutput,
|
|
152
|
+
output: reportForFix(report),
|
|
153
|
+
assessment: reportAssessment(report),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
158
|
+
return {
|
|
159
|
+
...reject(message),
|
|
160
|
+
reason: 'invalid',
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|