auditor-lambda 0.3.2 → 0.3.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/README.md +6 -1
- package/audit-code-wrapper-lib.mjs +78 -5
- package/dist/cli.js +205 -67
- package/dist/extractors/graph.d.ts +5 -1
- package/dist/extractors/graph.js +223 -3
- package/dist/extractors/pathPatterns.d.ts +3 -2
- package/dist/extractors/pathPatterns.js +97 -24
- package/dist/io/artifacts.d.ts +5 -0
- package/dist/io/artifacts.js +2 -0
- package/dist/io/json.js +3 -3
- package/dist/io/runArtifacts.js +4 -0
- package/dist/mcp/server.js +24 -11
- package/dist/orchestrator/advance.js +1 -1
- package/dist/orchestrator/dependencyMap.js +18 -0
- package/dist/orchestrator/internalExecutors.d.ts +1 -1
- package/dist/orchestrator/internalExecutors.js +120 -33
- package/dist/orchestrator/reviewPackets.d.ts +14 -0
- package/dist/orchestrator/reviewPackets.js +300 -0
- package/dist/orchestrator/selectiveDeepening.d.ts +14 -0
- package/dist/orchestrator/selectiveDeepening.js +392 -0
- package/dist/orchestrator/state.js +6 -1
- package/dist/orchestrator/taskBuilder.d.ts +16 -0
- package/dist/orchestrator/taskBuilder.js +68 -11
- package/dist/orchestrator.js +53 -2
- package/dist/prompts/renderWorkerPrompt.js +11 -4
- package/dist/providers/index.js +1 -1
- package/dist/supervisor/sessionConfig.js +1 -1
- package/dist/types/graph.d.ts +1 -0
- package/dist/types/reviewPlanning.d.ts +41 -0
- package/dist/types/reviewPlanning.js +1 -0
- package/dist/validation/artifacts.js +13 -0
- package/dist/validation/sessionConfig.js +1 -1
- package/docs/agent-integrations.md +17 -8
- package/docs/bootstrap-install.md +3 -0
- package/docs/dispatch-implementation-plan.md +179 -481
- package/docs/next-steps.md +13 -8
- package/docs/product-direction.md +5 -3
- package/docs/run-flow.md +23 -30
- package/docs/session-config.md +10 -3
- package/docs/supervisor.md +12 -4
- package/docs/workflow-refactor-brief.md +85 -147
- package/package.json +1 -1
- package/schemas/audit_results.schema.json +10 -0
- package/schemas/finding.schema.json +1 -15
- package/schemas/graph_bundle.schema.json +16 -0
- package/skills/audit-code/SKILL.md +12 -3
- package/skills/audit-code/audit-code.prompt.md +87 -57
|
@@ -12,6 +12,8 @@ export const ARTIFACT_DEPENDENCY_MAP = {
|
|
|
12
12
|
"coverage_matrix.json",
|
|
13
13
|
"flow_coverage.json",
|
|
14
14
|
"audit_tasks.json",
|
|
15
|
+
"audit_plan_metrics.json",
|
|
16
|
+
"review_packets.json",
|
|
15
17
|
"requeue_tasks.json",
|
|
16
18
|
"runtime_validation_tasks.json",
|
|
17
19
|
"runtime_validation_report.json",
|
|
@@ -26,6 +28,8 @@ export const ARTIFACT_DEPENDENCY_MAP = {
|
|
|
26
28
|
"coverage_matrix.json",
|
|
27
29
|
"flow_coverage.json",
|
|
28
30
|
"audit_tasks.json",
|
|
31
|
+
"audit_plan_metrics.json",
|
|
32
|
+
"review_packets.json",
|
|
29
33
|
"requeue_tasks.json",
|
|
30
34
|
"runtime_validation_tasks.json",
|
|
31
35
|
"runtime_validation_report.json",
|
|
@@ -35,6 +39,8 @@ export const ARTIFACT_DEPENDENCY_MAP = {
|
|
|
35
39
|
"risk_register.json",
|
|
36
40
|
"coverage_matrix.json",
|
|
37
41
|
"audit_tasks.json",
|
|
42
|
+
"audit_plan_metrics.json",
|
|
43
|
+
"review_packets.json",
|
|
38
44
|
"runtime_validation_tasks.json",
|
|
39
45
|
"requeue_tasks.json",
|
|
40
46
|
"audit-report.md",
|
|
@@ -49,6 +55,8 @@ export const ARTIFACT_DEPENDENCY_MAP = {
|
|
|
49
55
|
"flow_coverage.json",
|
|
50
56
|
"risk_register.json",
|
|
51
57
|
"audit_tasks.json",
|
|
58
|
+
"audit_plan_metrics.json",
|
|
59
|
+
"review_packets.json",
|
|
52
60
|
"requeue_tasks.json",
|
|
53
61
|
"runtime_validation_tasks.json",
|
|
54
62
|
"runtime_validation_report.json",
|
|
@@ -58,12 +66,16 @@ export const ARTIFACT_DEPENDENCY_MAP = {
|
|
|
58
66
|
"coverage_matrix.json",
|
|
59
67
|
"flow_coverage.json",
|
|
60
68
|
"requeue_tasks.json",
|
|
69
|
+
"audit_plan_metrics.json",
|
|
70
|
+
"review_packets.json",
|
|
61
71
|
"runtime_validation_tasks.json",
|
|
62
72
|
"runtime_validation_report.json",
|
|
63
73
|
"audit-report.md",
|
|
64
74
|
],
|
|
65
75
|
"coverage_matrix.json": [
|
|
66
76
|
"flow_coverage.json",
|
|
77
|
+
"audit_plan_metrics.json",
|
|
78
|
+
"review_packets.json",
|
|
67
79
|
"requeue_tasks.json",
|
|
68
80
|
"runtime_validation_tasks.json",
|
|
69
81
|
"runtime_validation_report.json",
|
|
@@ -75,6 +87,12 @@ export const ARTIFACT_DEPENDENCY_MAP = {
|
|
|
75
87
|
"runtime_validation_report.json",
|
|
76
88
|
"audit-report.md",
|
|
77
89
|
],
|
|
90
|
+
"audit_tasks.json": [
|
|
91
|
+
"audit_plan_metrics.json",
|
|
92
|
+
"review_packets.json",
|
|
93
|
+
],
|
|
94
|
+
"audit_plan_metrics.json": [],
|
|
95
|
+
"review_packets.json": [],
|
|
78
96
|
"runtime_validation_report.json": [
|
|
79
97
|
"audit-report.md",
|
|
80
98
|
],
|
|
@@ -8,7 +8,7 @@ export interface ExecutorRunResult {
|
|
|
8
8
|
progress_summary: string;
|
|
9
9
|
}
|
|
10
10
|
export declare function runIntakeExecutor(bundle: ArtifactBundle, root: string): Promise<ExecutorRunResult>;
|
|
11
|
-
export declare function runStructureExecutor(bundle: ArtifactBundle): ExecutorRunResult
|
|
11
|
+
export declare function runStructureExecutor(bundle: ArtifactBundle, root?: string): Promise<ExecutorRunResult>;
|
|
12
12
|
export declare function runPlanningExecutor(bundle: ArtifactBundle, root: string, lineIndex?: Record<string, number>): Promise<ExecutorRunResult>;
|
|
13
13
|
export declare function runResultIngestionExecutor(bundle: ArtifactBundle, results: AuditResult[]): ExecutorRunResult;
|
|
14
14
|
export declare function runRuntimeValidationExecutor(bundle: ArtifactBundle, root: string): Promise<ExecutorRunResult>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { buildFileDisposition } from "../extractors/disposition.js";
|
|
3
|
-
import { buildGraphBundle } from "../extractors/graph.js";
|
|
3
|
+
import { buildGraphBundle, buildGraphBundleFromFs, } from "../extractors/graph.js";
|
|
4
4
|
import { buildCriticalFlowManifest } from "../extractors/flows.js";
|
|
5
5
|
import { buildRiskRegister } from "../extractors/risk.js";
|
|
6
6
|
import { buildSurfaceManifest } from "../extractors/surfaces.js";
|
|
@@ -10,12 +10,50 @@ import { buildRequeuePayload } from "./requeueCommand.js";
|
|
|
10
10
|
import { buildRuntimeValidationTasks, discoverRuntimeValidationCommand, mergeRuntimeValidationReport, } from "./runtimeValidation.js";
|
|
11
11
|
import { buildAuditReportModel, renderAuditReportMarkdown, } from "../reporting/synthesis.js";
|
|
12
12
|
import { buildChunkedAuditTasks, } from "./taskBuilder.js";
|
|
13
|
+
import { buildAuditPlanMetrics, buildReviewPackets, } from "./reviewPackets.js";
|
|
13
14
|
import { buildUnitManifest } from "./unitBuilder.js";
|
|
14
15
|
import { buildRepoManifestFromFs } from "../extractors/fsIntake.js";
|
|
15
16
|
import { loadIgnoreFile } from "../extractors/ignore.js";
|
|
16
17
|
import { ingestAuditResults, updateAuditTaskStatuses, } from "./resultIngestion.js";
|
|
18
|
+
import { buildSelectiveDeepeningTasks } from "./selectiveDeepening.js";
|
|
17
19
|
import { updateRuntimeValidationReport } from "./runtimeValidationUpdate.js";
|
|
18
20
|
import { autoCompleteTrivialCoverage } from "./trivialAudit.js";
|
|
21
|
+
function lineIndexFromTasks(tasks) {
|
|
22
|
+
return Object.fromEntries((tasks ?? []).flatMap((task) => Object.entries(task.file_line_counts ?? {})));
|
|
23
|
+
}
|
|
24
|
+
function appendSelectiveDeepeningTasks(params) {
|
|
25
|
+
if (!params.bundle.audit_tasks) {
|
|
26
|
+
return { bundle: params.bundle, taskCount: 0, artifacts: [] };
|
|
27
|
+
}
|
|
28
|
+
const lineIndex = lineIndexFromTasks(params.bundle.audit_tasks);
|
|
29
|
+
const selectiveDeepeningTasks = buildSelectiveDeepeningTasks({
|
|
30
|
+
existingTasks: params.bundle.audit_tasks,
|
|
31
|
+
results: params.results,
|
|
32
|
+
lineIndex,
|
|
33
|
+
runtimeValidationTasks: params.bundle.runtime_validation_tasks,
|
|
34
|
+
runtimeValidationReport: params.runtimeValidationReport ?? params.bundle.runtime_validation_report,
|
|
35
|
+
});
|
|
36
|
+
if (selectiveDeepeningTasks.length === 0) {
|
|
37
|
+
return { bundle: params.bundle, taskCount: 0, artifacts: [] };
|
|
38
|
+
}
|
|
39
|
+
const auditTasks = [...params.bundle.audit_tasks, ...selectiveDeepeningTasks];
|
|
40
|
+
return {
|
|
41
|
+
bundle: {
|
|
42
|
+
...params.bundle,
|
|
43
|
+
audit_tasks: auditTasks,
|
|
44
|
+
audit_plan_metrics: buildAuditPlanMetrics(auditTasks, {
|
|
45
|
+
graphBundle: params.bundle.graph_bundle,
|
|
46
|
+
lineIndex,
|
|
47
|
+
}),
|
|
48
|
+
review_packets: buildReviewPackets(auditTasks, {
|
|
49
|
+
graphBundle: params.bundle.graph_bundle,
|
|
50
|
+
lineIndex,
|
|
51
|
+
}),
|
|
52
|
+
},
|
|
53
|
+
taskCount: selectiveDeepeningTasks.length,
|
|
54
|
+
artifacts: ["audit_tasks.json", "audit_plan_metrics.json", "review_packets.json"],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
19
57
|
async function runCommand(command, cwd) {
|
|
20
58
|
return await new Promise((resolve) => {
|
|
21
59
|
const child = spawn(command[0], command.slice(1), {
|
|
@@ -69,7 +107,7 @@ export async function runIntakeExecutor(bundle, root) {
|
|
|
69
107
|
progress_summary: `Created intake artifacts for ${repoManifest.files.length} files.`,
|
|
70
108
|
};
|
|
71
109
|
}
|
|
72
|
-
export function runStructureExecutor(bundle) {
|
|
110
|
+
export async function runStructureExecutor(bundle, root) {
|
|
73
111
|
if (!bundle.repo_manifest) {
|
|
74
112
|
throw new Error("Cannot run structure executor without repo_manifest");
|
|
75
113
|
}
|
|
@@ -77,7 +115,9 @@ export function runStructureExecutor(bundle) {
|
|
|
77
115
|
const disposition = bundle.file_disposition ?? buildFileDisposition(bundle.repo_manifest);
|
|
78
116
|
const unitManifest = buildUnitManifest(bundle.repo_manifest, disposition);
|
|
79
117
|
const surfaceManifest = buildSurfaceManifest(bundle.repo_manifest, disposition);
|
|
80
|
-
const graphBundle =
|
|
118
|
+
const graphBundle = root
|
|
119
|
+
? await buildGraphBundleFromFs(bundle.repo_manifest, root, disposition)
|
|
120
|
+
: buildGraphBundle(bundle.repo_manifest, disposition);
|
|
81
121
|
const criticalFlows = buildCriticalFlowManifest(bundle.repo_manifest, surfaceManifest, disposition);
|
|
82
122
|
const riskRegister = buildRiskRegister(unitManifest, criticalFlows, externalAnalyzerResults);
|
|
83
123
|
return {
|
|
@@ -137,6 +177,14 @@ export async function runPlanningExecutor(bundle, root, lineIndex = {}) {
|
|
|
137
177
|
...task,
|
|
138
178
|
status: task.status ?? "pending",
|
|
139
179
|
}));
|
|
180
|
+
const reviewPackets = buildReviewPackets(taggedAuditTasks, {
|
|
181
|
+
graphBundle: bundle.graph_bundle,
|
|
182
|
+
lineIndex,
|
|
183
|
+
});
|
|
184
|
+
const auditPlanMetrics = buildAuditPlanMetrics(taggedAuditTasks, {
|
|
185
|
+
graphBundle: bundle.graph_bundle,
|
|
186
|
+
lineIndex,
|
|
187
|
+
});
|
|
140
188
|
const requeuePayload = buildRequeuePayload(coverage, bundle.critical_flows, flowCoverage, externalAnalyzerResults);
|
|
141
189
|
return {
|
|
142
190
|
updated: {
|
|
@@ -146,6 +194,8 @@ export async function runPlanningExecutor(bundle, root, lineIndex = {}) {
|
|
|
146
194
|
runtime_validation_tasks: runtimeValidationTasks,
|
|
147
195
|
runtime_validation_report: runtimeValidationReport,
|
|
148
196
|
audit_tasks: taggedAuditTasks,
|
|
197
|
+
audit_plan_metrics: auditPlanMetrics,
|
|
198
|
+
review_packets: reviewPackets,
|
|
149
199
|
requeue_tasks: requeuePayload.tasks,
|
|
150
200
|
audit_report: undefined,
|
|
151
201
|
},
|
|
@@ -155,9 +205,11 @@ export async function runPlanningExecutor(bundle, root, lineIndex = {}) {
|
|
|
155
205
|
"runtime_validation_tasks.json",
|
|
156
206
|
...(runtimeValidationReport ? ["runtime_validation_report.json"] : []),
|
|
157
207
|
"audit_tasks.json",
|
|
208
|
+
"audit_plan_metrics.json",
|
|
209
|
+
"review_packets.json",
|
|
158
210
|
"requeue_tasks.json",
|
|
159
211
|
],
|
|
160
|
-
progress_summary: `Built planning artifacts; generated ${taggedAuditTasks.length} review
|
|
212
|
+
progress_summary: `Built planning artifacts; generated ${taggedAuditTasks.length} review tasks in ${reviewPackets.length} packet(s) and ${requeuePayload.task_count} requeue tasks.` +
|
|
161
213
|
(skippedTrivialPaths.length > 0
|
|
162
214
|
? ` Skipped ${skippedTrivialPaths.length} trivial path${skippedTrivialPaths.length === 1 ? "" : "s"} from semantic review.`
|
|
163
215
|
: "") +
|
|
@@ -188,19 +240,25 @@ export function runResultIngestionExecutor(bundle, results) {
|
|
|
188
240
|
: bundle.runtime_validation_report;
|
|
189
241
|
const requeuePayload = buildRequeuePayload(updatedCoverageMatrix, bundle.critical_flows, flowCoverage, bundle.external_analyzer_results);
|
|
190
242
|
const mergedResults = [...(bundle.audit_results ?? []), ...results];
|
|
191
|
-
const
|
|
243
|
+
const completedAuditTasks = updateAuditTaskStatuses(bundle.audit_tasks, mergedResults);
|
|
244
|
+
const baseUpdatedBundle = {
|
|
245
|
+
...bundle,
|
|
246
|
+
coverage_matrix: updatedCoverageMatrix,
|
|
247
|
+
flow_coverage: flowCoverage,
|
|
248
|
+
runtime_validation_tasks: runtimeValidationTasks,
|
|
249
|
+
runtime_validation_report: runtimeValidationReport,
|
|
250
|
+
audit_results: mergedResults,
|
|
251
|
+
audit_tasks: completedAuditTasks,
|
|
252
|
+
requeue_tasks: requeuePayload.tasks,
|
|
253
|
+
audit_report: undefined,
|
|
254
|
+
};
|
|
255
|
+
const selectiveDeepening = appendSelectiveDeepeningTasks({
|
|
256
|
+
bundle: baseUpdatedBundle,
|
|
257
|
+
results: mergedResults,
|
|
258
|
+
runtimeValidationReport,
|
|
259
|
+
});
|
|
192
260
|
return {
|
|
193
|
-
updated:
|
|
194
|
-
...bundle,
|
|
195
|
-
coverage_matrix: updatedCoverageMatrix,
|
|
196
|
-
flow_coverage: flowCoverage,
|
|
197
|
-
runtime_validation_tasks: runtimeValidationTasks,
|
|
198
|
-
runtime_validation_report: runtimeValidationReport,
|
|
199
|
-
audit_results: mergedResults,
|
|
200
|
-
audit_tasks: updatedAuditTasks,
|
|
201
|
-
requeue_tasks: requeuePayload.tasks,
|
|
202
|
-
audit_report: undefined,
|
|
203
|
-
},
|
|
261
|
+
updated: selectiveDeepening.bundle,
|
|
204
262
|
artifacts_written: [
|
|
205
263
|
"coverage_matrix.json",
|
|
206
264
|
"flow_coverage.json",
|
|
@@ -208,9 +266,13 @@ export function runResultIngestionExecutor(bundle, results) {
|
|
|
208
266
|
...(runtimeValidationReport ? ["runtime_validation_report.json"] : []),
|
|
209
267
|
"audit_results.jsonl",
|
|
210
268
|
"audit_tasks.json",
|
|
269
|
+
...selectiveDeepening.artifacts.filter((artifact) => artifact !== "audit_tasks.json"),
|
|
211
270
|
"requeue_tasks.json",
|
|
212
271
|
],
|
|
213
|
-
progress_summary: `Ingested ${results.length} audit result entries and refreshed dependent artifacts
|
|
272
|
+
progress_summary: `Ingested ${results.length} audit result entries and refreshed dependent artifacts.` +
|
|
273
|
+
(selectiveDeepening.taskCount > 0
|
|
274
|
+
? ` Added ${selectiveDeepening.taskCount} selective deepening task(s).`
|
|
275
|
+
: ""),
|
|
214
276
|
};
|
|
215
277
|
}
|
|
216
278
|
export async function runRuntimeValidationExecutor(bundle, root) {
|
|
@@ -247,16 +309,29 @@ export async function runRuntimeValidationExecutor(bundle, root) {
|
|
|
247
309
|
notes: [`Target paths: ${task.target_paths.join(", ")}`],
|
|
248
310
|
});
|
|
249
311
|
}
|
|
312
|
+
const runtimeValidationReport = {
|
|
313
|
+
results: [...byTaskId.values()].sort((a, b) => a.task_id.localeCompare(b.task_id)),
|
|
314
|
+
};
|
|
315
|
+
const baseUpdatedBundle = {
|
|
316
|
+
...bundle,
|
|
317
|
+
runtime_validation_report: runtimeValidationReport,
|
|
318
|
+
audit_report: undefined,
|
|
319
|
+
};
|
|
320
|
+
const selectiveDeepening = appendSelectiveDeepeningTasks({
|
|
321
|
+
bundle: baseUpdatedBundle,
|
|
322
|
+
results: bundle.audit_results ?? [],
|
|
323
|
+
runtimeValidationReport,
|
|
324
|
+
});
|
|
250
325
|
return {
|
|
251
|
-
updated:
|
|
252
|
-
|
|
253
|
-
runtime_validation_report
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
326
|
+
updated: selectiveDeepening.bundle,
|
|
327
|
+
artifacts_written: [
|
|
328
|
+
"runtime_validation_report.json",
|
|
329
|
+
...selectiveDeepening.artifacts,
|
|
330
|
+
],
|
|
331
|
+
progress_summary: `Executed deterministic runtime validation for ${bundle.runtime_validation_tasks.tasks.length} task(s).` +
|
|
332
|
+
(selectiveDeepening.taskCount > 0
|
|
333
|
+
? ` Added ${selectiveDeepening.taskCount} selective deepening task(s).`
|
|
334
|
+
: ""),
|
|
260
335
|
};
|
|
261
336
|
}
|
|
262
337
|
export function runRuntimeValidationUpdateExecutor(bundle, updates) {
|
|
@@ -265,14 +340,26 @@ export function runRuntimeValidationUpdateExecutor(bundle, updates) {
|
|
|
265
340
|
}
|
|
266
341
|
const existingReport = bundle.runtime_validation_report ?? { results: [] };
|
|
267
342
|
const mergedReport = updateRuntimeValidationReport(bundle.runtime_validation_tasks, existingReport, updates);
|
|
343
|
+
const baseUpdatedBundle = {
|
|
344
|
+
...bundle,
|
|
345
|
+
runtime_validation_report: mergedReport,
|
|
346
|
+
audit_report: undefined,
|
|
347
|
+
};
|
|
348
|
+
const selectiveDeepening = appendSelectiveDeepeningTasks({
|
|
349
|
+
bundle: baseUpdatedBundle,
|
|
350
|
+
results: bundle.audit_results ?? [],
|
|
351
|
+
runtimeValidationReport: mergedReport,
|
|
352
|
+
});
|
|
268
353
|
return {
|
|
269
|
-
updated:
|
|
270
|
-
|
|
271
|
-
runtime_validation_report
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
354
|
+
updated: selectiveDeepening.bundle,
|
|
355
|
+
artifacts_written: [
|
|
356
|
+
"runtime_validation_report.json",
|
|
357
|
+
...selectiveDeepening.artifacts,
|
|
358
|
+
],
|
|
359
|
+
progress_summary: `Merged ${updates.results.length} runtime validation updates.` +
|
|
360
|
+
(selectiveDeepening.taskCount > 0
|
|
361
|
+
? ` Added ${selectiveDeepening.taskCount} selective deepening task(s).`
|
|
362
|
+
: ""),
|
|
276
363
|
};
|
|
277
364
|
}
|
|
278
365
|
export function runSynthesisExecutor(bundle, results) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AuditTask } from "../types.js";
|
|
2
|
+
import type { AuditPlanMetrics, ReviewPacket } from "../types/reviewPlanning.js";
|
|
3
|
+
import type { GraphBundle } from "../types/graph.js";
|
|
4
|
+
export interface BuildReviewPacketOptions {
|
|
5
|
+
graphBundle?: GraphBundle;
|
|
6
|
+
lineIndex?: Record<string, number>;
|
|
7
|
+
maxTasksPerPacket?: number;
|
|
8
|
+
targetPacketLines?: number;
|
|
9
|
+
}
|
|
10
|
+
export declare function buildReviewPackets(tasks: AuditTask[], options?: BuildReviewPacketOptions): ReviewPacket[];
|
|
11
|
+
export declare function orderTasksForPacketReview(tasks: AuditTask[], options?: BuildReviewPacketOptions): AuditTask[];
|
|
12
|
+
export declare function buildAuditPlanMetrics(tasks: AuditTask[], options?: BuildReviewPacketOptions & {
|
|
13
|
+
generatedAt?: Date;
|
|
14
|
+
}): AuditPlanMetrics;
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { LENS_ORDER } from "./unitBuilder.js";
|
|
3
|
+
const DEFAULT_MAX_TASKS_PER_PACKET = 6;
|
|
4
|
+
const DEFAULT_TARGET_PACKET_LINES = 2500;
|
|
5
|
+
const ESTIMATED_TOKENS_PER_LINE = 4;
|
|
6
|
+
const ESTIMATED_PACKET_PROMPT_TOKENS = 900;
|
|
7
|
+
function priorityRank(priority) {
|
|
8
|
+
switch (priority) {
|
|
9
|
+
case "high":
|
|
10
|
+
return 3;
|
|
11
|
+
case "medium":
|
|
12
|
+
return 2;
|
|
13
|
+
case "low":
|
|
14
|
+
default:
|
|
15
|
+
return 1;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function normalizePriority(priority) {
|
|
19
|
+
return priority ?? "low";
|
|
20
|
+
}
|
|
21
|
+
function sortLenses(lenses) {
|
|
22
|
+
const set = new Set(lenses);
|
|
23
|
+
return LENS_ORDER.filter((lens) => set.has(lens));
|
|
24
|
+
}
|
|
25
|
+
function lineCountForPath(task, path, lineIndex) {
|
|
26
|
+
return task.file_line_counts?.[path] ?? lineIndex?.[path] ?? 0;
|
|
27
|
+
}
|
|
28
|
+
function taskLineCount(task, lineIndex) {
|
|
29
|
+
return task.file_paths.reduce((sum, path) => sum + lineCountForPath(task, path, lineIndex), 0);
|
|
30
|
+
}
|
|
31
|
+
function taskFileSignature(task) {
|
|
32
|
+
return [...new Set(task.file_paths)].sort((a, b) => a.localeCompare(b)).join("\0");
|
|
33
|
+
}
|
|
34
|
+
function packetGroupingKey(task) {
|
|
35
|
+
const criticalFlowTag = task.tags?.find((tag) => tag.startsWith("critical_flow:"));
|
|
36
|
+
const scope = criticalFlowTag ?? task.unit_id;
|
|
37
|
+
return `${scope}\0${taskFileSignature(task)}`;
|
|
38
|
+
}
|
|
39
|
+
function normalizeGraphPath(path) {
|
|
40
|
+
return path.replace(/\\/g, "/").replace(/^\.\//, "").toLowerCase();
|
|
41
|
+
}
|
|
42
|
+
function isRecord(value) {
|
|
43
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
44
|
+
}
|
|
45
|
+
function collectGraphEdges(graphBundle) {
|
|
46
|
+
if (!graphBundle?.graphs) {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
const edges = [];
|
|
50
|
+
for (const key of ["imports", "calls", "references"]) {
|
|
51
|
+
const raw = graphBundle.graphs[key];
|
|
52
|
+
if (!Array.isArray(raw)) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
for (const item of raw) {
|
|
56
|
+
if (!isRecord(item)) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (typeof item.from !== "string" || typeof item.to !== "string") {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
edges.push({
|
|
63
|
+
from: item.from,
|
|
64
|
+
to: item.to,
|
|
65
|
+
kind: typeof item.kind === "string" ? item.kind : undefined,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return edges;
|
|
70
|
+
}
|
|
71
|
+
function isPacketExpansionEdge(edge) {
|
|
72
|
+
return edge.kind !== "heuristic-container-edge";
|
|
73
|
+
}
|
|
74
|
+
function sanitizeSegment(value) {
|
|
75
|
+
const sanitized = value
|
|
76
|
+
.replace(/[^a-zA-Z0-9_-]+/g, "-")
|
|
77
|
+
.replace(/^-+|-+$/g, "");
|
|
78
|
+
return sanitized.length > 0 ? sanitized : "packet";
|
|
79
|
+
}
|
|
80
|
+
function packetIdFor(tasks, packetIndex) {
|
|
81
|
+
const unit = sanitizeSegment(tasks[0]?.unit_id ?? "review");
|
|
82
|
+
const lenses = sortLenses(tasks.map((task) => task.lens)).join("-");
|
|
83
|
+
const hash = createHash("sha1")
|
|
84
|
+
.update(tasks.map((task) => task.task_id).join("\0"))
|
|
85
|
+
.digest("hex")
|
|
86
|
+
.slice(0, 10);
|
|
87
|
+
return `${unit}:${lenses}:packet-${packetIndex + 1}-${hash}`;
|
|
88
|
+
}
|
|
89
|
+
function compareTasksForPacket(a, b) {
|
|
90
|
+
const lensDelta = LENS_ORDER.indexOf(a.lens) - LENS_ORDER.indexOf(b.lens);
|
|
91
|
+
if (lensDelta !== 0)
|
|
92
|
+
return lensDelta;
|
|
93
|
+
return a.task_id.localeCompare(b.task_id);
|
|
94
|
+
}
|
|
95
|
+
function comparePackets(a, b) {
|
|
96
|
+
const priorityDelta = priorityRank(b.priority) - priorityRank(a.priority);
|
|
97
|
+
if (priorityDelta !== 0)
|
|
98
|
+
return priorityDelta;
|
|
99
|
+
const sizeDelta = b.task_ids.length - a.task_ids.length;
|
|
100
|
+
if (sizeDelta !== 0)
|
|
101
|
+
return sizeDelta;
|
|
102
|
+
return a.packet_id.localeCompare(b.packet_id);
|
|
103
|
+
}
|
|
104
|
+
function chunkPacketTasks(tasks, options) {
|
|
105
|
+
const chunks = [];
|
|
106
|
+
let current = [];
|
|
107
|
+
for (const task of tasks.sort(compareTasksForPacket)) {
|
|
108
|
+
const candidate = [...current, task];
|
|
109
|
+
const uniquePaths = new Set(candidate.flatMap((item) => item.file_paths));
|
|
110
|
+
const candidateLines = [...uniquePaths].reduce((sum, path) => {
|
|
111
|
+
const owner = candidate.find((item) => item.file_paths.includes(path));
|
|
112
|
+
return sum + (owner ? lineCountForPath(owner, path, options.lineIndex) : 0);
|
|
113
|
+
}, 0);
|
|
114
|
+
const wouldExceedTaskCount = current.length > 0 && candidate.length > options.maxTasksPerPacket;
|
|
115
|
+
const wouldExceedLines = current.length > 0 && candidateLines > options.targetPacketLines;
|
|
116
|
+
if (wouldExceedTaskCount || wouldExceedLines) {
|
|
117
|
+
chunks.push(current);
|
|
118
|
+
current = [];
|
|
119
|
+
}
|
|
120
|
+
current.push(task);
|
|
121
|
+
}
|
|
122
|
+
if (current.length > 0) {
|
|
123
|
+
chunks.push(current);
|
|
124
|
+
}
|
|
125
|
+
return chunks;
|
|
126
|
+
}
|
|
127
|
+
function mergeGraphConnectedGroups(groups, graphBundle) {
|
|
128
|
+
const groupKeys = [...groups.keys()];
|
|
129
|
+
const parent = new Map(groupKeys.map((key) => [key, key]));
|
|
130
|
+
const fileToGroupKeys = new Map();
|
|
131
|
+
function find(key) {
|
|
132
|
+
const current = parent.get(key) ?? key;
|
|
133
|
+
if (current === key)
|
|
134
|
+
return key;
|
|
135
|
+
const root = find(current);
|
|
136
|
+
parent.set(key, root);
|
|
137
|
+
return root;
|
|
138
|
+
}
|
|
139
|
+
function union(a, b) {
|
|
140
|
+
const rootA = find(a);
|
|
141
|
+
const rootB = find(b);
|
|
142
|
+
if (rootA === rootB)
|
|
143
|
+
return;
|
|
144
|
+
const [keep, move] = rootA.localeCompare(rootB) <= 0 ? [rootA, rootB] : [rootB, rootA];
|
|
145
|
+
parent.set(move, keep);
|
|
146
|
+
}
|
|
147
|
+
for (const [key, tasks] of groups) {
|
|
148
|
+
for (const path of new Set(tasks.flatMap((task) => task.file_paths))) {
|
|
149
|
+
const normalized = normalizeGraphPath(path);
|
|
150
|
+
const existing = fileToGroupKeys.get(normalized) ?? new Set();
|
|
151
|
+
existing.add(key);
|
|
152
|
+
fileToGroupKeys.set(normalized, existing);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
for (const keys of fileToGroupKeys.values()) {
|
|
156
|
+
const [first, ...rest] = [...keys].sort((a, b) => a.localeCompare(b));
|
|
157
|
+
if (!first)
|
|
158
|
+
continue;
|
|
159
|
+
for (const key of rest) {
|
|
160
|
+
union(first, key);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
for (const edge of collectGraphEdges(graphBundle)) {
|
|
164
|
+
if (!isPacketExpansionEdge(edge)) {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const fromGroups = fileToGroupKeys.get(normalizeGraphPath(edge.from));
|
|
168
|
+
const toGroups = fileToGroupKeys.get(normalizeGraphPath(edge.to));
|
|
169
|
+
if (!fromGroups || !toGroups) {
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
for (const fromKey of fromGroups) {
|
|
173
|
+
for (const toKey of toGroups) {
|
|
174
|
+
union(fromKey, toKey);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const merged = new Map();
|
|
179
|
+
for (const key of groupKeys) {
|
|
180
|
+
const root = find(key);
|
|
181
|
+
const current = merged.get(root) ?? [];
|
|
182
|
+
current.push(...(groups.get(key) ?? []));
|
|
183
|
+
merged.set(root, current);
|
|
184
|
+
}
|
|
185
|
+
return [...merged.values()];
|
|
186
|
+
}
|
|
187
|
+
function buildPacket(tasks, packetIndex, lineIndex) {
|
|
188
|
+
const filePaths = [...new Set(tasks.flatMap((task) => task.file_paths))].sort((a, b) => a.localeCompare(b));
|
|
189
|
+
const fileLineCounts = Object.fromEntries(filePaths.map((path) => {
|
|
190
|
+
const owner = tasks.find((task) => task.file_paths.includes(path));
|
|
191
|
+
return [path, owner ? lineCountForPath(owner, path, lineIndex) : 0];
|
|
192
|
+
}));
|
|
193
|
+
const totalLines = Object.values(fileLineCounts).reduce((sum, value) => sum + value, 0);
|
|
194
|
+
const priority = tasks.reduce((highest, task) => priorityRank(task.priority) > priorityRank(highest)
|
|
195
|
+
? normalizePriority(task.priority)
|
|
196
|
+
: highest, "low");
|
|
197
|
+
const lenses = sortLenses(tasks.map((task) => task.lens));
|
|
198
|
+
const tags = [
|
|
199
|
+
...new Set(tasks.flatMap((task) => task.tags ?? [])),
|
|
200
|
+
].sort((a, b) => a.localeCompare(b));
|
|
201
|
+
return {
|
|
202
|
+
packet_id: packetIdFor(tasks, packetIndex),
|
|
203
|
+
task_ids: tasks.map((task) => task.task_id),
|
|
204
|
+
unit_ids: [...new Set(tasks.map((task) => task.unit_id))].sort((a, b) => a.localeCompare(b)),
|
|
205
|
+
pass_ids: [...new Set(tasks.map((task) => task.pass_id))].sort((a, b) => a.localeCompare(b)),
|
|
206
|
+
lenses,
|
|
207
|
+
file_paths: filePaths,
|
|
208
|
+
file_line_counts: fileLineCounts,
|
|
209
|
+
total_lines: totalLines,
|
|
210
|
+
priority,
|
|
211
|
+
tags: tags.length > 0 ? tags : undefined,
|
|
212
|
+
rationale: tasks.length === 1
|
|
213
|
+
? tasks[0].rationale
|
|
214
|
+
: `Review ${filePaths.length} related file(s) across ${lenses.length} lens(es): ${lenses.join(", ")}.`,
|
|
215
|
+
estimated_tokens: ESTIMATED_PACKET_PROMPT_TOKENS + totalLines * ESTIMATED_TOKENS_PER_LINE,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
export function buildReviewPackets(tasks, options = {}) {
|
|
219
|
+
const maxTasksPerPacket = options.maxTasksPerPacket ?? DEFAULT_MAX_TASKS_PER_PACKET;
|
|
220
|
+
const targetPacketLines = options.targetPacketLines ?? DEFAULT_TARGET_PACKET_LINES;
|
|
221
|
+
const groups = new Map();
|
|
222
|
+
for (const task of tasks) {
|
|
223
|
+
const key = packetGroupingKey(task);
|
|
224
|
+
const group = groups.get(key) ?? [];
|
|
225
|
+
group.push(task);
|
|
226
|
+
groups.set(key, group);
|
|
227
|
+
}
|
|
228
|
+
const packets = [];
|
|
229
|
+
let packetIndex = 0;
|
|
230
|
+
const groupedTasks = mergeGraphConnectedGroups(groups, options.graphBundle).sort((a, b) => {
|
|
231
|
+
const aPriority = Math.max(...a.map((task) => priorityRank(task.priority)));
|
|
232
|
+
const bPriority = Math.max(...b.map((task) => priorityRank(task.priority)));
|
|
233
|
+
if (aPriority !== bPriority)
|
|
234
|
+
return bPriority - aPriority;
|
|
235
|
+
return (a[0]?.task_id ?? "").localeCompare(b[0]?.task_id ?? "");
|
|
236
|
+
});
|
|
237
|
+
for (const group of groupedTasks) {
|
|
238
|
+
for (const chunk of chunkPacketTasks(group, {
|
|
239
|
+
lineIndex: options.lineIndex,
|
|
240
|
+
maxTasksPerPacket,
|
|
241
|
+
targetPacketLines,
|
|
242
|
+
})) {
|
|
243
|
+
packets.push(buildPacket(chunk, packetIndex, options.lineIndex));
|
|
244
|
+
packetIndex += 1;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return packets.sort(comparePackets);
|
|
248
|
+
}
|
|
249
|
+
export function orderTasksForPacketReview(tasks, options = {}) {
|
|
250
|
+
const taskById = new Map(tasks.map((task) => [task.task_id, task]));
|
|
251
|
+
return buildReviewPackets(tasks, options).flatMap((packet) => packet.task_ids
|
|
252
|
+
.map((taskId) => taskById.get(taskId))
|
|
253
|
+
.filter((task) => task !== undefined));
|
|
254
|
+
}
|
|
255
|
+
export function buildAuditPlanMetrics(tasks, options = {}) {
|
|
256
|
+
const packets = buildReviewPackets(tasks, options);
|
|
257
|
+
const taskLineCounts = tasks.map((task) => taskLineCount(task, options.lineIndex));
|
|
258
|
+
const totalTaskLines = taskLineCounts.reduce((sum, value) => sum + value, 0);
|
|
259
|
+
const totalPacketLines = packets.reduce((sum, packet) => sum + packet.total_lines, 0);
|
|
260
|
+
const largestTaskIndex = taskLineCounts.reduce((largest, value, index) => (value > taskLineCounts[largest] ? index : largest), 0);
|
|
261
|
+
const largestPacket = packets.reduce((largest, packet) => !largest || packet.total_lines > largest.total_lines ? packet : largest, undefined);
|
|
262
|
+
const taskFileReferences = tasks.reduce((sum, task) => sum + task.file_paths.length, 0);
|
|
263
|
+
const uniqueFiles = new Set(tasks.flatMap((task) => task.file_paths));
|
|
264
|
+
const lensTaskCounts = {};
|
|
265
|
+
const priorityTaskCounts = {
|
|
266
|
+
high: 0,
|
|
267
|
+
medium: 0,
|
|
268
|
+
low: 0,
|
|
269
|
+
};
|
|
270
|
+
for (const task of tasks) {
|
|
271
|
+
lensTaskCounts[task.lens] = (lensTaskCounts[task.lens] ?? 0) + 1;
|
|
272
|
+
priorityTaskCounts[normalizePriority(task.priority)] += 1;
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
generated_at: (options.generatedAt ?? new Date()).toISOString(),
|
|
276
|
+
task_count: tasks.length,
|
|
277
|
+
packet_count: packets.length,
|
|
278
|
+
estimated_agent_reduction: Math.max(0, tasks.length - packets.length),
|
|
279
|
+
estimated_agent_reduction_ratio: tasks.length === 0 ? 0 : Math.max(0, tasks.length - packets.length) / tasks.length,
|
|
280
|
+
unique_file_count: uniqueFiles.size,
|
|
281
|
+
task_file_reference_count: taskFileReferences,
|
|
282
|
+
repeated_file_reference_count: Math.max(0, taskFileReferences - uniqueFiles.size),
|
|
283
|
+
total_task_lines: totalTaskLines,
|
|
284
|
+
total_packet_lines: totalPacketLines,
|
|
285
|
+
repeated_line_reference_count: Math.max(0, totalTaskLines - totalPacketLines),
|
|
286
|
+
min_task_lines: taskLineCounts.length > 0 ? Math.min(...taskLineCounts) : 0,
|
|
287
|
+
max_task_lines: taskLineCounts.length > 0 ? Math.max(...taskLineCounts) : 0,
|
|
288
|
+
average_task_lines: taskLineCounts.length > 0 ? totalTaskLines / taskLineCounts.length : 0,
|
|
289
|
+
largest_task_id: tasks[largestTaskIndex]?.task_id,
|
|
290
|
+
largest_packet_id: largestPacket?.packet_id,
|
|
291
|
+
lens_task_counts: lensTaskCounts,
|
|
292
|
+
priority_task_counts: priorityTaskCounts,
|
|
293
|
+
packet_size: {
|
|
294
|
+
single_task_packets: packets.filter((packet) => packet.task_ids.length === 1).length,
|
|
295
|
+
multi_task_packets: packets.filter((packet) => packet.task_ids.length > 1).length,
|
|
296
|
+
max_tasks_per_packet: packets.length > 0 ? Math.max(...packets.map((packet) => packet.task_ids.length)) : 0,
|
|
297
|
+
max_files_per_packet: packets.length > 0 ? Math.max(...packets.map((packet) => packet.file_paths.length)) : 0,
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AuditResult, AuditTask } from "../types.js";
|
|
2
|
+
import type { RuntimeValidationReport, RuntimeValidationTaskManifest } from "../types/runtimeValidation.js";
|
|
3
|
+
export interface BuildSelectiveDeepeningTaskOptions {
|
|
4
|
+
existingTasks?: AuditTask[];
|
|
5
|
+
results: AuditResult[];
|
|
6
|
+
lineIndex?: Record<string, number>;
|
|
7
|
+
runtimeValidationTasks?: RuntimeValidationTaskManifest;
|
|
8
|
+
runtimeValidationReport?: RuntimeValidationReport;
|
|
9
|
+
maxTasks?: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function buildSelectiveDeepeningTasks(options: BuildSelectiveDeepeningTaskOptions): AuditTask[];
|
|
12
|
+
export declare const selectiveDeepeningTestUtils: {
|
|
13
|
+
DEEPENING_TAG: string;
|
|
14
|
+
};
|