auditor-lambda 0.11.2 → 0.12.0

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.
@@ -0,0 +1,13 @@
1
+ import type { ScopePreDigest } from "../orchestrator/intentCheckpointExecutor.js";
2
+ /**
3
+ * Render the host-facing prompt for the `confirm_intent` step. Shows the
4
+ * deterministically-computed scope picture and asks the host to write (or
5
+ * refine) `intent_checkpoint.json` — confirming scope/intent and optionally
6
+ * adding exclusions the disposition pass missed (the scope-pollution case),
7
+ * must-not-touch globs, and free-form audit intent that is threaded into
8
+ * worker prompts.
9
+ */
10
+ export declare function renderConfirmIntentPrompt(preDigest: ScopePreDigest, opts: {
11
+ intentCheckpointPath: string;
12
+ continueCommand: string;
13
+ }): string;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Render the host-facing prompt for the `confirm_intent` step. Shows the
3
+ * deterministically-computed scope picture and asks the host to write (or
4
+ * refine) `intent_checkpoint.json` — confirming scope/intent and optionally
5
+ * adding exclusions the disposition pass missed (the scope-pollution case),
6
+ * must-not-touch globs, and free-form audit intent that is threaded into
7
+ * worker prompts.
8
+ */
9
+ export function renderConfirmIntentPrompt(preDigest, opts) {
10
+ const dirLines = preDigest.scope_dirs
11
+ .slice(0, 20)
12
+ .map((d) => `- \`${d.dir}\` — ${d.files} file(s)`)
13
+ .join("\n") || "_(none)_";
14
+ const excludedLines = preDigest.auto_excluded.length > 0
15
+ ? preDigest.auto_excluded
16
+ .map((e) => `- \`${e.path}\` (${e.status})`)
17
+ .join("\n")
18
+ : "_(none)_";
19
+ return [
20
+ "# Confirm Audit Scope and Intent",
21
+ "",
22
+ "Before planning, confirm what this audit should cover. The scope below was",
23
+ "discovered deterministically from intake. Your job is to **confirm it** and,",
24
+ "if needed, **prune scope pollution** the automatic disposition missed (build",
25
+ "output, vendored code, fixtures, generated files, scratch directories).",
26
+ "",
27
+ `**Mode:** ${preDigest.mode}${preDigest.since ? ` (since ${preDigest.since})` : ""}`,
28
+ `**Files in scope:** ${preDigest.files_in_scope}`,
29
+ "",
30
+ "## In-scope top-level directories",
31
+ "",
32
+ dirLines,
33
+ "",
34
+ "## Already excluded (deterministic disposition)",
35
+ "",
36
+ excludedLines,
37
+ "",
38
+ "## What to do",
39
+ "",
40
+ "Write `intent_checkpoint.json` to:",
41
+ "",
42
+ ` ${opts.intentCheckpointPath}`,
43
+ "",
44
+ "Use this shape (only `scope_summary` and `intent_summary` are required; add",
45
+ "the optional fields to constrain the run):",
46
+ "",
47
+ "```json",
48
+ "{",
49
+ ' "schema_version": "intent-checkpoint/v1",',
50
+ ' "confirmed_at": "<ISO-8601 timestamp>",',
51
+ ' "confirmed_by": "host",',
52
+ ' "scope_summary": "<what is in scope>",',
53
+ ' "intent_summary": "<the goal, e.g. full-audit / security-focused>",',
54
+ ' "free_form_intent": "<optional: what to focus on; threaded into worker prompts>",',
55
+ ' "excluded_scope": [{ "path": "<path or prefix>", "reason": "<why>" }],',
56
+ ' "must_not_touch": ["<glob>"]',
57
+ "}",
58
+ "```",
59
+ "",
60
+ "- `excluded_scope` entries are pruned from planning so excluded files never",
61
+ " become audit tasks, and they are listed in the final report under",
62
+ ' "Excluded / Out-of-Scope".',
63
+ "- Leave the optional fields out to audit the full discovered scope.",
64
+ "",
65
+ `Then run: ${opts.continueCommand}`,
66
+ "",
67
+ ].join("\n");
68
+ }
@@ -124,6 +124,7 @@ export declare function buildPacketPrompt(params: {
124
124
  taskSections: string[];
125
125
  submitCommand: string;
126
126
  repoRoot?: string;
127
+ freeFormIntent?: string;
127
128
  }): string;
128
129
  /**
129
130
  * Extracts the context-budget warning loop.
@@ -303,13 +303,17 @@ 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, repoRoot } = params;
306
+ const { packet, fileList, largeFileSection, taskSections, submitCommand, repoRoot, freeFormIntent } = params;
307
307
  const largeFileMode = isIsolatedLargeFilePacket(packet);
308
+ const intentSection = freeFormIntent?.trim()
309
+ ? ["## Audit intent", freeFormIntent.trim(), ""]
310
+ : [];
308
311
  return [
309
312
  "You are a code auditor. Review this packet once, then submit exactly one result per listed task.",
310
313
  repoRoot ? `Repository root: ${repoRoot}` : "Repository root: use the root from the step contract.",
311
314
  "Set the shell/tool workdir to the repository root when running backend commands.",
312
315
  "",
316
+ ...intentSection,
313
317
  "## Packet",
314
318
  `packet_id: ${packet.packet_id}`,
315
319
  `task_count: ${packet.task_ids.length}`,
@@ -642,7 +646,7 @@ export async function prepareDispatchArtifacts(params) {
642
646
  result_path: resultPathByTaskId.get(task.task_id),
643
647
  });
644
648
  }
645
- const prompt = buildPacketPrompt({ packet, packetTasks, fileList, largeFileSection, taskSections, submitCommand, repoRoot: reviewRoot });
649
+ const prompt = buildPacketPrompt({ packet, packetTasks, fileList, largeFileSection, taskSections, submitCommand, repoRoot: reviewRoot, freeFormIntent: bundle.intent_checkpoint?.free_form_intent });
646
650
  await writeFile(promptPath, prompt, "utf8");
647
651
  const packetWritePaths = packetTasks
648
652
  .map((task) => resultPathByTaskId.get(task.task_id))
@@ -10,6 +10,7 @@ import { deriveAuditState } from "../orchestrator/state.js";
10
10
  import { checkFileIntegrity } from "../orchestrator/fileIntegrity.js";
11
11
  import { buildEdgeReasoningPrompt, collectLowConfidenceEdges, edgeReasoningContentHash, } from "../orchestrator/edgeReasoning.js";
12
12
  import { renderDesignReviewPrompt } from "../orchestrator/designReviewPrompt.js";
13
+ import { computeScopePreDigest } from "../orchestrator/intentCheckpointExecutor.js";
13
14
  import { renderSynthesisNarrativePrompt } from "../reporting/synthesisNarrativePrompt.js";
14
15
  import { buildPathLookup } from "../extractors/graph.js";
15
16
  import { buildDispositionMap } from "../extractors/disposition.js";
@@ -21,6 +22,7 @@ import { runAuditStep } from "./auditStep.js";
21
22
  import { writeHandoffOnly, ensureSemanticReviewRun, persistConfigErrorHandoff, } from "./reviewRun.js";
22
23
  import { buildPendingAuditTasks } from "./dispatch.js";
23
24
  import { renderSemanticReviewStep } from "./semanticReviewStep.js";
25
+ import { renderConfirmIntentPrompt } from "./confirmIntentStep.js";
24
26
  import { writeCurrentStep } from "./steps.js";
25
27
  import { nextStepCommand, renderAnalyzerInstallPrompt, renderBlockedStepPrompt, renderEdgeReasoningDispatchPrompt, renderEdgeReasoningStepPrompt, renderPresentReportPrompt, } from "./prompts.js";
26
28
  import { getArtifactsDir, getFlag, getHostMaxActiveSubagents, getMaxRuns, getOptionalBooleanFlag, getRootDir, getTimeoutMs, resolveHostDispatchCapability, warnIfNotGitRepo, } from "./args.js";
@@ -344,6 +346,13 @@ async function runDeterministicForNextStep(params) {
344
346
  continue;
345
347
  return branch.result;
346
348
  }
349
+ // Confirm-intent host step: when the checkpoint is missing, hand control to
350
+ // the host to confirm scope/intent. The host writes intent_checkpoint.json
351
+ // (detected by deriveAuditState on re-invocation), so there is no incoming
352
+ // artifact to consume — emit the step directly.
353
+ if (decision.selected_executor === "intent_checkpoint_executor") {
354
+ return { kind: "confirm_intent", state, bundle };
355
+ }
347
356
  if (isHostDelegationExecutor(decision.selected_executor ?? "")) {
348
357
  return {
349
358
  kind: "semantic_review",
@@ -532,6 +541,29 @@ export async function cmdNextStep(argv) {
532
541
  console.log(JSON.stringify(step, null, 2));
533
542
  return;
534
543
  }
544
+ if (result.kind === "confirm_intent") {
545
+ const intentCheckpointPath = join(artifactsDir, "intent_checkpoint.json");
546
+ const continueCommand = nextStepCommand(root, artifactsDir);
547
+ const preDigest = computeScopePreDigest(result.bundle, root, getFlag(argv, "--since"));
548
+ const step = await writeCurrentStep({
549
+ artifactsDir,
550
+ stepKind: "confirm_intent",
551
+ status: "ready",
552
+ runId: null,
553
+ allowedCommands: [continueCommand],
554
+ stopCondition: "Write intent_checkpoint.json with the confirmed scope and intent, then run next-step.",
555
+ repoRoot: root,
556
+ artifactPaths: {
557
+ intent_checkpoint: intentCheckpointPath,
558
+ },
559
+ prompt: renderConfirmIntentPrompt(preDigest, {
560
+ intentCheckpointPath,
561
+ continueCommand,
562
+ }),
563
+ });
564
+ console.log(JSON.stringify(step, null, 2));
565
+ return;
566
+ }
535
567
  if (result.kind === "analyzer_install") {
536
568
  const decisionsPath = join(artifactsDir, "incoming", "analyzer-decisions.json");
537
569
  await mkdir(join(artifactsDir, "incoming"), { recursive: true });
@@ -1,7 +1,7 @@
1
1
  import type { StepStatus } from "@audit-tools/shared";
2
2
  import type { AccessDeclaration } from "../types/workerSession.js";
3
3
  export declare const STEP_CONTRACT_VERSION = "audit-code-step/v1alpha1";
4
- export type StepKind = "dispatch_review" | "single_task_fallback" | "design_review" | "analyzer_install" | "edge_reasoning" | "edge_reasoning_dispatch" | "synthesis_narrative" | "present_report" | "blocked";
4
+ export type StepKind = "dispatch_review" | "single_task_fallback" | "design_review" | "confirm_intent" | "analyzer_install" | "edge_reasoning" | "edge_reasoning_dispatch" | "synthesis_narrative" | "present_report" | "blocked";
5
5
  /**
6
6
  * Lightweight run-level orientation surfaced in the step contract so a host
7
7
  * resuming an in-flight audit knows where it stands without reading artifacts.
@@ -3,7 +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
+ import { runIntentCheckpointAutoComplete } from "./intentCheckpointExecutor.js";
7
7
  import { runStructureExecutor, runDesignAssessmentExecutor, runDesignReviewAutoComplete, } from "./structureExecutors.js";
8
8
  import { runPlanningExecutor } from "./planningExecutors.js";
9
9
  import { runResultIngestionExecutor, runRuntimeValidationExecutor, runRuntimeValidationUpdateExecutor, runExternalAnalyzerImportExecutor, } from "./ingestionExecutors.js";
@@ -94,7 +94,7 @@ export async function advanceAudit(bundle, options = {}) {
94
94
  }
95
95
  case "intent_checkpoint_executor": {
96
96
  const root = requireRoot(options.root, "intent_checkpoint_executor");
97
- run = await runIntentCheckpointExecutor(bundle, root, options.since);
97
+ run = runIntentCheckpointAutoComplete(bundle, root, options.since);
98
98
  break;
99
99
  }
100
100
  case "structure_executor":
@@ -16,9 +16,9 @@ export const EXECUTOR_REGISTRY = [
16
16
  },
17
17
  {
18
18
  id: "intent_checkpoint_executor",
19
- kind: "deterministic",
19
+ kind: "host_delegation",
20
20
  obligation_ids: ["intent_checkpoint_current"],
21
- description: "Write intent_checkpoint.json with confirmed scope and intent.",
21
+ description: "Pause for the host to confirm scope and intent (the confirm_intent step writes intent_checkpoint.json); deterministic auto-complete writes a default full-scope checkpoint when run headless.",
22
22
  },
23
23
  {
24
24
  id: "structure_executor",
@@ -1,3 +1,33 @@
1
1
  import type { ArtifactBundle } from "../io/artifacts.js";
2
2
  import type { ExecutorRunResult } from "./executorResult.js";
3
- export declare function runIntentCheckpointExecutor(bundle: ArtifactBundle, root: string, since?: string): Promise<ExecutorRunResult>;
3
+ /**
4
+ * Deterministic pre-digest of the audit scope, shown to the host in the
5
+ * `confirm_intent` step and used to seed the headless auto-complete checkpoint.
6
+ * Everything here is computed deterministically from the intake artifacts; the
7
+ * host uses it to confirm the discovered scope and add any exclusions the
8
+ * disposition pass missed (the scope-pollution case).
9
+ */
10
+ export interface ScopePreDigest {
11
+ mode: "full" | "delta";
12
+ since: string | null;
13
+ files_in_scope: number;
14
+ /** Top-level directories of in-scope files, with file counts (desc). */
15
+ scope_dirs: Array<{
16
+ dir: string;
17
+ files: number;
18
+ }>;
19
+ /** A sample of files already excluded by the deterministic disposition pass. */
20
+ auto_excluded: Array<{
21
+ path: string;
22
+ status: string;
23
+ }>;
24
+ }
25
+ export declare function computeScopePreDigest(bundle: ArtifactBundle, root: string, since?: string): ScopePreDigest;
26
+ /**
27
+ * Headless deterministic fallback for the intent checkpoint — the analog of
28
+ * `runDesignReviewAutoComplete`. The conversation-first flow instead emits a
29
+ * `confirm_intent` host step (see `cli/confirmIntentStep.ts`); this runs only
30
+ * when `advanceAudit` is driven headlessly with no host to confirm scope,
31
+ * writing a default full-scope checkpoint so the pipeline can proceed.
32
+ */
33
+ export declare function runIntentCheckpointAutoComplete(bundle: ArtifactBundle, root: string, since?: string): ExecutorRunResult;
@@ -1,29 +1,60 @@
1
1
  import { resolveAuditScope } from "./scope.js";
2
2
  import { isAuditExcludedStatus } from "../extractors/disposition.js";
3
- export async function runIntentCheckpointExecutor(bundle, root, since) {
3
+ const AUTO_EXCLUDED_SAMPLE_LIMIT = 25;
4
+ export function computeScopePreDigest(bundle, root, since) {
4
5
  const scope = resolveAuditScope({ root, since, bundle });
5
- let filesInScope = 0;
6
+ const dispositionFiles = bundle.file_disposition?.files ?? [];
7
+ const auditable = dispositionFiles.filter((file) => !isAuditExcludedStatus(file.status));
8
+ const excluded = dispositionFiles.filter((file) => isAuditExcludedStatus(file.status));
9
+ let inScopePaths;
6
10
  if (scope.mode === "delta") {
7
- filesInScope = scope.seed_files.length + scope.expanded_files.length;
11
+ inScopePaths = [...scope.seed_files, ...scope.expanded_files];
12
+ }
13
+ else if (auditable.length > 0) {
14
+ inScopePaths = auditable.map((file) => file.path);
8
15
  }
9
16
  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;
17
+ inScopePaths = bundle.repo_manifest?.files.map((file) => file.path) ?? [];
18
+ }
19
+ const dirCounts = new Map();
20
+ for (const path of inScopePaths) {
21
+ const top = path.split(/[\\/]/)[0] || ".";
22
+ dirCounts.set(top, (dirCounts.get(top) ?? 0) + 1);
13
23
  }
24
+ const scope_dirs = [...dirCounts.entries()]
25
+ .map(([dir, files]) => ({ dir, files }))
26
+ .sort((a, b) => b.files - a.files);
27
+ return {
28
+ mode: scope.mode === "delta" ? "delta" : "full",
29
+ since: scope.since ?? null,
30
+ files_in_scope: inScopePaths.length,
31
+ scope_dirs,
32
+ auto_excluded: excluded
33
+ .slice(0, AUTO_EXCLUDED_SAMPLE_LIMIT)
34
+ .map((file) => ({ path: file.path, status: file.status })),
35
+ };
36
+ }
37
+ /**
38
+ * Headless deterministic fallback for the intent checkpoint — the analog of
39
+ * `runDesignReviewAutoComplete`. The conversation-first flow instead emits a
40
+ * `confirm_intent` host step (see `cli/confirmIntentStep.ts`); this runs only
41
+ * when `advanceAudit` is driven headlessly with no host to confirm scope,
42
+ * writing a default full-scope checkpoint so the pipeline can proceed.
43
+ */
44
+ export function runIntentCheckpointAutoComplete(bundle, root, since) {
45
+ const preDigest = computeScopePreDigest(bundle, root, since);
14
46
  const intent = {
15
47
  schema_version: "intent-checkpoint/v1",
16
48
  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
49
  confirmed_by: "host",
50
+ scope_summary: `Root: ${root}${preDigest.since ? ` (since ${preDigest.since})` : ""}, files in scope: ${preDigest.files_in_scope}`,
51
+ intent_summary: preDigest.mode === "delta"
52
+ ? `delta-audit since ${preDigest.since}`
53
+ : "full-audit",
20
54
  };
21
55
  return {
22
- updated: {
23
- ...bundle,
24
- intent_checkpoint: intent,
25
- },
56
+ updated: { ...bundle, intent_checkpoint: intent },
26
57
  artifacts_written: ["intent_checkpoint.json"],
27
- progress_summary: `Recorded scope/intent checkpoint: ${intent.scope_summary} (${intent.intent_summary}).`,
58
+ progress_summary: `Auto-completed scope/intent checkpoint (headless): ${intent.scope_summary} (${intent.intent_summary}).`,
28
59
  };
29
60
  }
@@ -9,6 +9,7 @@ export const PRIORITY = [
9
9
  "graph_enrichment_current",
10
10
  "design_assessment_current",
11
11
  "design_review_completed",
12
+ "intent_checkpoint_current",
12
13
  "planning_artifacts",
13
14
  "audit_tasks_completed",
14
15
  "audit_results_ingested",
@@ -1,5 +1,5 @@
1
1
  import { initializeCoverageFromPlan } from "./planning.js";
2
- import { applyScopeToCoverage, fullAuditScope } from "./scope.js";
2
+ import { applyIntentExclusionsToCoverage, applyScopeToCoverage, fullAuditScope, } from "./scope.js";
3
3
  import { buildFlowCoverage } from "./flowCoverage.js";
4
4
  import { buildRequeuePayload } from "./requeueCommand.js";
5
5
  import { buildRuntimeValidationTasks, discoverRuntimeValidationCommand, mergeRuntimeValidationReport, } from "./runtimeValidation.js";
@@ -25,6 +25,9 @@ export async function runPlanningExecutor(bundle, root, lineIndex = {}, sizeInde
25
25
  // Delta scope: only seed + expanded files stay pending; the rest inherit prior
26
26
  // completion or are excluded from this run. Full scope is a no-op.
27
27
  applyScopeToCoverage(coverage, resolvedScope, bundle.coverage_matrix);
28
+ // Layer the host-confirmed intent exclusions on top of disposition + scope so
29
+ // user-pruned scope pollution never becomes an audit task.
30
+ const intentExcludedPaths = applyIntentExclusionsToCoverage(coverage, bundle.intent_checkpoint?.excluded_scope);
28
31
  const flowCoverage = buildFlowCoverage(bundle.critical_flows, coverage);
29
32
  const runtimeCommand = await discoverRuntimeValidationCommand(root);
30
33
  const runtimeValidationTasks = buildRuntimeValidationTasks({
@@ -103,6 +106,9 @@ export async function runPlanningExecutor(bundle, root, lineIndex = {}, sizeInde
103
106
  (skippedTrivialPaths.length > 0
104
107
  ? ` Skipped ${skippedTrivialPaths.length} trivial path${skippedTrivialPaths.length === 1 ? "" : "s"} from semantic review.`
105
108
  : "") +
109
+ (intentExcludedPaths.length > 0
110
+ ? ` Excluded ${intentExcludedPaths.length} path${intentExcludedPaths.length === 1 ? "" : "s"} per the intent checkpoint.`
111
+ : "") +
106
112
  (runtimeCommand
107
113
  ? ` Runtime validation will use: ${runtimeCommand.join(" ")}.`
108
114
  : " No deterministic runtime validation command was discovered."),
@@ -60,3 +60,15 @@ export declare function resolveAuditScope(input: ResolveAuditScopeInput): AuditS
60
60
  * exclusions (non-auditable/trivial) are left untouched. A full scope is a no-op.
61
61
  */
62
62
  export declare function applyScopeToCoverage(coverage: CoverageMatrix, scope: AuditScopeManifest, priorCoverage?: CoverageMatrix): CoverageMatrix;
63
+ /**
64
+ * Apply the intent checkpoint's `excluded_scope` to a coverage matrix: any file
65
+ * whose path matches an exclusion (exact or directory-prefix) is marked excluded
66
+ * so it never becomes an audit task. The user's exclusions layer on top of the
67
+ * deterministic disposition — they catch scope pollution the automatic pass
68
+ * missed. Returns the newly-excluded paths (for the run summary / report); a
69
+ * checkpoint with no exclusions is a no-op.
70
+ */
71
+ export declare function applyIntentExclusionsToCoverage(coverage: CoverageMatrix, excludedScope: Array<{
72
+ path: string;
73
+ reason: string;
74
+ }> | undefined): string[];
@@ -225,3 +225,36 @@ export function applyScopeToCoverage(coverage, scope, priorCoverage) {
225
225
  }
226
226
  return coverage;
227
227
  }
228
+ function pathMatchesExclusion(filePath, entryPath) {
229
+ const f = filePath.replace(/\\/g, "/");
230
+ const p = entryPath.replace(/\\/g, "/").replace(/\/+$/, "");
231
+ if (!p)
232
+ return false;
233
+ return f === p || f.startsWith(`${p}/`);
234
+ }
235
+ /**
236
+ * Apply the intent checkpoint's `excluded_scope` to a coverage matrix: any file
237
+ * whose path matches an exclusion (exact or directory-prefix) is marked excluded
238
+ * so it never becomes an audit task. The user's exclusions layer on top of the
239
+ * deterministic disposition — they catch scope pollution the automatic pass
240
+ * missed. Returns the newly-excluded paths (for the run summary / report); a
241
+ * checkpoint with no exclusions is a no-op.
242
+ */
243
+ export function applyIntentExclusionsToCoverage(coverage, excludedScope) {
244
+ if (!excludedScope || excludedScope.length === 0)
245
+ return [];
246
+ const excluded = [];
247
+ for (const file of coverage.files) {
248
+ if (file.audit_status === "excluded")
249
+ continue;
250
+ if (excludedScope.some((entry) => pathMatchesExclusion(file.path, entry.path))) {
251
+ file.required_lenses = [];
252
+ file.completed_lenses = [];
253
+ file.unit_ids = [];
254
+ file.audit_status = "excluded";
255
+ file.classification_status = "out_of_scope_intent";
256
+ excluded.push(file.path);
257
+ }
258
+ }
259
+ return excluded;
260
+ }
@@ -32,6 +32,7 @@ export function deriveAuditState(bundle) {
32
32
  obligations.push(obligation("graph_enrichment_current", staleOrSatisfied(staleArtifacts, ["analyzer_capability.json"], has(bundle.analyzer_capability))));
33
33
  obligations.push(obligation("design_assessment_current", staleOrSatisfied(staleArtifacts, ["design_assessment.json"], has(bundle.design_assessment))));
34
34
  obligations.push(obligation("design_review_completed", bundle.design_assessment?.reviewed ? "satisfied" : "missing"));
35
+ obligations.push(obligation("intent_checkpoint_current", staleOrSatisfied(staleArtifacts, ["intent_checkpoint.json"], has(bundle.intent_checkpoint))));
35
36
  const planningReady = has(bundle.coverage_matrix) &&
36
37
  has(bundle.flow_coverage) &&
37
38
  has(bundle.runtime_validation_tasks) &&
@@ -1,6 +1,6 @@
1
1
  import { applyNarrative, buildAuditFindingsReport, buildAuditReportModel, renderAuditReportMarkdown, } from "../reporting/synthesis.js";
2
2
  function buildBaseFindingsReport(bundle, results) {
3
- return buildAuditFindingsReport(buildAuditReportModel({
3
+ const report = buildAuditFindingsReport(buildAuditReportModel({
4
4
  results,
5
5
  unitManifest: bundle.unit_manifest,
6
6
  graphBundle: bundle.graph_bundle,
@@ -11,6 +11,12 @@ function buildBaseFindingsReport(bundle, results) {
11
11
  externalAnalyzerResults: bundle.external_analyzer_results,
12
12
  designAssessment: bundle.design_assessment,
13
13
  }));
14
+ // Record the host-confirmed exclusions in the machine contract so omissions
15
+ // are explicit and machine-readable, not just rendered in the markdown.
16
+ const excludedScope = bundle.intent_checkpoint?.excluded_scope;
17
+ return excludedScope && excludedScope.length > 0
18
+ ? { ...report, excluded_scope: excludedScope }
19
+ : report;
14
20
  }
15
21
  export function runSynthesisExecutor(bundle, results) {
16
22
  const finalResults = results ?? bundle.audit_results ?? [];
@@ -28,7 +34,7 @@ export function runSynthesisExecutor(bundle, results) {
28
34
  updated: {
29
35
  ...bundle,
30
36
  audit_findings: findings,
31
- audit_report: renderAuditReportMarkdown(findings, { scope: bundle.scope }),
37
+ audit_report: renderAuditReportMarkdown(findings, { scope: bundle.scope, intent_checkpoint: bundle.intent_checkpoint }),
32
38
  },
33
39
  artifacts_written: ["audit-findings.json", "audit-report.md"],
34
40
  progress_summary: `Rendered deterministic audit report and canonical findings for ${finalResults.length} audit result entries.`,
@@ -78,7 +84,7 @@ export function runSynthesisNarrativeExecutor(bundle, narrative) {
78
84
  updated: {
79
85
  ...bundle,
80
86
  audit_findings: enriched,
81
- audit_report: renderAuditReportMarkdown(enriched, { scope: bundle.scope }),
87
+ audit_report: renderAuditReportMarkdown(enriched, { scope: bundle.scope, intent_checkpoint: bundle.intent_checkpoint }),
82
88
  synthesis_narrative: record,
83
89
  },
84
90
  artifacts_written: [
@@ -1,5 +1,6 @@
1
1
  import type { AuditResult, CoverageMatrix, Finding, UnitManifest } from "../types.js";
2
2
  import type { AuditScopeManifest } from "../types/auditScope.js";
3
+ import type { IntentCheckpoint } from "@audit-tools/shared";
3
4
  import type { DesignAssessment } from "../types/designAssessment.js";
4
5
  import type { ExternalAnalyzerResults } from "../types/externalAnalyzer.js";
5
6
  import type { AuditFindingsReport, CriticalFlowManifest, Finding as SharedFinding, FindingTheme, GraphBundle, SynthesisNarrative } from "@audit-tools/shared";
@@ -78,6 +79,11 @@ export interface RenderAuditReportOptions {
78
79
  * populates this from `agent-feedback.jsonl` is wired separately.
79
80
  */
80
81
  reflections?: AgentReflection[];
82
+ /**
83
+ * The accepted intent checkpoint; its `excluded_scope` is surfaced in an
84
+ * "Excluded / Out-of-Scope" section so omissions are explicit in the report.
85
+ */
86
+ intent_checkpoint?: IntentCheckpoint;
81
87
  }
82
88
  export declare function renderAuditReportMarkdown(report: RenderableAuditReport, options?: RenderAuditReportOptions): string;
83
89
  /**
@@ -217,6 +217,15 @@ export function renderAuditReportMarkdown(report, options = {}) {
217
217
  }
218
218
  }
219
219
  lines.push(...renderProcessFeedbackSection(options.reflections ?? []));
220
+ const excludedScope = options.intent_checkpoint?.excluded_scope ?? [];
221
+ if (excludedScope.length > 0) {
222
+ lines.push("## Excluded / Out-of-Scope", "");
223
+ lines.push(`${excludedScope.length} path(s) were excluded from this audit per the intent checkpoint:`, "");
224
+ for (const entry of excludedScope) {
225
+ lines.push(`- \`${entry.path}\` — ${entry.reason}`);
226
+ }
227
+ lines.push("");
228
+ }
220
229
  lines.push("## Scope and Coverage", "");
221
230
  const scope = options.scope;
222
231
  if (scope && scope.mode === "delta") {
@@ -28,6 +28,15 @@ export function validateArtifactBundle(bundle) {
28
28
  "budget",
29
29
  ]));
30
30
  }
31
+ if (bundle.intent_checkpoint) {
32
+ issues.push(...requireKeys(bundle.intent_checkpoint, "intent_checkpoint", [
33
+ "schema_version",
34
+ "confirmed_at",
35
+ "confirmed_by",
36
+ "scope_summary",
37
+ "intent_summary",
38
+ ]));
39
+ }
31
40
  if (bundle.graph_bundle) {
32
41
  issues.push(...requireKeys(bundle.graph_bundle, "graph_bundle", ["graphs"]));
33
42
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auditor-lambda",
3
- "version": "0.11.2",
3
+ "version": "0.12.0",
4
4
  "private": false,
5
5
  "description": "Portable hybrid code-auditing framework for arbitrary repositories.",
6
6
  "type": "module",
@@ -0,0 +1,77 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "intent_checkpoint.schema.json",
4
+ "title": "Intent Checkpoint",
5
+ "description": "intent_checkpoint.json — the accepted scope and intent for a run, confirmed by the host (or auto-completed headlessly) before planning. Single-sourced across audit-code and remediate-code: audit-code consumes `excluded_scope`/`must_not_touch`/`free_form_intent` to prune planning and thread intent into worker prompts; remediate-code additionally uses `filters` to narrow findings. Sits upstream of the planning artifacts in the staleness DAG.",
6
+ "type": "object",
7
+ "required": [
8
+ "schema_version",
9
+ "confirmed_at",
10
+ "confirmed_by",
11
+ "scope_summary",
12
+ "intent_summary"
13
+ ],
14
+ "properties": {
15
+ "schema_version": {
16
+ "const": "intent-checkpoint/v1",
17
+ "description": "Contract version marker."
18
+ },
19
+ "confirmed_at": {
20
+ "type": "string",
21
+ "format": "date-time",
22
+ "description": "When the scope/intent was confirmed."
23
+ },
24
+ "confirmed_by": {
25
+ "const": "host",
26
+ "description": "Who confirmed the checkpoint (the host, or the headless harness acting as host)."
27
+ },
28
+ "scope_summary": {
29
+ "type": "string",
30
+ "description": "Human-readable description of the confirmed scope."
31
+ },
32
+ "intent_summary": {
33
+ "type": "string",
34
+ "description": "Human-readable description of the goal (e.g. full-audit / delta / security-focused)."
35
+ },
36
+ "free_form_intent": {
37
+ "type": "string",
38
+ "description": "Free-form intent threaded into worker/dispatch prompts."
39
+ },
40
+ "excluded_scope": {
41
+ "type": "array",
42
+ "description": "Paths intentionally excluded from the run; pruned from planning and listed in the report.",
43
+ "items": {
44
+ "type": "object",
45
+ "required": ["path", "reason"],
46
+ "properties": {
47
+ "path": {
48
+ "type": "string",
49
+ "description": "Path or path prefix to exclude."
50
+ },
51
+ "reason": {
52
+ "type": "string",
53
+ "description": "Why the path is excluded."
54
+ }
55
+ },
56
+ "additionalProperties": false
57
+ }
58
+ },
59
+ "must_not_touch": {
60
+ "type": "array",
61
+ "items": { "type": "string" },
62
+ "description": "Path globs that must never be written to."
63
+ },
64
+ "filters": {
65
+ "type": "object",
66
+ "description": "Remediate-only finding filters; audit-code ignores these.",
67
+ "properties": {
68
+ "severity": { "type": "array", "items": { "type": "string" } },
69
+ "lenses": { "type": "array", "items": { "type": "string" } },
70
+ "packages": { "type": "array", "items": { "type": "string" } },
71
+ "themes": { "type": "array", "items": { "type": "string" } }
72
+ },
73
+ "additionalProperties": false
74
+ }
75
+ },
76
+ "additionalProperties": false
77
+ }