@rallycry/conveyor-agent 8.12.0 → 9.0.0
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/{chunk-FSUVTOFP.js → chunk-JH7GUOGW.js} +6673 -8717
- package/dist/chunk-JH7GUOGW.js.map +1 -0
- package/dist/cli.js +148 -208
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +8 -500
- package/dist/index.js +51 -1473
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-FDWECEDJ.js +0 -33
- package/dist/chunk-FDWECEDJ.js.map +0 -1
- package/dist/chunk-FSUVTOFP.js.map +0 -1
- package/dist/chunk-TN2YYT2V.js +0 -1444
- package/dist/chunk-TN2YYT2V.js.map +0 -1
- package/dist/tag-audit-handler-X3LEX63H.js +0 -222
- package/dist/tag-audit-handler-X3LEX63H.js.map +0 -1
- package/dist/task-audit-handler-BRCXB5V6.js +0 -801
- package/dist/task-audit-handler-BRCXB5V6.js.map +0 -1
|
@@ -1,801 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
textResult
|
|
3
|
-
} from "./chunk-FDWECEDJ.js";
|
|
4
|
-
import {
|
|
5
|
-
createHarness,
|
|
6
|
-
createServiceLogger,
|
|
7
|
-
defineTool
|
|
8
|
-
} from "./chunk-TN2YYT2V.js";
|
|
9
|
-
|
|
10
|
-
// src/runner/task-audit-handler.ts
|
|
11
|
-
import { z } from "zod";
|
|
12
|
-
|
|
13
|
-
// src/runner/task-audit-parsing.ts
|
|
14
|
-
function formatMacroEvaluation(macro) {
|
|
15
|
-
if (!macro) return null;
|
|
16
|
-
const lines = [];
|
|
17
|
-
if (macro.instructionsQuality) lines.push(`- Instructions Quality: ${macro.instructionsQuality}`);
|
|
18
|
-
if (macro.modelSuitability) lines.push(`- Model Suitability: ${macro.modelSuitability}`);
|
|
19
|
-
if (macro.contextAndPlanning) lines.push(`- Context & Planning: ${macro.contextAndPlanning}`);
|
|
20
|
-
if (macro.overallResult) lines.push(`- Overall Result: ${macro.overallResult}`);
|
|
21
|
-
return lines.length > 0 ? lines.join("\n") : null;
|
|
22
|
-
}
|
|
23
|
-
function formatHumanEvaluations(evals) {
|
|
24
|
-
if (evals.length === 0) return null;
|
|
25
|
-
const total = evals.reduce((sum, e) => sum + e.rating, 0);
|
|
26
|
-
const avg = total / evals.length;
|
|
27
|
-
const label = avg > 0.3 ? "Helpful" : avg < -0.3 ? "Unhelpful" : "Neutral";
|
|
28
|
-
const lines = [
|
|
29
|
-
`- Overall: ${label} (avg ${avg.toFixed(2)}, ${evals.length} message${evals.length === 1 ? "" : "s"})`
|
|
30
|
-
];
|
|
31
|
-
for (const e of evals) {
|
|
32
|
-
const ratingLabel = e.rating > 0 ? "helpful" : e.rating < 0 ? "unhelpful" : "neutral";
|
|
33
|
-
lines.push(` - Message ${e.messageIndex}: ${ratingLabel} \u2014 ${e.reasoning}`);
|
|
34
|
-
}
|
|
35
|
-
return lines.join("\n");
|
|
36
|
-
}
|
|
37
|
-
function computeGradeCounts(turnGrades) {
|
|
38
|
-
const counts = {
|
|
39
|
-
planningCorrect: 0,
|
|
40
|
-
planningNeutral: 0,
|
|
41
|
-
planningBlunder: 0,
|
|
42
|
-
buildingCorrect: 0,
|
|
43
|
-
buildingNeutral: 0,
|
|
44
|
-
buildingBlunder: 0,
|
|
45
|
-
humanCorrect: 0,
|
|
46
|
-
humanNeutral: 0,
|
|
47
|
-
humanBlunder: 0
|
|
48
|
-
};
|
|
49
|
-
for (const tg of turnGrades) {
|
|
50
|
-
const key = `${tg.phase}${tg.grade.charAt(0).toUpperCase()}${tg.grade.slice(1)}`;
|
|
51
|
-
if (key in counts) {
|
|
52
|
-
counts[key]++;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
return counts;
|
|
56
|
-
}
|
|
57
|
-
function addCosts(base, addition) {
|
|
58
|
-
if (base === null) return addition;
|
|
59
|
-
if (addition === null) return base;
|
|
60
|
-
return base + addition;
|
|
61
|
-
}
|
|
62
|
-
function buildErrorResult(projectId, requestId, taskId, error) {
|
|
63
|
-
return {
|
|
64
|
-
projectId,
|
|
65
|
-
requestId,
|
|
66
|
-
taskId,
|
|
67
|
-
summary: "",
|
|
68
|
-
turnGrades: [],
|
|
69
|
-
planningAccuracy: null,
|
|
70
|
-
buildingAccuracy: null,
|
|
71
|
-
humanAccuracy: null,
|
|
72
|
-
planningCorrect: 0,
|
|
73
|
-
planningNeutral: 0,
|
|
74
|
-
planningBlunder: 0,
|
|
75
|
-
buildingCorrect: 0,
|
|
76
|
-
buildingNeutral: 0,
|
|
77
|
-
buildingBlunder: 0,
|
|
78
|
-
humanCorrect: 0,
|
|
79
|
-
humanNeutral: 0,
|
|
80
|
-
humanBlunder: 0,
|
|
81
|
-
suggestionIds: [],
|
|
82
|
-
auditCostUsd: null,
|
|
83
|
-
model: null,
|
|
84
|
-
error
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
function extractJsonBlock(text) {
|
|
88
|
-
const codeBlockMatch = text.match(/```(?:json)?\s*\n?([\s\S]*?)```/);
|
|
89
|
-
const jsonStr = codeBlockMatch ? codeBlockMatch[1].trim() : text;
|
|
90
|
-
try {
|
|
91
|
-
const parsed = JSON.parse(jsonStr);
|
|
92
|
-
if (parsed && typeof parsed === "object") return parsed;
|
|
93
|
-
} catch {
|
|
94
|
-
}
|
|
95
|
-
const objectMatch = text.match(/\{[\s\S]*\}/);
|
|
96
|
-
if (objectMatch) {
|
|
97
|
-
try {
|
|
98
|
-
const parsed = JSON.parse(objectMatch[0]);
|
|
99
|
-
if (parsed && typeof parsed === "object") return parsed;
|
|
100
|
-
} catch {
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
throw new Error("Could not parse JSON from Claude response");
|
|
104
|
-
}
|
|
105
|
-
var VALID_GRADING_PHASES = /* @__PURE__ */ new Set(["planning", "building"]);
|
|
106
|
-
function extractCombinedGradingResult(text) {
|
|
107
|
-
const parsed = extractJsonBlock(text);
|
|
108
|
-
const turnGrades = Array.isArray(parsed.turnGrades) ? parsed.turnGrades.map((tg) => ({
|
|
109
|
-
...tg,
|
|
110
|
-
phase: VALID_GRADING_PHASES.has(tg.phase) ? tg.phase : "building"
|
|
111
|
-
})) : [];
|
|
112
|
-
const summary = parsed.summary ?? "";
|
|
113
|
-
const planningAccuracy = typeof parsed.planningAccuracy === "number" ? parsed.planningAccuracy : null;
|
|
114
|
-
const buildingAccuracy = typeof parsed.buildingAccuracy === "number" ? parsed.buildingAccuracy : null;
|
|
115
|
-
return { turnGrades, summary, planningAccuracy, buildingAccuracy };
|
|
116
|
-
}
|
|
117
|
-
function extractHumanResult(text) {
|
|
118
|
-
const parsed = extractJsonBlock(text);
|
|
119
|
-
const humanEvaluations = Array.isArray(parsed.humanEvaluations) ? parsed.humanEvaluations : [];
|
|
120
|
-
const summary = parsed.summary ?? "";
|
|
121
|
-
const humanAccuracy = typeof parsed.humanAccuracy === "number" ? parsed.humanAccuracy : null;
|
|
122
|
-
return { humanEvaluations, summary, humanAccuracy };
|
|
123
|
-
}
|
|
124
|
-
function extractAnalysisResult(text) {
|
|
125
|
-
const parsed = extractJsonBlock(text);
|
|
126
|
-
const summary = parsed.summary ?? "";
|
|
127
|
-
const macro = parsed.macroEvaluation;
|
|
128
|
-
return {
|
|
129
|
-
summary,
|
|
130
|
-
macroEvaluation: macro ? {
|
|
131
|
-
instructionsQuality: macro.instructionsQuality ?? void 0,
|
|
132
|
-
modelSuitability: macro.modelSuitability ?? void 0,
|
|
133
|
-
contextAndPlanning: macro.contextAndPlanning ?? void 0,
|
|
134
|
-
overallResult: macro.overallResult ?? void 0
|
|
135
|
-
} : null
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
function buildMergedSummary(gradingResult, humanResult, analysisResult) {
|
|
139
|
-
const summaryParts = [];
|
|
140
|
-
if (analysisResult?.summary) {
|
|
141
|
-
summaryParts.push(analysisResult.summary);
|
|
142
|
-
} else {
|
|
143
|
-
if (gradingResult?.summary) summaryParts.push(gradingResult.summary);
|
|
144
|
-
if (humanResult?.summary) summaryParts.push(`Human: ${humanResult.summary}`);
|
|
145
|
-
}
|
|
146
|
-
const macroText = analysisResult?.macroEvaluation ? formatMacroEvaluation(analysisResult.macroEvaluation) : null;
|
|
147
|
-
const humanEvalText = humanResult ? formatHumanEvaluations(humanResult.humanEvaluations) : null;
|
|
148
|
-
let summary = summaryParts.join("\n\n");
|
|
149
|
-
if (macroText) summary += `
|
|
150
|
-
|
|
151
|
-
---
|
|
152
|
-
|
|
153
|
-
Macro Evaluation:
|
|
154
|
-
${macroText}`;
|
|
155
|
-
if (humanEvalText) summary += `
|
|
156
|
-
|
|
157
|
-
---
|
|
158
|
-
|
|
159
|
-
Human Contributions:
|
|
160
|
-
${humanEvalText}`;
|
|
161
|
-
return summary;
|
|
162
|
-
}
|
|
163
|
-
function mergeStepResults(gradingResult, humanResult, analysisResult) {
|
|
164
|
-
const agentGrades = gradingResult?.turnGrades ?? [];
|
|
165
|
-
const humanGrades = (humanResult?.humanEvaluations ?? []).map((e) => ({
|
|
166
|
-
turnIndex: e.messageIndex,
|
|
167
|
-
phase: "human",
|
|
168
|
-
grade: e.rating > 0 ? "correct" : e.rating < 0 ? "blunder" : "neutral",
|
|
169
|
-
reasoning: e.reasoning,
|
|
170
|
-
eventType: "human_message",
|
|
171
|
-
eventSummary: `Message ${e.messageIndex}`
|
|
172
|
-
}));
|
|
173
|
-
return {
|
|
174
|
-
turnGrades: [...agentGrades, ...humanGrades],
|
|
175
|
-
summary: buildMergedSummary(gradingResult, humanResult, analysisResult),
|
|
176
|
-
planningAccuracy: gradingResult?.planningAccuracy ?? null,
|
|
177
|
-
buildingAccuracy: gradingResult?.buildingAccuracy ?? null,
|
|
178
|
-
humanAccuracy: humanResult?.humanAccuracy ?? null,
|
|
179
|
-
humanEvaluations: humanResult?.humanEvaluations ?? [],
|
|
180
|
-
macroEvaluation: analysisResult?.macroEvaluation ?? null
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// src/runner/task-audit-prompts.ts
|
|
185
|
-
var COMBINED_GRADING_SYSTEM_PROMPT = `You are a chess-style analysis engine performing a holistic audit of an AI agent's full task execution \u2014 from initial planning through final implementation. You receive the complete agent trace covering all phases and sessions. Grade each turn objectively.
|
|
186
|
-
|
|
187
|
-
## Phase Identification
|
|
188
|
-
|
|
189
|
-
Identify the boundary between planning and building phases:
|
|
190
|
-
- **Planning phase**: Turns from the start through the last \`update_task_plan\` (plan save) call. Includes reading requirements, exploring code, analyzing dependencies, and formulating the plan.
|
|
191
|
-
- **Building phase**: Turns after the last plan save through PR creation (or end of trace). Includes implementation, testing, and PR creation.
|
|
192
|
-
- If no \`update_task_plan\` call is found, treat all turns as building phase.
|
|
193
|
-
|
|
194
|
-
You MUST label each turn with \`"phase": "planning"\` or \`"phase": "building"\`.
|
|
195
|
-
|
|
196
|
-
## Multi-Session Awareness
|
|
197
|
-
|
|
198
|
-
The trace may contain multiple agent sessions (indicated by \`session_manifest\` events). Grade ALL sessions \u2014 earlier sessions often contain valuable planning work or reflect restart patterns worth evaluating. If an earlier session was abandoned, grade its turns based on quality (exploration reads may be neutral, repeated failures may be blunders).
|
|
199
|
-
|
|
200
|
-
## Grading Scale
|
|
201
|
-
|
|
202
|
-
### Planning Turns
|
|
203
|
-
- **Correct** (\u265F): Sound moves toward understanding the task \u2014 reading relevant files, analyzing requirements, logical reasoning about approach, following project patterns
|
|
204
|
-
- **Neutral** (\xB7): No significant positive or negative effect \u2014 exploratory reads, minor tangents
|
|
205
|
-
- **Blunder** (??): Wasted effort \u2014 reading irrelevant files repeatedly, ignoring requirements, spending many turns without making progress toward a plan
|
|
206
|
-
|
|
207
|
-
### Building Turns
|
|
208
|
-
- **Correct** (\u265F): Sound implementation moves \u2014 proper edits, running tests, fixing errors, following plan steps, adapting to feedback
|
|
209
|
-
- **Neutral** (\xB7): No significant positive or negative effect \u2014 exploratory reads during implementation, minor tangents
|
|
210
|
-
- **Blunder** (??): Wasted effort \u2014 incorrect edits immediately reverted, repeated failures without adapting, ignoring the plan, using wrong APIs despite visible patterns
|
|
211
|
-
|
|
212
|
-
## Output Format
|
|
213
|
-
|
|
214
|
-
Output a JSON block:
|
|
215
|
-
|
|
216
|
-
\`\`\`json
|
|
217
|
-
{
|
|
218
|
-
"turnGrades": [
|
|
219
|
-
{
|
|
220
|
-
"turnIndex": 0,
|
|
221
|
-
"phase": "planning",
|
|
222
|
-
"grade": "correct",
|
|
223
|
-
"reasoning": "Read relevant config files to understand project structure",
|
|
224
|
-
"eventType": "tool_use",
|
|
225
|
-
"eventSummary": "Read package.json and tsconfig.json"
|
|
226
|
-
}
|
|
227
|
-
],
|
|
228
|
-
"summary": "Brief 2-3 sentence holistic assessment of the full execution",
|
|
229
|
-
"planningAccuracy": 0.85,
|
|
230
|
-
"buildingAccuracy": 0.72
|
|
231
|
-
}
|
|
232
|
-
\`\`\`
|
|
233
|
-
|
|
234
|
-
**Accuracy Calculation**: For each phase separately: accuracy = correct / (correct + blunder). Neutral turns are excluded. Return null for a phase if it has no correct or blunder turns.
|
|
235
|
-
|
|
236
|
-
Turn indices are 0-based relative to the full trace provided. Every turn grade MUST include the phase field.`;
|
|
237
|
-
var HUMAN_AUDIT_SYSTEM_PROMPT = `You are evaluating human contributions to an AI agent's task execution. For each human message, assess whether it helped, hindered, or had no effect on the agent's work.
|
|
238
|
-
|
|
239
|
-
## Rating Scale
|
|
240
|
-
|
|
241
|
-
- **Helpful (+1)**: Provided useful context, clear corrections, actionable feedback, or unblocked the agent
|
|
242
|
-
- **Neutral (0)**: Acknowledgements, status checks, or messages with no material impact on execution
|
|
243
|
-
- **Unhelpful (-1)**: Contradictory instructions, incorrect guidance, unnecessary interruptions, or vague requests that caused wasted effort
|
|
244
|
-
|
|
245
|
-
## Output Format
|
|
246
|
-
|
|
247
|
-
Output a JSON block:
|
|
248
|
-
|
|
249
|
-
\`\`\`json
|
|
250
|
-
{
|
|
251
|
-
"humanEvaluations": [
|
|
252
|
-
{
|
|
253
|
-
"messageIndex": 0,
|
|
254
|
-
"rating": 1,
|
|
255
|
-
"reasoning": "Provided clear context about the bug with reproduction steps"
|
|
256
|
-
}
|
|
257
|
-
],
|
|
258
|
-
"summary": "Brief 2-3 sentence assessment of human contributions",
|
|
259
|
-
"humanAccuracy": 0.75
|
|
260
|
-
}
|
|
261
|
-
\`\`\`
|
|
262
|
-
|
|
263
|
-
**humanAccuracy Calculation**: humanAccuracy = helpful / (helpful + unhelpful). Neutral messages are excluded. Return null if no helpful or unhelpful messages exist.
|
|
264
|
-
|
|
265
|
-
Evaluate EVERY human message \u2014 do not skip any. Each entry in humanEvaluations must have a messageIndex (0-based index into the Human Messages list), rating (+1, 0, or -1), and reasoning.`;
|
|
266
|
-
var ANALYSIS_SYSTEM_PROMPT = `You are performing a macro-level analysis of an AI agent's task execution and generating improvement suggestions. You receive grading results from planning, building, and human evaluation phases.
|
|
267
|
-
|
|
268
|
-
## Your Tasks
|
|
269
|
-
|
|
270
|
-
1. **Write an overall summary** (2-4 sentences) that synthesizes the planning, building, and human evaluation findings into a cohesive assessment.
|
|
271
|
-
|
|
272
|
-
2. **Perform a macro evaluation** addressing:
|
|
273
|
-
- **Instructions Quality**: Were the agent instructions appropriate? Too vague, too prescriptive, or missing critical guidance?
|
|
274
|
-
- **Model Suitability**: Was the model choice appropriate for the complexity of this task?
|
|
275
|
-
- **Context & Planning**: Was the plan/context sufficient? Were there information gaps that caused wasted effort?
|
|
276
|
-
- **Overall Result**: Did the final result meet requirements? Was the outcome proportional to effort?
|
|
277
|
-
|
|
278
|
-
3. **Generate suggestions** using the create_suggestion tool. Focus on:
|
|
279
|
-
- Recurring patterns of wasted effort that could be prevented
|
|
280
|
-
- Missing documentation or context that caused confusion
|
|
281
|
-
- Process improvements to prevent common blunders
|
|
282
|
-
- What worked well that should be replicated
|
|
283
|
-
|
|
284
|
-
Suggestion guidelines:
|
|
285
|
-
- Keep titles short and actionable (e.g. "Add error handling docs for payment service")
|
|
286
|
-
- Keep descriptions to 1-2 sentences max \u2014 what should change and why
|
|
287
|
-
- Be project-focused: describe what the PROJECT should change, not what happened in this task
|
|
288
|
-
- Never reference specific task details, agent behavior, accuracy scores, or execution phases
|
|
289
|
-
- If this task revealed a general project improvement, frame it as a standalone recommendation anyone could understand without task context
|
|
290
|
-
|
|
291
|
-
You MUST call create_suggestion at least once. If the execution was flawless, suggest what made it work well so the pattern can be replicated.
|
|
292
|
-
|
|
293
|
-
## Output Format
|
|
294
|
-
|
|
295
|
-
After calling create_suggestion, output a JSON block:
|
|
296
|
-
|
|
297
|
-
\`\`\`json
|
|
298
|
-
{
|
|
299
|
-
"summary": "Overall 2-4 sentence assessment synthesizing all phases",
|
|
300
|
-
"macroEvaluation": {
|
|
301
|
-
"instructionsQuality": "Brief assessment of instruction quality",
|
|
302
|
-
"modelSuitability": "Brief assessment of model choice",
|
|
303
|
-
"contextAndPlanning": "Brief assessment of context and planning",
|
|
304
|
-
"overallResult": "Brief assessment of the outcome"
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
\`\`\``;
|
|
308
|
-
function buildSessionInfoSection(info) {
|
|
309
|
-
const lines = ["## Session Info"];
|
|
310
|
-
if (info.model) lines.push(`- Model: ${info.model}`);
|
|
311
|
-
if (info.totalCostUsd !== null) lines.push(`- Total Cost: $${info.totalCostUsd.toFixed(4)}`);
|
|
312
|
-
if (info.planningTurns !== null) lines.push(`- Planning Turns: ${info.planningTurns}`);
|
|
313
|
-
if (info.buildingTurns !== null) lines.push(`- Building Turns: ${info.buildingTurns}`);
|
|
314
|
-
if (info.locDiff !== null) lines.push(`- Lines Changed: ${info.locDiff}`);
|
|
315
|
-
lines.push("");
|
|
316
|
-
return lines.join("\n");
|
|
317
|
-
}
|
|
318
|
-
function buildTraceSection(trace) {
|
|
319
|
-
const lines = ["## Agent Trace\n"];
|
|
320
|
-
if (trace.length === 0) {
|
|
321
|
-
lines.push("No agent trace data available.\n");
|
|
322
|
-
} else {
|
|
323
|
-
for (let i = 0; i < trace.length; i++) {
|
|
324
|
-
const event = trace[i];
|
|
325
|
-
const dataStr = JSON.stringify(event.data);
|
|
326
|
-
const truncated = dataStr.length > 2e3 ? dataStr.slice(0, 2e3) + "..." : dataStr;
|
|
327
|
-
lines.push(`### Turn ${i} [${event.type}] @ ${event.timestamp}`);
|
|
328
|
-
lines.push(truncated);
|
|
329
|
-
lines.push("");
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
return lines.join("\n");
|
|
333
|
-
}
|
|
334
|
-
function buildTaskContextSection(task) {
|
|
335
|
-
const parts = [];
|
|
336
|
-
if (task.taskDescription) parts.push(`## Description
|
|
337
|
-
${task.taskDescription}
|
|
338
|
-
`);
|
|
339
|
-
if (task.taskPlan) parts.push(`## Plan
|
|
340
|
-
${task.taskPlan}
|
|
341
|
-
`);
|
|
342
|
-
parts.push(`## Task Status: ${task.taskStatus}`);
|
|
343
|
-
if (task.storyPointValue !== null) parts.push(`## Story Points: ${task.storyPointValue}`);
|
|
344
|
-
parts.push("");
|
|
345
|
-
if (task.sessionInfo) parts.push(buildSessionInfoSection(task.sessionInfo));
|
|
346
|
-
if (task.agentInstructions) parts.push(`## Agent Instructions
|
|
347
|
-
${task.agentInstructions}
|
|
348
|
-
`);
|
|
349
|
-
return parts.join("\n");
|
|
350
|
-
}
|
|
351
|
-
function buildMultiSessionNote(task) {
|
|
352
|
-
const count = task.sessionInfo?.sessionCount;
|
|
353
|
-
if (!count || count <= 1) return null;
|
|
354
|
-
return `> **Note:** This task had ${count} total agent sessions. All sessions are included in the trace below.
|
|
355
|
-
`;
|
|
356
|
-
}
|
|
357
|
-
function buildCombinedGradingPrompt(task) {
|
|
358
|
-
const parts = [`# Full Workflow Audit: ${task.taskTitle}
|
|
359
|
-
`];
|
|
360
|
-
const sessionNote = buildMultiSessionNote(task);
|
|
361
|
-
if (sessionNote) parts.push(sessionNote);
|
|
362
|
-
parts.push(buildTaskContextSection(task));
|
|
363
|
-
parts.push(buildTraceSection(task.agentTrace));
|
|
364
|
-
parts.push(
|
|
365
|
-
"Grade each turn above with its phase (planning or building), then output the structured JSON result."
|
|
366
|
-
);
|
|
367
|
-
return parts.join("\n");
|
|
368
|
-
}
|
|
369
|
-
function buildHumanAuditPrompt(task) {
|
|
370
|
-
const parts = [`# Human Contribution Evaluation: ${task.taskTitle}
|
|
371
|
-
`];
|
|
372
|
-
if (task.taskDescription) parts.push(`## Description
|
|
373
|
-
${task.taskDescription}
|
|
374
|
-
`);
|
|
375
|
-
if (task.taskPlan) parts.push(`## Plan
|
|
376
|
-
${task.taskPlan}
|
|
377
|
-
`);
|
|
378
|
-
parts.push(`## Task Status: ${task.taskStatus}
|
|
379
|
-
`);
|
|
380
|
-
parts.push("## Human Messages\n");
|
|
381
|
-
for (let i = 0; i < task.humanMessages.length; i++) {
|
|
382
|
-
const msg = task.humanMessages[i];
|
|
383
|
-
parts.push(`### Message ${i} [${msg.createdAt}]`);
|
|
384
|
-
parts.push(`**${msg.role}**: ${msg.content}
|
|
385
|
-
`);
|
|
386
|
-
}
|
|
387
|
-
parts.push("Evaluate each human message above, then output the structured JSON result.");
|
|
388
|
-
return parts.join("\n");
|
|
389
|
-
}
|
|
390
|
-
function buildPhaseResultSection(label, grades, accuracy, summary) {
|
|
391
|
-
const correct = grades.filter((g) => g.grade === "correct").length;
|
|
392
|
-
const neutral = grades.filter((g) => g.grade === "neutral").length;
|
|
393
|
-
const blunder = grades.filter((g) => g.grade === "blunder").length;
|
|
394
|
-
const accuracyStr = accuracy === null ? "N/A" : accuracy.toFixed(2);
|
|
395
|
-
return [
|
|
396
|
-
`## ${label} Phase Results`,
|
|
397
|
-
`- Summary: ${summary}`,
|
|
398
|
-
`- Accuracy: ${accuracyStr}`,
|
|
399
|
-
`- ${grades.length} turns: ${correct} correct, ${neutral} neutral, ${blunder} blunders`,
|
|
400
|
-
""
|
|
401
|
-
].join("\n");
|
|
402
|
-
}
|
|
403
|
-
function buildAnalysisPrompt(task, gradingResult, humanResult) {
|
|
404
|
-
const parts = [`# Task Analysis: ${task.taskTitle}
|
|
405
|
-
`];
|
|
406
|
-
if (task.taskDescription) parts.push(`## Description
|
|
407
|
-
${task.taskDescription}
|
|
408
|
-
`);
|
|
409
|
-
if (task.taskPlan) parts.push(`## Plan
|
|
410
|
-
${task.taskPlan}
|
|
411
|
-
`);
|
|
412
|
-
parts.push(`## Task Status: ${task.taskStatus}`);
|
|
413
|
-
if (task.storyPointValue !== null) parts.push(`## Story Points: ${task.storyPointValue}`);
|
|
414
|
-
parts.push("");
|
|
415
|
-
if (task.sessionInfo) parts.push(buildSessionInfoSection(task.sessionInfo));
|
|
416
|
-
if (gradingResult) {
|
|
417
|
-
const planGrades = gradingResult.turnGrades.filter((g) => g.phase === "planning");
|
|
418
|
-
const buildGrades = gradingResult.turnGrades.filter((g) => g.phase === "building");
|
|
419
|
-
if (planGrades.length > 0) {
|
|
420
|
-
parts.push(
|
|
421
|
-
buildPhaseResultSection(
|
|
422
|
-
"Planning",
|
|
423
|
-
planGrades,
|
|
424
|
-
gradingResult.planningAccuracy,
|
|
425
|
-
gradingResult.summary
|
|
426
|
-
)
|
|
427
|
-
);
|
|
428
|
-
}
|
|
429
|
-
if (buildGrades.length > 0) {
|
|
430
|
-
parts.push(
|
|
431
|
-
buildPhaseResultSection(
|
|
432
|
-
"Building",
|
|
433
|
-
buildGrades,
|
|
434
|
-
gradingResult.buildingAccuracy,
|
|
435
|
-
gradingResult.summary
|
|
436
|
-
)
|
|
437
|
-
);
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
if (humanResult) {
|
|
441
|
-
const evals = humanResult.humanEvaluations;
|
|
442
|
-
const helpful = evals.filter((e) => e.rating > 0).length;
|
|
443
|
-
const neutralCount = evals.filter((e) => e.rating === 0).length;
|
|
444
|
-
const unhelpful = evals.filter((e) => e.rating < 0).length;
|
|
445
|
-
parts.push("## Human Evaluation Results");
|
|
446
|
-
parts.push(`- Summary: ${humanResult.summary}`);
|
|
447
|
-
parts.push(
|
|
448
|
-
`- ${evals.length} messages: ${helpful} helpful, ${neutralCount} neutral, ${unhelpful} unhelpful`
|
|
449
|
-
);
|
|
450
|
-
parts.push("");
|
|
451
|
-
}
|
|
452
|
-
const blunders = (gradingResult?.turnGrades ?? []).filter((g) => g.grade === "blunder");
|
|
453
|
-
if (blunders.length > 0) {
|
|
454
|
-
parts.push("## Notable Blunders");
|
|
455
|
-
for (const b of blunders.slice(0, 10)) {
|
|
456
|
-
parts.push(`- Turn ${b.turnIndex} (${b.phase}): ${b.reasoning}`);
|
|
457
|
-
}
|
|
458
|
-
parts.push("");
|
|
459
|
-
}
|
|
460
|
-
parts.push(
|
|
461
|
-
"Based on the findings above, call create_suggestion for each actionable improvement, then output the JSON result."
|
|
462
|
-
);
|
|
463
|
-
return parts.join("\n");
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
// src/runner/task-audit-progress.ts
|
|
467
|
-
function reportProgress(connection, requestId, taskId, tool, input) {
|
|
468
|
-
void connection.call("reportTaskAuditProgress", {
|
|
469
|
-
projectId: connection.projectId,
|
|
470
|
-
requestId,
|
|
471
|
-
taskId,
|
|
472
|
-
activity: { tool, input, timestamp: (/* @__PURE__ */ new Date()).toISOString() }
|
|
473
|
-
}).catch(() => {
|
|
474
|
-
});
|
|
475
|
-
}
|
|
476
|
-
function processAssistantBlocks(blocks, responseParts, ctx, reportedFirstText) {
|
|
477
|
-
for (const block of blocks) {
|
|
478
|
-
if (block.type === "text" && block.text) {
|
|
479
|
-
responseParts.push(block.text);
|
|
480
|
-
if (reportedFirstText.value) continue;
|
|
481
|
-
reportedFirstText.value = true;
|
|
482
|
-
reportProgress(
|
|
483
|
-
ctx.connection,
|
|
484
|
-
ctx.requestId,
|
|
485
|
-
ctx.taskId,
|
|
486
|
-
"thinking",
|
|
487
|
-
block.text.slice(0, 200)
|
|
488
|
-
);
|
|
489
|
-
} else if (block.type === "tool_use" && block.name) {
|
|
490
|
-
const inputStr = typeof block.input === "string" ? block.input : JSON.stringify(block.input);
|
|
491
|
-
reportProgress(ctx.connection, ctx.requestId, ctx.taskId, block.name, inputStr.slice(0, 500));
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
async function collectResponseFromEvents(events, connection, requestId, taskId) {
|
|
496
|
-
const responseParts = [];
|
|
497
|
-
let costUsd = null;
|
|
498
|
-
let model = null;
|
|
499
|
-
const reportedFirstText = { value: false };
|
|
500
|
-
const ctx = { connection, requestId, taskId };
|
|
501
|
-
for await (const event of events) {
|
|
502
|
-
if (event.type === "system") {
|
|
503
|
-
const sysEvent = event;
|
|
504
|
-
if (sysEvent.subtype === "init" && sysEvent.model) {
|
|
505
|
-
model = sysEvent.model;
|
|
506
|
-
reportProgress(
|
|
507
|
-
connection,
|
|
508
|
-
requestId,
|
|
509
|
-
taskId,
|
|
510
|
-
"system",
|
|
511
|
-
`Initialized model: ${sysEvent.model}`
|
|
512
|
-
);
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
if (event.type === "assistant") {
|
|
516
|
-
const { message } = event;
|
|
517
|
-
processAssistantBlocks(message.content, responseParts, ctx, reportedFirstText);
|
|
518
|
-
}
|
|
519
|
-
if (event.type === "result") {
|
|
520
|
-
const resultEvent = event;
|
|
521
|
-
if (resultEvent.subtype === "success" && resultEvent.total_cost_usd !== void 0) {
|
|
522
|
-
costUsd = resultEvent.total_cost_usd;
|
|
523
|
-
}
|
|
524
|
-
const status = resultEvent.subtype === "success" ? "success" : `error: ${resultEvent.subtype}`;
|
|
525
|
-
reportProgress(connection, requestId, taskId, "complete", status);
|
|
526
|
-
break;
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
return { text: responseParts.join("\n\n").trim(), costUsd, model };
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
// src/runner/task-audit-handler.ts
|
|
533
|
-
var logger = createServiceLogger("TaskAudit");
|
|
534
|
-
var FALLBACK_MODEL = "claude-sonnet-4-6";
|
|
535
|
-
function buildTrackedSuggestionTool(connection, projectId, suggestionIds) {
|
|
536
|
-
return defineTool(
|
|
537
|
-
"create_suggestion",
|
|
538
|
-
"Suggest a cross-cutting improvement for the project based on patterns found during audit. If a similar suggestion exists, your vote is added instead of creating a duplicate.",
|
|
539
|
-
{
|
|
540
|
-
title: z.string().describe("Short title for the suggestion"),
|
|
541
|
-
description: z.string().optional().describe(
|
|
542
|
-
"1-2 sentence description of what the project should change and why. Keep concise and task-agnostic."
|
|
543
|
-
)
|
|
544
|
-
},
|
|
545
|
-
async ({ title, description }) => {
|
|
546
|
-
try {
|
|
547
|
-
const result = await connection.call("createProjectSuggestion", {
|
|
548
|
-
projectId,
|
|
549
|
-
title,
|
|
550
|
-
description
|
|
551
|
-
});
|
|
552
|
-
const id = result.mergedIntoId ?? result.id;
|
|
553
|
-
if (id) {
|
|
554
|
-
suggestionIds.push(id);
|
|
555
|
-
logger.info("Suggestion tracked", { id, merged: result.merged });
|
|
556
|
-
}
|
|
557
|
-
if (result.merged) {
|
|
558
|
-
return textResult(`Merged into existing suggestion (ID: ${id}).`);
|
|
559
|
-
}
|
|
560
|
-
return textResult(`Suggestion created (ID: ${result.id}).`);
|
|
561
|
-
} catch (error) {
|
|
562
|
-
logger.warn("Suggestion creation failed", {
|
|
563
|
-
error: error instanceof Error ? error.message : String(error)
|
|
564
|
-
});
|
|
565
|
-
return textResult(
|
|
566
|
-
`Failed to create suggestion: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
567
|
-
);
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
);
|
|
571
|
-
}
|
|
572
|
-
async function fetchModel(connection) {
|
|
573
|
-
try {
|
|
574
|
-
const config = await connection.call("getProjectFunctionConfig", {
|
|
575
|
-
projectId: connection.projectId,
|
|
576
|
-
functionId: "taskAudit"
|
|
577
|
-
});
|
|
578
|
-
return config.model || FALLBACK_MODEL;
|
|
579
|
-
} catch {
|
|
580
|
-
try {
|
|
581
|
-
const ctx = await connection.call("getProjectAgentContextByRole", {
|
|
582
|
-
projectId: connection.projectId,
|
|
583
|
-
role: "reviewer"
|
|
584
|
-
});
|
|
585
|
-
return ctx.model || FALLBACK_MODEL;
|
|
586
|
-
} catch {
|
|
587
|
-
return FALLBACK_MODEL;
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
function buildQueryOptions(systemPrompt, opts, limits, mcpServers = {}) {
|
|
592
|
-
return {
|
|
593
|
-
model: opts.model,
|
|
594
|
-
systemPrompt,
|
|
595
|
-
cwd: opts.projectDir,
|
|
596
|
-
permissionMode: "bypassPermissions",
|
|
597
|
-
allowDangerouslySkipPermissions: true,
|
|
598
|
-
tools: { type: "preset", preset: "claude_code" },
|
|
599
|
-
mcpServers,
|
|
600
|
-
maxTurns: limits.maxTurns,
|
|
601
|
-
maxBudgetUsd: limits.maxBudgetUsd,
|
|
602
|
-
disallowedTools: ["Edit", "Write", "Bash", "NotebookEdit", "MultiEdit"]
|
|
603
|
-
};
|
|
604
|
-
}
|
|
605
|
-
async function runCombinedGradingStep(taskId, userPrompt, limits, opts) {
|
|
606
|
-
const harness = createHarness();
|
|
607
|
-
const events = harness.executeQuery({
|
|
608
|
-
prompt: userPrompt,
|
|
609
|
-
options: buildQueryOptions(COMBINED_GRADING_SYSTEM_PROMPT, opts, limits)
|
|
610
|
-
});
|
|
611
|
-
const response = await collectResponseFromEvents(events, opts.connection, opts.requestId, taskId);
|
|
612
|
-
if (!response.text) throw new Error("No response from combined grading step");
|
|
613
|
-
return {
|
|
614
|
-
result: extractCombinedGradingResult(response.text),
|
|
615
|
-
costUsd: response.costUsd,
|
|
616
|
-
model: response.model
|
|
617
|
-
};
|
|
618
|
-
}
|
|
619
|
-
async function runHumanStep(taskId, userPrompt, limits, opts) {
|
|
620
|
-
const harness = createHarness();
|
|
621
|
-
const events = harness.executeQuery({
|
|
622
|
-
prompt: userPrompt,
|
|
623
|
-
options: buildQueryOptions(HUMAN_AUDIT_SYSTEM_PROMPT, opts, limits)
|
|
624
|
-
});
|
|
625
|
-
const response = await collectResponseFromEvents(events, opts.connection, opts.requestId, taskId);
|
|
626
|
-
if (!response.text) throw new Error("No response from human evaluation step");
|
|
627
|
-
return { result: extractHumanResult(response.text), costUsd: response.costUsd };
|
|
628
|
-
}
|
|
629
|
-
async function runAnalysisStep(task, gradingResult, humanResult, projectId, suggestionIds, opts) {
|
|
630
|
-
const wrappedTool = buildTrackedSuggestionTool(opts.connection, projectId, suggestionIds);
|
|
631
|
-
const harness = createHarness();
|
|
632
|
-
const mcpServer = harness.createMcpServer({ name: "conveyor", tools: [wrappedTool] });
|
|
633
|
-
const events = harness.executeQuery({
|
|
634
|
-
prompt: buildAnalysisPrompt(task, gradingResult, humanResult),
|
|
635
|
-
options: buildQueryOptions(
|
|
636
|
-
ANALYSIS_SYSTEM_PROMPT,
|
|
637
|
-
opts,
|
|
638
|
-
{ maxTurns: 6, maxBudgetUsd: 1 },
|
|
639
|
-
{ conveyor: mcpServer }
|
|
640
|
-
)
|
|
641
|
-
});
|
|
642
|
-
const response = await collectResponseFromEvents(
|
|
643
|
-
events,
|
|
644
|
-
opts.connection,
|
|
645
|
-
opts.requestId,
|
|
646
|
-
task.taskId
|
|
647
|
-
);
|
|
648
|
-
if (!response.text) throw new Error("No response from analysis step");
|
|
649
|
-
return { result: extractAnalysisResult(response.text), costUsd: response.costUsd };
|
|
650
|
-
}
|
|
651
|
-
var EMPTY_STEP = { result: null, costUsd: null, model: null };
|
|
652
|
-
var GRADING_LIMITS = { maxTurns: 24, maxBudgetUsd: 3 };
|
|
653
|
-
async function executeStepSafely(stepLabel, requestId, taskId, fn) {
|
|
654
|
-
try {
|
|
655
|
-
const step = await fn();
|
|
656
|
-
return { result: step.result, costUsd: step.costUsd, model: step.model ?? null };
|
|
657
|
-
} catch (err) {
|
|
658
|
-
logger.warn(`${stepLabel} failed`, {
|
|
659
|
-
requestId,
|
|
660
|
-
taskId,
|
|
661
|
-
error: err instanceof Error ? err.message : String(err)
|
|
662
|
-
});
|
|
663
|
-
return EMPTY_STEP;
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
async function executeCombinedGradingStep(taskPayload, opts) {
|
|
667
|
-
if (taskPayload.agentTrace.length === 0) return EMPTY_STEP;
|
|
668
|
-
reportProgress(
|
|
669
|
-
opts.connection,
|
|
670
|
-
opts.requestId,
|
|
671
|
-
taskPayload.taskId,
|
|
672
|
-
"status",
|
|
673
|
-
"Step 1/3: Auditing full workflow..."
|
|
674
|
-
);
|
|
675
|
-
return await executeStepSafely(
|
|
676
|
-
"Combined grading audit",
|
|
677
|
-
opts.requestId,
|
|
678
|
-
taskPayload.taskId,
|
|
679
|
-
() => runCombinedGradingStep(
|
|
680
|
-
taskPayload.taskId,
|
|
681
|
-
buildCombinedGradingPrompt(taskPayload),
|
|
682
|
-
GRADING_LIMITS,
|
|
683
|
-
opts
|
|
684
|
-
)
|
|
685
|
-
);
|
|
686
|
-
}
|
|
687
|
-
async function executeHumanStep(taskPayload, opts) {
|
|
688
|
-
if (taskPayload.humanMessages.length === 0) return EMPTY_STEP;
|
|
689
|
-
reportProgress(
|
|
690
|
-
opts.connection,
|
|
691
|
-
opts.requestId,
|
|
692
|
-
taskPayload.taskId,
|
|
693
|
-
"status",
|
|
694
|
-
"Step 2/3: Auditing human contributions..."
|
|
695
|
-
);
|
|
696
|
-
return await executeStepSafely(
|
|
697
|
-
"Human audit",
|
|
698
|
-
opts.requestId,
|
|
699
|
-
taskPayload.taskId,
|
|
700
|
-
() => runHumanStep(
|
|
701
|
-
taskPayload.taskId,
|
|
702
|
-
buildHumanAuditPrompt(taskPayload),
|
|
703
|
-
{ maxTurns: 6, maxBudgetUsd: 0.5 },
|
|
704
|
-
opts
|
|
705
|
-
)
|
|
706
|
-
);
|
|
707
|
-
}
|
|
708
|
-
async function executeAnalysisStep(taskPayload, grading, human, projectId, suggestionIds, opts) {
|
|
709
|
-
reportProgress(
|
|
710
|
-
opts.connection,
|
|
711
|
-
opts.requestId,
|
|
712
|
-
taskPayload.taskId,
|
|
713
|
-
"status",
|
|
714
|
-
"Step 3/3: Generating analysis & suggestions..."
|
|
715
|
-
);
|
|
716
|
-
return await executeStepSafely(
|
|
717
|
-
"Analysis",
|
|
718
|
-
opts.requestId,
|
|
719
|
-
taskPayload.taskId,
|
|
720
|
-
() => runAnalysisStep(taskPayload, grading.result, human.result, projectId, suggestionIds, opts)
|
|
721
|
-
);
|
|
722
|
-
}
|
|
723
|
-
async function reportAuditResult(connection, requestId, taskPayload, steps, suggestionIds) {
|
|
724
|
-
let totalCostUsd = null;
|
|
725
|
-
for (const step of [steps.grading, steps.human, steps.analysis])
|
|
726
|
-
totalCostUsd = addCosts(totalCostUsd, step.costUsd);
|
|
727
|
-
const merged = mergeStepResults(steps.grading.result, steps.human.result, steps.analysis.result);
|
|
728
|
-
await connection.call("reportTaskAuditResult", {
|
|
729
|
-
projectId: connection.projectId,
|
|
730
|
-
requestId,
|
|
731
|
-
taskId: taskPayload.taskId,
|
|
732
|
-
summary: merged.summary,
|
|
733
|
-
turnGrades: merged.turnGrades,
|
|
734
|
-
planningAccuracy: merged.planningAccuracy,
|
|
735
|
-
buildingAccuracy: merged.buildingAccuracy,
|
|
736
|
-
humanAccuracy: merged.humanAccuracy,
|
|
737
|
-
humanEvaluations: merged.humanEvaluations,
|
|
738
|
-
...computeGradeCounts(merged.turnGrades),
|
|
739
|
-
suggestionIds,
|
|
740
|
-
auditCostUsd: totalCostUsd,
|
|
741
|
-
model: steps.grading.model ?? null
|
|
742
|
-
});
|
|
743
|
-
}
|
|
744
|
-
async function auditSingleTask(taskPayload, request, connection, projectDir, model) {
|
|
745
|
-
const { requestId, projectId } = request;
|
|
746
|
-
const suggestionIds = [];
|
|
747
|
-
const opts = { connection, requestId, projectDir, model };
|
|
748
|
-
const grading = await executeCombinedGradingStep(taskPayload, opts);
|
|
749
|
-
if (!grading.result && taskPayload.agentTrace.length > 0) {
|
|
750
|
-
throw new Error("Grading audit step failed");
|
|
751
|
-
}
|
|
752
|
-
const human = await executeHumanStep(taskPayload, opts);
|
|
753
|
-
const analysis = await executeAnalysisStep(
|
|
754
|
-
taskPayload,
|
|
755
|
-
grading,
|
|
756
|
-
human,
|
|
757
|
-
projectId,
|
|
758
|
-
suggestionIds,
|
|
759
|
-
opts
|
|
760
|
-
);
|
|
761
|
-
await reportAuditResult(
|
|
762
|
-
connection,
|
|
763
|
-
requestId,
|
|
764
|
-
taskPayload,
|
|
765
|
-
{ grading, human, analysis },
|
|
766
|
-
suggestionIds
|
|
767
|
-
);
|
|
768
|
-
logger.info("Task audit complete", {
|
|
769
|
-
requestId,
|
|
770
|
-
taskId: taskPayload.taskId,
|
|
771
|
-
suggestions: suggestionIds.length
|
|
772
|
-
});
|
|
773
|
-
}
|
|
774
|
-
async function handleTaskAudit(request, connection, projectDir) {
|
|
775
|
-
const { requestId } = request;
|
|
776
|
-
logger.info("Starting task audit", { requestId, taskCount: request.tasks.length });
|
|
777
|
-
const model = await fetchModel(connection);
|
|
778
|
-
for (const taskPayload of request.tasks) {
|
|
779
|
-
try {
|
|
780
|
-
await auditSingleTask(taskPayload, request, connection, projectDir, model);
|
|
781
|
-
} catch (err) {
|
|
782
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
783
|
-
logger.error("Task audit failed for task", {
|
|
784
|
-
requestId,
|
|
785
|
-
taskId: taskPayload.taskId,
|
|
786
|
-
error: msg
|
|
787
|
-
});
|
|
788
|
-
await connection.call(
|
|
789
|
-
"reportTaskAuditResult",
|
|
790
|
-
buildErrorResult(connection.projectId, requestId, taskPayload.taskId, msg)
|
|
791
|
-
).catch(() => {
|
|
792
|
-
});
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
await connection.call("reportTaskAuditBatchComplete", { projectId: connection.projectId, requestId }).catch(() => {
|
|
796
|
-
});
|
|
797
|
-
}
|
|
798
|
-
export {
|
|
799
|
-
handleTaskAudit
|
|
800
|
-
};
|
|
801
|
-
//# sourceMappingURL=task-audit-handler-BRCXB5V6.js.map
|