auditor-lambda 0.10.8 → 0.11.1

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.
Files changed (41) hide show
  1. package/README.md +5 -0
  2. package/dist/cli/args.d.ts +3 -3
  3. package/dist/cli/args.js +4 -7
  4. package/dist/cli/auditStep.js +15 -1
  5. package/dist/cli/dispatch.d.ts +1 -0
  6. package/dist/cli/dispatch.js +27 -5
  7. package/dist/cli/prompts.js +7 -6
  8. package/dist/cli/resynthesizeCommand.d.ts +1 -0
  9. package/dist/cli/resynthesizeCommand.js +50 -0
  10. package/dist/cli.js +5 -1
  11. package/dist/extractors/disposition.js +15 -1
  12. package/dist/extractors/pathPatterns.d.ts +13 -0
  13. package/dist/extractors/pathPatterns.js +28 -1
  14. package/dist/io/artifacts.d.ts +3 -1
  15. package/dist/io/artifacts.js +1 -0
  16. package/dist/orchestrator/advance.js +6 -0
  17. package/dist/orchestrator/dependencyMap.js +7 -0
  18. package/dist/orchestrator/designReviewPrompt.js +31 -10
  19. package/dist/orchestrator/executors.js +6 -0
  20. package/dist/orchestrator/ingestionExecutors.js +29 -1
  21. package/dist/orchestrator/intentCheckpointExecutor.d.ts +3 -0
  22. package/dist/orchestrator/intentCheckpointExecutor.js +29 -0
  23. package/dist/orchestrator/lensSelection.d.ts +32 -0
  24. package/dist/orchestrator/lensSelection.js +69 -0
  25. package/dist/orchestrator/planningExecutors.js +18 -3
  26. package/dist/orchestrator/resultIngestion.d.ts +14 -0
  27. package/dist/orchestrator/resultIngestion.js +28 -0
  28. package/dist/orchestrator.d.ts +1 -1
  29. package/dist/orchestrator.js +3 -4
  30. package/dist/prompts/renderWorkerPrompt.js +9 -0
  31. package/dist/quota/index.d.ts +5 -3
  32. package/dist/quota/index.js +1 -1
  33. package/dist/reporting/synthesis.d.ts +10 -0
  34. package/dist/reporting/synthesis.js +23 -0
  35. package/dist/reporting/synthesisNarrativePrompt.js +3 -1
  36. package/dist/validation/auditResults.js +7 -3
  37. package/docs/development.md +6 -0
  38. package/package.json +2 -1
  39. package/schemas/audit_findings.schema.json +14 -1
  40. package/schemas/dispatch_quota.schema.json +86 -2
  41. package/schemas/finding.schema.json +14 -1
@@ -112,9 +112,36 @@ export function runResultIngestionExecutor(bundle, results) {
112
112
  excludeArtifacts: ["audit_tasks.json"],
113
113
  });
114
114
  const requeuePayload = buildRequeuePayload(updatedCoverageMatrix, selectiveDeepening.bundle.critical_flows, selectiveDeepening.bundle.flow_coverage, selectiveDeepening.bundle.external_analyzer_results);
115
+ // Fold pending requeue tasks into the dispatch task list so mandatory
116
+ // coverage gaps produce actual dispatch packets. Enrich with line-count
117
+ // hints and dedupe against existing tasks by task_id.
118
+ const deepenedTasks = selectiveDeepening.bundle.audit_tasks ?? [];
119
+ const lineIndex = lineIndexFromTasks(deepenedTasks);
120
+ const sizeIndex = sizeIndexFromManifest(selectiveDeepening.bundle.repo_manifest);
121
+ const existingTaskIds = new Set(deepenedTasks.map((t) => t.task_id));
122
+ const pendingRequeueTasks = requeuePayload.tasks
123
+ .filter((t) => t.status === "pending")
124
+ .filter((t) => !existingTaskIds.has(t.task_id))
125
+ .map((t) => ({
126
+ ...t,
127
+ file_line_counts: Object.fromEntries(t.file_paths
128
+ .filter((p) => lineIndex[p] != null)
129
+ .map((p) => [p, lineIndex[p]])),
130
+ }));
131
+ const allDispatchTasks = [...deepenedTasks, ...pendingRequeueTasks];
115
132
  const finalBundle = {
116
133
  ...selectiveDeepening.bundle,
117
134
  requeue_tasks: requeuePayload.tasks,
135
+ audit_plan_metrics: buildAuditPlanMetrics(allDispatchTasks, {
136
+ graphBundle: selectiveDeepening.bundle.graph_bundle,
137
+ lineIndex,
138
+ sizeIndex,
139
+ }),
140
+ review_packets: buildReviewPackets(allDispatchTasks, {
141
+ graphBundle: selectiveDeepening.bundle.graph_bundle,
142
+ lineIndex,
143
+ sizeIndex,
144
+ }),
118
145
  };
119
146
  return {
120
147
  updated: finalBundle,
@@ -125,7 +152,8 @@ export function runResultIngestionExecutor(bundle, results) {
125
152
  ...(runtimeValidationReport ? ["runtime_validation_report.json"] : []),
126
153
  "audit_results.jsonl",
127
154
  "audit_tasks.json",
128
- ...selectiveDeepening.artifacts,
155
+ "audit_plan_metrics.json",
156
+ "review_packets.json",
129
157
  "requeue_tasks.json",
130
158
  ],
131
159
  progress_summary: `Ingested ${results.length} audit result entries and refreshed dependent artifacts.` +
@@ -0,0 +1,3 @@
1
+ import type { ArtifactBundle } from "../io/artifacts.js";
2
+ import type { ExecutorRunResult } from "./executorResult.js";
3
+ export declare function runIntentCheckpointExecutor(bundle: ArtifactBundle, root: string, since?: string): Promise<ExecutorRunResult>;
@@ -0,0 +1,29 @@
1
+ import { resolveAuditScope } from "./scope.js";
2
+ import { isAuditExcludedStatus } from "../extractors/disposition.js";
3
+ export async function runIntentCheckpointExecutor(bundle, root, since) {
4
+ const scope = resolveAuditScope({ root, since, bundle });
5
+ let filesInScope = 0;
6
+ if (scope.mode === "delta") {
7
+ filesInScope = scope.seed_files.length + scope.expanded_files.length;
8
+ }
9
+ else {
10
+ // Count auditable files in disposition. Fall back to manifest or 0.
11
+ const auditableCount = bundle.file_disposition?.files.filter((file) => !isAuditExcludedStatus(file.status)).length ?? (bundle.repo_manifest?.files.length ?? 0);
12
+ filesInScope = auditableCount;
13
+ }
14
+ const intent = {
15
+ schema_version: "intent-checkpoint/v1",
16
+ confirmed_at: new Date().toISOString(),
17
+ scope_summary: `Root: ${root}${scope.since ? ` (since ${scope.since})` : ""}, files in scope: ${filesInScope}`,
18
+ intent_summary: scope.mode === "delta" ? `delta-audit since ${scope.since}` : "full-audit",
19
+ confirmed_by: "host",
20
+ };
21
+ return {
22
+ updated: {
23
+ ...bundle,
24
+ intent_checkpoint: intent,
25
+ },
26
+ artifacts_written: ["intent_checkpoint.json"],
27
+ progress_summary: `Recorded scope/intent checkpoint: ${intent.scope_summary} (${intent.intent_summary}).`,
28
+ };
29
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Lens selection resolver for operator-selected audit lenses.
3
+ *
4
+ * Accepts the `lenses.selected` array from session-config, validates it,
5
+ * de-duplicates, sorts with the canonical LENSES registry order, and always
6
+ * unions in the mandatory base set required for cross-perspective coverage.
7
+ */
8
+ import { type Lens } from "@audit-tools/shared";
9
+ import type { ValidationIssue } from "@audit-tools/shared";
10
+ /**
11
+ * The mandatory base lenses that are always included regardless of the
12
+ * operator's selection. These are required for cross-perspective obligations
13
+ * that every audit must satisfy.
14
+ */
15
+ export declare const MANDATORY_LENSES: readonly Lens[];
16
+ /**
17
+ * Validate the `lenses.selected` session-config value and return any errors.
18
+ * Returns an empty array when the value is undefined (meaning "all lenses").
19
+ */
20
+ export declare function validateLensSelection(value: unknown, path?: string): ValidationIssue[];
21
+ /**
22
+ * Resolve the effective lens set from the operator-selected lenses.
23
+ *
24
+ * - When `selected` is undefined/null, returns all lenses (current behavior).
25
+ * - When `selected` is an array of valid lens names, unions in the mandatory
26
+ * base lenses, de-duplicates, and sorts to the canonical LENSES order.
27
+ */
28
+ export declare function resolveEffectiveLenses(selected: string[] | undefined | null): Lens[];
29
+ /** Returns true when the given lens is in the effective set. */
30
+ export declare function isLensEffective(lens: Lens, effectiveLenses: Lens[]): boolean;
31
+ /** Returns true when the given lens is in the mandatory base set. */
32
+ export declare function isMandatoryLens(lens: Lens): boolean;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Lens selection resolver for operator-selected audit lenses.
3
+ *
4
+ * Accepts the `lenses.selected` array from session-config, validates it,
5
+ * de-duplicates, sorts with the canonical LENSES registry order, and always
6
+ * unions in the mandatory base set required for cross-perspective coverage.
7
+ */
8
+ import { LENSES, VALID_LENSES } from "@audit-tools/shared";
9
+ import { pushValidationIssue } from "@audit-tools/shared";
10
+ /**
11
+ * The mandatory base lenses that are always included regardless of the
12
+ * operator's selection. These are required for cross-perspective obligations
13
+ * that every audit must satisfy.
14
+ */
15
+ export const MANDATORY_LENSES = [
16
+ "security",
17
+ "correctness",
18
+ "reliability",
19
+ "data_integrity",
20
+ ];
21
+ const MANDATORY_LENS_SET = new Set(MANDATORY_LENSES);
22
+ /**
23
+ * Validate the `lenses.selected` session-config value and return any errors.
24
+ * Returns an empty array when the value is undefined (meaning "all lenses").
25
+ */
26
+ export function validateLensSelection(value, path = "lenses.selected") {
27
+ const issues = [];
28
+ if (value === undefined || value === null) {
29
+ return issues;
30
+ }
31
+ if (!Array.isArray(value)) {
32
+ pushValidationIssue(issues, path, `${path} must be an array of lens names.`);
33
+ return issues;
34
+ }
35
+ for (const [index, item] of value.entries()) {
36
+ if (typeof item !== "string" || !VALID_LENSES.has(item)) {
37
+ pushValidationIssue(issues, `${path}[${index}]`, `${path}[${index}] "${String(item)}" is not a valid lens. Valid lenses: ${[...LENSES].join(", ")}.`);
38
+ }
39
+ }
40
+ return issues;
41
+ }
42
+ /**
43
+ * Resolve the effective lens set from the operator-selected lenses.
44
+ *
45
+ * - When `selected` is undefined/null, returns all lenses (current behavior).
46
+ * - When `selected` is an array of valid lens names, unions in the mandatory
47
+ * base lenses, de-duplicates, and sorts to the canonical LENSES order.
48
+ */
49
+ export function resolveEffectiveLenses(selected) {
50
+ if (selected === undefined || selected === null) {
51
+ // Default: all lenses.
52
+ return [...LENSES];
53
+ }
54
+ // Filter to valid lenses only (invalid ones are caught by validation; tolerate
55
+ // them here so the runtime path doesn't throw on a misconfigured file).
56
+ const validSelected = selected.filter((s) => VALID_LENSES.has(s));
57
+ // Union with mandatory base lenses.
58
+ const combined = new Set([...validSelected, ...MANDATORY_LENSES]);
59
+ // Sort to canonical registry order.
60
+ return LENSES.filter((lens) => combined.has(lens));
61
+ }
62
+ /** Returns true when the given lens is in the effective set. */
63
+ export function isLensEffective(lens, effectiveLenses) {
64
+ return effectiveLenses.includes(lens);
65
+ }
66
+ /** Returns true when the given lens is in the mandatory base set. */
67
+ export function isMandatoryLens(lens) {
68
+ return MANDATORY_LENS_SET.has(lens);
69
+ }
@@ -44,17 +44,32 @@ export async function runPlanningExecutor(bundle, root, lineIndex = {}, sizeInde
44
44
  ...task,
45
45
  status: task.status ?? "pending",
46
46
  }));
47
- const reviewPackets = buildReviewPackets(taggedAuditTasks, {
47
+ const requeuePayload = buildRequeuePayload(coverage, bundle.critical_flows, flowCoverage, externalAnalyzerResults);
48
+ // Fold pending requeue tasks into the dispatch task list so mandatory coverage
49
+ // gaps produce actual dispatch packets. Enrich with line-count hints from the
50
+ // index and dedupe against existing audit tasks by task_id so each task
51
+ // appears exactly once in the merged list.
52
+ const existingTaskIds = new Set(taggedAuditTasks.map((t) => t.task_id));
53
+ const pendingRequeueTasks = requeuePayload.tasks
54
+ .filter((t) => t.status === "pending")
55
+ .filter((t) => !existingTaskIds.has(t.task_id))
56
+ .map((t) => ({
57
+ ...t,
58
+ file_line_counts: Object.fromEntries(t.file_paths
59
+ .filter((p) => lineIndex[p] != null)
60
+ .map((p) => [p, lineIndex[p]])),
61
+ }));
62
+ const allDispatchTasks = [...taggedAuditTasks, ...pendingRequeueTasks];
63
+ const reviewPackets = buildReviewPackets(allDispatchTasks, {
48
64
  graphBundle: bundle.graph_bundle,
49
65
  lineIndex,
50
66
  sizeIndex: resolvedSizeIndex,
51
67
  });
52
- const auditPlanMetrics = buildAuditPlanMetrics(taggedAuditTasks, {
68
+ const auditPlanMetrics = buildAuditPlanMetrics(allDispatchTasks, {
53
69
  graphBundle: bundle.graph_bundle,
54
70
  lineIndex,
55
71
  sizeIndex: resolvedSizeIndex,
56
72
  });
57
- const requeuePayload = buildRequeuePayload(coverage, bundle.critical_flows, flowCoverage, externalAnalyzerResults);
58
73
  const scopeSummary = resolvedScope.mode === "delta"
59
74
  ? ` Delta scope since ${resolvedScope.since}: ${resolvedScope.seed_files.length} changed file(s) + ${resolvedScope.expanded_files.length} graph neighbour(s) queued; a full audit is advised before release.`
60
75
  : "";
@@ -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
+ }
@@ -3,4 +3,4 @@ export interface TaskBuildOptions {
3
3
  pass_prefix?: string;
4
4
  limit_lenses?: Lens[];
5
5
  }
6
- export declare function buildAuditTasks(unitManifest: UnitManifest, options?: TaskBuildOptions): AuditTask[];
6
+ export declare function buildAuditTasks(unitManifest: UnitManifest, options?: TaskBuildOptions | string): AuditTask[];
@@ -1,4 +1,5 @@
1
1
  import { isLens } from "./types.js";
2
+ import { coerceJsonObjectArg } from "@audit-tools/shared";
2
3
  const DEFAULT_LENS_ORDER = [
3
4
  "correctness",
4
5
  "architecture",
@@ -43,10 +44,8 @@ function assertUnitManifest(value) {
43
44
  assertLensArray(unit.required_lenses, `${label}.required_lenses`);
44
45
  });
45
46
  }
46
- function normalizedOptions(options) {
47
- if (!isRecord(options)) {
48
- throw new TypeError("buildAuditTasks options must be an object.");
49
- }
47
+ function normalizedOptions(rawOptions) {
48
+ const options = coerceJsonObjectArg(rawOptions, "buildAuditTasks options");
50
49
  if (options.pass_prefix !== undefined && typeof options.pass_prefix !== "string") {
51
50
  throw new TypeError("buildAuditTasks options.pass_prefix must be a string.");
52
51
  }
@@ -23,6 +23,8 @@ export function renderWorkerPrompt(task) {
23
23
  `${task.artifacts_dir}/audit_tasks.json`;
24
24
  const lines = [
25
25
  `Audit run: ${task.run_id}`,
26
+ `Repository root: ${task.repo_root}`,
27
+ "Set the shell/tool workdir to the repository root before executing worker_command.",
26
28
  `Read: ${tasksPath}`,
27
29
  "Scope: review only the tasks listed in the Read file. Do not add tasks,",
28
30
  "edit source files, remediate findings, run unrelated audits, or write result_path.",
@@ -40,6 +42,11 @@ export function renderWorkerPrompt(task) {
40
42
  " and include verification {verified, needs_followup, concerns, coverage_concerns,",
41
43
  " confidence_concerns, followup_tasks}.",
42
44
  "Constraint: line_end must not exceed total_lines for that file.",
45
+ "Windows PowerShell: do not pipe an inline foreach statement directly into ConvertTo-Json.",
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.",
43
50
  `Write only the JSON array of AuditResult objects to: ${task.audit_results_path}`,
44
51
  ];
45
52
  if (usesDeferredWorkerCommand(task)) {
@@ -56,6 +63,8 @@ export function renderWorkerPrompt(task) {
56
63
  return [
57
64
  `Task: ${task.run_id}`,
58
65
  `Executor: ${task.preferred_executor}`,
66
+ `Repository root: ${task.repo_root}`,
67
+ "Set the shell/tool workdir to the repository root before executing worker_command.",
59
68
  "Execute worker_command from task.json exactly.",
60
69
  `Command: ${commandArgv}`,
61
70
  "Write result to: " + task.result_path,
@@ -1,6 +1,6 @@
1
- import type { ResolvedLimits as _ResolvedLimits, LimitConfidence as _LimitConfidence, LimitSource as _LimitSource, HostConcurrencyLimit as _HostConcurrencyLimit, QuotaUsageSnapshot as _QuotaUsageSnapshot, BackoffState as _BackoffState } from "@audit-tools/shared";
2
- export { resolveLimits, lookupKnownModel, classifyProvider, readQuotaState, writeQuotaState, computeMaxSafeConcurrency, recordWaveOutcome, getQuotaStatePath, decayWeight, applyDecayToEntry, computeBackoffCooldownMs, computeBackoffFailureWeight, computeRampUpConcurrency, setQuotaStateDir, detectRateLimitError, computeCooldownUntil, acquireLock, releaseLock, withFileLock, FileLockTimeoutError, runSlidingWindow, LearnedQuotaSource, CompositeQuotaSource, GenericErrorParser, ClaudeCodeErrorParser, getErrorParserForProvider, } from "@audit-tools/shared";
3
- export type { LimitResolutionResult, ResolveLimitsOptions, ProviderType, ResolvedLimits, LimitSource, LimitConfidence, HostConcurrencyLimit, HostConcurrencyLimitSource, QuotaState, QuotaStateEntry, ConcurrencyBucket, WaveSchedule, BackoffState, ObservedWaveOutcome, RateLimitDetectionResult, SlidingWindowResult, QuotaSource, QuotaUsageSnapshot, ErrorParser, } from "@audit-tools/shared";
1
+ import type { ResolvedLimits as _ResolvedLimits, LimitConfidence as _LimitConfidence, LimitSource as _LimitSource, HostConcurrencyLimit as _HostConcurrencyLimit, QuotaUsageSnapshot as _QuotaUsageSnapshot, BackoffState as _BackoffState, WaveBindingCap as _WaveBindingCap, DispatchCapacityPoolSummary as _DispatchCapacityPoolSummary } from "@audit-tools/shared";
2
+ export { resolveLimits, lookupKnownModel, classifyProvider, readQuotaState, writeQuotaState, computeMaxSafeConcurrency, recordWaveOutcome, getQuotaStatePath, decayWeight, applyDecayToEntry, computeBackoffCooldownMs, computeBackoffFailureWeight, computeRampUpConcurrency, setQuotaStateDir, detectRateLimitError, computeCooldownUntil, acquireLock, releaseLock, withFileLock, FileLockTimeoutError, runSlidingWindow, LearnedQuotaSource, CompositeQuotaSource, GenericErrorParser, ClaudeCodeErrorParser, getErrorParserForProvider, summarizeDispatchCapacityPools, } from "@audit-tools/shared";
3
+ export type { LimitResolutionResult, ResolveLimitsOptions, ProviderType, ResolvedLimits, LimitSource, LimitConfidence, HostConcurrencyLimit, HostConcurrencyLimitSource, QuotaState, QuotaStateEntry, ConcurrencyBucket, WaveSchedule, BackoffState, ObservedWaveOutcome, RateLimitDetectionResult, SlidingWindowResult, QuotaSource, QuotaUsageSnapshot, ErrorParser, WaveBindingCap, DispatchCapacityPoolSummary, } from "@audit-tools/shared";
4
4
  export { scheduleWave, buildProviderModelKey, resolveHostModel } from "@audit-tools/shared";
5
5
  export type { ScheduleWaveOptions } from "@audit-tools/shared";
6
6
  export { computeDispatchCapacity } from "@audit-tools/shared";
@@ -25,6 +25,8 @@ export interface DispatchQuota {
25
25
  wave_size: number;
26
26
  estimated_wave_tokens: number;
27
27
  cooldown_until: string | null;
28
+ binding_cap?: _WaveBindingCap;
29
+ capacity_pools?: _DispatchCapacityPoolSummary[];
28
30
  quota_source_snapshot?: _QuotaUsageSnapshot | null;
29
31
  backoff_state?: _BackoffState | null;
30
32
  }
@@ -1,5 +1,5 @@
1
1
  // Re-exported from @audit-tools/shared
2
- export { resolveLimits, lookupKnownModel, classifyProvider, readQuotaState, writeQuotaState, computeMaxSafeConcurrency, recordWaveOutcome, getQuotaStatePath, decayWeight, applyDecayToEntry, computeBackoffCooldownMs, computeBackoffFailureWeight, computeRampUpConcurrency, setQuotaStateDir, detectRateLimitError, computeCooldownUntil, acquireLock, releaseLock, withFileLock, FileLockTimeoutError, runSlidingWindow, LearnedQuotaSource, CompositeQuotaSource, GenericErrorParser, ClaudeCodeErrorParser, getErrorParserForProvider, } from "@audit-tools/shared";
2
+ export { resolveLimits, lookupKnownModel, classifyProvider, readQuotaState, writeQuotaState, computeMaxSafeConcurrency, recordWaveOutcome, getQuotaStatePath, decayWeight, applyDecayToEntry, computeBackoffCooldownMs, computeBackoffFailureWeight, computeRampUpConcurrency, setQuotaStateDir, detectRateLimitError, computeCooldownUntil, acquireLock, releaseLock, withFileLock, FileLockTimeoutError, runSlidingWindow, LearnedQuotaSource, CompositeQuotaSource, GenericErrorParser, ClaudeCodeErrorParser, getErrorParserForProvider, summarizeDispatchCapacityPools, } from "@audit-tools/shared";
3
3
  // Wave scheduler now lives in @audit-tools/shared (single source of truth for
4
4
  // both orchestrators). Auditor passes its discovered-limits via the structural
5
5
  // DiscoveredRateLimitsInput the shared scheduler accepts.
@@ -73,3 +73,13 @@ export interface RenderAuditReportOptions {
73
73
  scope?: AuditScopeManifest;
74
74
  }
75
75
  export declare function renderAuditReportMarkdown(report: RenderableAuditReport, options?: RenderAuditReportOptions): string;
76
+ /**
77
+ * Re-derive the summary fields that can be computed from the existing findings
78
+ * and work_blocks, bump the contract_version to the current constant, and leave
79
+ * upstream-derived fields that cannot be reconstructed (audited/excluded counts,
80
+ * runtime validation breakdown) untouched.
81
+ *
82
+ * Safe to call on already-promoted `audit-findings.json` files without access to
83
+ * the pruned `.audit-tools/audit` working-bundle intermediates.
84
+ */
85
+ export declare function normalizeExistingFindingsReport(report: AuditFindingsReport): AuditFindingsReport;
@@ -200,6 +200,7 @@ export function renderAuditReportMarkdown(report, options = {}) {
200
200
  lines.push(`- Severity: ${finding.severity}`);
201
201
  lines.push(`- Confidence: ${finding.confidence}`);
202
202
  lines.push(`- Lens: ${finding.lens}`);
203
+ lines.push(`- Category: ${finding.category}`);
203
204
  if (finding.theme_id) {
204
205
  lines.push(`- Theme: ${finding.theme_id}`);
205
206
  }
@@ -236,3 +237,25 @@ export function renderAuditReportMarkdown(report, options = {}) {
236
237
  lines.push("");
237
238
  return lines.join("\n");
238
239
  }
240
+ /**
241
+ * Re-derive the summary fields that can be computed from the existing findings
242
+ * and work_blocks, bump the contract_version to the current constant, and leave
243
+ * upstream-derived fields that cannot be reconstructed (audited/excluded counts,
244
+ * runtime validation breakdown) untouched.
245
+ *
246
+ * Safe to call on already-promoted `audit-findings.json` files without access to
247
+ * the pruned `.audit-tools/audit` working-bundle intermediates.
248
+ */
249
+ export function normalizeExistingFindingsReport(report) {
250
+ return {
251
+ ...report,
252
+ contract_version: AUDIT_FINDINGS_CONTRACT_VERSION,
253
+ summary: {
254
+ ...report.summary,
255
+ finding_count: report.findings.length,
256
+ work_block_count: report.work_blocks.length,
257
+ severity_breakdown: severityBreakdown(report.findings),
258
+ lens_breakdown: lensBreakdown(report.findings),
259
+ },
260
+ };
261
+ }
@@ -4,7 +4,7 @@ function summarizeFinding(finding) {
4
4
  .map((file) => file.path)
5
5
  .slice(0, 4)
6
6
  .join(", ");
7
- return `- ${finding.id} [${finding.severity}/${finding.lens}] ${finding.title} — ${finding.summary}${files ? ` (files: ${files})` : ""}`;
7
+ return `- ${finding.id} [${finding.severity}/${finding.lens}/${finding.category}] ${finding.title} — ${finding.summary}${files ? ` (files: ${files})` : ""}`;
8
8
  }
9
9
  /**
10
10
  * Prompt for the optional synthesis-narrative pass. The host groups the
@@ -27,6 +27,8 @@ export function renderSynthesisNarrativePrompt(report) {
27
27
  "",
28
28
  "Do not re-audit the code, change severities, or invent new findings. Use only the findings below; reference them by their exact `id`.",
29
29
  "",
30
+ "When categories distinguish observational contract assessment findings from conceptual design critique findings, keep that distinction visible in themes and top risks instead of flattening them into one architecture bucket.",
31
+ "",
30
32
  "## Summary",
31
33
  "",
32
34
  `- Findings: ${report.summary.finding_count}`,
@@ -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
  }
@@ -23,9 +23,15 @@ once the work ships.
23
23
  npm install
24
24
  npm run check
25
25
  npm test
26
+ npm run test:single -- tests/next-step.test.mjs
26
27
  npm run verify:release
27
28
  ```
28
29
 
30
+ Run `npm install` from the repository root before running build, check, test, or
31
+ package-scoped workflows in a fresh clone or git worktree. Missing
32
+ `node_modules` can surface as misleading `@audit-tools/shared` export or type
33
+ errors because dependents may resolve stale compiled `dist/` output.
34
+
29
35
  The test suite is intentionally contract-heavy. Update tests when changing
30
36
  schema shape, prompt contracts, dispatch behavior, installer output, or release
31
37
  workflow semantics.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auditor-lambda",
3
- "version": "0.10.8",
3
+ "version": "0.11.1",
4
4
  "private": false,
5
5
  "description": "Portable hybrid code-auditing framework for arbitrary repositories.",
6
6
  "type": "module",
@@ -24,6 +24,7 @@
24
24
  "build": "tsc -p tsconfig.json",
25
25
  "check": "tsc -p tsconfig.json --noEmit",
26
26
  "test": "npm run build && node --import tsx/esm --test tests/*.test.mjs",
27
+ "test:single": "npm run build && node --import tsx/esm --test",
27
28
  "release:patch": "node scripts/release-and-publish.mjs patch --bump-only",
28
29
  "release:minor": "node scripts/release-and-publish.mjs minor --bump-only",
29
30
  "release:major": "node scripts/release-and-publish.mjs major --bump-only",
@@ -86,7 +86,20 @@
86
86
  "reproduction": { "type": "array", "minItems": 1, "items": { "type": "string" } },
87
87
  "systemic": { "type": "boolean" },
88
88
  "related_findings": { "type": "array", "minItems": 1, "items": { "type": "string" } },
89
- "theme_id": { "type": "string" }
89
+ "theme_id": { "type": "string" },
90
+ "contract_goal_id": { "type": "string" },
91
+ "contract_obligation_ids": {
92
+ "type": "array",
93
+ "items": { "type": "string" }
94
+ },
95
+ "verification_obligation_ids": {
96
+ "type": "array",
97
+ "items": { "type": "string" }
98
+ },
99
+ "targeted_commands": {
100
+ "type": "array",
101
+ "items": { "type": "string" }
102
+ }
90
103
  },
91
104
  "additionalProperties": false
92
105
  }
@@ -56,7 +56,7 @@
56
56
  },
57
57
  "source": {
58
58
  "type": "string",
59
- "enum": ["explicit_config", "cli_flags", "known_metadata", "learned", "default"],
59
+ "enum": ["explicit_config", "cli_flags", "known_metadata", "provider_default", "learned", "default"],
60
60
  "description": "Where the resolved limits came from."
61
61
  },
62
62
  "host_concurrency_limit": {
@@ -75,7 +75,7 @@
75
75
  },
76
76
  "source": {
77
77
  "type": "string",
78
- "enum": ["cli_flags", "session_config", "environment"]
78
+ "enum": ["cli_flags", "host_reported", "session_config", "environment"]
79
79
  },
80
80
  "description": {
81
81
  "type": "string",
@@ -98,6 +98,90 @@
98
98
  "format": "date-time",
99
99
  "description": "If non-null, the host should wait until this timestamp before launching the next wave."
100
100
  },
101
+ "binding_cap": {
102
+ "type": "string",
103
+ "enum": ["rpm", "tpm", "learned", "fallback", "first_contact", "cooldown", "host_concurrency", "none"],
104
+ "description": "The most constraining cap across capacity pools."
105
+ },
106
+ "capacity_pools": {
107
+ "type": "array",
108
+ "description": "Per-pool allocation summaries when dispatch capacity was computed across one or more pools.",
109
+ "items": {
110
+ "type": "object",
111
+ "required": [
112
+ "pool_id",
113
+ "slots",
114
+ "model",
115
+ "confidence",
116
+ "source",
117
+ "resolved_limits",
118
+ "host_concurrency_limit",
119
+ "cooldown_until",
120
+ "estimated_wave_tokens",
121
+ "binding_cap"
122
+ ],
123
+ "additionalProperties": false,
124
+ "properties": {
125
+ "pool_id": { "type": "string", "minLength": 1 },
126
+ "slots": { "type": "integer", "minimum": 1 },
127
+ "model": { "type": ["string", "null"] },
128
+ "confidence": { "type": "string", "enum": ["high", "medium", "low"] },
129
+ "source": {
130
+ "type": "string",
131
+ "enum": ["explicit_config", "cli_flags", "known_metadata", "provider_default", "learned", "default"]
132
+ },
133
+ "resolved_limits": {
134
+ "type": "object",
135
+ "required": [
136
+ "context_tokens",
137
+ "output_tokens",
138
+ "requests_per_minute",
139
+ "input_tokens_per_minute",
140
+ "output_tokens_per_minute"
141
+ ],
142
+ "additionalProperties": false,
143
+ "properties": {
144
+ "context_tokens": { "type": "integer", "minimum": 1 },
145
+ "output_tokens": { "type": "integer", "minimum": 1 },
146
+ "requests_per_minute": { "type": ["integer", "null"], "minimum": 1 },
147
+ "input_tokens_per_minute": { "type": ["integer", "null"], "minimum": 1 },
148
+ "output_tokens_per_minute": { "type": ["integer", "null"], "minimum": 1 }
149
+ }
150
+ },
151
+ "host_concurrency_limit": {
152
+ "type": ["object", "null"],
153
+ "required": ["active_subagents", "source", "description"],
154
+ "additionalProperties": false,
155
+ "properties": {
156
+ "active_subagents": { "type": "integer", "minimum": 1 },
157
+ "source": {
158
+ "type": "string",
159
+ "enum": ["cli_flags", "host_reported", "session_config", "environment"]
160
+ },
161
+ "description": { "type": "string", "minLength": 1 }
162
+ }
163
+ },
164
+ "cooldown_until": { "type": ["string", "null"], "format": "date-time" },
165
+ "estimated_wave_tokens": { "type": "integer", "minimum": 0 },
166
+ "binding_cap": {
167
+ "type": "string",
168
+ "enum": ["rpm", "tpm", "learned", "fallback", "first_contact", "cooldown", "host_concurrency", "none"]
169
+ },
170
+ "quota_source_snapshot": {
171
+ "type": ["object", "null"],
172
+ "additionalProperties": false,
173
+ "properties": {
174
+ "remaining_pct": { "type": ["number", "null"] },
175
+ "reset_at": { "type": ["string", "null"], "format": "date-time" },
176
+ "requests_remaining": { "type": ["integer", "null"] },
177
+ "tokens_remaining": { "type": ["integer", "null"] },
178
+ "captured_at": { "type": "string", "format": "date-time" },
179
+ "source": { "type": "string" }
180
+ }
181
+ }
182
+ }
183
+ }
184
+ },
101
185
  "quota_source_snapshot": {
102
186
  "type": ["object", "null"],
103
187
  "description": "Real-time usage snapshot from a QuotaSource, if available.",