auditor-lambda 0.3.39 → 0.3.40

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/cli.js CHANGED
@@ -2142,6 +2142,7 @@ async function cmdWorkerRun(argv) {
2142
2142
  }
2143
2143
  }
2144
2144
  const DISPATCH_RESULT_MAP_FILENAME = "dispatch-result-map.json";
2145
+ const ACTIVE_DISPATCH_FILENAME = "active-dispatch.json";
2145
2146
  function dispatchResultMapPath(runDir) {
2146
2147
  return join(runDir, DISPATCH_RESULT_MAP_FILENAME);
2147
2148
  }
@@ -2437,6 +2438,10 @@ async function prepareDispatchArtifacts(params) {
2437
2438
  .map(([key, value]) => `input.${key}: ${value}`)
2438
2439
  : [];
2439
2440
  const isLensVerification = task.tags?.includes("lens_verification") ?? false;
2441
+ const coverageTemplate = task.file_paths.map((path) => ({
2442
+ path,
2443
+ total_lines: task.file_line_counts?.[path] ?? lineIndex[path] ?? 0,
2444
+ }));
2440
2445
  return [
2441
2446
  `### ${task.task_id}`,
2442
2447
  `unit_id: ${task.unit_id}`,
@@ -2457,6 +2462,11 @@ async function prepareDispatchArtifacts(params) {
2457
2462
  ]
2458
2463
  : []),
2459
2464
  "",
2465
+ "file_coverage (copy exactly into your AuditResult for this task):",
2466
+ "```json",
2467
+ JSON.stringify(coverageTemplate),
2468
+ "```",
2469
+ "",
2460
2470
  ];
2461
2471
  });
2462
2472
  const submitCommand = `"${process.execPath}" "${join(packageRoot, "audit-code.mjs")}" submit-packet ` +
@@ -2501,7 +2511,7 @@ async function prepareDispatchArtifacts(params) {
2501
2511
  " unit_id copy from the task metadata",
2502
2512
  " pass_id copy from the task metadata",
2503
2513
  " lens copy from the task metadata",
2504
- " file_coverage [{path, total_lines}] - one entry per assigned file; use the line counts listed above",
2514
+ " file_coverage [{path, total_lines}] - copy the exact template from each task section above",
2505
2515
  " findings [] or array of finding objects",
2506
2516
  "",
2507
2517
  "Lens verification tasks:",
@@ -2614,6 +2624,14 @@ async function prepareDispatchArtifacts(params) {
2614
2624
  if (warningsPath) {
2615
2625
  await writeJsonFile(warningsPath, warnings);
2616
2626
  }
2627
+ const activeDispatch = {
2628
+ run_id: runId,
2629
+ created_at: new Date().toISOString(),
2630
+ packet_count: plan.length,
2631
+ task_count: orderedTasks.length,
2632
+ status: "active",
2633
+ };
2634
+ await writeJsonFile(join(artifactsDir, ACTIVE_DISPATCH_FILENAME), activeDispatch);
2617
2635
  return {
2618
2636
  run_id: runId,
2619
2637
  dispatch_plan_path: dispatchPlanPath,
@@ -2661,12 +2679,23 @@ async function cmdSubmitPacket(argv) {
2661
2679
  if (!resultMap) {
2662
2680
  throw new Error(`No ${DISPATCH_RESULT_MAP_FILENAME} found for run ${runId}; run prepare-dispatch first.`);
2663
2681
  }
2664
- const packetEntries = resultMap.entries.filter((entry) => entry.packet_id === packetId);
2682
+ let packetEntries = resultMap.entries.filter((entry) => entry.packet_id === packetId);
2683
+ let resolvedPacketId = packetId;
2665
2684
  if (packetEntries.length === 0) {
2666
- throw new Error(`Unknown packet_id '${packetId}' for run ${runId}.`);
2685
+ const trimmed = packetId.trim();
2686
+ packetEntries = resultMap.entries.filter((entry) => entry.packet_id.trim().toLowerCase() === trimmed.toLowerCase());
2687
+ if (packetEntries.length > 0) {
2688
+ resolvedPacketId = packetEntries[0].packet_id;
2689
+ process.stderr.write(`[submit-packet] Resolved packet_id '${packetId}' → '${resolvedPacketId}' (case/whitespace normalization)\n`);
2690
+ }
2691
+ }
2692
+ if (packetEntries.length === 0) {
2693
+ const knownIds = [...new Set(resultMap.entries.map((e) => e.packet_id))];
2694
+ throw new Error(`Unknown packet_id '${packetId}' for run ${runId}.\n` +
2695
+ `Valid packet IDs: ${knownIds.join(", ")}`);
2667
2696
  }
2668
2697
  if (entriesByTaskId(packetEntries).size !== packetEntries.length) {
2669
- throw new Error(`Dispatch result map has duplicate task entries for packet '${packetId}'.`);
2698
+ throw new Error(`Dispatch result map has duplicate task entries for packet '${resolvedPacketId}'.`);
2670
2699
  }
2671
2700
  const allTasks = await readJsonFile(tasksPath);
2672
2701
  const taskById = new Map(allTasks.map((task) => [task.task_id, task]));
@@ -2717,7 +2746,7 @@ async function cmdSubmitPacket(argv) {
2717
2746
  }
2718
2747
  seen.add(taskId);
2719
2748
  if (!expectedTaskIds.has(taskId)) {
2720
- resultErrors.push(`Result at index ${index} uses task_id '${taskId}', which is not assigned to packet '${packetId}'.`);
2749
+ resultErrors.push(`Result at index ${index} uses task_id '${taskId}', which is not assigned to packet '${resolvedPacketId}'.`);
2721
2750
  }
2722
2751
  }
2723
2752
  for (const task of tasks) {
@@ -2727,7 +2756,44 @@ async function cmdSubmitPacket(argv) {
2727
2756
  }
2728
2757
  }
2729
2758
  if (resultErrors.length > 0) {
2730
- throw new Error(`submit-packet rejected ${packetId}:\n${resultErrors.join("\n")}`);
2759
+ throw new Error(`submit-packet rejected ${resolvedPacketId}:\n${resultErrors.join("\n")}`);
2760
+ }
2761
+ // Check for duplicate findings against already-submitted results in this run
2762
+ const existingFindingKeys = new Set();
2763
+ const otherEntries = resultMap.entries.filter((e) => e.packet_id !== resolvedPacketId);
2764
+ for (const other of otherEntries) {
2765
+ try {
2766
+ const existing = JSON.parse(await readFile(other.result_path, "utf8"));
2767
+ if (existing?.findings) {
2768
+ for (const f of existing.findings) {
2769
+ const key = [
2770
+ (f.lens ?? "").trim().toLowerCase(),
2771
+ (f.category ?? "").trim().toLowerCase(),
2772
+ (f.title ?? "").trim().toLowerCase(),
2773
+ f.affected_files?.[0]?.path ?? "",
2774
+ ].join("|");
2775
+ existingFindingKeys.add(key);
2776
+ }
2777
+ }
2778
+ }
2779
+ catch { /* file doesn't exist yet or invalid — skip */ }
2780
+ }
2781
+ let dupCount = 0;
2782
+ for (const result of payload) {
2783
+ for (const f of result.findings ?? []) {
2784
+ const key = [
2785
+ (f.lens ?? "").trim().toLowerCase(),
2786
+ (f.category ?? "").trim().toLowerCase(),
2787
+ (f.title ?? "").trim().toLowerCase(),
2788
+ f.affected_files?.[0]?.path ?? "",
2789
+ ].join("|");
2790
+ if (existingFindingKeys.has(key)) {
2791
+ dupCount++;
2792
+ }
2793
+ }
2794
+ }
2795
+ if (dupCount > 0) {
2796
+ process.stderr.write(`[submit-packet] Warning: ${dupCount} finding(s) appear to duplicate findings from other packets in this run.\n`);
2731
2797
  }
2732
2798
  const entryByTaskId = entriesByTaskId(packetEntries);
2733
2799
  for (const result of payload) {
@@ -2740,9 +2806,10 @@ async function cmdSubmitPacket(argv) {
2740
2806
  const findingCount = payload.reduce((sum, result) => sum + result.findings.length, 0);
2741
2807
  console.log(JSON.stringify({
2742
2808
  run_id: runId,
2743
- packet_id: packetId,
2809
+ packet_id: resolvedPacketId,
2744
2810
  accepted_count: payload.length,
2745
2811
  finding_count: findingCount,
2812
+ ...(dupCount > 0 ? { duplicate_warning_count: dupCount } : {}),
2746
2813
  }, null, 2));
2747
2814
  }
2748
2815
  async function cmdMergeAndIngest(argv) {
@@ -2781,11 +2848,24 @@ async function cmdMergeAndIngest(argv) {
2781
2848
  const failing = [];
2782
2849
  const seenTaskIds = new Set();
2783
2850
  let spuriousFileCount = 0;
2851
+ const fallbackByTaskId = new Map();
2784
2852
  for (const filename of files) {
2785
2853
  const filePath = resolve(join(taskResultsDir, filename));
2786
2854
  if (!expectedPaths.has(filePath)) {
2787
2855
  spuriousFileCount++;
2788
- process.stderr.write(`[merge-and-ingest] Warning: ignoring unexpected file in task-results/: ${filename}\n`);
2856
+ try {
2857
+ const raw = await readFile(filePath, "utf8");
2858
+ const parsed = JSON.parse(raw);
2859
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
2860
+ const tid = typeof parsed.task_id === "string"
2861
+ ? String(parsed.task_id) : undefined;
2862
+ if (tid && !fallbackByTaskId.has(tid)) {
2863
+ fallbackByTaskId.set(tid, parsed);
2864
+ }
2865
+ }
2866
+ }
2867
+ catch { /* not parseable — skip */ }
2868
+ process.stderr.write(`[merge-and-ingest] Warning: unexpected file in task-results/: ${filename}\n`);
2789
2869
  }
2790
2870
  }
2791
2871
  for (const task of allTasks) {
@@ -2804,15 +2884,23 @@ async function cmdMergeAndIngest(argv) {
2804
2884
  }
2805
2885
  catch (e) {
2806
2886
  if (isFileMissingError(e)) {
2807
- failing.push({
2808
- task_id: task.task_id,
2809
- errors: ["Missing audit result for assigned task."],
2810
- });
2887
+ const fallback = fallbackByTaskId.get(task.task_id);
2888
+ if (fallback) {
2889
+ process.stderr.write(`[merge-and-ingest] Recovered result for '${task.task_id}' from unexpected file (matched by task_id)\n`);
2890
+ obj = fallback;
2891
+ }
2892
+ else {
2893
+ failing.push({
2894
+ task_id: task.task_id,
2895
+ errors: ["Missing audit result for assigned task."],
2896
+ });
2897
+ continue;
2898
+ }
2811
2899
  }
2812
2900
  else {
2813
2901
  failing.push({ task_id: task.task_id, errors: [`Invalid JSON: ${e.message}`] });
2902
+ continue;
2814
2903
  }
2815
- continue;
2816
2904
  }
2817
2905
  const record = obj && typeof obj === "object" && !Array.isArray(obj)
2818
2906
  ? obj
@@ -2843,45 +2931,87 @@ async function cmdMergeAndIngest(argv) {
2843
2931
  }
2844
2932
  }
2845
2933
  await writeJsonFile(auditResultsPath, passing);
2934
+ const failedTasksPath = join(runDir, "failed-tasks.json");
2846
2935
  if (failing.length > 0) {
2847
- const failedTasksPath = join(runDir, "failed-tasks.json");
2848
2936
  await writeJsonFile(failedTasksPath, failing);
2849
- throw new Error(`${failing.length} assigned task result(s) were missing or invalid; blocked before ingestion. See ${failedTasksPath}`);
2937
+ }
2938
+ if (passing.length === 0 && failing.length > 0) {
2939
+ throw new Error(`All ${failing.length} assigned task result(s) were missing or invalid; blocked before ingestion. See ${failedTasksPath}`);
2850
2940
  }
2851
2941
  const findingCount = passing.reduce((sum, result) => sum + result.findings.length, 0);
2852
- const result = await runAuditStep({
2853
- root: workerTask.repo_root,
2854
- artifactsDir,
2855
- preferredExecutor: "result_ingestion_executor",
2856
- auditResultsPath,
2857
- });
2858
- const updatedPendingTasks = await addFileLineCountHints(workerTask.repo_root, buildPendingAuditTasks(result.updated_bundle));
2859
- await writeJsonFile(tasksPath, updatedPendingTasks);
2942
+ let result = null;
2943
+ if (passing.length > 0) {
2944
+ result = await runAuditStep({
2945
+ root: workerTask.repo_root,
2946
+ artifactsDir,
2947
+ preferredExecutor: "result_ingestion_executor",
2948
+ auditResultsPath,
2949
+ });
2950
+ const updatedPendingTasks = await addFileLineCountHints(workerTask.repo_root, buildPendingAuditTasks(result.updated_bundle));
2951
+ await writeJsonFile(tasksPath, updatedPendingTasks);
2952
+ }
2953
+ const activeDispatchPath = join(artifactsDir, ACTIVE_DISPATCH_FILENAME);
2954
+ try {
2955
+ const dispatch = await readJsonFile(activeDispatchPath);
2956
+ if (dispatch.run_id === runId) {
2957
+ dispatch.status = failing.length > 0 ? "active" : "merged";
2958
+ await writeJsonFile(activeDispatchPath, dispatch);
2959
+ }
2960
+ }
2961
+ catch { /* no active dispatch file — skip */ }
2962
+ let retryDispatchPath = null;
2963
+ if (failing.length > 0) {
2964
+ const failedTaskIds = new Set(failing.map((f) => f.task_id));
2965
+ const failedPacketIds = [
2966
+ ...new Set(resultMap.entries
2967
+ .filter((e) => failedTaskIds.has(e.task_id))
2968
+ .map((e) => e.packet_id)),
2969
+ ];
2970
+ const retryDispatch = {
2971
+ run_id: runId,
2972
+ retry_packet_ids: failedPacketIds,
2973
+ failed_task_count: failing.length,
2974
+ accepted_task_count: passing.length,
2975
+ };
2976
+ retryDispatchPath = join(runDir, "retry-dispatch.json");
2977
+ await writeJsonFile(retryDispatchPath, retryDispatch);
2978
+ process.stderr.write(`[merge-and-ingest] ${passing.length} accepted, ${failing.length} failed. ` +
2979
+ `Retry packets: ${failedPacketIds.join(", ")}\n`);
2980
+ }
2981
+ const status = failing.length > 0
2982
+ ? "partial"
2983
+ : (result?.progress_made ? "completed" : "no_progress");
2860
2984
  const workerResult = buildWorkerResult({
2861
2985
  runId,
2862
2986
  obligationId: workerTask.obligation_id,
2863
- status: result.progress_made ? "completed" : "no_progress",
2864
- progressMade: result.progress_made,
2865
- selectedExecutor: result.selected_executor,
2866
- artifactsWritten: result.artifacts_written,
2867
- summary: result.progress_summary,
2868
- nextLikelyStep: result.next_likely_step,
2987
+ status: failing.length > 0 ? "no_progress" : (result?.progress_made ? "completed" : "no_progress"),
2988
+ progressMade: result?.progress_made ?? false,
2989
+ selectedExecutor: result?.selected_executor ?? null,
2990
+ artifactsWritten: result?.artifacts_written ?? [],
2991
+ summary: result?.progress_summary ?? `${failing.length} task(s) failed`,
2992
+ nextLikelyStep: result?.next_likely_step ?? null,
2869
2993
  errors: [],
2870
2994
  });
2871
2995
  await writeJsonFile(workerTask.result_path, workerResult);
2872
2996
  console.log(JSON.stringify({
2873
2997
  run_id: runId,
2874
- status: workerResult.status,
2998
+ status,
2875
2999
  accepted_count: passing.length,
2876
- rejected_count: 0,
3000
+ rejected_count: failing.length,
2877
3001
  spurious_file_count: spuriousFileCount,
2878
3002
  finding_count: findingCount,
2879
3003
  audit_results_path: auditResultsPath,
2880
- selected_executor: workerResult.selected_executor,
2881
- progress_made: workerResult.progress_made,
2882
- progress_summary: workerResult.summary,
2883
- next_likely_step: workerResult.next_likely_step,
3004
+ ...(retryDispatchPath ? { retry_dispatch_path: retryDispatchPath } : {}),
3005
+ ...(result ? {
3006
+ selected_executor: workerResult.selected_executor,
3007
+ progress_made: workerResult.progress_made,
3008
+ progress_summary: workerResult.summary,
3009
+ next_likely_step: workerResult.next_likely_step,
3010
+ } : {}),
2884
3011
  }, null, 2));
3012
+ if (failing.length > 0) {
3013
+ process.exitCode = 2;
3014
+ }
2885
3015
  }
2886
3016
  async function cmdValidateResult(argv) {
2887
3017
  const rawRunId = getFlag(argv, "--run-id");
@@ -3357,6 +3487,69 @@ async function cmdQuota(argv) {
3357
3487
  quota_state_path: getQuotaStatePath(),
3358
3488
  }, null, 2));
3359
3489
  }
3490
+ async function cmdDispatchStatus(argv) {
3491
+ const artifactsDir = getArtifactsDir(argv);
3492
+ const activeDispatchPath = join(artifactsDir, ACTIVE_DISPATCH_FILENAME);
3493
+ let activeDispatch = null;
3494
+ try {
3495
+ activeDispatch = await readJsonFile(activeDispatchPath);
3496
+ }
3497
+ catch (e) {
3498
+ if (!isFileMissingError(e))
3499
+ throw e;
3500
+ }
3501
+ if (!activeDispatch) {
3502
+ console.log(JSON.stringify({ status: "no_active_dispatch" }, null, 2));
3503
+ return;
3504
+ }
3505
+ const runDir = join(artifactsDir, "runs", activeDispatch.run_id);
3506
+ const resultMap = await loadDispatchResultMap(runDir);
3507
+ if (!resultMap) {
3508
+ console.log(JSON.stringify({
3509
+ status: "missing_result_map",
3510
+ run_id: activeDispatch.run_id,
3511
+ }, null, 2));
3512
+ return;
3513
+ }
3514
+ const packetIds = [...new Set(resultMap.entries.map((e) => e.packet_id))];
3515
+ const packetStatus = [];
3516
+ for (const pid of packetIds) {
3517
+ if (pid === "__prior_dispatch__")
3518
+ continue;
3519
+ const entries = resultMap.entries.filter((e) => e.packet_id === pid);
3520
+ let completed = 0;
3521
+ const missing = [];
3522
+ for (const entry of entries) {
3523
+ try {
3524
+ await readFile(entry.result_path, "utf8");
3525
+ completed++;
3526
+ }
3527
+ catch {
3528
+ missing.push(entry.task_id);
3529
+ }
3530
+ }
3531
+ packetStatus.push({
3532
+ packet_id: pid,
3533
+ task_count: entries.length,
3534
+ completed_count: completed,
3535
+ missing_task_ids: missing,
3536
+ });
3537
+ }
3538
+ const totalTasks = packetStatus.reduce((s, p) => s + p.task_count, 0);
3539
+ const completedTasks = packetStatus.reduce((s, p) => s + p.completed_count, 0);
3540
+ const completedPackets = packetStatus.filter((p) => p.missing_task_ids.length === 0).length;
3541
+ console.log(JSON.stringify({
3542
+ run_id: activeDispatch.run_id,
3543
+ dispatch_status: activeDispatch.status,
3544
+ created_at: activeDispatch.created_at,
3545
+ total_packets: packetStatus.length,
3546
+ completed_packets: completedPackets,
3547
+ total_tasks: totalTasks,
3548
+ completed_tasks: completedTasks,
3549
+ missing_tasks: totalTasks - completedTasks,
3550
+ packets: packetStatus,
3551
+ }, null, 2));
3552
+ }
3360
3553
  async function main(argv) {
3361
3554
  const command = argv[2] ?? "sample-run";
3362
3555
  switch (command) {
@@ -3429,9 +3622,12 @@ async function main(argv) {
3429
3622
  case "status":
3430
3623
  await cmdStatus(argv);
3431
3624
  return;
3625
+ case "dispatch-status":
3626
+ await cmdDispatchStatus(argv);
3627
+ return;
3432
3628
  default:
3433
3629
  console.error(`Unknown command: ${command}`);
3434
- console.error("Available commands: sample-run, advance-audit, next-step, run-to-completion, worker-run, import-external-analyzer, intake, plan, ingest-results, explain-task, update-runtime-validation, validate, validate-results, requeue, synthesize, cleanup, mcp, prepare-dispatch, merge-and-ingest, submit-packet, validate-result, quota, status");
3630
+ console.error("Available commands: sample-run, advance-audit, next-step, run-to-completion, worker-run, import-external-analyzer, intake, plan, ingest-results, explain-task, update-runtime-validation, validate, validate-results, requeue, synthesize, cleanup, mcp, prepare-dispatch, merge-and-ingest, submit-packet, validate-result, quota, status, dispatch-status");
3435
3631
  process.exitCode = 1;
3436
3632
  }
3437
3633
  }
@@ -1,6 +1,7 @@
1
1
  import type { AuditTask } from "../types.js";
2
2
  import { type ValidationIssue } from "./basic.js";
3
3
  export type IssueSeverity = "error" | "warning";
4
+ export declare function normalizeCoveragePath(path: string): string;
4
5
  export interface AuditResultIssue extends ValidationIssue {
5
6
  result_index: number;
6
7
  task_id: string;
@@ -1,4 +1,7 @@
1
1
  import { describeValue, formatValidationIssues, isRecord, } from "./basic.js";
2
+ export function normalizeCoveragePath(path) {
3
+ return path.replace(/\\/g, "/").replace(/^\.\//, "");
4
+ }
2
5
  const REQUIRED_FINDING_FIELDS = [
3
6
  "id",
4
7
  "title",
@@ -423,6 +426,18 @@ export function validateAuditResults(results, tasks, options = {}) {
423
426
  tasks.map((item) => item.task_id).join(", "),
424
427
  });
425
428
  }
429
+ const taskNormMap = new Map();
430
+ if (task) {
431
+ for (const fp of task.file_paths) {
432
+ taskNormMap.set(normalizeCoveragePath(fp), fp);
433
+ }
434
+ }
435
+ const normLineIndex = new Map();
436
+ if (options.lineIndex) {
437
+ for (const [k, v] of Object.entries(options.lineIndex)) {
438
+ normLineIndex.set(normalizeCoveragePath(k), v);
439
+ }
440
+ }
426
441
  const fileCoverage = result.file_coverage;
427
442
  const normalizedFileCoverage = [];
428
443
  const declaredAssignedCoveragePaths = new Set();
@@ -447,6 +462,10 @@ export function validateAuditResults(results, tasks, options = {}) {
447
462
  });
448
463
  continue;
449
464
  }
465
+ const entryNorm = isNonEmptyString(entry.path)
466
+ ? normalizeCoveragePath(entry.path)
467
+ : "";
468
+ const canonicalPath = taskNormMap.get(entryNorm);
450
469
  if (!isNonEmptyString(entry.path)) {
451
470
  pushIssue(issues, {
452
471
  result_index: i,
@@ -455,7 +474,7 @@ export function validateAuditResults(results, tasks, options = {}) {
455
474
  message: "file_coverage entry has an empty path.",
456
475
  });
457
476
  }
458
- else if (task && !task.file_paths.includes(entry.path)) {
477
+ else if (task && !canonicalPath) {
459
478
  pushIssue(issues, {
460
479
  result_index: i,
461
480
  task_id: taskId,
@@ -463,7 +482,7 @@ export function validateAuditResults(results, tasks, options = {}) {
463
482
  message: `file_coverage path '${entry.path}' is not listed in the task file_paths.`,
464
483
  });
465
484
  }
466
- else if (seenCoveragePaths.has(entry.path)) {
485
+ else if (seenCoveragePaths.has(entryNorm)) {
467
486
  pushIssue(issues, {
468
487
  result_index: i,
469
488
  task_id: taskId,
@@ -472,11 +491,10 @@ export function validateAuditResults(results, tasks, options = {}) {
472
491
  });
473
492
  }
474
493
  else {
475
- seenCoveragePaths.add(entry.path);
494
+ seenCoveragePaths.add(entryNorm);
476
495
  }
477
- if (isNonEmptyString(entry.path) &&
478
- (!task || task.file_paths.includes(entry.path))) {
479
- declaredAssignedCoveragePaths.add(entry.path);
496
+ if (entryNorm.length > 0 && (!task || canonicalPath)) {
497
+ declaredAssignedCoveragePaths.add(canonicalPath ?? entryNorm);
480
498
  }
481
499
  if (!Number.isInteger(entry.total_lines)) {
482
500
  pushIssue(issues, {
@@ -495,8 +513,8 @@ export function validateAuditResults(results, tasks, options = {}) {
495
513
  message: "file_coverage total_lines must be zero or greater.",
496
514
  });
497
515
  }
498
- const expectedLineCount = typeof entry.path === "string"
499
- ? options.lineIndex?.[entry.path]
516
+ const expectedLineCount = entryNorm.length > 0
517
+ ? normLineIndex.get(entryNorm)
500
518
  : undefined;
501
519
  if (Number.isInteger(entry.total_lines) &&
502
520
  typeof expectedLineCount === "number" &&
@@ -509,19 +527,19 @@ export function validateAuditResults(results, tasks, options = {}) {
509
527
  `(expected ${expectedLineCount}, got ${entry.total_lines}).`,
510
528
  });
511
529
  }
512
- if (isNonEmptyString(entry.path) &&
530
+ if (entryNorm.length > 0 &&
513
531
  Number.isInteger(entry.total_lines) &&
514
532
  Number(entry.total_lines) >= 0 &&
515
- (!task || task.file_paths.includes(entry.path))) {
533
+ (!task || canonicalPath)) {
516
534
  normalizedFileCoverage.push({
517
- path: entry.path,
535
+ path: canonicalPath ?? entryNorm,
518
536
  total_lines: Number(entry.total_lines),
519
537
  });
520
538
  }
521
539
  }
522
540
  if (task) {
523
541
  for (const path of task.file_paths) {
524
- if (!seenCoveragePaths.has(path)) {
542
+ if (!seenCoveragePaths.has(normalizeCoveragePath(path))) {
525
543
  pushIssue(issues, {
526
544
  result_index: i,
527
545
  task_id: taskId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auditor-lambda",
3
- "version": "0.3.39",
3
+ "version": "0.3.40",
4
4
  "private": false,
5
5
  "description": "Portable hybrid code-auditing framework for arbitrary repositories.",
6
6
  "type": "module",