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
package/README.md CHANGED
@@ -212,11 +212,16 @@ Optional backend config:
212
212
 
213
213
  ```bash
214
214
  npm install
215
+ npm run test:single -- tests/next-step.test.mjs
215
216
  npm run verify:release
216
217
  npm run release:patch
217
218
  npm run release:patch:publish
218
219
  ```
219
220
 
221
+ When developing from a fresh clone or git worktree, run repo-root `npm install`
222
+ before package checks. Missing workspace links can look like stale
223
+ `@audit-tools/shared` export or type errors.
224
+
220
225
  For GitHub Actions publication and npm Trusted Publishing setup, see `docs/release.md`.
221
226
 
222
227
  ## Key Docs
@@ -1,4 +1,4 @@
1
- import type { SessionConfig } from "@audit-tools/shared";
1
+ import { toPromptPathToken, quotePromptCommandArg, type SessionConfig } from "@audit-tools/shared";
2
2
  export type UiMode = "visible" | "headless";
3
3
  export declare const DIRECT_CLI_DEFAULTS: {
4
4
  rootDir: string;
@@ -23,7 +23,7 @@ export declare function fromBase64Url(value: string): string;
23
23
  export declare function digestId(value: string): string;
24
24
  export declare function safeArtifactStem(value: string): string;
25
25
  export declare function artifactNameForId(value: string, extension: string): string;
26
- export declare function quoteCommandArg(value: string): string;
26
+ export declare const quoteCommandArg: typeof quotePromptCommandArg;
27
27
  /**
28
28
  * Normalize a generated command token to POSIX path separators. These command
29
29
  * strings are embedded in step prompts and `allowed_commands` and run by the
@@ -34,7 +34,7 @@ export declare function quoteCommandArg(value: string): string;
34
34
  * are touched, and no non-path argument in this CLI contains one, so this is a
35
35
  * targeted normalization rather than a blanket rewrite.
36
36
  */
37
- export declare function toPosixCommandToken(value: string): string;
37
+ export declare const toPosixCommandToken: typeof toPromptPathToken;
38
38
  export declare function renderCommand(argv: string[]): string;
39
39
  export declare function summarizeLaunchExit(result: {
40
40
  accepted?: boolean;
package/dist/cli/args.js CHANGED
@@ -3,6 +3,7 @@ import { readdir } from "node:fs/promises";
3
3
  import { Buffer } from "node:buffer";
4
4
  import { createHash } from "node:crypto";
5
5
  import { join, resolve } from "node:path";
6
+ import { renderPromptCommand, toPromptPathToken, quotePromptCommandArg, } from "@audit-tools/shared";
6
7
  import { resolveFreshSessionProviderName } from "../providers/index.js";
7
8
  export const DIRECT_CLI_DEFAULTS = {
8
9
  rootDir: ".",
@@ -76,9 +77,7 @@ export function safeArtifactStem(value) {
76
77
  export function artifactNameForId(value, extension) {
77
78
  return `${safeArtifactStem(value)}_${digestId(value)}.${extension}`;
78
79
  }
79
- export function quoteCommandArg(value) {
80
- return /[\s"]/u.test(value) ? `"${value.replace(/"/g, '\\"')}"` : value;
81
- }
80
+ export const quoteCommandArg = quotePromptCommandArg;
82
81
  /**
83
82
  * Normalize a generated command token to POSIX path separators. These command
84
83
  * strings are embedded in step prompts and `allowed_commands` and run by the
@@ -89,11 +88,9 @@ export function quoteCommandArg(value) {
89
88
  * are touched, and no non-path argument in this CLI contains one, so this is a
90
89
  * targeted normalization rather than a blanket rewrite.
91
90
  */
92
- export function toPosixCommandToken(value) {
93
- return value.includes("\\") ? value.replace(/\\/g, "/") : value;
94
- }
91
+ export const toPosixCommandToken = toPromptPathToken;
95
92
  export function renderCommand(argv) {
96
- return argv.map((item) => quoteCommandArg(toPosixCommandToken(item))).join(" ");
93
+ return renderPromptCommand(argv);
97
94
  }
98
95
  export function summarizeLaunchExit(result) {
99
96
  if (result.accepted !== false && !result.error) {
@@ -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
  });
@@ -123,6 +123,7 @@ export declare function buildPacketPrompt(params: {
123
123
  largeFileSection: string[];
124
124
  taskSections: string[];
125
125
  submitCommand: string;
126
+ repoRoot?: string;
126
127
  }): string;
127
128
  /**
128
129
  * Extracts the context-budget warning loop.
@@ -10,7 +10,7 @@ import { orderTasksForPacketReview, buildReviewPackets, sizeIndexFromManifest, }
10
10
  import { buildFileAnchorSummary } from "../orchestrator/fileAnchors.js";
11
11
  import { resolveFreshSessionProviderName } from "../providers/index.js";
12
12
  import { loadSessionConfig } from "../supervisor/sessionConfig.js";
13
- import { computeDispatchCapacity, buildProviderModelKey, resolveHostModel, readQuotaState, resolveHostActiveSubagentLimit, lookupDiscoveredLimits, mergeDiscoveredLimits, } from "../quota/index.js";
13
+ import { computeDispatchCapacity, buildProviderModelKey, resolveHostModel, readQuotaState, resolveHostActiveSubagentLimit, lookupDiscoveredLimits, mergeDiscoveredLimits, summarizeDispatchCapacityPools, } from "../quota/index.js";
14
14
  import { taskResultPath, packetPromptPath, artifactNameForId, toBase64Url, fromBase64Url, getFlag, } from "./args.js";
15
15
  export const LARGE_FILE_PACKET_TARGET_LINES = 2500;
16
16
  export const SMALL_MODEL_HINT_MAX_LINES = 500;
@@ -303,10 +303,12 @@ export function buildTaskSections(packetTasks, lensDefs, lineIndex) {
303
303
  * Wraps the 75-line array-join block and returns the assembled prompt string.
304
304
  */
305
305
  export function buildPacketPrompt(params) {
306
- const { packet, fileList, largeFileSection, taskSections, submitCommand } = params;
306
+ const { packet, fileList, largeFileSection, taskSections, submitCommand, repoRoot } = params;
307
307
  const largeFileMode = isIsolatedLargeFilePacket(packet);
308
308
  return [
309
309
  "You are a code auditor. Review this packet once, then submit exactly one result per listed task.",
310
+ repoRoot ? `Repository root: ${repoRoot}` : "Repository root: use the root from the step contract.",
311
+ "Set the shell/tool workdir to the repository root when running backend commands.",
310
312
  "",
311
313
  "## Packet",
312
314
  `packet_id: ${packet.packet_id}`,
@@ -316,8 +318,8 @@ export function buildPacketPrompt(params) {
316
318
  "",
317
319
  "## Files to read",
318
320
  largeFileMode
319
- ? "Use targeted Read/Grep calls. Paths are repo-relative from the current working directory."
320
- : "Use your Read tool. Paths are repo-relative from the current working directory.",
321
+ ? "Use targeted Read/Grep calls. Paths are repo-relative to the repository root above."
322
+ : "Use your Read tool. Paths are repo-relative to the repository root above.",
321
323
  "Use host Read and Grep tools for source inspection. Do not use shell search commands.",
322
324
  fileList,
323
325
  "",
@@ -331,6 +333,11 @@ export function buildPacketPrompt(params) {
331
333
  "packet-*-result.json / audit_result_*.json) — the submit-packet command below is the only",
332
334
  "way to record results, and it writes them inside the artifacts directory for you.",
333
335
  "Produce one JSON array containing exactly one AuditResult object for each listed task.",
336
+ "Windows PowerShell: do not pipe an inline foreach statement directly into ConvertTo-Json.",
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.",
334
341
  "",
335
342
  "Schema file (resolve relative to this prompt's directory): audit_result.schema.json",
336
343
  " $refs resolved from the same directory: finding.schema.json, audit_task.schema.json",
@@ -437,6 +444,8 @@ async function computeDispatchQuota(params) {
437
444
  wave_size: dispatchCapacity.total_slots,
438
445
  estimated_wave_tokens: dispatchCapacity.estimated_wave_tokens,
439
446
  cooldown_until: dispatchCapacity.cooldown_until,
447
+ binding_cap: dispatchCapacity.binding_cap,
448
+ capacity_pools: summarizeDispatchCapacityPools(dispatchCapacity),
440
449
  quota_source_snapshot: waveSchedule.quota_source_snapshot ?? null,
441
450
  backoff_state: null,
442
451
  };
@@ -633,8 +642,11 @@ export async function prepareDispatchArtifacts(params) {
633
642
  result_path: resultPathByTaskId.get(task.task_id),
634
643
  });
635
644
  }
636
- const prompt = buildPacketPrompt({ packet, packetTasks, fileList, largeFileSection, taskSections, submitCommand });
645
+ const prompt = buildPacketPrompt({ packet, packetTasks, fileList, largeFileSection, taskSections, submitCommand, repoRoot: reviewRoot });
637
646
  await writeFile(promptPath, prompt, "utf8");
647
+ const packetWritePaths = packetTasks
648
+ .map((task) => resultPathByTaskId.get(task.task_id))
649
+ .filter((p) => p !== undefined);
638
650
  plan.push({
639
651
  packet_id: packet.packet_id,
640
652
  description: `Audit ${packet.file_paths.length} file(s), ${packet.task_ids.length} task(s), ${packet.lenses.length} lens(es) (~${packet.total_lines} lines)` +
@@ -642,6 +654,16 @@ export async function prepareDispatchArtifacts(params) {
642
654
  prompt_path: promptPath,
643
655
  complexity,
644
656
  model_hint: buildDispatchModelHint(complexity),
657
+ access: {
658
+ read_paths: [
659
+ promptPath,
660
+ ...(reviewRoot
661
+ ? packet.file_paths.map((p) => join(reviewRoot, p))
662
+ : packet.file_paths),
663
+ ],
664
+ write_paths: packetWritePaths,
665
+ forbidden_patterns: ["packet-*-result.json", "audit_result_*.json"],
666
+ },
645
667
  });
646
668
  }
647
669
  await writeJsonFile(dispatchPlanPath, plan);
@@ -1,4 +1,4 @@
1
- import { DO_NOT_TOKEN_WRAP_NOTE } from "@audit-tools/shared";
1
+ import { DO_NOT_TOKEN_WRAP_NOTE, DISPATCH_PROMPT_HANDOFF_NOTE } from "@audit-tools/shared";
2
2
  import { renderCommand } from "./args.js";
3
3
  /**
4
4
  * Token prefix the host should use to re-invoke the backend in generated
@@ -93,7 +93,7 @@ export function renderDispatchReviewPrompt(params) {
93
93
  ...dispatchDataLines,
94
94
  ...canaryLines,
95
95
  "",
96
- "Pass each `entry.prompt_path` literally to its subagent; do not load packet prompt files into this orchestrator context.",
96
+ DISPATCH_PROMPT_HANDOFF_NOTE,
97
97
  "",
98
98
  "Subagent prompt shape:",
99
99
  "",
@@ -104,7 +104,7 @@ export function renderDispatchReviewPrompt(params) {
104
104
  "",
105
105
  "Each subagent must submit its packet through the submit command printed in its packet prompt and stop after successful submission.",
106
106
  "",
107
- "**File access pre-approval:** Each dispatch plan entry includes an `access` object with `read_paths` and `write_paths`. If your host supports per-subagent file access restrictions, pre-approve those paths before launching each subagent. Workers should not access files outside their declared paths.",
107
+ "**File access pre-approval:** Each dispatch plan entry includes an `access` object with `read_paths`, `write_paths`, and `forbidden_patterns`. If your host supports per-subagent file access restrictions, pre-approve exactly `entry.access.read_paths` and `entry.access.write_paths` for each subagent. Do not grant broad workspace or task-results directory write access. Workers should not access files outside their declared paths.",
108
108
  "",
109
109
  "**After all waves complete:**",
110
110
  "",
@@ -172,10 +172,11 @@ export function renderEdgeReasoningDispatchPrompt(params) {
172
172
  "optional pass: it only rewrites the `reason` string of those edges — it never",
173
173
  "adds, removes, re-targets, or re-weights an edge.",
174
174
  "",
175
- "Dispatch exactly ONE subagent (via the `task` tool or equivalent). Hand it this",
176
- "prompt file path; do not load the file into this orchestrator context:",
175
+ "Dispatch exactly ONE subagent (via the `task` tool or equivalent).",
177
176
  "",
178
- ` ${params.promptPath}`,
177
+ DISPATCH_PROMPT_HANDOFF_NOTE,
178
+ "",
179
+ ` Prompt path: ${params.promptPath}`,
179
180
  "",
180
181
  "Subagent prompt shape:",
181
182
  "",
@@ -0,0 +1 @@
1
+ export declare function cmdResynthesize(argv: string[]): Promise<void>;
@@ -0,0 +1,50 @@
1
+ import { join } from "node:path";
2
+ import { existsSync } from "node:fs";
3
+ import { readFile, writeFile, mkdir } from "node:fs/promises";
4
+ import { getRootDir } from "./args.js";
5
+ import { normalizeExistingFindingsReport, renderAuditReportMarkdown, } from "../reporting/synthesis.js";
6
+ const AUDIT_TOOLS_DIR = ".audit-tools";
7
+ const FINDINGS_FILENAME = "audit-findings.json";
8
+ const REPORT_FILENAME = "audit-report.md";
9
+ function getFlagValue(argv, flag) {
10
+ const idx = argv.indexOf(flag);
11
+ return idx >= 0 && idx + 1 < argv.length ? argv[idx + 1] : undefined;
12
+ }
13
+ export async function cmdResynthesize(argv) {
14
+ const root = getRootDir(argv);
15
+ const auditToolsDir = join(root, AUDIT_TOOLS_DIR);
16
+ const defaultInput = join(auditToolsDir, FINDINGS_FILENAME);
17
+ const inputPath = getFlagValue(argv, "--input") ?? defaultInput;
18
+ if (!existsSync(inputPath)) {
19
+ console.error(`resynthesize: ${FINDINGS_FILENAME} not found at ${inputPath}. ` +
20
+ `Run the full audit first, or supply --input <path>.`);
21
+ process.exitCode = 1;
22
+ return;
23
+ }
24
+ const raw = await readFile(inputPath, "utf8");
25
+ let report;
26
+ try {
27
+ report = JSON.parse(raw);
28
+ }
29
+ catch (error) {
30
+ const msg = error instanceof Error ? error.message : String(error);
31
+ console.error(`resynthesize: could not parse ${inputPath}: ${msg}`);
32
+ process.exitCode = 1;
33
+ return;
34
+ }
35
+ const normalized = normalizeExistingFindingsReport(report);
36
+ const markdown = renderAuditReportMarkdown(normalized);
37
+ await mkdir(auditToolsDir, { recursive: true });
38
+ const outputFindingsPath = join(auditToolsDir, FINDINGS_FILENAME);
39
+ const outputReportPath = join(auditToolsDir, REPORT_FILENAME);
40
+ await writeFile(outputFindingsPath, JSON.stringify(normalized, null, 2), "utf8");
41
+ await writeFile(outputReportPath, markdown, "utf8");
42
+ console.log(JSON.stringify({
43
+ source: inputPath,
44
+ findings_output: outputFindingsPath,
45
+ report_output: outputReportPath,
46
+ finding_count: normalized.summary.finding_count,
47
+ work_block_count: normalized.summary.work_block_count,
48
+ contract_version: normalized.contract_version,
49
+ }, null, 2));
50
+ }
package/dist/cli.js CHANGED
@@ -23,6 +23,7 @@ import { cmdValidate } from "./cli/validateCommand.js";
23
23
  import { cmdValidateResults } from "./cli/validateResultsCommand.js";
24
24
  import { cmdRequeue } from "./cli/requeueCommand.js";
25
25
  import { cmdSynthesize } from "./cli/synthesizeCommand.js";
26
+ import { cmdResynthesize } from "./cli/resynthesizeCommand.js";
26
27
  import { cmdCleanup } from "./cli/cleanupCommand.js";
27
28
  import { cmdQuota } from "./cli/quotaCommand.js";
28
29
  import { cmdDispatchStatus } from "./cli/dispatchStatusCommand.js";
@@ -93,6 +94,9 @@ async function main(argv) {
93
94
  case "synthesize":
94
95
  await cmdSynthesize(argv);
95
96
  return;
97
+ case "resynthesize":
98
+ await cmdResynthesize(argv);
99
+ return;
96
100
  case "cleanup":
97
101
  await cmdCleanup(argv);
98
102
  return;
@@ -119,7 +123,7 @@ async function main(argv) {
119
123
  return;
120
124
  default:
121
125
  console.error(`Unknown command: ${command}`);
122
- 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, prepare-dispatch, merge-and-ingest, submit-packet, validate-result, quota, status, dispatch-status");
126
+ 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, resynthesize, cleanup, prepare-dispatch, merge-and-ingest, submit-packet, validate-result, quota, status, dispatch-status");
123
127
  process.exitCode = 1;
124
128
  }
125
129
  }
@@ -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);
@@ -2,7 +2,7 @@ import { cp, rm } from "node:fs/promises";
2
2
  import type { AuditResult, AuditTask, CoverageMatrix, RepoManifest, UnitManifest } from "../types.js";
3
3
  import type { AuditState } from "../types/auditState.js";
4
4
  import type { ArtifactMetadataManifest } from "../types/artifactMetadata.js";
5
- import type { AuditFindingsReport, FileDisposition, CriticalFlowManifest, GraphBundle, RiskRegister, SurfaceManifest } from "@audit-tools/shared";
5
+ import type { AuditFindingsReport, FileDisposition, CriticalFlowManifest, GraphBundle, RiskRegister, SurfaceManifest, IntentCheckpoint } from "@audit-tools/shared";
6
6
  import type { SynthesisNarrativeRecord } from "../types/synthesisNarrative.js";
7
7
  import type { ExternalAnalyzerResults } from "../types/externalAnalyzer.js";
8
8
  import type { FlowCoverageManifest } from "../types/flowCoverage.js";
@@ -17,6 +17,7 @@ type ArtifactPayloadMap = {
17
17
  repo_manifest: RepoManifest;
18
18
  file_disposition: FileDisposition;
19
19
  auto_fixes_applied: unknown;
20
+ intent_checkpoint: IntentCheckpoint;
20
21
  unit_manifest: UnitManifest;
21
22
  graph_bundle: GraphBundle;
22
23
  surface_manifest: SurfaceManifest;
@@ -68,6 +69,7 @@ export declare const ARTIFACT_DEFINITIONS: {
68
69
  readonly repo_manifest: ArtifactDefinition<"repo_manifest">;
69
70
  readonly file_disposition: ArtifactDefinition<"file_disposition">;
70
71
  readonly auto_fixes_applied: ArtifactDefinition<"auto_fixes_applied">;
72
+ readonly intent_checkpoint: ArtifactDefinition<"intent_checkpoint">;
71
73
  readonly unit_manifest: ArtifactDefinition<"unit_manifest">;
72
74
  readonly graph_bundle: ArtifactDefinition<"graph_bundle">;
73
75
  readonly surface_manifest: ArtifactDefinition<"surface_manifest">;
@@ -34,6 +34,7 @@ export const ARTIFACT_DEFINITIONS = {
34
34
  repo_manifest: jsonArtifact("repo_manifest.json", "intake"),
35
35
  file_disposition: jsonArtifact("file_disposition.json", "intake"),
36
36
  auto_fixes_applied: jsonArtifact("auto_fixes_applied.json", "intake"),
37
+ intent_checkpoint: jsonArtifact("intent_checkpoint.json", "intake"),
37
38
  unit_manifest: jsonArtifact("unit_manifest.json", "analysis"),
38
39
  graph_bundle: jsonArtifact("graph_bundle.json", "analysis"),
39
40
  surface_manifest: jsonArtifact("surface_manifest.json", "analysis"),
@@ -3,6 +3,7 @@ import { decideNextStep, findObligation } from "./nextStep.js";
3
3
  import { deriveAuditState } from "./state.js";
4
4
  import { computeArtifactMetadata } from "./artifactMetadata.js";
5
5
  import { runIntakeExecutor } from "./intakeExecutors.js";
6
+ import { runIntentCheckpointExecutor } from "./intentCheckpointExecutor.js";
6
7
  import { runStructureExecutor, runDesignAssessmentExecutor, runDesignReviewAutoComplete, } from "./structureExecutors.js";
7
8
  import { runPlanningExecutor } from "./planningExecutors.js";
8
9
  import { runResultIngestionExecutor, runRuntimeValidationExecutor, runRuntimeValidationUpdateExecutor, runExternalAnalyzerImportExecutor, } from "./ingestionExecutors.js";
@@ -91,6 +92,11 @@ export async function advanceAudit(bundle, options = {}) {
91
92
  run = await runIntakeExecutor(bundle, root);
92
93
  break;
93
94
  }
95
+ case "intent_checkpoint_executor": {
96
+ const root = requireRoot(options.root, "intent_checkpoint_executor");
97
+ run = await runIntentCheckpointExecutor(bundle, root, options.since);
98
+ break;
99
+ }
94
100
  case "structure_executor":
95
101
  // root is intentionally optional: present → buildGraphBundleFromFs, absent → manifest-only buildGraphBundle
96
102
  run = await runStructureExecutor(bundle, options.root);
@@ -8,6 +8,13 @@ export const ARTIFACT_DEPENDENTS_MAP = {
8
8
  "tooling_manifest.json": [
9
9
  "repo_manifest.json",
10
10
  ],
11
+ "intent_checkpoint.json": [
12
+ "coverage_matrix.json",
13
+ "audit_tasks.json",
14
+ "audit_plan_metrics.json",
15
+ "review_packets.json",
16
+ "requeue_tasks.json",
17
+ ],
11
18
  "repo_manifest.json": [
12
19
  "file_disposition.json",
13
20
  "unit_manifest.json",
@@ -26,12 +26,17 @@ function summarizeGraph(bundle) {
26
26
  return "Dependency graph is empty.";
27
27
  return `Dependency graph: ${counts.join(", ")}.`;
28
28
  }
29
- function summarizeFlows(bundle) {
29
+ function summarizeFlows(bundle, max = 15) {
30
30
  const flows = bundle.critical_flows?.flows ?? [];
31
31
  if (flows.length === 0)
32
32
  return "No critical flows identified.";
33
- const lines = flows.map((flow) => `- ${flow.name}: ${flow.paths.length} files, concerns: ${flow.concerns.join(", ") || "none"}`);
34
- return [`${flows.length} critical flows:`, ...lines].join("\n");
33
+ const shown = flows.slice(0, max);
34
+ const lines = shown.map((flow) => `- ${flow.name}: ${flow.paths.length} files, concerns: ${flow.concerns.join(", ") || "none"}`);
35
+ return [
36
+ `${flows.length} critical flows:`,
37
+ ...lines,
38
+ ...(flows.length > max ? [` ... and ${flows.length - max} more`] : []),
39
+ ].join("\n");
35
40
  }
36
41
  function summarizeRisk(bundle) {
37
42
  const items = bundle.risk_register?.items ?? [];
@@ -78,12 +83,17 @@ function buildPrioritizedReadingList(bundle, maxUnits) {
78
83
  ...lines,
79
84
  ].join("\n");
80
85
  }
81
- function summarizeSurfaces(bundle) {
86
+ function summarizeSurfaces(bundle, max = 20) {
82
87
  const surfaces = bundle.surface_manifest?.surfaces ?? [];
83
88
  if (surfaces.length === 0)
84
89
  return "No externally reachable surfaces identified.";
85
- const lines = surfaces.map((surface) => `- ${surface.id} (${surface.kind}): ${surface.entrypoint}${surface.methods?.length ? ` [${surface.methods.join(", ")}]` : ""}`);
86
- return [`${surfaces.length} surfaces:`, ...lines].join("\n");
90
+ const shown = surfaces.slice(0, max);
91
+ const lines = shown.map((surface) => `- ${surface.id} (${surface.kind}): ${surface.entrypoint}${surface.methods?.length ? ` [${surface.methods.join(", ")}]` : ""}`);
92
+ return [
93
+ `${surfaces.length} surfaces:`,
94
+ ...lines,
95
+ ...(surfaces.length > max ? [` ... and ${surfaces.length - max} more`] : []),
96
+ ].join("\n");
87
97
  }
88
98
  function summarizeFiles(bundle) {
89
99
  const files = bundle.repo_manifest?.files ?? [];
@@ -100,13 +110,15 @@ function summarizeFiles(bundle) {
100
110
  .join(", ");
101
111
  return `${files.length} files (${langSummary}).`;
102
112
  }
103
- function formatDeterministicFindings(findings) {
113
+ function formatDeterministicFindings(findings, max = 20) {
104
114
  if (findings.length === 0)
105
115
  return "No structural issues detected by deterministic analysis.";
106
- const lines = findings.map((finding) => `- [${finding.severity}] ${finding.title}: ${finding.summary}`);
116
+ const shown = findings.slice(0, max);
117
+ const lines = shown.map((finding) => `- [${finding.severity}] ${finding.title}: ${finding.summary}`);
107
118
  return [
108
119
  `${findings.length} structural findings from deterministic analysis:`,
109
120
  ...lines,
121
+ ...(findings.length > max ? [` ... and ${findings.length - max} more`] : []),
110
122
  ].join("\n");
111
123
  }
112
124
  export function renderDesignReviewPrompt(bundle, options = {}) {
@@ -118,7 +130,7 @@ export function renderDesignReviewPrompt(bundle, options = {}) {
118
130
  return [
119
131
  "# Project design review",
120
132
  "",
121
- "You are reviewing the overall design of this project. The deterministic audit pipeline has already analyzed the codebase structure. Your job is to provide qualitative, big-picture design observations that static analysis cannot produce.",
133
+ "You are reviewing the overall design of this project. The deterministic audit pipeline has already analyzed the codebase structure. Your job is to provide qualitative observations in two distinct modes: contract assessment for inferred or existing project contracts, and conceptual design critique for broader architecture ideas that static analysis cannot produce.",
122
134
  "",
123
135
  "## Project context",
124
136
  "",
@@ -160,6 +172,15 @@ export function renderDesignReviewPrompt(bundle, options = {}) {
160
172
  "",
161
173
  prioritizedReadingList,
162
174
  "",
175
+ "### Contract assessment",
176
+ "",
177
+ "- Infer existing contracts from the repository artifacts and code you inspect: invariants, trust boundaries, preconditions, postconditions, data lifecycle obligations, and critical-flow guarantees.",
178
+ "- Attack those inferred contracts with concrete counterexamples. Report evidenced gaps where the code appears to rely on an invariant or boundary that is missing, unenforced, unclear, or uncovered for a critical flow.",
179
+ "- Use contract-assessment categories such as inferred_contract_gap, trust_boundary_gap, invariant_counterexample, and critical_invariant_coverage_gap when those best describe the finding.",
180
+ "- Stay observational: do not invent a new contract DSL, write a remediation plan, remediate code, or turn the audit into an implementation pipeline.",
181
+ "",
182
+ "### Conceptual design critique",
183
+ "",
163
184
  "- **Tool and library opportunities**: third-party tools, libraries, or frameworks that would improve the project. Concrete suggestions with rationale, not generic advice.",
164
185
  "- **Architecture pattern improvements**: structural changes that would improve extensibility, testability, or maintainability. Consider whether the current abstractions match the problem domain.",
165
186
  "- **Design simplification**: areas where the design is over-engineered or where simpler alternatives would work. Conversely, areas that are under-designed for their importance.",
@@ -174,7 +195,7 @@ export function renderDesignReviewPrompt(bundle, options = {}) {
174
195
  "{",
175
196
  ' "id": "DR-001",',
176
197
  ' "title": "short descriptive title",',
177
- ' "category": "one of: tool_opportunity, architecture_pattern, design_simplification, integration, missing_capability",',
198
+ ' "category": "one of: inferred_contract_gap, trust_boundary_gap, invariant_counterexample, critical_invariant_coverage_gap, tool_opportunity, architecture_pattern, design_simplification, integration, missing_capability",',
178
199
  ' "severity": "one of: critical, high, medium, low, info",',
179
200
  ' "confidence": "one of: high, medium, low",',
180
201
  ' "lens": "architecture",',
@@ -14,6 +14,12 @@ export const EXECUTOR_REGISTRY = [
14
14
  obligation_ids: ["repo_manifest", "file_disposition"],
15
15
  description: "Create intake artifacts for repository discovery and disposition.",
16
16
  },
17
+ {
18
+ id: "intent_checkpoint_executor",
19
+ kind: "deterministic",
20
+ obligation_ids: ["intent_checkpoint_current"],
21
+ description: "Write intent_checkpoint.json with confirmed scope and intent.",
22
+ },
17
23
  {
18
24
  id: "structure_executor",
19
25
  kind: "deterministic",