@rallycry/conveyor-agent 7.1.1 → 7.1.4
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-C5YAMQJ2.js +17 -0
- package/dist/chunk-C5YAMQJ2.js.map +1 -0
- package/dist/{chunk-23JCJ2GV.js → chunk-H2GKXPFI.js} +232 -742
- package/dist/chunk-H2GKXPFI.js.map +1 -0
- package/dist/{chunk-KNBG2634.js → chunk-U6AYNVS7.js} +25 -2
- package/dist/chunk-U6AYNVS7.js.map +1 -0
- package/dist/cli.js +3 -3
- package/dist/index.d.ts +10 -15
- package/dist/index.js +3 -3
- package/dist/{tag-audit-handler-PACJJEDB.js → tag-audit-handler-BM6MMS5T.js} +3 -5
- package/dist/{tag-audit-handler-PACJJEDB.js.map → tag-audit-handler-BM6MMS5T.js.map} +1 -1
- package/dist/task-audit-handler-URD2BOC4.js +381 -0
- package/dist/task-audit-handler-URD2BOC4.js.map +1 -0
- package/package.json +2 -5
- package/dist/chunk-23JCJ2GV.js.map +0 -1
- package/dist/chunk-CYZPFJGN.js +0 -26
- package/dist/chunk-CYZPFJGN.js.map +0 -1
- package/dist/chunk-KNBG2634.js.map +0 -1
- package/dist/tunnel-client.d.ts +0 -1
- package/dist/tunnel-client.js +0 -226
- package/dist/tunnel-client.js.map +0 -1
|
@@ -44,8 +44,31 @@ function createHarness() {
|
|
|
44
44
|
return new ClaudeCodeHarness();
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
// src/utils/logger.ts
|
|
48
|
+
function createServiceLogger(service) {
|
|
49
|
+
const prefix = `[conveyor-agent:${service}]`;
|
|
50
|
+
return {
|
|
51
|
+
info(message, data) {
|
|
52
|
+
const extra = data ? ` ${JSON.stringify(data)}` : "";
|
|
53
|
+
process.stderr.write(`${prefix} ${message}${extra}
|
|
54
|
+
`);
|
|
55
|
+
},
|
|
56
|
+
warn(message, data) {
|
|
57
|
+
const extra = data ? ` ${JSON.stringify(data)}` : "";
|
|
58
|
+
process.stderr.write(`${prefix} WARN ${message}${extra}
|
|
59
|
+
`);
|
|
60
|
+
},
|
|
61
|
+
error(message, data) {
|
|
62
|
+
const extra = data ? ` ${JSON.stringify(data)}` : "";
|
|
63
|
+
process.stderr.write(`${prefix} ERROR ${message}${extra}
|
|
64
|
+
`);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
47
69
|
export {
|
|
48
70
|
defineTool,
|
|
49
|
-
createHarness
|
|
71
|
+
createHarness,
|
|
72
|
+
createServiceLogger
|
|
50
73
|
};
|
|
51
|
-
//# sourceMappingURL=chunk-
|
|
74
|
+
//# sourceMappingURL=chunk-U6AYNVS7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/harness/types.ts","../src/harness/claude-code/index.ts","../src/harness/index.ts","../src/utils/logger.ts"],"sourcesContent":["/**\n * Harness-neutral types for agent query execution.\n *\n * These types abstract the underlying agent SDK so that the runner,\n * execution, and tool layers never reference SDK-specific imports\n * directly. The only place that should import from\n * `@anthropic-ai/claude-agent-sdk` is `harness/claude-code/`.\n */\n\nimport type { z } from \"zod\";\n\n// ── Events emitted by the harness during a query ────────────────────────\n\nexport interface HarnessSystemInitEvent {\n type: \"system\";\n subtype: \"init\";\n session_id?: string;\n model: string;\n}\n\nexport interface HarnessCompactBoundaryEvent {\n type: \"system\";\n subtype: \"compact_boundary\";\n compact_metadata: { trigger: \"manual\" | \"auto\"; pre_tokens: number };\n}\n\nexport interface HarnessTaskStartedEvent {\n type: \"system\";\n subtype: \"task_started\";\n task_id: string;\n description: string;\n}\n\nexport interface HarnessTaskProgressEvent {\n type: \"system\";\n subtype: \"task_progress\";\n task_id: string;\n description: string;\n usage?: { tool_uses: number; duration_ms: number };\n}\n\nexport type HarnessSystemEvent =\n | HarnessSystemInitEvent\n | HarnessCompactBoundaryEvent\n | HarnessTaskStartedEvent\n | HarnessTaskProgressEvent;\n\nexport interface HarnessContentBlock {\n type: string;\n text?: string;\n name?: string;\n input?: unknown;\n id?: string;\n}\n\nexport interface HarnessAssistantEvent {\n type: \"assistant\";\n message: {\n role: \"assistant\";\n content: HarnessContentBlock[];\n usage?: {\n input_tokens?: number;\n cache_read_input_tokens?: number;\n cache_creation_input_tokens?: number;\n };\n };\n}\n\nexport interface HarnessResultSuccessEvent {\n type: \"result\";\n subtype: \"success\";\n result: string;\n total_cost_usd: number;\n modelUsage?: Record<string, unknown>;\n sessionId?: string;\n}\n\nexport interface HarnessResultErrorEvent {\n type: \"result\";\n subtype: \"error\";\n errors: string[];\n sessionId?: string;\n}\n\nexport type HarnessResultEvent = HarnessResultSuccessEvent | HarnessResultErrorEvent;\n\nexport interface HarnessRateLimitEvent {\n type: \"rate_limit_event\";\n rate_limit_info: {\n status: string;\n rateLimitType?: string;\n utilization?: number;\n resetsAt?: unknown;\n };\n}\n\nexport interface HarnessToolProgressEvent {\n type: \"tool_progress\";\n tool_name?: string;\n elapsed_time_seconds?: number;\n}\n\nexport type HarnessEvent =\n | HarnessSystemEvent\n | HarnessAssistantEvent\n | HarnessResultEvent\n | HarnessRateLimitEvent\n | HarnessToolProgressEvent;\n\n// ── User message fed into a query ───────────────────────────────────────\n\nexport interface HarnessUserMessage {\n type: \"user\";\n session_id: string;\n message: { role: \"user\"; content: string | unknown[] };\n parent_tool_use_id: null;\n}\n\n// ── Tool definition (harness-neutral) ───────────────────────────────────\n\nexport interface HarnessToolAnnotations {\n readOnlyHint?: boolean;\n}\n\n/* eslint-disable @typescript-eslint/no-explicit-any -- generic tool handler */\nexport interface HarnessToolDefinition {\n name: string;\n description: string;\n schema: z.ZodRawShape;\n handler: (\n input: any,\n ) => Promise<{ content: { type: string; text?: string; data?: string; mimeType?: string }[] }>;\n annotations?: HarnessToolAnnotations;\n}\n/* eslint-enable @typescript-eslint/no-explicit-any */\n\n// ── Hook types ──────────────────────────────────────────────────────────\n\nexport interface HarnessHookInput {\n hook_event_name: string;\n tool_name: string;\n tool_response: unknown;\n}\n\nexport interface HarnessHookOutput {\n continue: boolean;\n}\n\nexport type HarnessPostToolUseHook = (input: HarnessHookInput) => Promise<HarnessHookOutput>;\n\n// ── MCP server handle (opaque to the runner) ────────────────────────────\n\n/** Opaque handle returned by `AgentHarness.createMcpServer()`. */\nexport type HarnessMcpServer = unknown;\n\n// ── Query options ───────────────────────────────────────────────────────\n\nexport interface HarnessQueryOptions {\n model: string;\n systemPrompt: unknown;\n cwd: string;\n permissionMode: \"plan\" | \"bypassPermissions\";\n allowDangerouslySkipPermissions: boolean;\n tools: { type: \"preset\"; preset: \"claude_code\" };\n mcpServers: Record<string, HarnessMcpServer>;\n settingSources?: (\"user\" | \"project\" | \"local\")[];\n sandbox?: { enabled: boolean };\n maxTurns?: number;\n maxBudgetUsd?: number;\n effort?: string;\n thinking?: unknown;\n betas?: unknown;\n disallowedTools?: string[];\n abortController?: AbortController;\n enableFileCheckpointing?: boolean;\n canUseTool?: (\n toolName: string,\n input: Record<string, unknown>,\n ) => Promise<\n | { behavior: \"allow\"; updatedInput?: Record<string, unknown> }\n | { behavior: \"deny\"; message: string }\n >;\n hooks?: Record<string, { hooks: HarnessPostToolUseHook[]; timeout: number }[]>;\n resume?: string;\n stderr?: (data: string) => void;\n}\n\n// ── Tool definition helper ──────────────────────────────────────────────\n\n/**\n * Construct a `HarnessToolDefinition` with the same signature as the SDK's\n * `tool()` helper, but without importing the SDK. The harness implementation\n * converts these into SDK-native tools when `createMcpServer()` is called.\n */\nexport function defineTool<T extends z.ZodRawShape>(\n name: string,\n description: string,\n schema: T,\n handler: (\n input: z.infer<z.ZodObject<T>>,\n ) => Promise<{ content: { type: string; text?: string; data?: string; mimeType?: string }[] }>,\n options?: { annotations?: HarnessToolAnnotations },\n): HarnessToolDefinition {\n return {\n name,\n description,\n schema,\n handler: handler as HarnessToolDefinition[\"handler\"],\n annotations: options?.annotations,\n };\n}\n\n// ── AgentHarness interface ──────────────────────────────────────────────\n\nexport interface AgentHarness {\n /** Start a streaming query and return an async generator of events. */\n executeQuery(options: {\n prompt: string | AsyncGenerator<HarnessUserMessage, void, unknown>;\n options: HarnessQueryOptions;\n resume?: string;\n }): AsyncGenerator<HarnessEvent, void>;\n\n /** Wrap an array of harness-neutral tool definitions into an MCP server. */\n createMcpServer(config: { name: string; tools: HarnessToolDefinition[] }): HarnessMcpServer;\n}\n","/**\n * ClaudeCodeHarness — wraps `@anthropic-ai/claude-agent-sdk` behind the\n * generic `AgentHarness` interface.\n *\n * This is the ONLY module that should import from the SDK.\n */\n\nimport { query, tool, createSdkMcpServer } from \"@anthropic-ai/claude-agent-sdk\";\nimport type {\n AgentHarness,\n HarnessEvent,\n HarnessQueryOptions,\n HarnessToolDefinition,\n HarnessMcpServer,\n HarnessUserMessage,\n} from \"../types.js\";\n\nexport class ClaudeCodeHarness implements AgentHarness {\n async *executeQuery(opts: {\n prompt: string | AsyncGenerator<HarnessUserMessage, void, unknown>;\n options: HarnessQueryOptions;\n resume?: string;\n }): AsyncGenerator<HarnessEvent, void> {\n const sdkEvents = query({\n prompt: opts.prompt as Parameters<typeof query>[0][\"prompt\"],\n options: {\n ...(opts.options as Parameters<typeof query>[0][\"options\"]),\n ...(opts.resume ? { resume: opts.resume } : {}),\n ...(opts.options.abortController ? { abortController: opts.options.abortController } : {}),\n },\n });\n\n for await (const event of sdkEvents) {\n yield event as unknown as HarnessEvent;\n }\n }\n\n createMcpServer(config: { name: string; tools: HarnessToolDefinition[] }): HarnessMcpServer {\n const sdkTools = config.tools.map((t) =>\n tool(\n t.name,\n t.description,\n t.schema,\n t.handler as Parameters<typeof tool>[3],\n t.annotations ? { annotations: t.annotations } : undefined,\n ),\n );\n return createSdkMcpServer({ name: config.name, tools: sdkTools });\n }\n}\n","export { defineTool } from \"./types.js\";\n\nexport type {\n AgentHarness,\n HarnessEvent,\n HarnessSystemEvent,\n HarnessSystemInitEvent,\n HarnessCompactBoundaryEvent,\n HarnessTaskStartedEvent,\n HarnessTaskProgressEvent,\n HarnessAssistantEvent,\n HarnessContentBlock,\n HarnessResultEvent,\n HarnessResultSuccessEvent,\n HarnessResultErrorEvent,\n HarnessRateLimitEvent,\n HarnessToolProgressEvent,\n HarnessUserMessage,\n HarnessToolDefinition,\n HarnessToolAnnotations,\n HarnessMcpServer,\n HarnessQueryOptions,\n HarnessHookInput,\n HarnessHookOutput,\n HarnessPostToolUseHook,\n} from \"./types.js\";\n\nexport { ClaudeCodeHarness } from \"./claude-code/index.js\";\n\nimport { ClaudeCodeHarness } from \"./claude-code/index.js\";\nimport type { AgentHarness } from \"./types.js\";\n\nexport function createHarness(): AgentHarness {\n return new ClaudeCodeHarness();\n}\n","/** Minimal structured logger for conveyor-agent (writes to stderr). */\nexport function createServiceLogger(service: string) {\n const prefix = `[conveyor-agent:${service}]`;\n return {\n info(message: string, data?: Record<string, unknown>): void {\n const extra = data ? ` ${JSON.stringify(data)}` : \"\";\n process.stderr.write(`${prefix} ${message}${extra}\\n`);\n },\n warn(message: string, data?: Record<string, unknown>): void {\n const extra = data ? ` ${JSON.stringify(data)}` : \"\";\n process.stderr.write(`${prefix} WARN ${message}${extra}\\n`);\n },\n error(message: string, data?: Record<string, unknown>): void {\n const extra = data ? ` ${JSON.stringify(data)}` : \"\";\n process.stderr.write(`${prefix} ERROR ${message}${extra}\\n`);\n },\n };\n}\n"],"mappings":";AAkMO,SAAS,WACd,MACA,aACA,QACA,SAGA,SACuB;AACvB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,aAAa,SAAS;AAAA,EACxB;AACF;;;AC3MA,SAAS,OAAO,MAAM,0BAA0B;AAUzC,IAAM,oBAAN,MAAgD;AAAA,EACrD,OAAO,aAAa,MAImB;AACrC,UAAM,YAAY,MAAM;AAAA,MACtB,QAAQ,KAAK;AAAA,MACb,SAAS;AAAA,QACP,GAAI,KAAK;AAAA,QACT,GAAI,KAAK,SAAS,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;AAAA,QAC7C,GAAI,KAAK,QAAQ,kBAAkB,EAAE,iBAAiB,KAAK,QAAQ,gBAAgB,IAAI,CAAC;AAAA,MAC1F;AAAA,IACF,CAAC;AAED,qBAAiB,SAAS,WAAW;AACnC,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,gBAAgB,QAA4E;AAC1F,UAAM,WAAW,OAAO,MAAM;AAAA,MAAI,CAAC,MACjC;AAAA,QACE,EAAE;AAAA,QACF,EAAE;AAAA,QACF,EAAE;AAAA,QACF,EAAE;AAAA,QACF,EAAE,cAAc,EAAE,aAAa,EAAE,YAAY,IAAI;AAAA,MACnD;AAAA,IACF;AACA,WAAO,mBAAmB,EAAE,MAAM,OAAO,MAAM,OAAO,SAAS,CAAC;AAAA,EAClE;AACF;;;ACjBO,SAAS,gBAA8B;AAC5C,SAAO,IAAI,kBAAkB;AAC/B;;;ACjCO,SAAS,oBAAoB,SAAiB;AACnD,QAAM,SAAS,mBAAmB,OAAO;AACzC,SAAO;AAAA,IACL,KAAK,SAAiB,MAAsC;AAC1D,YAAM,QAAQ,OAAO,IAAI,KAAK,UAAU,IAAI,CAAC,KAAK;AAClD,cAAQ,OAAO,MAAM,GAAG,MAAM,IAAI,OAAO,GAAG,KAAK;AAAA,CAAI;AAAA,IACvD;AAAA,IACA,KAAK,SAAiB,MAAsC;AAC1D,YAAM,QAAQ,OAAO,IAAI,KAAK,UAAU,IAAI,CAAC,KAAK;AAClD,cAAQ,OAAO,MAAM,GAAG,MAAM,SAAS,OAAO,GAAG,KAAK;AAAA,CAAI;AAAA,IAC5D;AAAA,IACA,MAAM,SAAiB,MAAsC;AAC3D,YAAM,QAAQ,OAAO,IAAI,KAAK,UAAU,IAAI,CAAC,KAAK;AAClD,cAAQ,OAAO,MAAM,GAAG,MAAM,UAAU,OAAO,GAAG,KAAK;AAAA,CAAI;AAAA,IAC7D;AAAA,EACF;AACF;","names":[]}
|
package/dist/cli.js
CHANGED
|
@@ -5,11 +5,11 @@ import {
|
|
|
5
5
|
loadConveyorConfig,
|
|
6
6
|
runSetupCommand,
|
|
7
7
|
runStartCommand
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-H2GKXPFI.js";
|
|
9
|
+
import "./chunk-C5YAMQJ2.js";
|
|
10
10
|
import {
|
|
11
11
|
createServiceLogger
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-U6AYNVS7.js";
|
|
13
13
|
|
|
14
14
|
// src/cli.ts
|
|
15
15
|
import { readFileSync } from "fs";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _project_shared from '@project/shared';
|
|
2
|
-
import { AgentSessionServiceMethods, AgentMode, AgentQuestion, AgentRunnerStatus, TagAuditRunnerRequest, DebugTelemetryEvent, DebugSessionSummary } from '@project/shared';
|
|
2
|
+
import { AgentSessionServiceMethods, AgentMode, AgentQuestion, AgentRunnerStatus, TagAuditRunnerRequest, TaskAuditRunnerRequest, DebugTelemetryEvent, DebugSessionSummary } from '@project/shared';
|
|
3
3
|
export * from '@project/shared';
|
|
4
4
|
import { ChildProcess } from 'node:child_process';
|
|
5
5
|
|
|
@@ -146,6 +146,13 @@ declare class ModeController {
|
|
|
146
146
|
get isReadOnly(): boolean;
|
|
147
147
|
get isAutoPlanning(): boolean;
|
|
148
148
|
get isBuildCapable(): boolean;
|
|
149
|
+
/**
|
|
150
|
+
* Apply authoritative mode from the server's task context.
|
|
151
|
+
* Called after getTaskContext to override env-var defaults with the
|
|
152
|
+
* actual task state — ensures pods that launch without CONVEYOR_AGENT_MODE
|
|
153
|
+
* still get the correct mode.
|
|
154
|
+
*/
|
|
155
|
+
applyServerMode(agentMode: AgentMode | null | undefined, isAuto: boolean | undefined): void;
|
|
149
156
|
/** Resolve the initial mode based on task context */
|
|
150
157
|
resolveInitialMode(context: ModeTaskContext): AgentMode;
|
|
151
158
|
/** Check if auto-mode can bypass planning (task already has plan + properties) */
|
|
@@ -221,9 +228,7 @@ declare class SessionRunner {
|
|
|
221
228
|
private readonly callbacks;
|
|
222
229
|
private _state;
|
|
223
230
|
private stopped;
|
|
224
|
-
private hasCompleted;
|
|
225
231
|
private interrupted;
|
|
226
|
-
private _isReviewing;
|
|
227
232
|
private taskContext;
|
|
228
233
|
private fullContext;
|
|
229
234
|
private queryBridge;
|
|
@@ -247,17 +252,6 @@ declare class SessionRunner {
|
|
|
247
252
|
run(): Promise<void>;
|
|
248
253
|
/** Convenience wrapper: connect() then run(). */
|
|
249
254
|
start(): Promise<void>;
|
|
250
|
-
/**
|
|
251
|
-
* Returns true if the message should be skipped (not processed).
|
|
252
|
-
*
|
|
253
|
-
* After the agent has completed, we only process:
|
|
254
|
-
* 1. Critical automated sources (e.g. CI failure) — always process
|
|
255
|
-
* 2. Messages with source: "user" — real user typed in chat
|
|
256
|
-
* 3. Messages from flushAllCombined — have a real userId but no source
|
|
257
|
-
*
|
|
258
|
-
* Everything else (system messages, stale history replays) is skipped.
|
|
259
|
-
*/
|
|
260
|
-
private shouldSkipMessage;
|
|
261
255
|
private coreLoop;
|
|
262
256
|
/** Returns true if an initial query was executed, false otherwise. */
|
|
263
257
|
private executeInitialMode;
|
|
@@ -276,7 +270,6 @@ declare class SessionRunner {
|
|
|
276
270
|
private needsPRNudge;
|
|
277
271
|
private refreshTaskContext;
|
|
278
272
|
private maybeSendPRNudge;
|
|
279
|
-
private maybeTransitionToReview;
|
|
280
273
|
private buildFullContext;
|
|
281
274
|
private createQueryBridge;
|
|
282
275
|
private wireConnectionCallbacks;
|
|
@@ -362,6 +355,7 @@ declare class ProjectConnection {
|
|
|
362
355
|
error?: string;
|
|
363
356
|
}) => void) => void): void;
|
|
364
357
|
onAuditTags(callback: (request: TagAuditRunnerRequest) => void): void;
|
|
358
|
+
onAuditTasks(callback: (request: TaskAuditRunnerRequest) => void): void;
|
|
365
359
|
sendHeartbeat(): void;
|
|
366
360
|
sendEvent(event: Record<string, unknown>): void;
|
|
367
361
|
emitStatus(status: "busy" | "idle"): void;
|
|
@@ -400,6 +394,7 @@ declare class ProjectRunner {
|
|
|
400
394
|
private wireEventHandlers;
|
|
401
395
|
private handleReconnect;
|
|
402
396
|
private handleAuditTags;
|
|
397
|
+
private handleAuditTasks;
|
|
403
398
|
private killAgent;
|
|
404
399
|
private handleAssignment;
|
|
405
400
|
private handleStopTask;
|
package/dist/index.js
CHANGED
|
@@ -24,9 +24,9 @@ import {
|
|
|
24
24
|
runStartCommand,
|
|
25
25
|
stageAndCommit,
|
|
26
26
|
updateRemoteToken
|
|
27
|
-
} from "./chunk-
|
|
28
|
-
import "./chunk-
|
|
29
|
-
import "./chunk-
|
|
27
|
+
} from "./chunk-H2GKXPFI.js";
|
|
28
|
+
import "./chunk-C5YAMQJ2.js";
|
|
29
|
+
import "./chunk-U6AYNVS7.js";
|
|
30
30
|
|
|
31
31
|
// src/runner/file-cache.ts
|
|
32
32
|
var DEFAULT_MAX_SIZE_BYTES = 50 * 1024 * 1024;
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
createHarness
|
|
3
|
-
} from "./chunk-KNBG2634.js";
|
|
4
|
-
import {
|
|
2
|
+
createHarness,
|
|
5
3
|
createServiceLogger
|
|
6
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-U6AYNVS7.js";
|
|
7
5
|
|
|
8
6
|
// src/runner/tag-audit-handler.ts
|
|
9
7
|
var logger = createServiceLogger("TagAudit");
|
|
@@ -213,4 +211,4 @@ export {
|
|
|
213
211
|
buildTagAuditPrompt,
|
|
214
212
|
handleTagAudit
|
|
215
213
|
};
|
|
216
|
-
//# sourceMappingURL=tag-audit-handler-
|
|
214
|
+
//# sourceMappingURL=tag-audit-handler-BM6MMS5T.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/runner/tag-audit-handler.ts"],"sourcesContent":["import { createHarness } from \"../harness/index.js\";\nimport type { ProjectConnection } from \"../connection/project-connection.js\";\nimport type { TagAuditRunnerRequest } from \"@project/shared\";\nimport { createServiceLogger } from \"../utils/logger.js\";\n\nconst logger = createServiceLogger(\"TagAudit\");\n\nconst FALLBACK_MODEL = \"claude-sonnet-4-20250514\";\n\n// ── Prompt section builders ────────────────────────────────────────────\n\nfunction buildTagsSection(request: TagAuditRunnerRequest, parts: string[]): void {\n parts.push(\"## Current Tags\\n\");\n if (request.tags.length === 0) {\n parts.push(\"No tags currently exist.\\n\");\n } else {\n for (const tag of request.tags) {\n parts.push(`### ${tag.name} (id: ${tag.id})`);\n parts.push(`- Description: ${tag.description ?? \"(none)\"}`);\n parts.push(`- Active tasks: ${tag.activeTaskCount}`);\n if (tag.contextPaths) {\n parts.push(`- Context paths: ${JSON.stringify(tag.contextPaths)}`);\n }\n parts.push(\"\");\n }\n }\n}\n\nfunction buildContextSections(request: TagAuditRunnerRequest, parts: string[]): void {\n if (request.objectives && request.objectives.length > 0) {\n parts.push(\"## Project Objectives\\n\");\n for (const obj of request.objectives) {\n parts.push(`- **${obj.name}**${obj.description ? `: ${obj.description}` : \"\"}`);\n }\n parts.push(\"\");\n }\n\n if (request.tagRules && request.tagRules.length > 0) {\n parts.push(\"## Tag Rules\\n\");\n parts.push(\"These tags have associated rule files that agents load automatically:\\n\");\n for (const rule of request.tagRules) {\n parts.push(`- **${rule.tagName}** → \\`${rule.rulePath}\\``);\n }\n parts.push(\"\");\n }\n\n if (request.fileHeatmap.length > 0) {\n parts.push(\"## File Access Heatmap\\n\");\n parts.push(\"Files most frequently read by agents, broken down by tag:\\n\");\n for (const entry of request.fileHeatmap.slice(0, 50)) {\n const tagBreakdown = Object.entries(entry.byTag)\n .map(([tag, count]) => `${tag}: ${count}`)\n .join(\", \");\n parts.push(`- \\`${entry.filePath}\\` (total: ${entry.totalReads}) — ${tagBreakdown}`);\n }\n parts.push(\"\");\n }\n\n if (request.recentTaskSamples && request.recentTaskSamples.length > 0) {\n parts.push(\"## Recent Task Samples\\n\");\n parts.push(\"Recent tasks per tag (to understand how tags are used):\\n\");\n for (const sample of request.recentTaskSamples) {\n parts.push(`### ${sample.tagName}`);\n for (const task of sample.tasks) {\n parts.push(`- ${task.title} (${task.status})`);\n }\n parts.push(\"\");\n }\n }\n}\n\n// ── Prompt builder ──────────────────────────────────────────────────────\n\nexport function buildTagAuditPrompt(request: TagAuditRunnerRequest): string {\n const parts: string[] = [];\n\n parts.push(`# Tag Audit for project: ${request.projectName}\\n`);\n buildTagsSection(request, parts);\n buildContextSections(request, parts);\n parts.push(\"Analyze the tags above and the codebase, then output your recommendations.\");\n\n return parts.join(\"\\n\");\n}\n\nconst TAG_AUDIT_SYSTEM_PROMPT = `You are analyzing tags for a software project. Tags are used to organize tasks, associate context paths (files/directories that agents should read), and track work areas.\n\nGiven the current tags (with descriptions, context paths, and active task counts) and a file access heatmap (which files agents read most, broken down by tag), analyze the project and generate recommendations.\n\nYou have full access to the codebase. Read relevant files — especially .claude/rules/, README files, and any context paths referenced by existing tags — to validate your recommendations.\n\nGenerate recommendations of these types:\n- **create_tag**: Suggest new tags for uncovered areas of the codebase\n- **update_description**: Improve a tag's description to be more useful\n- **add_context_link**: Add context paths (files/directories) that agents should read when working on tasks with this tag\n- **documentation_gap**: Identify areas where documentation or context is missing\n- **merge_tags**: Suggest merging overlapping tags\n- **rename_tag**: Suggest a better name for a tag\n\nEach recommendation must include:\n- \\`id\\`: A unique UUID\n- \\`type\\`: One of the types above\n- \\`tagName\\`: The tag name this applies to\n- \\`tagId\\`: The existing tag's ID (if modifying an existing tag, omit for create_tag)\n- \\`suggestion\\`: A short description of the recommendation\n- \\`reasoning\\`: Why this recommendation would help\n- \\`payload\\`: Type-specific data:\n - create_tag: \\`{ name: string, description: string, contextPaths?: string[] }\\`\n - update_description: \\`{ description: string }\\`\n - add_context_link: \\`{ type: \"rule\" | \"doc\" | \"file\" | \"folder\", path: string, label?: string }\\`\n - documentation_gap: \\`{ area: string, suggestedContent?: string }\\`\n - merge_tags: \\`{ sourceTagIds: string[], targetName: string }\\`\n - rename_tag: \\`{ newName: string }\\`\n\nAfter your analysis, output a JSON block in this exact format:\n\n\\`\\`\\`json\n{\n \"recommendations\": [ ...array of recommendations... ],\n \"summary\": \"Brief summary of findings\"\n}\n\\`\\`\\`\n\nAlways provide recommendations. Even well-organized projects have room for improvement — look for missing context links, stale descriptions, documentation gaps, and tag coverage opportunities. Aim for at least 3-5 recommendations. Be specific, actionable, and concise.`;\n\n// ── Fetch agent context ─────────────────────────────────────────────────\n\nasync function fetchModel(connection: ProjectConnection): Promise<string> {\n try {\n const ctx = await connection.call(\"getProjectAgentContext\", {\n projectId: connection.projectId,\n });\n return ctx.model || FALLBACK_MODEL;\n } catch {\n return FALLBACK_MODEL;\n }\n}\n\n// ── JSON extraction ─────────────────────────────────────────────────────\n\nfunction extractJsonFromResponse(text: string): { recommendations: unknown[]; summary: string } {\n // Try to find a JSON code block first\n const codeBlockMatch = text.match(/```(?:json)?\\s*\\n?([\\s\\S]*?)```/);\n const jsonStr = codeBlockMatch ? codeBlockMatch[1].trim() : text;\n\n try {\n const parsed = JSON.parse(jsonStr);\n if (parsed && Array.isArray(parsed.recommendations) && typeof parsed.summary === \"string\") {\n return parsed;\n }\n } catch {\n // Try to find a JSON object in the raw text\n const objectMatch = text.match(/\\{[\\s\\S]*\"recommendations\"[\\s\\S]*\\}/);\n if (objectMatch) {\n try {\n const parsed = JSON.parse(objectMatch[0]);\n if (parsed && Array.isArray(parsed.recommendations)) {\n return { recommendations: parsed.recommendations, summary: parsed.summary ?? \"\" };\n }\n } catch {\n // Fall through\n }\n }\n }\n\n throw new Error(\"Could not parse tag audit recommendations from Claude response\");\n}\n\n// ── Event stream processing ─────────────────────────────────────────────\n\nasync function collectResponseFromEvents(\n events: AsyncIterable<{ type: string }>,\n connection: ProjectConnection,\n requestId: string,\n): Promise<string> {\n const responseParts: string[] = [];\n for await (const event of events) {\n if (event.type === \"assistant\") {\n const { message } = event as {\n type: string;\n message: {\n content: Array<{ type: string; text?: string; name?: string; input?: unknown }>;\n };\n };\n for (const block of message.content) {\n if (block.type === \"text\" && block.text) {\n responseParts.push(block.text);\n } else if (block.type === \"tool_use\" && block.name) {\n const inputStr =\n typeof block.input === \"string\" ? block.input : JSON.stringify(block.input);\n await connection\n .call(\"reportTagAuditProgress\", {\n projectId: connection.projectId,\n requestId,\n activity: {\n tool: block.name,\n input: inputStr.slice(0, 10_000),\n timestamp: new Date().toISOString(),\n },\n })\n .catch(() => {});\n }\n }\n }\n if (event.type === \"result\") break;\n }\n return responseParts.join(\"\\n\\n\").trim();\n}\n\n// ── Main handler ────────────────────────────────────────────────────────\n\nexport async function handleTagAudit(\n request: TagAuditRunnerRequest,\n connection: ProjectConnection,\n projectDir: string,\n): Promise<void> {\n const { requestId } = request;\n\n logger.info(\"Starting tag audit\", { requestId, tagCount: request.tags.length });\n\n await connection\n .call(\"reportTagAuditProgress\", {\n projectId: connection.projectId,\n requestId,\n activity: {\n tool: \"audit\",\n input: \"Starting tag audit...\",\n timestamp: new Date().toISOString(),\n },\n })\n .catch(() => {});\n\n const model = await fetchModel(connection);\n const harness = createHarness();\n const events = harness.executeQuery({\n prompt: buildTagAuditPrompt(request),\n options: {\n model,\n systemPrompt: TAG_AUDIT_SYSTEM_PROMPT,\n cwd: projectDir,\n permissionMode: \"bypassPermissions\",\n allowDangerouslySkipPermissions: true,\n tools: { type: \"preset\" as const, preset: \"claude_code\" as const },\n mcpServers: {},\n maxTurns: 8,\n maxBudgetUsd: 5,\n },\n });\n\n const responseText = await collectResponseFromEvents(events, connection, requestId);\n if (!responseText) throw new Error(\"No response from Claude\");\n\n const { recommendations, summary } = extractJsonFromResponse(responseText);\n logger.info(\"Tag audit complete\", { requestId, recommendationCount: recommendations.length });\n\n await connection.call(\"reportTagAuditResult\", {\n projectId: connection.projectId,\n requestId,\n recommendations: recommendations as Array<Record<string, unknown>>,\n summary,\n complete: true,\n });\n}\n"],"mappings":";;;;;;;;AAKA,IAAM,SAAS,oBAAoB,UAAU;AAE7C,IAAM,iBAAiB;AAIvB,SAAS,iBAAiB,SAAgC,OAAuB;AAC/E,QAAM,KAAK,mBAAmB;AAC9B,MAAI,QAAQ,KAAK,WAAW,GAAG;AAC7B,UAAM,KAAK,4BAA4B;AAAA,EACzC,OAAO;AACL,eAAW,OAAO,QAAQ,MAAM;AAC9B,YAAM,KAAK,OAAO,IAAI,IAAI,SAAS,IAAI,EAAE,GAAG;AAC5C,YAAM,KAAK,kBAAkB,IAAI,eAAe,QAAQ,EAAE;AAC1D,YAAM,KAAK,mBAAmB,IAAI,eAAe,EAAE;AACnD,UAAI,IAAI,cAAc;AACpB,cAAM,KAAK,oBAAoB,KAAK,UAAU,IAAI,YAAY,CAAC,EAAE;AAAA,MACnE;AACA,YAAM,KAAK,EAAE;AAAA,IACf;AAAA,EACF;AACF;AAEA,SAAS,qBAAqB,SAAgC,OAAuB;AACnF,MAAI,QAAQ,cAAc,QAAQ,WAAW,SAAS,GAAG;AACvD,UAAM,KAAK,yBAAyB;AACpC,eAAW,OAAO,QAAQ,YAAY;AACpC,YAAM,KAAK,OAAO,IAAI,IAAI,KAAK,IAAI,cAAc,KAAK,IAAI,WAAW,KAAK,EAAE,EAAE;AAAA,IAChF;AACA,UAAM,KAAK,EAAE;AAAA,EACf;AAEA,MAAI,QAAQ,YAAY,QAAQ,SAAS,SAAS,GAAG;AACnD,UAAM,KAAK,gBAAgB;AAC3B,UAAM,KAAK,yEAAyE;AACpF,eAAW,QAAQ,QAAQ,UAAU;AACnC,YAAM,KAAK,OAAO,KAAK,OAAO,eAAU,KAAK,QAAQ,IAAI;AAAA,IAC3D;AACA,UAAM,KAAK,EAAE;AAAA,EACf;AAEA,MAAI,QAAQ,YAAY,SAAS,GAAG;AAClC,UAAM,KAAK,0BAA0B;AACrC,UAAM,KAAK,6DAA6D;AACxE,eAAW,SAAS,QAAQ,YAAY,MAAM,GAAG,EAAE,GAAG;AACpD,YAAM,eAAe,OAAO,QAAQ,MAAM,KAAK,EAC5C,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM,GAAG,GAAG,KAAK,KAAK,EAAE,EACxC,KAAK,IAAI;AACZ,YAAM,KAAK,OAAO,MAAM,QAAQ,cAAc,MAAM,UAAU,YAAO,YAAY,EAAE;AAAA,IACrF;AACA,UAAM,KAAK,EAAE;AAAA,EACf;AAEA,MAAI,QAAQ,qBAAqB,QAAQ,kBAAkB,SAAS,GAAG;AACrE,UAAM,KAAK,0BAA0B;AACrC,UAAM,KAAK,2DAA2D;AACtE,eAAW,UAAU,QAAQ,mBAAmB;AAC9C,YAAM,KAAK,OAAO,OAAO,OAAO,EAAE;AAClC,iBAAW,QAAQ,OAAO,OAAO;AAC/B,cAAM,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,MAAM,GAAG;AAAA,MAC/C;AACA,YAAM,KAAK,EAAE;AAAA,IACf;AAAA,EACF;AACF;AAIO,SAAS,oBAAoB,SAAwC;AAC1E,QAAM,QAAkB,CAAC;AAEzB,QAAM,KAAK,4BAA4B,QAAQ,WAAW;AAAA,CAAI;AAC9D,mBAAiB,SAAS,KAAK;AAC/B,uBAAqB,SAAS,KAAK;AACnC,QAAM,KAAK,4EAA4E;AAEvF,SAAO,MAAM,KAAK,IAAI;AACxB;AAEA,IAAM,0BAA0B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA0ChC,eAAe,WAAW,YAAgD;AACxE,MAAI;AACF,UAAM,MAAM,MAAM,WAAW,KAAK,0BAA0B;AAAA,MAC1D,WAAW,WAAW;AAAA,IACxB,CAAC;AACD,WAAO,IAAI,SAAS;AAAA,EACtB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAIA,SAAS,wBAAwB,MAA+D;AAE9F,QAAM,iBAAiB,KAAK,MAAM,iCAAiC;AACnE,QAAM,UAAU,iBAAiB,eAAe,CAAC,EAAE,KAAK,IAAI;AAE5D,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,OAAO;AACjC,QAAI,UAAU,MAAM,QAAQ,OAAO,eAAe,KAAK,OAAO,OAAO,YAAY,UAAU;AACzF,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AAEN,UAAM,cAAc,KAAK,MAAM,qCAAqC;AACpE,QAAI,aAAa;AACf,UAAI;AACF,cAAM,SAAS,KAAK,MAAM,YAAY,CAAC,CAAC;AACxC,YAAI,UAAU,MAAM,QAAQ,OAAO,eAAe,GAAG;AACnD,iBAAO,EAAE,iBAAiB,OAAO,iBAAiB,SAAS,OAAO,WAAW,GAAG;AAAA,QAClF;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAEA,QAAM,IAAI,MAAM,gEAAgE;AAClF;AAIA,eAAe,0BACb,QACA,YACA,WACiB;AACjB,QAAM,gBAA0B,CAAC;AACjC,mBAAiB,SAAS,QAAQ;AAChC,QAAI,MAAM,SAAS,aAAa;AAC9B,YAAM,EAAE,QAAQ,IAAI;AAMpB,iBAAW,SAAS,QAAQ,SAAS;AACnC,YAAI,MAAM,SAAS,UAAU,MAAM,MAAM;AACvC,wBAAc,KAAK,MAAM,IAAI;AAAA,QAC/B,WAAW,MAAM,SAAS,cAAc,MAAM,MAAM;AAClD,gBAAM,WACJ,OAAO,MAAM,UAAU,WAAW,MAAM,QAAQ,KAAK,UAAU,MAAM,KAAK;AAC5E,gBAAM,WACH,KAAK,0BAA0B;AAAA,YAC9B,WAAW,WAAW;AAAA,YACtB;AAAA,YACA,UAAU;AAAA,cACR,MAAM,MAAM;AAAA,cACZ,OAAO,SAAS,MAAM,GAAG,GAAM;AAAA,cAC/B,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,YACpC;AAAA,UACF,CAAC,EACA,MAAM,MAAM;AAAA,UAAC,CAAC;AAAA,QACnB;AAAA,MACF;AAAA,IACF;AACA,QAAI,MAAM,SAAS,SAAU;AAAA,EAC/B;AACA,SAAO,cAAc,KAAK,MAAM,EAAE,KAAK;AACzC;AAIA,eAAsB,eACpB,SACA,YACA,YACe;AACf,QAAM,EAAE,UAAU,IAAI;AAEtB,SAAO,KAAK,sBAAsB,EAAE,WAAW,UAAU,QAAQ,KAAK,OAAO,CAAC;AAE9E,QAAM,WACH,KAAK,0BAA0B;AAAA,IAC9B,WAAW,WAAW;AAAA,IACtB;AAAA,IACA,UAAU;AAAA,MACR,MAAM;AAAA,MACN,OAAO;AAAA,MACP,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC;AAAA,EACF,CAAC,EACA,MAAM,MAAM;AAAA,EAAC,CAAC;AAEjB,QAAM,QAAQ,MAAM,WAAW,UAAU;AACzC,QAAM,UAAU,cAAc;AAC9B,QAAM,SAAS,QAAQ,aAAa;AAAA,IAClC,QAAQ,oBAAoB,OAAO;AAAA,IACnC,SAAS;AAAA,MACP;AAAA,MACA,cAAc;AAAA,MACd,KAAK;AAAA,MACL,gBAAgB;AAAA,MAChB,iCAAiC;AAAA,MACjC,OAAO,EAAE,MAAM,UAAmB,QAAQ,cAAuB;AAAA,MACjE,YAAY,CAAC;AAAA,MACb,UAAU;AAAA,MACV,cAAc;AAAA,IAChB;AAAA,EACF,CAAC;AAED,QAAM,eAAe,MAAM,0BAA0B,QAAQ,YAAY,SAAS;AAClF,MAAI,CAAC,aAAc,OAAM,IAAI,MAAM,yBAAyB;AAE5D,QAAM,EAAE,iBAAiB,QAAQ,IAAI,wBAAwB,YAAY;AACzE,SAAO,KAAK,sBAAsB,EAAE,WAAW,qBAAqB,gBAAgB,OAAO,CAAC;AAE5F,QAAM,WAAW,KAAK,wBAAwB;AAAA,IAC5C,WAAW,WAAW;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,EACZ,CAAC;AACH;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/runner/tag-audit-handler.ts"],"sourcesContent":["import { createHarness } from \"../harness/index.js\";\nimport type { ProjectConnection } from \"../connection/project-connection.js\";\nimport type { TagAuditRunnerRequest } from \"@project/shared\";\nimport { createServiceLogger } from \"../utils/logger.js\";\n\nconst logger = createServiceLogger(\"TagAudit\");\n\nconst FALLBACK_MODEL = \"claude-sonnet-4-20250514\";\n\n// ── Prompt section builders ────────────────────────────────────────────\n\nfunction buildTagsSection(request: TagAuditRunnerRequest, parts: string[]): void {\n parts.push(\"## Current Tags\\n\");\n if (request.tags.length === 0) {\n parts.push(\"No tags currently exist.\\n\");\n } else {\n for (const tag of request.tags) {\n parts.push(`### ${tag.name} (id: ${tag.id})`);\n parts.push(`- Description: ${tag.description ?? \"(none)\"}`);\n parts.push(`- Active tasks: ${tag.activeTaskCount}`);\n if (tag.contextPaths) {\n parts.push(`- Context paths: ${JSON.stringify(tag.contextPaths)}`);\n }\n parts.push(\"\");\n }\n }\n}\n\nfunction buildContextSections(request: TagAuditRunnerRequest, parts: string[]): void {\n if (request.objectives && request.objectives.length > 0) {\n parts.push(\"## Project Objectives\\n\");\n for (const obj of request.objectives) {\n parts.push(`- **${obj.name}**${obj.description ? `: ${obj.description}` : \"\"}`);\n }\n parts.push(\"\");\n }\n\n if (request.tagRules && request.tagRules.length > 0) {\n parts.push(\"## Tag Rules\\n\");\n parts.push(\"These tags have associated rule files that agents load automatically:\\n\");\n for (const rule of request.tagRules) {\n parts.push(`- **${rule.tagName}** → \\`${rule.rulePath}\\``);\n }\n parts.push(\"\");\n }\n\n if (request.fileHeatmap.length > 0) {\n parts.push(\"## File Access Heatmap\\n\");\n parts.push(\"Files most frequently read by agents, broken down by tag:\\n\");\n for (const entry of request.fileHeatmap.slice(0, 50)) {\n const tagBreakdown = Object.entries(entry.byTag)\n .map(([tag, count]) => `${tag}: ${count}`)\n .join(\", \");\n parts.push(`- \\`${entry.filePath}\\` (total: ${entry.totalReads}) — ${tagBreakdown}`);\n }\n parts.push(\"\");\n }\n\n if (request.recentTaskSamples && request.recentTaskSamples.length > 0) {\n parts.push(\"## Recent Task Samples\\n\");\n parts.push(\"Recent tasks per tag (to understand how tags are used):\\n\");\n for (const sample of request.recentTaskSamples) {\n parts.push(`### ${sample.tagName}`);\n for (const task of sample.tasks) {\n parts.push(`- ${task.title} (${task.status})`);\n }\n parts.push(\"\");\n }\n }\n}\n\n// ── Prompt builder ──────────────────────────────────────────────────────\n\nexport function buildTagAuditPrompt(request: TagAuditRunnerRequest): string {\n const parts: string[] = [];\n\n parts.push(`# Tag Audit for project: ${request.projectName}\\n`);\n buildTagsSection(request, parts);\n buildContextSections(request, parts);\n parts.push(\"Analyze the tags above and the codebase, then output your recommendations.\");\n\n return parts.join(\"\\n\");\n}\n\nconst TAG_AUDIT_SYSTEM_PROMPT = `You are analyzing tags for a software project. Tags are used to organize tasks, associate context paths (files/directories that agents should read), and track work areas.\n\nGiven the current tags (with descriptions, context paths, and active task counts) and a file access heatmap (which files agents read most, broken down by tag), analyze the project and generate recommendations.\n\nYou have full access to the codebase. Read relevant files — especially .claude/rules/, README files, and any context paths referenced by existing tags — to validate your recommendations.\n\nGenerate recommendations of these types:\n- **create_tag**: Suggest new tags for uncovered areas of the codebase\n- **update_description**: Improve a tag's description to be more useful\n- **add_context_link**: Add context paths (files/directories) that agents should read when working on tasks with this tag\n- **documentation_gap**: Identify areas where documentation or context is missing\n- **merge_tags**: Suggest merging overlapping tags\n- **rename_tag**: Suggest a better name for a tag\n\nEach recommendation must include:\n- \\`id\\`: A unique UUID\n- \\`type\\`: One of the types above\n- \\`tagName\\`: The tag name this applies to\n- \\`tagId\\`: The existing tag's ID (if modifying an existing tag, omit for create_tag)\n- \\`suggestion\\`: A short description of the recommendation\n- \\`reasoning\\`: Why this recommendation would help\n- \\`payload\\`: Type-specific data:\n - create_tag: \\`{ name: string, description: string, contextPaths?: string[] }\\`\n - update_description: \\`{ description: string }\\`\n - add_context_link: \\`{ type: \"rule\" | \"doc\" | \"file\" | \"folder\", path: string, label?: string }\\`\n - documentation_gap: \\`{ area: string, suggestedContent?: string }\\`\n - merge_tags: \\`{ sourceTagIds: string[], targetName: string }\\`\n - rename_tag: \\`{ newName: string }\\`\n\nAfter your analysis, output a JSON block in this exact format:\n\n\\`\\`\\`json\n{\n \"recommendations\": [ ...array of recommendations... ],\n \"summary\": \"Brief summary of findings\"\n}\n\\`\\`\\`\n\nAlways provide recommendations. Even well-organized projects have room for improvement — look for missing context links, stale descriptions, documentation gaps, and tag coverage opportunities. Aim for at least 3-5 recommendations. Be specific, actionable, and concise.`;\n\n// ── Fetch agent context ─────────────────────────────────────────────────\n\nasync function fetchModel(connection: ProjectConnection): Promise<string> {\n try {\n const ctx = await connection.call(\"getProjectAgentContext\", {\n projectId: connection.projectId,\n });\n return ctx.model || FALLBACK_MODEL;\n } catch {\n return FALLBACK_MODEL;\n }\n}\n\n// ── JSON extraction ─────────────────────────────────────────────────────\n\nfunction extractJsonFromResponse(text: string): { recommendations: unknown[]; summary: string } {\n // Try to find a JSON code block first\n const codeBlockMatch = text.match(/```(?:json)?\\s*\\n?([\\s\\S]*?)```/);\n const jsonStr = codeBlockMatch ? codeBlockMatch[1].trim() : text;\n\n try {\n const parsed = JSON.parse(jsonStr);\n if (parsed && Array.isArray(parsed.recommendations) && typeof parsed.summary === \"string\") {\n return parsed;\n }\n } catch {\n // Try to find a JSON object in the raw text\n const objectMatch = text.match(/\\{[\\s\\S]*\"recommendations\"[\\s\\S]*\\}/);\n if (objectMatch) {\n try {\n const parsed = JSON.parse(objectMatch[0]);\n if (parsed && Array.isArray(parsed.recommendations)) {\n return { recommendations: parsed.recommendations, summary: parsed.summary ?? \"\" };\n }\n } catch {\n // Fall through\n }\n }\n }\n\n throw new Error(\"Could not parse tag audit recommendations from Claude response\");\n}\n\n// ── Event stream processing ─────────────────────────────────────────────\n\nasync function collectResponseFromEvents(\n events: AsyncIterable<{ type: string }>,\n connection: ProjectConnection,\n requestId: string,\n): Promise<string> {\n const responseParts: string[] = [];\n for await (const event of events) {\n if (event.type === \"assistant\") {\n const { message } = event as {\n type: string;\n message: {\n content: Array<{ type: string; text?: string; name?: string; input?: unknown }>;\n };\n };\n for (const block of message.content) {\n if (block.type === \"text\" && block.text) {\n responseParts.push(block.text);\n } else if (block.type === \"tool_use\" && block.name) {\n const inputStr =\n typeof block.input === \"string\" ? block.input : JSON.stringify(block.input);\n await connection\n .call(\"reportTagAuditProgress\", {\n projectId: connection.projectId,\n requestId,\n activity: {\n tool: block.name,\n input: inputStr.slice(0, 10_000),\n timestamp: new Date().toISOString(),\n },\n })\n .catch(() => {});\n }\n }\n }\n if (event.type === \"result\") break;\n }\n return responseParts.join(\"\\n\\n\").trim();\n}\n\n// ── Main handler ────────────────────────────────────────────────────────\n\nexport async function handleTagAudit(\n request: TagAuditRunnerRequest,\n connection: ProjectConnection,\n projectDir: string,\n): Promise<void> {\n const { requestId } = request;\n\n logger.info(\"Starting tag audit\", { requestId, tagCount: request.tags.length });\n\n await connection\n .call(\"reportTagAuditProgress\", {\n projectId: connection.projectId,\n requestId,\n activity: {\n tool: \"audit\",\n input: \"Starting tag audit...\",\n timestamp: new Date().toISOString(),\n },\n })\n .catch(() => {});\n\n const model = await fetchModel(connection);\n const harness = createHarness();\n const events = harness.executeQuery({\n prompt: buildTagAuditPrompt(request),\n options: {\n model,\n systemPrompt: TAG_AUDIT_SYSTEM_PROMPT,\n cwd: projectDir,\n permissionMode: \"bypassPermissions\",\n allowDangerouslySkipPermissions: true,\n tools: { type: \"preset\" as const, preset: \"claude_code\" as const },\n mcpServers: {},\n maxTurns: 8,\n maxBudgetUsd: 5,\n },\n });\n\n const responseText = await collectResponseFromEvents(events, connection, requestId);\n if (!responseText) throw new Error(\"No response from Claude\");\n\n const { recommendations, summary } = extractJsonFromResponse(responseText);\n logger.info(\"Tag audit complete\", { requestId, recommendationCount: recommendations.length });\n\n await connection.call(\"reportTagAuditResult\", {\n projectId: connection.projectId,\n requestId,\n recommendations: recommendations as Array<Record<string, unknown>>,\n summary,\n complete: true,\n });\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import {
|
|
2
|
+
textResult
|
|
3
|
+
} from "./chunk-C5YAMQJ2.js";
|
|
4
|
+
import {
|
|
5
|
+
createHarness,
|
|
6
|
+
createServiceLogger,
|
|
7
|
+
defineTool
|
|
8
|
+
} from "./chunk-U6AYNVS7.js";
|
|
9
|
+
|
|
10
|
+
// src/runner/task-audit-handler.ts
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
var logger = createServiceLogger("TaskAudit");
|
|
13
|
+
var FALLBACK_MODEL = "claude-sonnet-4-20250514";
|
|
14
|
+
var TASK_AUDIT_SYSTEM_PROMPT = `You are a chess-style analysis engine for AI agent task execution. You grade each turn of an agent's work trace like a chess engine grades moves: Correct, Neutral, or Blunder.
|
|
15
|
+
|
|
16
|
+
You receive the agent's full execution trace (tool calls, reasoning, errors) along with the task plan, description, and human messages. Your job is to objectively assess the quality of the agent's work.
|
|
17
|
+
|
|
18
|
+
## Grading Scale
|
|
19
|
+
|
|
20
|
+
- **Correct** (\u265F): Sound moves toward completion \u2014 necessary file reads, proper tool use, following plan steps, logical reasoning, adapting to feedback
|
|
21
|
+
- **Neutral** (\xB7): No significant positive or negative effect \u2014 exploratory reads, clarifying questions, minor tangents that don't waste resources
|
|
22
|
+
- **Blunder** (??): Wasted effort or wrong direction \u2014 incorrect edits, reading irrelevant files repeatedly, ignoring the plan, repeated failures without adapting
|
|
23
|
+
|
|
24
|
+
## Phase Identification
|
|
25
|
+
|
|
26
|
+
Classify each turn into a phase based on the trace context:
|
|
27
|
+
- **planning**: Events before the first file modification (Edit/Write tool use) \u2014 reading files, analyzing requirements, reasoning about approach
|
|
28
|
+
- **building**: File modification events and related reasoning \u2014 writing code, running tests, fixing errors
|
|
29
|
+
- **human**: Turns that are direct responses to user/human chat messages
|
|
30
|
+
|
|
31
|
+
## Specific Blunder Examples
|
|
32
|
+
- Reading the same file 5+ times without making meaningful changes
|
|
33
|
+
- Making edits that are immediately reverted or overwritten
|
|
34
|
+
- Ignoring explicit plan steps or user corrections
|
|
35
|
+
- Repeated failed tool calls (isError: true) without changing approach
|
|
36
|
+
- Using wrong APIs despite project patterns visible in the trace
|
|
37
|
+
- Spending many turns on tangential work not in the plan
|
|
38
|
+
|
|
39
|
+
## Specific Correct Examples
|
|
40
|
+
- Reading context files before implementation
|
|
41
|
+
- Following plan steps in logical sequence
|
|
42
|
+
- Proper error handling and recovery after tool failures
|
|
43
|
+
- Adapting approach based on user feedback
|
|
44
|
+
- Efficient tool usage with clear purpose
|
|
45
|
+
|
|
46
|
+
## Output Format
|
|
47
|
+
|
|
48
|
+
After analyzing all turns, output a JSON block:
|
|
49
|
+
|
|
50
|
+
\`\`\`json
|
|
51
|
+
{
|
|
52
|
+
"turnGrades": [
|
|
53
|
+
{
|
|
54
|
+
"turnIndex": 0,
|
|
55
|
+
"phase": "planning",
|
|
56
|
+
"grade": "correct",
|
|
57
|
+
"reasoning": "Read relevant config files to understand project structure before making changes",
|
|
58
|
+
"eventType": "tool_use",
|
|
59
|
+
"eventSummary": "Read package.json and tsconfig.json"
|
|
60
|
+
}
|
|
61
|
+
],
|
|
62
|
+
"summary": "Brief 2-3 sentence assessment of overall execution quality",
|
|
63
|
+
"planningAccuracy": 0.85,
|
|
64
|
+
"buildingAccuracy": 0.72,
|
|
65
|
+
"humanAccuracy": 1.0
|
|
66
|
+
}
|
|
67
|
+
\`\`\`
|
|
68
|
+
|
|
69
|
+
**Accuracy Calculation**: For each phase, accuracy = correct / (correct + blunder). Neutral turns are excluded from the ratio. Return null if a phase has no correct or blunder turns.
|
|
70
|
+
|
|
71
|
+
## Suggestion Guidelines
|
|
72
|
+
|
|
73
|
+
After grading, if you identify actionable cross-cutting improvements that would help future tasks (not one-off fixes), call the \`create_suggestion\` tool. Focus on:
|
|
74
|
+
- Recurring patterns of wasted effort
|
|
75
|
+
- Missing documentation or context that caused confusion
|
|
76
|
+
- Process improvements that would prevent common blunders
|
|
77
|
+
- Tool or workflow optimizations`;
|
|
78
|
+
function buildSessionInfoSection(info) {
|
|
79
|
+
const lines = ["## Session Info"];
|
|
80
|
+
if (info.model) lines.push(`- Model: ${info.model}`);
|
|
81
|
+
if (info.totalCostUsd !== null) lines.push(`- Total Cost: $${info.totalCostUsd.toFixed(4)}`);
|
|
82
|
+
if (info.planningTurns !== null) lines.push(`- Planning Turns: ${info.planningTurns}`);
|
|
83
|
+
if (info.buildingTurns !== null) lines.push(`- Building Turns: ${info.buildingTurns}`);
|
|
84
|
+
if (info.locDiff !== null) lines.push(`- Lines Changed: ${info.locDiff}`);
|
|
85
|
+
lines.push("");
|
|
86
|
+
return lines.join("\n");
|
|
87
|
+
}
|
|
88
|
+
function buildTraceSection(trace) {
|
|
89
|
+
const lines = ["## Agent Trace\n"];
|
|
90
|
+
if (trace.length === 0) {
|
|
91
|
+
lines.push("No agent trace data available.\n");
|
|
92
|
+
} else {
|
|
93
|
+
for (let i = 0; i < trace.length; i++) {
|
|
94
|
+
const event = trace[i];
|
|
95
|
+
const dataStr = JSON.stringify(event.data);
|
|
96
|
+
const truncated = dataStr.length > 2e3 ? dataStr.slice(0, 2e3) + "..." : dataStr;
|
|
97
|
+
lines.push(`### Turn ${i} [${event.type}] @ ${event.timestamp}`);
|
|
98
|
+
lines.push(truncated);
|
|
99
|
+
lines.push("");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return lines.join("\n");
|
|
103
|
+
}
|
|
104
|
+
function buildTaskAuditPrompt(task) {
|
|
105
|
+
const parts = [`# Task Audit: ${task.taskTitle}
|
|
106
|
+
`];
|
|
107
|
+
if (task.taskDescription) parts.push(`## Description
|
|
108
|
+
${task.taskDescription}
|
|
109
|
+
`);
|
|
110
|
+
if (task.taskPlan) parts.push(`## Plan
|
|
111
|
+
${task.taskPlan}
|
|
112
|
+
`);
|
|
113
|
+
parts.push(`## Task Status: ${task.taskStatus}`);
|
|
114
|
+
if (task.storyPointValue !== null) parts.push(`## Story Points: ${task.storyPointValue}`);
|
|
115
|
+
parts.push("");
|
|
116
|
+
if (task.sessionInfo) parts.push(buildSessionInfoSection(task.sessionInfo));
|
|
117
|
+
if (task.agentInstructions) parts.push(`## Agent Instructions
|
|
118
|
+
${task.agentInstructions}
|
|
119
|
+
`);
|
|
120
|
+
if (task.humanMessages.length > 0) {
|
|
121
|
+
parts.push("## Human Messages\n");
|
|
122
|
+
for (const msg of task.humanMessages) {
|
|
123
|
+
parts.push(`[${msg.createdAt}] **${msg.role}**: ${msg.content}`);
|
|
124
|
+
}
|
|
125
|
+
parts.push("");
|
|
126
|
+
}
|
|
127
|
+
parts.push(buildTraceSection(task.agentTrace));
|
|
128
|
+
parts.push("Grade each turn above, then output the structured JSON result.");
|
|
129
|
+
return parts.join("\n");
|
|
130
|
+
}
|
|
131
|
+
function buildProjectSuggestionTool(connection, projectId) {
|
|
132
|
+
return defineTool(
|
|
133
|
+
"create_suggestion",
|
|
134
|
+
"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.",
|
|
135
|
+
{
|
|
136
|
+
title: z.string().describe("Short title for the suggestion"),
|
|
137
|
+
description: z.string().optional().describe("Details about the suggestion"),
|
|
138
|
+
tag_names: z.array(z.string()).optional().describe("Tag names to categorize the suggestion")
|
|
139
|
+
},
|
|
140
|
+
async ({ title, description, tag_names }) => {
|
|
141
|
+
try {
|
|
142
|
+
const result = await connection.call("createProjectSuggestion", {
|
|
143
|
+
projectId,
|
|
144
|
+
title,
|
|
145
|
+
description,
|
|
146
|
+
tagNames: tag_names
|
|
147
|
+
});
|
|
148
|
+
if (result.merged) {
|
|
149
|
+
return textResult(
|
|
150
|
+
`Merged into existing suggestion (ID: ${result.mergedIntoId ?? result.id}).`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
return textResult(`Suggestion created (ID: ${result.id}).`);
|
|
154
|
+
} catch (error) {
|
|
155
|
+
return textResult(
|
|
156
|
+
`Failed to create suggestion: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
async function fetchModel(connection) {
|
|
163
|
+
try {
|
|
164
|
+
const ctx = await connection.call("getProjectAgentContext", {
|
|
165
|
+
projectId: connection.projectId
|
|
166
|
+
});
|
|
167
|
+
return ctx.model || FALLBACK_MODEL;
|
|
168
|
+
} catch {
|
|
169
|
+
return FALLBACK_MODEL;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function toAuditResult(parsed) {
|
|
173
|
+
if (!parsed || !Array.isArray(parsed.turnGrades)) return null;
|
|
174
|
+
return {
|
|
175
|
+
turnGrades: parsed.turnGrades,
|
|
176
|
+
summary: parsed.summary ?? "",
|
|
177
|
+
planningAccuracy: parsed.planningAccuracy ?? null,
|
|
178
|
+
buildingAccuracy: parsed.buildingAccuracy ?? null,
|
|
179
|
+
humanAccuracy: parsed.humanAccuracy ?? null
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function extractJsonFromResponse(text) {
|
|
183
|
+
const codeBlockMatch = text.match(/```(?:json)?\s*\n?([\s\S]*?)```/);
|
|
184
|
+
const jsonStr = codeBlockMatch ? codeBlockMatch[1].trim() : text;
|
|
185
|
+
try {
|
|
186
|
+
const result = toAuditResult(JSON.parse(jsonStr));
|
|
187
|
+
if (result) return result;
|
|
188
|
+
} catch {
|
|
189
|
+
}
|
|
190
|
+
const objectMatch = text.match(/\{[\s\S]*"turnGrades"[\s\S]*\}/);
|
|
191
|
+
if (objectMatch) {
|
|
192
|
+
try {
|
|
193
|
+
const result = toAuditResult(JSON.parse(objectMatch[0]));
|
|
194
|
+
if (result) return result;
|
|
195
|
+
} catch {
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
throw new Error("Could not parse task audit grades from Claude response");
|
|
199
|
+
}
|
|
200
|
+
function computeGradeCounts(turnGrades) {
|
|
201
|
+
const counts = {
|
|
202
|
+
planningCorrect: 0,
|
|
203
|
+
planningNeutral: 0,
|
|
204
|
+
planningBlunder: 0,
|
|
205
|
+
buildingCorrect: 0,
|
|
206
|
+
buildingNeutral: 0,
|
|
207
|
+
buildingBlunder: 0,
|
|
208
|
+
humanCorrect: 0,
|
|
209
|
+
humanNeutral: 0,
|
|
210
|
+
humanBlunder: 0
|
|
211
|
+
};
|
|
212
|
+
for (const tg of turnGrades) {
|
|
213
|
+
const key = `${tg.phase}${tg.grade.charAt(0).toUpperCase()}${tg.grade.slice(1)}`;
|
|
214
|
+
if (key in counts) {
|
|
215
|
+
counts[key]++;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return counts;
|
|
219
|
+
}
|
|
220
|
+
async function collectResponseFromEvents(events, connection, requestId, taskId) {
|
|
221
|
+
const responseParts = [];
|
|
222
|
+
let costUsd = null;
|
|
223
|
+
let model = null;
|
|
224
|
+
for await (const event of events) {
|
|
225
|
+
if (event.type === "system") {
|
|
226
|
+
const sysEvent = event;
|
|
227
|
+
if (sysEvent.subtype === "init" && sysEvent.model) {
|
|
228
|
+
model = sysEvent.model;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (event.type === "assistant") {
|
|
232
|
+
const { message } = event;
|
|
233
|
+
for (const block of message.content) {
|
|
234
|
+
if (block.type === "text" && block.text) {
|
|
235
|
+
responseParts.push(block.text);
|
|
236
|
+
} else if (block.type === "tool_use" && block.name) {
|
|
237
|
+
const inputStr = typeof block.input === "string" ? block.input : JSON.stringify(block.input);
|
|
238
|
+
await connection.call("reportTaskAuditProgress", {
|
|
239
|
+
projectId: connection.projectId,
|
|
240
|
+
requestId,
|
|
241
|
+
taskId,
|
|
242
|
+
activity: `${block.name}: ${inputStr.slice(0, 500)}`
|
|
243
|
+
}).catch(() => {
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (event.type === "result") {
|
|
249
|
+
const resultEvent = event;
|
|
250
|
+
if (resultEvent.subtype === "success" && resultEvent.total_cost_usd !== void 0) {
|
|
251
|
+
costUsd = resultEvent.total_cost_usd;
|
|
252
|
+
}
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return { text: responseParts.join("\n\n").trim(), costUsd, model };
|
|
257
|
+
}
|
|
258
|
+
function buildErrorResult(projectId, requestId, taskId, error) {
|
|
259
|
+
return {
|
|
260
|
+
projectId,
|
|
261
|
+
requestId,
|
|
262
|
+
taskId,
|
|
263
|
+
summary: "",
|
|
264
|
+
turnGrades: [],
|
|
265
|
+
planningAccuracy: null,
|
|
266
|
+
buildingAccuracy: null,
|
|
267
|
+
humanAccuracy: null,
|
|
268
|
+
planningCorrect: 0,
|
|
269
|
+
planningNeutral: 0,
|
|
270
|
+
planningBlunder: 0,
|
|
271
|
+
buildingCorrect: 0,
|
|
272
|
+
buildingNeutral: 0,
|
|
273
|
+
buildingBlunder: 0,
|
|
274
|
+
humanCorrect: 0,
|
|
275
|
+
humanNeutral: 0,
|
|
276
|
+
humanBlunder: 0,
|
|
277
|
+
suggestionIds: [],
|
|
278
|
+
auditCostUsd: null,
|
|
279
|
+
model: null,
|
|
280
|
+
error
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
async function auditSingleTask(taskPayload, request, connection, projectDir, model) {
|
|
284
|
+
const { requestId, projectId } = request;
|
|
285
|
+
await connection.call("reportTaskAuditProgress", {
|
|
286
|
+
projectId: connection.projectId,
|
|
287
|
+
requestId,
|
|
288
|
+
taskId: taskPayload.taskId,
|
|
289
|
+
activity: "Starting task audit..."
|
|
290
|
+
}).catch(() => {
|
|
291
|
+
});
|
|
292
|
+
const suggestionIds = [];
|
|
293
|
+
const suggestionTool = buildProjectSuggestionTool(connection, projectId);
|
|
294
|
+
const wrappedSuggestionTool = defineTool(
|
|
295
|
+
suggestionTool.name,
|
|
296
|
+
suggestionTool.description,
|
|
297
|
+
suggestionTool.schema,
|
|
298
|
+
async (input) => {
|
|
299
|
+
const result = await suggestionTool.handler(input);
|
|
300
|
+
const text = result.content?.[0]?.text ?? "";
|
|
301
|
+
const idMatch = text.match(/ID: ([a-z0-9]+)/i);
|
|
302
|
+
if (idMatch) suggestionIds.push(idMatch[1]);
|
|
303
|
+
return result;
|
|
304
|
+
}
|
|
305
|
+
);
|
|
306
|
+
const harness = createHarness();
|
|
307
|
+
const mcpServer = harness.createMcpServer({
|
|
308
|
+
name: "task-audit",
|
|
309
|
+
tools: [wrappedSuggestionTool]
|
|
310
|
+
});
|
|
311
|
+
const events = harness.executeQuery({
|
|
312
|
+
prompt: buildTaskAuditPrompt(taskPayload),
|
|
313
|
+
options: {
|
|
314
|
+
model,
|
|
315
|
+
systemPrompt: TASK_AUDIT_SYSTEM_PROMPT,
|
|
316
|
+
cwd: projectDir,
|
|
317
|
+
permissionMode: "bypassPermissions",
|
|
318
|
+
allowDangerouslySkipPermissions: true,
|
|
319
|
+
tools: { type: "preset", preset: "claude_code" },
|
|
320
|
+
mcpServers: { "task-audit": mcpServer },
|
|
321
|
+
maxTurns: 6,
|
|
322
|
+
maxBudgetUsd: 3,
|
|
323
|
+
disallowedTools: ["Edit", "Write", "Bash", "NotebookEdit", "MultiEdit"]
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
const response = await collectResponseFromEvents(
|
|
327
|
+
events,
|
|
328
|
+
connection,
|
|
329
|
+
requestId,
|
|
330
|
+
taskPayload.taskId
|
|
331
|
+
);
|
|
332
|
+
if (!response.text) throw new Error("No response from Claude");
|
|
333
|
+
const parsed = extractJsonFromResponse(response.text);
|
|
334
|
+
const counts = computeGradeCounts(parsed.turnGrades);
|
|
335
|
+
logger.info("Task audit complete", {
|
|
336
|
+
requestId,
|
|
337
|
+
taskId: taskPayload.taskId,
|
|
338
|
+
grades: parsed.turnGrades.length,
|
|
339
|
+
suggestions: suggestionIds.length
|
|
340
|
+
});
|
|
341
|
+
await connection.call("reportTaskAuditResult", {
|
|
342
|
+
projectId: connection.projectId,
|
|
343
|
+
requestId,
|
|
344
|
+
taskId: taskPayload.taskId,
|
|
345
|
+
summary: parsed.summary,
|
|
346
|
+
turnGrades: parsed.turnGrades,
|
|
347
|
+
planningAccuracy: parsed.planningAccuracy,
|
|
348
|
+
buildingAccuracy: parsed.buildingAccuracy,
|
|
349
|
+
humanAccuracy: parsed.humanAccuracy,
|
|
350
|
+
...counts,
|
|
351
|
+
suggestionIds,
|
|
352
|
+
auditCostUsd: response.costUsd,
|
|
353
|
+
model: response.model
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
async function handleTaskAudit(request, connection, projectDir) {
|
|
357
|
+
const { requestId } = request;
|
|
358
|
+
logger.info("Starting task audit", { requestId, taskCount: request.tasks.length });
|
|
359
|
+
const model = await fetchModel(connection);
|
|
360
|
+
for (const taskPayload of request.tasks) {
|
|
361
|
+
try {
|
|
362
|
+
await auditSingleTask(taskPayload, request, connection, projectDir, model);
|
|
363
|
+
} catch (err) {
|
|
364
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
365
|
+
logger.error("Task audit failed for task", {
|
|
366
|
+
requestId,
|
|
367
|
+
taskId: taskPayload.taskId,
|
|
368
|
+
error: msg
|
|
369
|
+
});
|
|
370
|
+
await connection.call(
|
|
371
|
+
"reportTaskAuditResult",
|
|
372
|
+
buildErrorResult(connection.projectId, requestId, taskPayload.taskId, msg)
|
|
373
|
+
).catch(() => {
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
export {
|
|
379
|
+
handleTaskAudit
|
|
380
|
+
};
|
|
381
|
+
//# sourceMappingURL=task-audit-handler-URD2BOC4.js.map
|