auditor-lambda 0.11.0 → 0.11.2

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.
@@ -6,6 +6,7 @@ import { advanceAudit } from "../orchestrator/advance.js";
6
6
  import { deriveAuditState } from "../orchestrator/state.js";
7
7
  import { decideNextStep } from "../orchestrator/nextStep.js";
8
8
  import { sizeIndexFromManifest } from "../orchestrator/reviewPackets.js";
9
+ import { partitionOrphanedAuditResults } from "../orchestrator/resultIngestion.js";
9
10
  import { validateAuditResults, formatAuditResultIssues, } from "../validation/auditResults.js";
10
11
  import { formatAuditResultValidationError } from "./workerResult.js";
11
12
  import { looksLikeCliFlag, listBatchResultFiles } from "./args.js";
@@ -38,10 +39,23 @@ export async function runAuditStep(options) {
38
39
  if (looksLikeCliFlag(options.auditResultsPath)) {
39
40
  throw new Error(`Invalid audit results path '${options.auditResultsPath}'. This looks like a CLI flag rather than a file path.`);
40
41
  }
41
- const auditResults = options.auditResultsPath
42
+ let auditResults = options.auditResultsPath
42
43
  ? await readJsonFile(options.auditResultsPath)
43
44
  : undefined;
44
45
  if (auditResults !== undefined) {
46
+ // Drop results whose task_id is no longer in the active manifest — e.g.
47
+ // selective-deepening tasks pruned by a later re-plan, whose orphaned answers
48
+ // would otherwise abort the whole batch at the validation gate below and
49
+ // strand every valid result. They cannot be ingested (coverage is keyed by
50
+ // the active task set), so skip-and-warn instead of throwing; results for
51
+ // KNOWN tasks with real errors still abort. The filtered array is what flows
52
+ // to advanceAudit, so orphans are neither validated nor ingested.
53
+ const partition = partitionOrphanedAuditResults(auditResults, new Set((bundle.audit_tasks ?? []).map((task) => task.task_id)));
54
+ if (partition && partition.orphanedTaskIds.length > 0) {
55
+ process.stderr.write(`audit-results ingestion: skipped ${partition.orphanedTaskIds.length} result(s) whose task_id ` +
56
+ `is not in the active manifest (orphaned by re-planning): ${partition.orphanedTaskIds.join(", ")}\n`);
57
+ auditResults = partition.retained;
58
+ }
45
59
  const issues = validateAuditResults(auditResults, bundle.audit_tasks ?? [], {
46
60
  lineIndex,
47
61
  });
@@ -335,6 +335,9 @@ export function buildPacketPrompt(params) {
335
335
  "Produce one JSON array containing exactly one AuditResult object for each listed task.",
336
336
  "Windows PowerShell: do not pipe an inline foreach statement directly into ConvertTo-Json.",
337
337
  "Assign the foreach output to a variable first, then pipe that variable to ConvertTo-Json.",
338
+ "PowerShell also unwraps single-element arrays: @(@{...}) collapses to one object, so a",
339
+ "one-result submission serializes as an object (not a 1-element array) and is rejected. Wrap it",
340
+ "yourself: '[' + (ConvertTo-Json $obj -Depth 12) + ']', or build the array with Write-Output -NoEnumerate.",
338
341
  "",
339
342
  "Schema file (resolve relative to this prompt's directory): audit_result.schema.json",
340
343
  " $refs resolved from the same directory: finding.schema.json, audit_task.schema.json",
@@ -1,9 +1,16 @@
1
- import { isNodeModulesOrGit, isTmpPath, isBuildOutput, isVendorPath, isBinaryArtifact, isLicensePath, isLockfilePath, isLogPath, isDocPath, isGeneratedPath, isAuditArtifactPath, isGeneratedTestArtifactPath, isGeneratedInstallArtifactPath, isExamplesOrFixturesPath, normalizeExtractorPath, } from "./pathPatterns.js";
1
+ import { isNodeModulesOrGit, isPackageManagerCachePath, isTmpPath, isBuildOutput, isVendorPath, isBinaryArtifact, isLicensePath, isLockfilePath, isLogPath, isDocPath, isGeneratedPath, isAuditArtifactPath, isAuditToolOutputArtifact, isGeneratedTestArtifactPath, isGeneratedInstallArtifactPath, isExamplesOrFixturesPath, normalizeExtractorPath, } from "./pathPatterns.js";
2
2
  function inferDisposition(path) {
3
3
  const normalized = normalizeExtractorPath(path);
4
4
  if (isNodeModulesOrGit(normalized)) {
5
5
  return { path, status: "excluded", reason: "node_modules or .git excluded by convention." };
6
6
  }
7
+ if (isPackageManagerCachePath(normalized)) {
8
+ return {
9
+ path,
10
+ status: "excluded",
11
+ reason: "Package-manager cache (npm _cacache/npm-cache) excluded by convention.",
12
+ };
13
+ }
7
14
  if (isTmpPath(normalized)) {
8
15
  return {
9
16
  path,
@@ -40,6 +47,13 @@ function inferDisposition(path) {
40
47
  reason: "Generated audit artifact.",
41
48
  };
42
49
  }
50
+ if (isAuditToolOutputArtifact(normalized)) {
51
+ return {
52
+ path,
53
+ status: "generated",
54
+ reason: "audit-tools pipeline output (findings/report) — a data deliverable, not source.",
55
+ };
56
+ }
43
57
  if (isGeneratedPath(normalized)) {
44
58
  return {
45
59
  path,
@@ -24,6 +24,19 @@ export declare function isDocPath(normalized: string): boolean;
24
24
  export declare function isGeneratedInstallArtifactPath(normalized: string): boolean;
25
25
  export declare function isGeneratedTestArtifactPath(normalized: string): boolean;
26
26
  export declare function isAuditArtifactPath(normalized: string): boolean;
27
+ /**
28
+ * Package-manager cache stores — npm's content-addressed `_cacache`, a nested
29
+ * `npm-cache`, or a local `.npm` — are dependency blobs, never the audited
30
+ * project's source. Smoke-test temp dirs can leave these inside the repo tree.
31
+ */
32
+ export declare function isPackageManagerCachePath(normalized: string): boolean;
33
+ /**
34
+ * The pipeline's own canonical machine contracts. When audit-tools audits itself
35
+ * (or any repo that checked one in) these are data deliverables, not source —
36
+ * auditing them yields only "this is JSON data" noise. The matching `*-report.md`
37
+ * renders are already handled as `doc_only` by `isDocPath`.
38
+ */
39
+ export declare function isAuditToolOutputArtifact(normalized: string): boolean;
27
40
  export declare function isTestPath(normalized: string): boolean;
28
41
  export declare function isInterfacePath(normalized: string): boolean;
29
42
  export declare function isDataLayerPath(normalized: string): boolean;
@@ -22,6 +22,9 @@ const BINARY_EXTENSIONS = [
22
22
  ".otf",
23
23
  ".pdf",
24
24
  ".zip",
25
+ ".tgz",
26
+ ".tar",
27
+ ".gz",
25
28
  ];
26
29
  const LOCKFILE_NAMES = [
27
30
  "package-lock.json",
@@ -204,7 +207,31 @@ export function isGeneratedTestArtifactPath(normalized) {
204
207
  return splitSegments(normalized).some((segment) => segment.startsWith(".test-") && segment.endsWith("-artifacts"));
205
208
  }
206
209
  export function isAuditArtifactPath(normalized) {
207
- return hasSegment(normalized, ".audit-tools");
210
+ return (hasSegment(normalized, ".audit-tools") ||
211
+ hasSegment(normalized, ".audit-artifacts"));
212
+ }
213
+ /**
214
+ * Package-manager cache stores — npm's content-addressed `_cacache`, a nested
215
+ * `npm-cache`, or a local `.npm` — are dependency blobs, never the audited
216
+ * project's source. Smoke-test temp dirs can leave these inside the repo tree.
217
+ */
218
+ export function isPackageManagerCachePath(normalized) {
219
+ return (hasSegment(normalized, "_cacache") ||
220
+ hasSegment(normalized, "npm-cache") ||
221
+ hasSegment(normalized, ".npm"));
222
+ }
223
+ const AUDIT_TOOL_OUTPUT_BASENAMES = new Set([
224
+ "audit-findings.json",
225
+ "remediation-outcomes.json",
226
+ ]);
227
+ /**
228
+ * The pipeline's own canonical machine contracts. When audit-tools audits itself
229
+ * (or any repo that checked one in) these are data deliverables, not source —
230
+ * auditing them yields only "this is JSON data" noise. The matching `*-report.md`
231
+ * renders are already handled as `doc_only` by `isDocPath`.
232
+ */
233
+ export function isAuditToolOutputArtifact(normalized) {
234
+ return AUDIT_TOOL_OUTPUT_BASENAMES.has(baseName(normalized));
208
235
  }
209
236
  export function isTestPath(normalized) {
210
237
  const segments = splitSegments(normalized);
@@ -1,3 +1,17 @@
1
1
  import type { AuditResult, AuditTask, CoverageMatrix } from "../types.js";
2
2
  export declare function ingestAuditResults(coverageMatrix: CoverageMatrix, results: AuditResult[]): CoverageMatrix;
3
3
  export declare function updateAuditTaskStatuses(tasks: AuditTask[] | undefined, results: AuditResult[]): AuditTask[] | undefined;
4
+ /**
5
+ * Splits raw (unvalidated) audit results into those whose `task_id` is still in
6
+ * the active task manifest and those orphaned by a later re-plan (e.g. selective-
7
+ * deepening tasks pruned in a subsequent round). Orphaned results cannot be
8
+ * ingested — coverage is keyed by the active task set — and must not abort an
9
+ * otherwise-valid batch at the ingestion validation gate. Returns the retained
10
+ * results plus the orphaned task ids so the caller can skip-and-warn, or `null`
11
+ * when there is nothing to filter (not an array, or no active manifest yet),
12
+ * signaling the caller to pass the results through unchanged.
13
+ */
14
+ export declare function partitionOrphanedAuditResults(results: unknown, activeTaskIds: Set<string>): {
15
+ retained: unknown[];
16
+ orphanedTaskIds: string[];
17
+ } | null;
@@ -32,3 +32,31 @@ export function updateAuditTaskStatuses(tasks, results) {
32
32
  };
33
33
  });
34
34
  }
35
+ /**
36
+ * Splits raw (unvalidated) audit results into those whose `task_id` is still in
37
+ * the active task manifest and those orphaned by a later re-plan (e.g. selective-
38
+ * deepening tasks pruned in a subsequent round). Orphaned results cannot be
39
+ * ingested — coverage is keyed by the active task set — and must not abort an
40
+ * otherwise-valid batch at the ingestion validation gate. Returns the retained
41
+ * results plus the orphaned task ids so the caller can skip-and-warn, or `null`
42
+ * when there is nothing to filter (not an array, or no active manifest yet),
43
+ * signaling the caller to pass the results through unchanged.
44
+ */
45
+ export function partitionOrphanedAuditResults(results, activeTaskIds) {
46
+ if (!Array.isArray(results) || activeTaskIds.size === 0) {
47
+ return null;
48
+ }
49
+ const orphanedTaskIds = [];
50
+ const retained = results.filter((entry) => {
51
+ const taskId = entry && typeof entry === "object" && !Array.isArray(entry) &&
52
+ typeof entry.task_id === "string"
53
+ ? entry.task_id
54
+ : undefined;
55
+ if (taskId !== undefined && !activeTaskIds.has(taskId)) {
56
+ orphanedTaskIds.push(taskId);
57
+ return false;
58
+ }
59
+ return true;
60
+ });
61
+ return { retained, orphanedTaskIds };
62
+ }
@@ -44,8 +44,12 @@ export function renderWorkerPrompt(task) {
44
44
  "Constraint: line_end must not exceed total_lines for that file.",
45
45
  "Windows PowerShell: do not pipe an inline foreach statement directly into ConvertTo-Json.",
46
46
  "Assign the foreach output to a variable first, then pipe that variable to ConvertTo-Json.",
47
+ "PowerShell also unwraps single-element arrays: @(@{...}) collapses to one object, so a one-result",
48
+ "submission serializes as an object (not a 1-element array) and is rejected. Wrap it yourself:",
49
+ "'[' + (ConvertTo-Json $obj -Depth 12) + ']', or build the array with Write-Output -NoEnumerate.",
47
50
  `Write only the JSON array of AuditResult objects to: ${task.audit_results_path}`,
48
51
  ];
52
+ lines.push("Optional — never let this delay or replace the audit result: if you hit task", "ambiguity, tool friction, or unclear instructions, you MAY append one JSON", `reflection line to ${task.artifacts_dir}/agent-feedback.jsonl with shape:`, " {task_id, lens, instruction_clarity (clear|mostly_clear|ambiguous|unclear),", " ambiguities: [string], tool_friction: [string], suggestions: [string],", " severity (info|low|medium|high)}. One object per line; never overwrite existing lines.");
49
53
  if (usesDeferredWorkerCommand(task)) {
50
54
  lines.push("Deferred mode: write results, do not execute worker_command.");
51
55
  }
@@ -0,0 +1,38 @@
1
+ export type ReflectionClarity = "clear" | "mostly_clear" | "ambiguous" | "unclear";
2
+ export type ReflectionSeverity = "info" | "low" | "medium" | "high";
3
+ export interface AgentReflection {
4
+ task_id: string;
5
+ lens?: string;
6
+ instruction_clarity: ReflectionClarity;
7
+ ambiguities?: string[];
8
+ tool_friction?: string[];
9
+ suggestions?: string[];
10
+ severity: ReflectionSeverity;
11
+ }
12
+ /**
13
+ * Parse NDJSON reflection text, keeping only schema-valid objects. Blank lines,
14
+ * non-JSON lines, and objects missing the required `task_id`/`instruction_clarity`/
15
+ * `severity` (or with out-of-enum values) are skipped silently — the channel is
16
+ * opt-in and best-effort, so a bad reflection must never break synthesis.
17
+ */
18
+ export declare function parseReflectionsNdjson(text: string): AgentReflection[];
19
+ export interface ReflectionAggregate {
20
+ total: number;
21
+ clarity_breakdown: Record<ReflectionClarity, number>;
22
+ severity_breakdown: Record<ReflectionSeverity, number>;
23
+ /** Deduped notes, highest reported impact first (ties broken alphabetically). */
24
+ friction: string[];
25
+ ambiguities: string[];
26
+ suggestions: string[];
27
+ }
28
+ /**
29
+ * Tally clarity/severity and dedupe the free-text notes across reflections,
30
+ * ranking each distinct note by the highest severity it was reported under so the
31
+ * most impactful friction surfaces first.
32
+ */
33
+ export declare function aggregateReflections(reflections: AgentReflection[]): ReflectionAggregate;
34
+ /**
35
+ * Render the "## Process Feedback" section. Returns `[]` when there are no
36
+ * reflections so the report omits the section entirely.
37
+ */
38
+ export declare function renderProcessFeedbackSection(reflections: AgentReflection[]): string[];
@@ -0,0 +1,162 @@
1
+ // Agent meta-audit reflections: a canonical, opt-in feedback channel. Workers may
2
+ // append one reflection per task (NDJSON) to `agent-feedback.jsonl` — schema
3
+ // `schemas/agent_reflection.schema.json`. Synthesis aggregates them into a
4
+ // "Process Feedback" report section so recurring operational friction is visible
5
+ // without hand-reading the JSONL. The channel is best-effort: a malformed line is
6
+ // skipped, never fatal, and never competes with the actual audit obligation.
7
+ const CLARITY_VALUES = new Set([
8
+ "clear",
9
+ "mostly_clear",
10
+ "ambiguous",
11
+ "unclear",
12
+ ]);
13
+ const SEVERITY_VALUES = new Set([
14
+ "info",
15
+ "low",
16
+ "medium",
17
+ "high",
18
+ ]);
19
+ const SEVERITY_RANK = {
20
+ high: 3,
21
+ medium: 2,
22
+ low: 1,
23
+ info: 0,
24
+ };
25
+ function isStringArray(value) {
26
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
27
+ }
28
+ /**
29
+ * Parse NDJSON reflection text, keeping only schema-valid objects. Blank lines,
30
+ * non-JSON lines, and objects missing the required `task_id`/`instruction_clarity`/
31
+ * `severity` (or with out-of-enum values) are skipped silently — the channel is
32
+ * opt-in and best-effort, so a bad reflection must never break synthesis.
33
+ */
34
+ export function parseReflectionsNdjson(text) {
35
+ const reflections = [];
36
+ for (const rawLine of text.split(/\r?\n/)) {
37
+ const line = rawLine.trim();
38
+ if (line.length === 0)
39
+ continue;
40
+ let parsed;
41
+ try {
42
+ parsed = JSON.parse(line);
43
+ }
44
+ catch {
45
+ continue;
46
+ }
47
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
48
+ continue;
49
+ const record = parsed;
50
+ if (typeof record.task_id !== "string" || record.task_id.length === 0) {
51
+ continue;
52
+ }
53
+ if (typeof record.instruction_clarity !== "string" ||
54
+ !CLARITY_VALUES.has(record.instruction_clarity)) {
55
+ continue;
56
+ }
57
+ if (typeof record.severity !== "string" ||
58
+ !SEVERITY_VALUES.has(record.severity)) {
59
+ continue;
60
+ }
61
+ const reflection = {
62
+ task_id: record.task_id,
63
+ instruction_clarity: record.instruction_clarity,
64
+ severity: record.severity,
65
+ };
66
+ if (typeof record.lens === "string")
67
+ reflection.lens = record.lens;
68
+ if (isStringArray(record.ambiguities))
69
+ reflection.ambiguities = record.ambiguities;
70
+ if (isStringArray(record.tool_friction))
71
+ reflection.tool_friction = record.tool_friction;
72
+ if (isStringArray(record.suggestions))
73
+ reflection.suggestions = record.suggestions;
74
+ reflections.push(reflection);
75
+ }
76
+ return reflections;
77
+ }
78
+ /**
79
+ * Tally clarity/severity and dedupe the free-text notes across reflections,
80
+ * ranking each distinct note by the highest severity it was reported under so the
81
+ * most impactful friction surfaces first.
82
+ */
83
+ export function aggregateReflections(reflections) {
84
+ const clarity_breakdown = {
85
+ clear: 0,
86
+ mostly_clear: 0,
87
+ ambiguous: 0,
88
+ unclear: 0,
89
+ };
90
+ const severity_breakdown = {
91
+ info: 0,
92
+ low: 0,
93
+ medium: 0,
94
+ high: 0,
95
+ };
96
+ const friction = new Map();
97
+ const ambiguities = new Map();
98
+ const suggestions = new Map();
99
+ const collect = (target, items, severity) => {
100
+ for (const item of items ?? []) {
101
+ const key = item.trim();
102
+ if (key.length === 0)
103
+ continue;
104
+ target.set(key, Math.max(target.get(key) ?? 0, SEVERITY_RANK[severity]));
105
+ }
106
+ };
107
+ for (const reflection of reflections) {
108
+ clarity_breakdown[reflection.instruction_clarity] += 1;
109
+ severity_breakdown[reflection.severity] += 1;
110
+ collect(friction, reflection.tool_friction, reflection.severity);
111
+ collect(ambiguities, reflection.ambiguities, reflection.severity);
112
+ collect(suggestions, reflection.suggestions, reflection.severity);
113
+ }
114
+ const rankedKeys = (target) => [...target.entries()]
115
+ .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
116
+ .map(([key]) => key);
117
+ return {
118
+ total: reflections.length,
119
+ clarity_breakdown,
120
+ severity_breakdown,
121
+ friction: rankedKeys(friction),
122
+ ambiguities: rankedKeys(ambiguities),
123
+ suggestions: rankedKeys(suggestions),
124
+ };
125
+ }
126
+ function formatCounts(counts) {
127
+ const parts = Object.entries(counts)
128
+ .filter(([, count]) => count > 0)
129
+ .map(([key, count]) => `${key}: ${count}`);
130
+ return parts.length > 0 ? parts.join(", ") : "none";
131
+ }
132
+ /**
133
+ * Render the "## Process Feedback" section. Returns `[]` when there are no
134
+ * reflections so the report omits the section entirely.
135
+ */
136
+ export function renderProcessFeedbackSection(reflections) {
137
+ if (reflections.length === 0)
138
+ return [];
139
+ const aggregate = aggregateReflections(reflections);
140
+ const lines = [
141
+ "## Process Feedback",
142
+ "",
143
+ `Aggregated from ${aggregate.total} agent reflection(s) appended during the run ` +
144
+ `(opt-in; schema: agent_reflection.schema.json).`,
145
+ "",
146
+ `- Instruction clarity: ${formatCounts(aggregate.clarity_breakdown)}`,
147
+ `- Reported impact: ${formatCounts(aggregate.severity_breakdown)}`,
148
+ "",
149
+ ];
150
+ const block = (title, items) => {
151
+ if (items.length === 0)
152
+ return;
153
+ lines.push(`### ${title}`, "");
154
+ for (const item of items)
155
+ lines.push(`- ${item}`);
156
+ lines.push("");
157
+ };
158
+ block("Tool & instruction friction", aggregate.friction);
159
+ block("Ambiguities", aggregate.ambiguities);
160
+ block("Suggestions", aggregate.suggestions);
161
+ return lines;
162
+ }
@@ -5,6 +5,7 @@ import type { ExternalAnalyzerResults } from "../types/externalAnalyzer.js";
5
5
  import type { AuditFindingsReport, CriticalFlowManifest, Finding as SharedFinding, FindingTheme, GraphBundle, SynthesisNarrative } from "@audit-tools/shared";
6
6
  import type { RuntimeValidationReport, RuntimeValidationTaskManifest } from "../types/runtimeValidation.js";
7
7
  import { type WorkBlock } from "./workBlocks.js";
8
+ import { type AgentReflection } from "./agentReflections.js";
8
9
  /** Contract version stamped onto the canonical `audit-findings.json`. */
9
10
  export declare const AUDIT_FINDINGS_CONTRACT_VERSION = "audit-tools/audit-findings/v1";
10
11
  /**
@@ -71,6 +72,12 @@ export declare function applyNarrative(report: AuditFindingsReport, narrative: S
71
72
  export interface RenderAuditReportOptions {
72
73
  /** Scope manifest for the run; when delta, the report header reports it honestly. */
73
74
  scope?: AuditScopeManifest;
75
+ /**
76
+ * Opt-in agent meta-audit reflections to surface in a "Process Feedback"
77
+ * section. Omitted/empty renders nothing. The synthesis disk-load that
78
+ * populates this from `agent-feedback.jsonl` is wired separately.
79
+ */
80
+ reflections?: AgentReflection[];
74
81
  }
75
82
  export declare function renderAuditReportMarkdown(report: RenderableAuditReport, options?: RenderAuditReportOptions): string;
76
83
  /**
@@ -2,6 +2,7 @@ import { AUDITOR_REPORT_MARKER } from "@audit-tools/shared";
2
2
  import { buildWorkBlocks } from "./workBlocks.js";
3
3
  import { mergeFindings } from "./mergeFindings.js";
4
4
  import { assignStableFindingIds } from "./findingIdentity.js";
5
+ import { renderProcessFeedbackSection, } from "./agentReflections.js";
5
6
  /** Contract version stamped onto the canonical `audit-findings.json`. */
6
7
  export const AUDIT_FINDINGS_CONTRACT_VERSION = "audit-tools/audit-findings/v1";
7
8
  function countBy(items, selectKey) {
@@ -215,6 +216,7 @@ export function renderAuditReportMarkdown(report, options = {}) {
215
216
  lines.push("");
216
217
  }
217
218
  }
219
+ lines.push(...renderProcessFeedbackSection(options.reflections ?? []));
218
220
  lines.push("## Scope and Coverage", "");
219
221
  const scope = options.scope;
220
222
  if (scope && scope.mode === "delta") {
@@ -305,7 +305,8 @@ function validateVerificationFollowupTask(task, label, taskId, resultIndex, expe
305
305
  result_index: resultIndex,
306
306
  task_id: taskId,
307
307
  field: `${label}.file_paths[${index}]`,
308
- message: `${label}.file_paths[${index}] references '${path}', which is outside the verification task's file_coverage.`,
308
+ message: `${label}.file_paths[${index}] references '${path}', which is outside the verification task's file_coverage. ` +
309
+ `Followup tasks list files in 'file_paths' (array of strings), not 'file_coverage'; allowed: ${[...allowedPaths].join(", ")}.`,
309
310
  });
310
311
  }
311
312
  }
@@ -481,7 +482,8 @@ export function validateAuditResults(results, tasks, options = {}) {
481
482
  result_index: i,
482
483
  task_id: taskId,
483
484
  field: `file_coverage[${j}].path`,
484
- message: `file_coverage path '${entry.path}' is not listed in the task file_paths.`,
485
+ message: `file_coverage path '${entry.path}' is not listed in the task file_paths. ` +
486
+ `Declare only assigned files; allowed for this task: ${task.file_paths.join(", ")}.`,
485
487
  });
486
488
  }
487
489
  else if (seenCoveragePaths.has(entryNorm)) {
@@ -595,7 +597,9 @@ export function validateAuditResults(results, tasks, options = {}) {
595
597
  result_index: i,
596
598
  task_id: taskId,
597
599
  field: `${label}.affected_files[${k}].path`,
598
- message: `affected_files path '${affected.path}' is not in the declared assigned file_coverage.`,
600
+ message: `affected_files path '${affected.path}' is not in the declared assigned file_coverage. ` +
601
+ `Add it to this result's file_coverage first` +
602
+ (task ? `; the task's assigned files are: ${task.file_paths.join(", ")}.` : "."),
599
603
  });
600
604
  continue;
601
605
  }
@@ -1,8 +1,8 @@
1
- import { exec } from "node:child_process";
1
+ import { execFile } from "node:child_process";
2
2
  import { accessSync, constants } from "node:fs";
3
3
  import { promisify } from "node:util";
4
4
  import { ANALYZER_SETTINGS, PROVIDER_NAMES, SESSION_UI_MODES, isRecord, pushValidationIssue, } from "@audit-tools/shared";
5
- const execAsync = promisify(exec);
5
+ const execFileAsync = promisify(execFile);
6
6
  const VALID_PROVIDERS = new Set(PROVIDER_NAMES);
7
7
  const VALID_UI_MODES = new Set(SESSION_UI_MODES);
8
8
  const VALID_ANALYZER_SETTINGS = new Set(ANALYZER_SETTINGS);
@@ -85,7 +85,10 @@ function validateAgentProviderSection(value, path, issues) {
85
85
  async function commandExists(command) {
86
86
  const lookupCommand = process.platform === "win32" ? "where" : "which";
87
87
  try {
88
- await execAsync(`${lookupCommand} ${command}`);
88
+ // execFile (no shell) passes `command` as a literal argv entry, so shell
89
+ // metacharacters in a config-supplied command cannot be interpreted or
90
+ // executed during environment validation.
91
+ await execFileAsync(lookupCommand, [command]);
89
92
  return true;
90
93
  }
91
94
  catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auditor-lambda",
3
- "version": "0.11.0",
3
+ "version": "0.11.2",
4
4
  "private": false,
5
5
  "description": "Portable hybrid code-auditing framework for arbitrary repositories.",
6
6
  "type": "module",
@@ -0,0 +1,44 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "agent_reflection.schema.json",
4
+ "title": "Agent Reflection",
5
+ "description": "One opt-in meta-audit reflection appended (NDJSON, one object per line) to agent-feedback.jsonl by a worker, describing how the tool felt to operate. Best-effort and never a substitute for the actual audit/remediation obligation.",
6
+ "type": "object",
7
+ "required": ["task_id", "instruction_clarity", "severity"],
8
+ "properties": {
9
+ "task_id": {
10
+ "type": "string",
11
+ "description": "The audit task or remediation item this reflection is about."
12
+ },
13
+ "lens": {
14
+ "type": "string",
15
+ "description": "Audit lens (or remediation phase) the worker operated under, when applicable."
16
+ },
17
+ "instruction_clarity": {
18
+ "type": "string",
19
+ "enum": ["clear", "mostly_clear", "ambiguous", "unclear"],
20
+ "description": "How clear the task instructions and scope were."
21
+ },
22
+ "ambiguities": {
23
+ "type": "array",
24
+ "items": { "type": "string" },
25
+ "description": "Specific things that were unclear about the task, scope, or contracts."
26
+ },
27
+ "tool_friction": {
28
+ "type": "array",
29
+ "items": { "type": "string" },
30
+ "description": "Friction encountered operating the tool (commands, prompts, artifacts, environment)."
31
+ },
32
+ "suggestions": {
33
+ "type": "array",
34
+ "items": { "type": "string" },
35
+ "description": "Concrete suggestions to reduce the friction or ambiguity."
36
+ },
37
+ "severity": {
38
+ "type": "string",
39
+ "enum": ["info", "low", "medium", "high"],
40
+ "description": "How much the reported friction/ambiguity impeded the work."
41
+ }
42
+ },
43
+ "additionalProperties": false
44
+ }