auditor-lambda 0.7.0 → 0.8.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.
Files changed (38) hide show
  1. package/README.md +0 -21
  2. package/dist/cli/auditStep.js +7 -1
  3. package/dist/cli.d.ts +0 -1
  4. package/dist/cli.js +0 -2
  5. package/dist/extractors/graph.js +12 -2
  6. package/dist/io/artifacts.d.ts +3 -1
  7. package/dist/io/artifacts.js +18 -2
  8. package/dist/orchestrator/advance.js +2 -1
  9. package/dist/orchestrator/artifactFreshness.js +12 -2
  10. package/dist/orchestrator/autoFixExecutor.d.ts +1 -1
  11. package/dist/orchestrator/autoFixExecutor.js +10 -0
  12. package/dist/orchestrator/executorResult.d.ts +12 -0
  13. package/dist/orchestrator/executorResult.js +1 -0
  14. package/dist/orchestrator/fileIntegrity.d.ts +1 -0
  15. package/dist/orchestrator/fileIntegrity.js +12 -3
  16. package/dist/orchestrator/graphEnrichmentExecutor.d.ts +1 -1
  17. package/dist/orchestrator/graphEnrichmentExecutor.js +3 -1
  18. package/dist/orchestrator/internalExecutors.d.ts +1 -18
  19. package/dist/orchestrator/internalExecutors.js +1 -158
  20. package/dist/orchestrator/reviewPacketGraph.d.ts +31 -0
  21. package/dist/orchestrator/reviewPacketGraph.js +691 -0
  22. package/dist/orchestrator/reviewPackets.d.ts +2 -15
  23. package/dist/orchestrator/reviewPackets.js +3 -685
  24. package/dist/orchestrator/runtimeCommand.d.ts +11 -0
  25. package/dist/orchestrator/runtimeCommand.js +79 -0
  26. package/dist/orchestrator/scope.js +1 -1
  27. package/dist/orchestrator/syntaxResolutionExecutor.d.ts +1 -1
  28. package/dist/orchestrator/synthesisExecutors.d.ts +12 -0
  29. package/dist/orchestrator/synthesisExecutors.js +90 -0
  30. package/docs/development.md +35 -139
  31. package/docs/history.md +26 -0
  32. package/docs/product.md +41 -108
  33. package/package.json +1 -1
  34. package/schemas/audit_findings.schema.json +3 -2
  35. package/schemas/dispatch_quota.schema.json +2 -0
  36. package/schemas/external_analyzer_results.schema.json +2 -2
  37. package/schemas/repo_manifest.schema.json +1 -1
  38. package/docs/handoff.md +0 -204
package/README.md CHANGED
@@ -215,26 +215,6 @@ Optional backend config:
215
215
  - use `provider: "auto"` only when you want best-effort routing across installed backends
216
216
  - treat explicit provider bridges as compatibility fallback, not as the intended owner of semantic review
217
217
 
218
- ## Current Development Focus
219
-
220
- The next implementation work is tracked in:
221
-
222
- - `docs/product.md`
223
- - `docs/development.md`
224
- - `docs/handoff.md`
225
-
226
- The short version is:
227
-
228
- - keep the packet dispatch workflow verified in real host environments
229
- - make graph-informed packetization observable before adding more ecosystem-specific parsers
230
- - consolidate graph extraction and exercise generic ownership hints for analyzer-supplied module roots
231
- - add deterministic Python import, package, and test/source graph support as a core language path
232
- - use semantic/NLP-style affinity only as low-authority context unless deterministic graph evidence supports it
233
- - keep generated Codex, Claude Desktop, OpenCode, VS Code, and Antigravity guidance aligned with real host behavior
234
- - tighten the repo-local MCP-first bootstrap where host smoke tests expose friction
235
- - polish provider-assisted continuation and failure guidance
236
- - keep schema contracts and examples easy for workers and host integrations to validate
237
-
238
218
  ## Build And Test
239
219
 
240
220
  ```bash
@@ -253,6 +233,5 @@ For GitHub Actions publication and npm Trusted Publishing setup, see `docs/relea
253
233
  - `docs/contracts.md`
254
234
  - `docs/release.md`
255
235
  - `docs/development.md`
256
- - `docs/handoff.md`
257
236
  - `docs/history.md`
258
237
  - `skills/audit-code/SKILL.md`
@@ -84,7 +84,13 @@ export async function runAuditStep(options) {
84
84
  opentoken: options.opentoken,
85
85
  runLogger,
86
86
  });
87
- await writeCoreArtifacts(options.artifactsDir, result.updated_bundle);
87
+ // Prune: result.updated_bundle is the full accumulated bundle, so an artifact
88
+ // an executor cleared to `undefined` must be removed from disk (not left to
89
+ // reload as a stale "present" artifact). Safe only because this is the
90
+ // authoritative per-step persist.
91
+ await writeCoreArtifacts(options.artifactsDir, result.updated_bundle, {
92
+ prune: true,
93
+ });
88
94
  const archivedPendingResults = await maybeArchiveLegacyPendingResults(options.auditResultsPath);
89
95
  if (archivedPendingResults) {
90
96
  result.progress_summary +=
package/dist/cli.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- export { resolveHostDispatchCapability, DIRECT_CLI_DEFAULTS, getFlag, hasFlag, getOptionalBooleanFlag, getArtifactsDir, getRootDir, getBatchResultsDir, getMaxRuns, getAgentBatchSize, getParallelWorkers, getTimeoutMs, chunkArray, getUiMode, looksLikeCliFlag, countLines, warnIfNotGitRepo, } from "./cli/args.js";
2
1
  import { getFlag, hasFlag, getArtifactsDir, getRootDir, warnIfNotGitRepo, getBatchResultsDir, getMaxRuns, getAgentBatchSize, getParallelWorkers, getTimeoutMs, chunkArray, getUiMode, looksLikeCliFlag, countLines } from "./cli/args.js";
3
2
  export declare const cliTestUtils: {
4
3
  defaults: {
package/dist/cli.js CHANGED
@@ -24,8 +24,6 @@ import { getSessionConfigPath, loadSessionConfig, readSessionConfigFile, } from
24
24
  import { clearDispatchFiles, ensureSupervisorDirs, } from "./io/runArtifacts.js";
25
25
  import { runAuditCodeMcpServer } from "./mcp/server.js";
26
26
  import { scheduleWave, buildProviderModelKey, readQuotaState, resolveLimits, resolveHostActiveSubagentLimit, probeProvider, computeMaxSafeConcurrency, getQuotaStatePath, lookupDiscoveredLimits, setQuotaStateDir, } from "./quota/index.js";
27
- // Re-exports from extracted modules
28
- export { resolveHostDispatchCapability, DIRECT_CLI_DEFAULTS, getFlag, hasFlag, getOptionalBooleanFlag, getArtifactsDir, getRootDir, getBatchResultsDir, getMaxRuns, getAgentBatchSize, getParallelWorkers, getTimeoutMs, chunkArray, getUiMode, looksLikeCliFlag, countLines, warnIfNotGitRepo, } from "./cli/args.js";
29
27
  import { DIRECT_CLI_DEFAULTS, getFlag, hasFlag, fromBase64Url, taskResultPath, isCanonicalResultFilename, readStdinText, getArtifactsDir, getRootDir, warnIfNotGitRepo, getBatchResultsDir, getMaxRuns, getAgentBatchSize, getParallelWorkers, getTimeoutMs, getExplicitProvider, getHostModel, getHostMaxActiveSubagents, getQuotaProbeMode, resolveRunProviderName, chunkArray, getUiMode, looksLikeCliFlag, countLines, } from "./cli/args.js";
30
28
  import { WORKER_RESULT_CONTRACT_VERSION, buildWorkerResult, formatAuditResultValidationError, } from "./cli/workerResult.js";
31
29
  import { DISPATCH_RESULT_MAP_FILENAME, ACTIVE_DISPATCH_FILENAME, resolveRunScopedArg, loadDispatchResultMap, entriesByTaskId, buildPendingAuditTasks, prepareDispatchArtifacts, } from "./cli/dispatch.js";
@@ -262,6 +262,7 @@ export async function buildGraphBundleFromFs(repoManifest, root, disposition, op
262
262
  const rootPath = resolve(root);
263
263
  const dispositionMap = buildDispositionMap(disposition);
264
264
  const fileContents = {};
265
+ const skippedFiles = [];
265
266
  await Promise.all(repoManifest.files.map(async (file) => {
266
267
  const status = dispositionMap.get(file.path);
267
268
  if ((status && isAuditExcludedStatus(status)) ||
@@ -277,10 +278,19 @@ export async function buildGraphBundleFromFs(repoManifest, root, disposition, op
277
278
  try {
278
279
  fileContents[file.path] = await readFile(absolutePath, "utf8");
279
280
  }
280
- catch {
281
- // Best-effort graph extraction should not block structure planning.
281
+ catch (e) {
282
+ // Best-effort graph extraction should not block structure planning,
283
+ // but record the skip so a coverage gap is observable to operators.
284
+ skippedFiles.push({ path: file.path, error: e.code ?? String(e) });
282
285
  }
283
286
  }));
287
+ if (skippedFiles.length > 0) {
288
+ process.stderr.write("[audit-code] graph: skipped " +
289
+ skippedFiles.length +
290
+ " unreadable file(s): " +
291
+ skippedFiles.map((s) => s.path + " (" + s.error + ")").join(", ") +
292
+ "\n");
293
+ }
284
294
  return buildGraphBundle(repoManifest, disposition, { ...options, fileContents });
285
295
  }
286
296
  export function buildGraphBundle(repoManifest, disposition, options = {}) {
@@ -89,7 +89,9 @@ export declare const ARTIFACT_DEFINITIONS: {
89
89
  export declare const ARTIFACT_FILE_TO_BUNDLE_KEY: Record<string, ArtifactBundleKey>;
90
90
  export declare function getArtifactValue(bundle: ArtifactBundle, artifactName: string): unknown;
91
91
  export declare function loadArtifactBundle(root: string): Promise<ArtifactBundle>;
92
- export declare function writeCoreArtifacts(root: string, bundle: ArtifactBundle): Promise<void>;
92
+ export declare function writeCoreArtifacts(root: string, bundle: ArtifactBundle, options?: {
93
+ prune?: boolean;
94
+ }): Promise<void>;
93
95
  export declare function cleanupIntermediateArtifacts(root: string): Promise<string[]>;
94
96
  export declare function promoteFinalAuditReport(params: {
95
97
  artifactsDir: string;
@@ -79,13 +79,29 @@ export async function loadArtifactBundle(root) {
79
79
  bundle.tooling_manifest = await buildToolingManifest();
80
80
  return bundle;
81
81
  }
82
- export async function writeCoreArtifacts(root, bundle) {
82
+ export async function writeCoreArtifacts(root, bundle, options = {}) {
83
83
  const bundleRecord = bundle;
84
84
  for (const entry of ARTIFACT_ENTRIES) {
85
85
  const [key, definition] = entry;
86
86
  const value = bundleRecord[key];
87
+ const path = join(root, definition.fileName);
87
88
  if (value !== undefined) {
88
- await definition.write(join(root, definition.fileName), value);
89
+ await definition.write(path, value);
90
+ }
91
+ else if (options.prune) {
92
+ // The bundle is authoritative. An executor that clears an artifact to
93
+ // `undefined` (to force a downstream rebuild — e.g. planning/ingestion
94
+ // reset audit_report) intends the file gone; if it lingers it reloads as a
95
+ // stale "present" artifact with no metadata entry, which deriveAuditState
96
+ // reads as satisfied — masking the invalidation and stranding a stale
97
+ // report. Only callers passing the full accumulated bundle may prune.
98
+ try {
99
+ await unlink(path);
100
+ }
101
+ catch (error) {
102
+ if (!isFileMissingError(error))
103
+ throw error;
104
+ }
89
105
  }
90
106
  }
91
107
  }
@@ -1,7 +1,8 @@
1
1
  import { decideNextStep, findObligation } from "./nextStep.js";
2
2
  import { deriveAuditState } from "./state.js";
3
3
  import { computeArtifactMetadata } from "./artifactMetadata.js";
4
- import { runIntakeExecutor, runStructureExecutor, runPlanningExecutor, runResultIngestionExecutor, runRuntimeValidationExecutor, runRuntimeValidationUpdateExecutor, runSynthesisExecutor, runSynthesisNarrativeExecutor, runDesignAssessmentExecutor, runDesignReviewAutoComplete, runExternalAnalyzerImportExecutor, } from "./internalExecutors.js";
4
+ import { runIntakeExecutor, runStructureExecutor, runPlanningExecutor, runResultIngestionExecutor, runRuntimeValidationExecutor, runRuntimeValidationUpdateExecutor, runDesignAssessmentExecutor, runDesignReviewAutoComplete, runExternalAnalyzerImportExecutor, } from "./internalExecutors.js";
5
+ import { runSynthesisExecutor, runSynthesisNarrativeExecutor, } from "./synthesisExecutors.js";
5
6
  import { runAutoFixExecutor } from "./autoFixExecutor.js";
6
7
  import { runSyntaxResolutionExecutor } from "./syntaxResolutionExecutor.js";
7
8
  import { runGraphEnrichmentExecutor } from "./graphEnrichmentExecutor.js";
@@ -15,9 +15,19 @@ export function stableStringify(value) {
15
15
  .sort(([a], [b]) => a.localeCompare(b));
16
16
  return `{${entries.map(([key, item]) => `${JSON.stringify(key)}:${stableStringify(item)}`).join(",")}}`;
17
17
  }
18
+ // Artifacts that stamp a wall-clock `generated_at` on every (re)build. The
19
+ // timestamp is provenance, not content: two rebuilds with identical data but
20
+ // different timestamps must hash equal, or the artifact's revision churns every
21
+ // rebuild and perpetually re-stales its downstreams (e.g. audit-report.md
22
+ // depends on design_assessment) — a finalization-oscillation hazard.
23
+ const GENERATED_AT_STRIPPED_ARTIFACTS = new Set([
24
+ "repo_manifest.json",
25
+ "tooling_manifest.json",
26
+ "audit_plan_metrics.json",
27
+ "design_assessment.json",
28
+ ]);
18
29
  export function normalizeForMetadataHash(artifactName, value) {
19
- if ((artifactName === "repo_manifest.json" ||
20
- artifactName === "tooling_manifest.json") &&
30
+ if (GENERATED_AT_STRIPPED_ARTIFACTS.has(artifactName) &&
21
31
  value &&
22
32
  typeof value === "object" &&
23
33
  !Array.isArray(value)) {
@@ -1,3 +1,3 @@
1
1
  import type { ArtifactBundle } from "../io/artifacts.js";
2
- import type { ExecutorRunResult } from "./internalExecutors.js";
2
+ import type { ExecutorRunResult } from "./executorResult.js";
3
3
  export declare function runAutoFixExecutor(bundle: ArtifactBundle, root: string): ExecutorRunResult;
@@ -49,6 +49,7 @@ export function runAutoFixExecutor(bundle, root) {
49
49
  }
50
50
  }
51
51
  const executedTools = [];
52
+ const toolTimings = [];
52
53
  // JS, TS, HTML, CSS, JSON, YAML, MD
53
54
  if (hasPrettierConfig(root) &&
54
55
  (extensions.has("ts") ||
@@ -61,16 +62,19 @@ export function runAutoFixExecutor(bundle, root) {
61
62
  extensions.has("yml") ||
62
63
  extensions.has("yaml") ||
63
64
  extensions.has("md"))) {
65
+ const prettierStart = Date.now();
64
66
  if (tryRunConfiguredFormatter(root, [
65
67
  ...resolveNodeTool(root, join("node_modules", "prettier", "bin", "prettier.cjs"), ["--write", "."], "prettier --write ."),
66
68
  { command: "prettier", args: ["--write", "."], display: "prettier --write ." },
67
69
  { command: "npx", args: ["--yes", "prettier", "--write", "."], display: "npx --yes prettier --write ." },
68
70
  ])) {
69
71
  executedTools.push("prettier");
72
+ toolTimings.push({ tool: "prettier", duration_ms: Date.now() - prettierStart });
70
73
  }
71
74
  }
72
75
  // Python
73
76
  if (extensions.has("py")) {
77
+ const blackStart = Date.now();
74
78
  if (tryRunConfiguredFormatter(root, [
75
79
  { command: "black", args: ["."], display: "black ." },
76
80
  { command: "python", args: ["-m", "black", "."], display: "python -m black ." },
@@ -78,28 +82,34 @@ export function runAutoFixExecutor(bundle, root) {
78
82
  { command: "pipx", args: ["run", "black", "."], display: "pipx run black ." },
79
83
  ])) {
80
84
  executedTools.push("black");
85
+ toolTimings.push({ tool: "black", duration_ms: Date.now() - blackStart });
81
86
  }
82
87
  }
83
88
  // SQL
84
89
  if (extensions.has("sql")) {
90
+ const sqlfluffStart = Date.now();
85
91
  if (tryRunConfiguredFormatter(root, [
86
92
  { command: "sqlfluff", args: ["fix", "--force", "."], display: "sqlfluff fix --force ." },
87
93
  { command: "uvx", args: ["sqlfluff", "fix", "--force", "."], display: "uvx sqlfluff fix --force ." },
88
94
  { command: "pipx", args: ["run", "sqlfluff", "fix", "--force", "."], display: "pipx run sqlfluff fix --force ." },
89
95
  ])) {
90
96
  executedTools.push("sqlfluff");
97
+ toolTimings.push({ tool: "sqlfluff", duration_ms: Date.now() - sqlfluffStart });
91
98
  }
92
99
  }
93
100
  // Go
94
101
  if (extensions.has("go")) {
102
+ const gofmtStart = Date.now();
95
103
  if (tryRunConfiguredFormatter(root, [
96
104
  { command: "gofmt", args: ["-w", "."], display: "gofmt -w ." },
97
105
  ])) {
98
106
  executedTools.push("gofmt");
107
+ toolTimings.push({ tool: "gofmt", duration_ms: Date.now() - gofmtStart });
99
108
  }
100
109
  }
101
110
  const resultsArtifact = {
102
111
  executed_tools: executedTools,
112
+ tool_timings: toolTimings,
103
113
  timestamp: new Date().toISOString(),
104
114
  };
105
115
  return {
@@ -0,0 +1,12 @@
1
+ import type { ArtifactBundle } from "../io/artifacts.js";
2
+ /**
3
+ * Uniform result of running one audit executor: the updated artifact bundle, the
4
+ * artifact filenames it wrote (which drive metadata/staleness bookkeeping in
5
+ * advanceAudit), and a one-line human progress summary. Shared by every executor
6
+ * module so they need not depend on the internalExecutors barrel.
7
+ */
8
+ export interface ExecutorRunResult {
9
+ updated: ArtifactBundle;
10
+ artifacts_written: string[];
11
+ progress_summary: string;
12
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -2,6 +2,7 @@ import type { RepoManifest } from "../types.js";
2
2
  export interface FileIntegrityResult {
3
3
  changed_files: string[];
4
4
  missing_files: string[];
5
+ io_errors: string[];
5
6
  is_clean: boolean;
6
7
  }
7
8
  export declare function checkFileIntegrity(root: string, manifest: RepoManifest, scope?: string[]): Promise<FileIntegrityResult>;
@@ -9,6 +9,7 @@ async function hashFile(absolutePath) {
9
9
  export async function checkFileIntegrity(root, manifest, scope) {
10
10
  const changed = [];
11
11
  const missing = [];
12
+ const ioErrors = [];
12
13
  const scopeSet = scope ? new Set(scope) : null;
13
14
  const files = scopeSet
14
15
  ? manifest.files.filter((f) => scopeSet.has(f.path))
@@ -29,13 +30,21 @@ export async function checkFileIntegrity(root, manifest, scope) {
29
30
  changed.push(record.path);
30
31
  }
31
32
  }
32
- catch {
33
- missing.push(record.path);
33
+ catch (err) {
34
+ const code = err.code;
35
+ if (code === "ENOENT") {
36
+ missing.push(record.path);
37
+ }
38
+ else {
39
+ console.warn(`fileIntegrity: I/O error on ${record.path}: ${code ?? String(err)}`);
40
+ ioErrors.push(record.path);
41
+ }
34
42
  }
35
43
  }
36
44
  return {
37
45
  changed_files: changed,
38
46
  missing_files: missing,
39
- is_clean: changed.length === 0 && missing.length === 0,
47
+ io_errors: ioErrors,
48
+ is_clean: changed.length === 0 && missing.length === 0 && ioErrors.length === 0,
40
49
  };
41
50
  }
@@ -1,5 +1,5 @@
1
1
  import type { ArtifactBundle } from "../io/artifacts.js";
2
- import type { ExecutorRunResult } from "./internalExecutors.js";
2
+ import type { ExecutorRunResult } from "./executorResult.js";
3
3
  import type { AnalyzerSetting } from "@audit-tools/shared";
4
4
  import type { LanguageAnalyzer } from "../extractors/analyzers/types.js";
5
5
  import { type EdgeReasoningResults } from "./edgeReasoning.js";
@@ -133,7 +133,9 @@ export async function runGraphEnrichmentExecutor(bundle, options = {}) {
133
133
  setting,
134
134
  edges_added: 0,
135
135
  routes_added: 0,
136
- note: `Analyzer failed: ${error instanceof Error ? error.message : String(error)}.`,
136
+ note: error instanceof Error
137
+ ? `Analyzer failed [${error.name}]: ${error.message}${error.stack ? ` — stack: ${error.stack.split("\n").slice(0, 4).join(" | ")}` : ""}`
138
+ : `Analyzer failed: ${String(error)}.`,
137
139
  });
138
140
  continue;
139
141
  }
@@ -3,16 +3,7 @@ import type { AuditResult } from "../types.js";
3
3
  import type { RuntimeValidationReport } from "../types/runtimeValidation.js";
4
4
  import type { ExternalAnalyzerResults } from "../types/externalAnalyzer.js";
5
5
  import type { AuditScopeManifest } from "../types/auditScope.js";
6
- import type { SynthesisNarrative } from "@audit-tools/shared";
7
- export interface ExecutorRunResult {
8
- updated: ArtifactBundle;
9
- artifacts_written: string[];
10
- progress_summary: string;
11
- }
12
- export declare function resolveRuntimeValidationSpawnCommand(command: string[], platform?: NodeJS.Platform, shellCommand?: string): {
13
- command: string;
14
- args: string[];
15
- };
6
+ import type { ExecutorRunResult } from "./executorResult.js";
16
7
  export declare function runIntakeExecutor(bundle: ArtifactBundle, root: string): Promise<ExecutorRunResult>;
17
8
  export declare function runStructureExecutor(bundle: ArtifactBundle, root?: string): Promise<ExecutorRunResult>;
18
9
  export declare function runDesignAssessmentExecutor(bundle: ArtifactBundle): ExecutorRunResult;
@@ -23,12 +14,4 @@ export declare function runRuntimeValidationExecutor(bundle: ArtifactBundle, roo
23
14
  opentoken?: boolean;
24
15
  }): Promise<ExecutorRunResult>;
25
16
  export declare function runRuntimeValidationUpdateExecutor(bundle: ArtifactBundle, updates: RuntimeValidationReport): ExecutorRunResult;
26
- export declare function runSynthesisExecutor(bundle: ArtifactBundle, results?: AuditResult[]): ExecutorRunResult;
27
- /**
28
- * Resolve the optional synthesis-narrative obligation. When a host/provider
29
- * narrative is supplied it is merged into the canonical findings report and the
30
- * human report is re-rendered with themes/executive-summary/top-risks; without
31
- * one the narrative is recorded as omitted and the deterministic report stands.
32
- */
33
- export declare function runSynthesisNarrativeExecutor(bundle: ArtifactBundle, narrative?: SynthesisNarrative): ExecutorRunResult;
34
17
  export declare function runExternalAnalyzerImportExecutor(bundle: ArtifactBundle, externalResults: ExternalAnalyzerResults): ExecutorRunResult;
@@ -1,4 +1,4 @@
1
- import { spawn } from "node:child_process";
1
+ import { runCommand } from "./runtimeCommand.js";
2
2
  import { buildFileDisposition, isAuditExcludedStatus, } from "../extractors/disposition.js";
3
3
  import { buildGraphBundle, buildGraphBundleFromFs, } from "../extractors/graph.js";
4
4
  import { buildCriticalFlowManifest } from "../extractors/flows.js";
@@ -9,7 +9,6 @@ import { applyScopeToCoverage, fullAuditScope } from "./scope.js";
9
9
  import { buildFlowCoverage } from "./flowCoverage.js";
10
10
  import { buildRequeuePayload } from "./requeueCommand.js";
11
11
  import { buildRuntimeValidationTasks, discoverRuntimeValidationCommand, mergeRuntimeValidationReport, } from "./runtimeValidation.js";
12
- import { applyNarrative, buildAuditFindingsReport, buildAuditReportModel, renderAuditReportMarkdown, } from "../reporting/synthesis.js";
13
12
  import { buildChunkedAuditTasks, } from "./taskBuilder.js";
14
13
  import { buildAuditPlanMetrics, buildReviewPackets, sizeIndexFromManifest, } from "./reviewPackets.js";
15
14
  import { buildUnitManifest } from "./unitBuilder.js";
@@ -60,79 +59,6 @@ function appendSelectiveDeepeningTasks(params) {
60
59
  artifacts: ["audit_tasks.json", "audit_plan_metrics.json", "review_packets.json"],
61
60
  };
62
61
  }
63
- function resolveOpentokenWrap(resolved, platform = process.platform) {
64
- if (platform === "win32") {
65
- const shell = process.env.ComSpec ?? "cmd.exe";
66
- const inner = [resolved.command, ...resolved.args]
67
- .map((v) => (/^[A-Za-z0-9_./:=@+-]+$/.test(v) ? v : `"${v.replace(/(["^&|<>%])/g, "^$1")}"`))
68
- .join(" ");
69
- return { command: shell, args: ["/d", "/s", "/c", `opentoken wrap ${inner}`] };
70
- }
71
- return { command: "opentoken", args: ["wrap", resolved.command, ...resolved.args] };
72
- }
73
- async function runCommand(command, cwd, options = {}) {
74
- let spawnCommand = resolveRuntimeValidationSpawnCommand(command);
75
- if (options.opentoken) {
76
- spawnCommand = resolveOpentokenWrap(spawnCommand);
77
- }
78
- const displayCommand = command.join(" ");
79
- return await new Promise((resolve) => {
80
- const child = spawn(spawnCommand.command, spawnCommand.args, {
81
- cwd,
82
- env: process.env,
83
- stdio: ["ignore", "pipe", "pipe"],
84
- });
85
- let stdout = "";
86
- let stderr = "";
87
- child.stdout.on("data", (chunk) => {
88
- stdout += String(chunk);
89
- });
90
- child.stderr.on("data", (chunk) => {
91
- stderr += String(chunk);
92
- });
93
- child.on("error", (error) => {
94
- resolve({
95
- status: "inconclusive",
96
- summary: `Failed to execute ${displayCommand}: ${error.message}`,
97
- evidence: [],
98
- });
99
- });
100
- child.on("exit", (code) => {
101
- const output = `${stdout}\n${stderr}`.trim();
102
- const evidence = output.length > 0 ? output.split(/\r?\n/).slice(-10) : [];
103
- resolve({
104
- status: code === 0 ? "confirmed" : "not_confirmed",
105
- summary: code === 0
106
- ? `Deterministic runtime command succeeded: ${displayCommand}`
107
- : `Deterministic runtime command failed with exit code ${code}: ${displayCommand}`,
108
- evidence,
109
- });
110
- });
111
- });
112
- }
113
- export function resolveRuntimeValidationSpawnCommand(command, platform = process.platform, shellCommand = process.env.ComSpec ?? "cmd.exe") {
114
- const [executable, ...args] = command;
115
- if (!executable) {
116
- return { command: "", args: [] };
117
- }
118
- if (platform !== "win32") {
119
- return { command: executable, args };
120
- }
121
- const packageManager = executable.replace(/\.(cmd|bat)$/i, "").toLowerCase();
122
- if (["npm", "npx", "pnpm", "yarn"].includes(packageManager)) {
123
- return {
124
- command: shellCommand,
125
- args: ["/d", "/s", "/c", command.map(quoteCmdArg).join(" ")],
126
- };
127
- }
128
- return { command: executable, args };
129
- }
130
- function quoteCmdArg(value) {
131
- if (/^[A-Za-z0-9_./:=+-]+$/.test(value)) {
132
- return value;
133
- }
134
- return `"${value.replace(/(["^&|<>%])/g, "^$1")}"`;
135
- }
136
62
  export async function runIntakeExecutor(bundle, root) {
137
63
  const ignore = await loadIgnoreFile(root);
138
64
  const repoManifest = await buildRepoManifestFromFs({
@@ -476,89 +402,6 @@ export function runRuntimeValidationUpdateExecutor(bundle, updates) {
476
402
  : ""),
477
403
  };
478
404
  }
479
- function buildBaseFindingsReport(bundle, results) {
480
- return buildAuditFindingsReport(buildAuditReportModel({
481
- results,
482
- unitManifest: bundle.unit_manifest,
483
- graphBundle: bundle.graph_bundle,
484
- criticalFlows: bundle.critical_flows,
485
- coverageMatrix: bundle.coverage_matrix,
486
- runtimeValidationReport: bundle.runtime_validation_report,
487
- externalAnalyzerResults: bundle.external_analyzer_results,
488
- designAssessment: bundle.design_assessment,
489
- }));
490
- }
491
- export function runSynthesisExecutor(bundle, results) {
492
- const finalResults = results ?? bundle.audit_results ?? [];
493
- // Emit the canonical machine contract and render the human report from it.
494
- // No narrative yet — that is layered by the synthesis-narrative obligation.
495
- const findings = buildBaseFindingsReport(bundle, finalResults);
496
- return {
497
- updated: {
498
- ...bundle,
499
- audit_results: finalResults,
500
- audit_findings: findings,
501
- audit_report: renderAuditReportMarkdown(findings, { scope: bundle.scope }),
502
- },
503
- artifacts_written: ["audit-findings.json", "audit-report.md"],
504
- progress_summary: `Rendered deterministic audit report and canonical findings for ${finalResults.length} audit result entries.`,
505
- };
506
- }
507
- /**
508
- * Resolve the optional synthesis-narrative obligation. When a host/provider
509
- * narrative is supplied it is merged into the canonical findings report and the
510
- * human report is re-rendered with themes/executive-summary/top-risks; without
511
- * one the narrative is recorded as omitted and the deterministic report stands.
512
- */
513
- export function runSynthesisNarrativeExecutor(bundle, narrative) {
514
- const baseReport = bundle.audit_findings ??
515
- buildBaseFindingsReport(bundle, bundle.audit_results ?? []);
516
- const needsBaseWrite = !bundle.audit_findings;
517
- const hasNarrative = Boolean(narrative &&
518
- ((narrative.themes?.length ?? 0) > 0 ||
519
- (narrative.executive_summary?.trim().length ?? 0) > 0 ||
520
- (narrative.top_risks?.length ?? 0) > 0));
521
- if (!hasNarrative) {
522
- const record = {
523
- status: "omitted",
524
- theme_count: 0,
525
- executive_summary_present: false,
526
- top_risk_count: 0,
527
- };
528
- return {
529
- updated: {
530
- ...bundle,
531
- audit_findings: baseReport,
532
- synthesis_narrative: record,
533
- },
534
- artifacts_written: needsBaseWrite
535
- ? ["audit-findings.json", "synthesis-narrative.json"]
536
- : ["synthesis-narrative.json"],
537
- progress_summary: "Synthesis narrative omitted; deterministic findings report retained.",
538
- };
539
- }
540
- const enriched = applyNarrative(baseReport, narrative);
541
- const record = {
542
- status: "applied",
543
- theme_count: enriched.themes?.length ?? 0,
544
- executive_summary_present: (enriched.executive_summary?.trim().length ?? 0) > 0,
545
- top_risk_count: enriched.top_risks?.length ?? 0,
546
- };
547
- return {
548
- updated: {
549
- ...bundle,
550
- audit_findings: enriched,
551
- audit_report: renderAuditReportMarkdown(enriched, { scope: bundle.scope }),
552
- synthesis_narrative: record,
553
- },
554
- artifacts_written: [
555
- "audit-findings.json",
556
- "audit-report.md",
557
- "synthesis-narrative.json",
558
- ],
559
- progress_summary: `Synthesis narrative applied: ${record.theme_count} theme(s), ${record.top_risk_count} top risk(s).`,
560
- };
561
- }
562
405
  export function runExternalAnalyzerImportExecutor(bundle, externalResults) {
563
406
  const summary = `Imported ${externalResults.results.length} normalized findings from ${externalResults.tool}.`;
564
407
  return {
@@ -0,0 +1,31 @@
1
+ import type { AuditTask } from "../types.js";
2
+ import type { ReviewPacketGraphEdge, ReviewPacketQuality } from "../types/reviewPlanning.js";
3
+ import type { GraphBundle, GraphEdge } from "@audit-tools/shared";
4
+ import { UnionFind } from "./unionFind.js";
5
+ import { normalizeGraphPath } from "../extractors/graphPathUtils.js";
6
+ export { normalizeGraphPath };
7
+ /**
8
+ * Fan-in / fan-out degree above which a node is treated as a hub. Exported so
9
+ * the Phase 3 delta-scope expansion skips the same hubs that packet planning
10
+ * skips, preventing scope blow-up through highly-connected modules.
11
+ */
12
+ export declare const HIGH_FAN_DEGREE_THRESHOLD = 12;
13
+ export declare function collectGraphEdges(graphBundle?: GraphBundle): GraphEdge[];
14
+ export declare function graphEdgeConfidence(edge: GraphEdge): number;
15
+ export declare function isConcreteGraphEdge(edge: GraphEdge): boolean;
16
+ export interface GraphDegreeIndex {
17
+ fanIn: Map<string, number>;
18
+ fanOut: Map<string, number>;
19
+ }
20
+ export declare function buildGraphDegreeIndex(edges: GraphEdge[]): GraphDegreeIndex;
21
+ export declare function isPacketExpansionEdge(edge: GraphEdge, degreeIndex: GraphDegreeIndex): boolean;
22
+ export declare function buildFileToGroupKeys(groups: Map<string, AuditTask[]>): Map<string, Set<string>>;
23
+ export declare function unionFindFromGroups(groups: Map<string, AuditTask[]>, graphEdges: GraphEdge[]): UnionFind;
24
+ export declare function buildPlanningGraphEdges(groups: Map<string, AuditTask[]>, graphEdges: GraphEdge[], graphBundle?: GraphBundle, lineIndex?: Record<string, number>, sizeIndex?: Record<string, number>, targetPacketTokens?: number): GraphEdge[];
25
+ export declare function roundQuality(value: number): number;
26
+ export declare function buildPacketGraphContext(filePaths: string[], graphEdges: GraphEdge[], graphBundle?: GraphBundle): {
27
+ keyEdges: ReviewPacketGraphEdge[];
28
+ boundaryFiles: string[];
29
+ entrypoints: string[];
30
+ quality: ReviewPacketQuality;
31
+ };