auditor-lambda 0.6.12 → 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 (56) hide show
  1. package/README.md +0 -21
  2. package/audit-code-wrapper-lib.mjs +44 -1
  3. package/dist/cli/args.d.ts +1 -0
  4. package/dist/cli/args.js +8 -0
  5. package/dist/cli/auditStep.js +7 -1
  6. package/dist/cli/dispatch.js +14 -3
  7. package/dist/cli/nextStepCommand.js +37 -0
  8. package/dist/cli/prompts.js +2 -0
  9. package/dist/cli.d.ts +0 -1
  10. package/dist/cli.js +22 -15
  11. package/dist/extractors/fileInventory.js +15 -2
  12. package/dist/extractors/graph.js +12 -2
  13. package/dist/io/artifacts.d.ts +3 -1
  14. package/dist/io/artifacts.js +18 -2
  15. package/dist/orchestrator/advance.js +2 -1
  16. package/dist/orchestrator/artifactFreshness.js +12 -2
  17. package/dist/orchestrator/artifactMetadata.d.ts +1 -0
  18. package/dist/orchestrator/artifactMetadata.js +15 -0
  19. package/dist/orchestrator/autoFixExecutor.d.ts +1 -1
  20. package/dist/orchestrator/autoFixExecutor.js +10 -0
  21. package/dist/orchestrator/executorResult.d.ts +12 -0
  22. package/dist/orchestrator/executorResult.js +1 -0
  23. package/dist/orchestrator/fileIntegrity.d.ts +1 -0
  24. package/dist/orchestrator/fileIntegrity.js +12 -3
  25. package/dist/orchestrator/flowRequeue.js +1 -14
  26. package/dist/orchestrator/graphEnrichmentExecutor.d.ts +1 -1
  27. package/dist/orchestrator/graphEnrichmentExecutor.js +3 -1
  28. package/dist/orchestrator/internalExecutors.d.ts +1 -18
  29. package/dist/orchestrator/internalExecutors.js +1 -158
  30. package/dist/orchestrator/reviewPacketGraph.d.ts +31 -0
  31. package/dist/orchestrator/reviewPacketGraph.js +691 -0
  32. package/dist/orchestrator/reviewPacketSizing.d.ts +25 -0
  33. package/dist/orchestrator/reviewPacketSizing.js +60 -0
  34. package/dist/orchestrator/reviewPackets.d.ts +3 -28
  35. package/dist/orchestrator/reviewPackets.js +6 -740
  36. package/dist/orchestrator/runtimeCommand.d.ts +11 -0
  37. package/dist/orchestrator/runtimeCommand.js +79 -0
  38. package/dist/orchestrator/scope.js +1 -1
  39. package/dist/orchestrator/syntaxResolutionExecutor.d.ts +1 -1
  40. package/dist/orchestrator/synthesisExecutors.d.ts +12 -0
  41. package/dist/orchestrator/synthesisExecutors.js +90 -0
  42. package/dist/orchestrator.js +1 -4
  43. package/dist/quota/index.d.ts +1 -1
  44. package/dist/quota/index.js +1 -1
  45. package/dist/types/workerSession.d.ts +1 -3
  46. package/dist/types.d.ts +6 -0
  47. package/dist/types.js +20 -1
  48. package/docs/development.md +35 -139
  49. package/docs/history.md +26 -0
  50. package/docs/product.md +41 -108
  51. package/package.json +1 -1
  52. package/schemas/audit_findings.schema.json +3 -2
  53. package/schemas/dispatch_quota.schema.json +2 -0
  54. package/schemas/external_analyzer_results.schema.json +2 -2
  55. package/schemas/repo_manifest.schema.json +1 -1
  56. package/docs/handoff.md +0 -204
@@ -0,0 +1,11 @@
1
+ export declare function runCommand(command: string[], cwd: string, options?: {
2
+ opentoken?: boolean;
3
+ }): Promise<{
4
+ status: "confirmed" | "not_confirmed" | "inconclusive";
5
+ summary: string;
6
+ evidence: string[];
7
+ }>;
8
+ export declare function resolveRuntimeValidationSpawnCommand(command: string[], platform?: NodeJS.Platform, shellCommand?: string): {
9
+ command: string;
10
+ args: string[];
11
+ };
@@ -0,0 +1,79 @@
1
+ import { spawn } from "node:child_process";
2
+ // Deterministic runtime-validation command execution: resolve a command to a
3
+ // platform-correct spawn invocation (Windows package-manager shims need a
4
+ // cmd.exe wrapper), optionally wrap it for opentoken accounting, and run it
5
+ // capturing a confirmed/not_confirmed/inconclusive outcome. Hoisted out of
6
+ // internalExecutors.ts as a shared, side-effect-only helper module.
7
+ function resolveOpentokenWrap(resolved, platform = process.platform) {
8
+ if (platform === "win32") {
9
+ const shell = process.env.ComSpec ?? "cmd.exe";
10
+ const inner = [resolved.command, ...resolved.args]
11
+ .map((v) => (/^[A-Za-z0-9_./:=@+-]+$/.test(v) ? v : `"${v.replace(/(["^&|<>%])/g, "^$1")}"`))
12
+ .join(" ");
13
+ return { command: shell, args: ["/d", "/s", "/c", `opentoken wrap ${inner}`] };
14
+ }
15
+ return { command: "opentoken", args: ["wrap", resolved.command, ...resolved.args] };
16
+ }
17
+ export async function runCommand(command, cwd, options = {}) {
18
+ let spawnCommand = resolveRuntimeValidationSpawnCommand(command);
19
+ if (options.opentoken) {
20
+ spawnCommand = resolveOpentokenWrap(spawnCommand);
21
+ }
22
+ const displayCommand = command.join(" ");
23
+ return await new Promise((resolve) => {
24
+ const child = spawn(spawnCommand.command, spawnCommand.args, {
25
+ cwd,
26
+ env: process.env,
27
+ stdio: ["ignore", "pipe", "pipe"],
28
+ });
29
+ let stdout = "";
30
+ let stderr = "";
31
+ child.stdout.on("data", (chunk) => {
32
+ stdout += String(chunk);
33
+ });
34
+ child.stderr.on("data", (chunk) => {
35
+ stderr += String(chunk);
36
+ });
37
+ child.on("error", (error) => {
38
+ resolve({
39
+ status: "inconclusive",
40
+ summary: `Failed to execute ${displayCommand}: ${error.message}`,
41
+ evidence: [],
42
+ });
43
+ });
44
+ child.on("exit", (code) => {
45
+ const output = `${stdout}\n${stderr}`.trim();
46
+ const evidence = output.length > 0 ? output.split(/\r?\n/).slice(-10) : [];
47
+ resolve({
48
+ status: code === 0 ? "confirmed" : "not_confirmed",
49
+ summary: code === 0
50
+ ? `Deterministic runtime command succeeded: ${displayCommand}`
51
+ : `Deterministic runtime command failed with exit code ${code}: ${displayCommand}`,
52
+ evidence,
53
+ });
54
+ });
55
+ });
56
+ }
57
+ export function resolveRuntimeValidationSpawnCommand(command, platform = process.platform, shellCommand = process.env.ComSpec ?? "cmd.exe") {
58
+ const [executable, ...args] = command;
59
+ if (!executable) {
60
+ return { command: "", args: [] };
61
+ }
62
+ if (platform !== "win32") {
63
+ return { command: executable, args };
64
+ }
65
+ const packageManager = executable.replace(/\.(cmd|bat)$/i, "").toLowerCase();
66
+ if (["npm", "npx", "pnpm", "yarn"].includes(packageManager)) {
67
+ return {
68
+ command: shellCommand,
69
+ args: ["/d", "/s", "/c", command.map(quoteCmdArg).join(" ")],
70
+ };
71
+ }
72
+ return { command: executable, args };
73
+ }
74
+ function quoteCmdArg(value) {
75
+ if (/^[A-Za-z0-9_./:=+-]+$/.test(value)) {
76
+ return value;
77
+ }
78
+ return `"${value.replace(/(["^&|<>%])/g, "^$1")}"`;
79
+ }
@@ -1,7 +1,7 @@
1
1
  import { changedFiles, gitRefExists, isGitRepo } from "@audit-tools/shared";
2
2
  import { buildDispositionMap } from "../extractors/disposition.js";
3
3
  import { buildPathLookup } from "../extractors/graph.js";
4
- import { HIGH_FAN_DEGREE_THRESHOLD, buildGraphDegreeIndex, collectGraphEdges, graphEdgeConfidence, normalizeGraphPath, } from "./reviewPackets.js";
4
+ import { HIGH_FAN_DEGREE_THRESHOLD, buildGraphDegreeIndex, collectGraphEdges, graphEdgeConfidence, normalizeGraphPath, } from "./reviewPacketGraph.js";
5
5
  /** Default cap on in-scope files (seeds + expanded) before expansion stops. */
6
6
  export const DEFAULT_SCOPE_MAX_FILES = 200;
7
7
  /** Graph edges below this confidence are never traversed during expansion. */
@@ -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 runSyntaxResolutionExecutor(bundle: ArtifactBundle, root: string): ExecutorRunResult;
@@ -0,0 +1,12 @@
1
+ import type { ArtifactBundle } from "../io/artifacts.js";
2
+ import type { AuditResult } from "../types.js";
3
+ import type { ExecutorRunResult } from "./executorResult.js";
4
+ import type { SynthesisNarrative } from "@audit-tools/shared";
5
+ export declare function runSynthesisExecutor(bundle: ArtifactBundle, results?: AuditResult[]): ExecutorRunResult;
6
+ /**
7
+ * Resolve the optional synthesis-narrative obligation. When a host/provider
8
+ * narrative is supplied it is merged into the canonical findings report and the
9
+ * human report is re-rendered with themes/executive-summary/top-risks; without
10
+ * one the narrative is recorded as omitted and the deterministic report stands.
11
+ */
12
+ export declare function runSynthesisNarrativeExecutor(bundle: ArtifactBundle, narrative?: SynthesisNarrative): ExecutorRunResult;
@@ -0,0 +1,90 @@
1
+ import { applyNarrative, buildAuditFindingsReport, buildAuditReportModel, renderAuditReportMarkdown, } from "../reporting/synthesis.js";
2
+ function buildBaseFindingsReport(bundle, results) {
3
+ return buildAuditFindingsReport(buildAuditReportModel({
4
+ results,
5
+ unitManifest: bundle.unit_manifest,
6
+ graphBundle: bundle.graph_bundle,
7
+ criticalFlows: bundle.critical_flows,
8
+ coverageMatrix: bundle.coverage_matrix,
9
+ runtimeValidationReport: bundle.runtime_validation_report,
10
+ externalAnalyzerResults: bundle.external_analyzer_results,
11
+ designAssessment: bundle.design_assessment,
12
+ }));
13
+ }
14
+ export function runSynthesisExecutor(bundle, results) {
15
+ const finalResults = results ?? bundle.audit_results ?? [];
16
+ // Emit the canonical machine contract and render the human report from it.
17
+ // No narrative yet — that is layered by the synthesis-narrative obligation.
18
+ const findings = buildBaseFindingsReport(bundle, finalResults);
19
+ // Synthesis renders findings; it does NOT own audit_results. Writing
20
+ // audit_results back here desyncs it from its metadata entry (it isn't in
21
+ // artifacts_written, so computeArtifactMetadata reuses the prior hash) and, in
22
+ // the zero-result case, materializes an empty audit_results.jsonl that did not
23
+ // exist before — both perpetually re-stale coverage_matrix → planning,
24
+ // forcing a planning re-run that rewrites runtime_validation_report.json (the
25
+ // finalization-oscillation engine). Leave audit_results as the ingested value.
26
+ return {
27
+ updated: {
28
+ ...bundle,
29
+ audit_findings: findings,
30
+ audit_report: renderAuditReportMarkdown(findings, { scope: bundle.scope }),
31
+ },
32
+ artifacts_written: ["audit-findings.json", "audit-report.md"],
33
+ progress_summary: `Rendered deterministic audit report and canonical findings for ${finalResults.length} audit result entries.`,
34
+ };
35
+ }
36
+ /**
37
+ * Resolve the optional synthesis-narrative obligation. When a host/provider
38
+ * narrative is supplied it is merged into the canonical findings report and the
39
+ * human report is re-rendered with themes/executive-summary/top-risks; without
40
+ * one the narrative is recorded as omitted and the deterministic report stands.
41
+ */
42
+ export function runSynthesisNarrativeExecutor(bundle, narrative) {
43
+ const baseReport = bundle.audit_findings ??
44
+ buildBaseFindingsReport(bundle, bundle.audit_results ?? []);
45
+ const needsBaseWrite = !bundle.audit_findings;
46
+ const hasNarrative = Boolean(narrative &&
47
+ ((narrative.themes?.length ?? 0) > 0 ||
48
+ (narrative.executive_summary?.trim().length ?? 0) > 0 ||
49
+ (narrative.top_risks?.length ?? 0) > 0));
50
+ if (!hasNarrative) {
51
+ const record = {
52
+ status: "omitted",
53
+ theme_count: 0,
54
+ executive_summary_present: false,
55
+ top_risk_count: 0,
56
+ };
57
+ return {
58
+ updated: {
59
+ ...bundle,
60
+ audit_findings: baseReport,
61
+ synthesis_narrative: record,
62
+ },
63
+ artifacts_written: needsBaseWrite
64
+ ? ["audit-findings.json", "synthesis-narrative.json"]
65
+ : ["synthesis-narrative.json"],
66
+ progress_summary: "Synthesis narrative omitted; deterministic findings report retained.",
67
+ };
68
+ }
69
+ const enriched = applyNarrative(baseReport, narrative);
70
+ const record = {
71
+ status: "applied",
72
+ theme_count: enriched.themes?.length ?? 0,
73
+ executive_summary_present: (enriched.executive_summary?.trim().length ?? 0) > 0,
74
+ top_risk_count: enriched.top_risks?.length ?? 0,
75
+ };
76
+ return {
77
+ updated: {
78
+ ...bundle,
79
+ audit_findings: enriched,
80
+ audit_report: renderAuditReportMarkdown(enriched, { scope: bundle.scope }),
81
+ synthesis_narrative: record,
82
+ },
83
+ artifacts_written: [
84
+ "audit-findings.json",
85
+ "audit-report.md",
86
+ "synthesis-narrative.json",
87
+ ],
88
+ progress_summary: `Synthesis narrative applied: ${record.theme_count} theme(s), ${record.top_risk_count} top risk(s).`,
89
+ };
90
+ }
@@ -1,3 +1,4 @@
1
+ import { isLens } from "./types.js";
1
2
  const DEFAULT_LENS_ORDER = [
2
3
  "correctness",
3
4
  "architecture",
@@ -10,13 +11,9 @@ const DEFAULT_LENS_ORDER = [
10
11
  "operability",
11
12
  "config_deployment",
12
13
  ];
13
- const VALID_LENSES = new Set(DEFAULT_LENS_ORDER);
14
14
  function isRecord(value) {
15
15
  return value !== null && typeof value === "object";
16
16
  }
17
- function isLens(value) {
18
- return typeof value === "string" && VALID_LENSES.has(value);
19
- }
20
17
  function assertStringArray(value, label) {
21
18
  if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
22
19
  throw new TypeError(`${label} must be an array of strings.`);
@@ -1,7 +1,7 @@
1
1
  import type { ResolvedLimits as _ResolvedLimits, LimitConfidence as _LimitConfidence, LimitSource as _LimitSource, HostConcurrencyLimit as _HostConcurrencyLimit, QuotaUsageSnapshot as _QuotaUsageSnapshot, BackoffState as _BackoffState } from "@audit-tools/shared";
2
2
  export { resolveLimits, lookupKnownModel, classifyProvider, readQuotaState, writeQuotaState, computeMaxSafeConcurrency, recordWaveOutcome, getQuotaStatePath, decayWeight, applyDecayToEntry, computeBackoffCooldownMs, computeBackoffFailureWeight, computeRampUpConcurrency, setQuotaStateDir, detectRateLimitError, computeCooldownUntil, acquireLock, releaseLock, withFileLock, FileLockTimeoutError, runSlidingWindow, LearnedQuotaSource, CompositeQuotaSource, GenericErrorParser, ClaudeCodeErrorParser, getErrorParserForProvider, } from "@audit-tools/shared";
3
3
  export type { LimitResolutionResult, ResolveLimitsOptions, ProviderType, ResolvedLimits, LimitSource, LimitConfidence, HostConcurrencyLimit, HostConcurrencyLimitSource, QuotaState, QuotaStateEntry, ConcurrencyBucket, WaveSchedule, BackoffState, ObservedWaveOutcome, RateLimitDetectionResult, SlidingWindowResult, QuotaSource, QuotaUsageSnapshot, ErrorParser, } from "@audit-tools/shared";
4
- export { scheduleWave, buildProviderModelKey } from "@audit-tools/shared";
4
+ export { scheduleWave, buildProviderModelKey, resolveHostModel } from "@audit-tools/shared";
5
5
  export type { ScheduleWaveOptions } from "@audit-tools/shared";
6
6
  export { detectHostActiveSubagentLimit, resolveHostActiveSubagentLimit, } from "./hostLimits.js";
7
7
  export { probeProvider } from "./probe.js";
@@ -3,7 +3,7 @@ export { resolveLimits, lookupKnownModel, classifyProvider, readQuotaState, writ
3
3
  // Wave scheduler now lives in @audit-tools/shared (single source of truth for
4
4
  // both orchestrators). Auditor passes its discovered-limits via the structural
5
5
  // DiscoveredRateLimitsInput the shared scheduler accepts.
6
- export { scheduleWave, buildProviderModelKey } from "@audit-tools/shared";
6
+ export { scheduleWave, buildProviderModelKey, resolveHostModel } from "@audit-tools/shared";
7
7
  // Auditor-specific: probe, discovered limits, header extraction
8
8
  export { detectHostActiveSubagentLimit, resolveHostActiveSubagentLimit, } from "./hostLimits.js";
9
9
  export { probeProvider } from "./probe.js";
@@ -23,10 +23,8 @@ export interface WorkerTask {
23
23
  runtime_updates_path?: string;
24
24
  external_analyzer_results_path?: string;
25
25
  worker_command_mode?: WorkerCommandMode;
26
- /** @deprecated Prefer worker_command_mode: "deferred" for new task files. */
27
- skip_worker_command?: boolean;
28
26
  timeout_ms?: number;
29
27
  max_retries?: number;
30
28
  access?: AccessDeclaration;
31
29
  }
32
- export declare function usesDeferredWorkerCommand(task: Pick<WorkerTask, "worker_command_mode" | "skip_worker_command">): boolean;
30
+ export declare function usesDeferredWorkerCommand(task: Pick<WorkerTask, "worker_command_mode">): boolean;
package/dist/types.d.ts CHANGED
@@ -1,5 +1,11 @@
1
1
  import type { Finding as SharedFinding } from "@audit-tools/shared";
2
2
  export type Lens = "correctness" | "architecture" | "maintainability" | "security" | "reliability" | "performance" | "data_integrity" | "tests" | "operability" | "config_deployment" | "observability";
3
+ /** Canonical list of every valid {@link Lens}. Single source of truth — import
4
+ * {@link isLens} / `ALL_LENSES` instead of hand-copying lens lists into local
5
+ * guards, which drift (a copy omitting "observability" caused it to be wrongly
6
+ * rejected in flow requeue). */
7
+ export declare const ALL_LENSES: readonly Lens[];
8
+ export declare function isLens(value: unknown): value is Lens;
3
9
  export interface FileRecord {
4
10
  path: string;
5
11
  language: string;
package/dist/types.js CHANGED
@@ -1 +1,20 @@
1
- export {};
1
+ /** Canonical list of every valid {@link Lens}. Single source of truth — import
2
+ * {@link isLens} / `ALL_LENSES` instead of hand-copying lens lists into local
3
+ * guards, which drift (a copy omitting "observability" caused it to be wrongly
4
+ * rejected in flow requeue). */
5
+ export const ALL_LENSES = [
6
+ "correctness",
7
+ "architecture",
8
+ "maintainability",
9
+ "security",
10
+ "reliability",
11
+ "performance",
12
+ "data_integrity",
13
+ "tests",
14
+ "operability",
15
+ "config_deployment",
16
+ "observability",
17
+ ];
18
+ export function isLens(value) {
19
+ return (typeof value === "string" && ALL_LENSES.includes(value));
20
+ }
@@ -12,10 +12,10 @@
12
12
 
13
13
  ## Agent handoff
14
14
 
15
- Use `docs/handoff.md` as the current pickup note for the next implementation
16
- agent. It should name the latest completed slice, verification status, files
17
- touched, and the most practical next steps. Keep long-term product direction in
18
- `docs/product.md`; keep transient implementation pickup notes in the handoff.
15
+ Keep long-term product direction in `docs/product.md` and archival context
16
+ (shipped sprints, field-trial lessons) in `docs/history.md`. There is no
17
+ standing per-sprint handoff file; sprint notes are folded into `docs/history.md`
18
+ once the work ships.
19
19
 
20
20
  ## Build and test
21
21
 
@@ -30,79 +30,6 @@ The test suite is intentionally contract-heavy. Update tests when changing
30
30
  schema shape, prompt contracts, dispatch behavior, installer output, or release
31
31
  workflow semantics.
32
32
 
33
- ## Production-readiness workflow
34
-
35
- Use field trials to decide what to fix next. For each representative repository,
36
- run to the local review handoff, validate the artifact bundle, and compare
37
- `audit_plan_metrics.json` across runs. Track at least packet count, weak packet
38
- count, average cohesion, `merge_edge_kind_counts`,
39
- `boundary_edge_kind_counts`, and `weakly_explained_packet_samples`.
40
-
41
- Only promote an extractor or planner change when those metrics expose a
42
- deterministic gap. Prefer improving shared graph resolution or importing
43
- generic analyzer ownership roots before adding another ecosystem-specific
44
- manifest parser.
45
-
46
- The latest remediator field trial closed the remaining mixed code/schema/test
47
- weak packet by adding package script links, schema contract test links, bounded
48
- TypeScript type contract suites, package-script-seeded script suite links, and
49
- generated test artifact disposition. Keep future suite links similarly bounded
50
- and evidence-led.
51
-
52
- The Polar field trial added `conftest-link` (conftest.py → Python files in
53
- scope) and `pyproject-testpaths-link` (pyproject.toml → conftest.py via
54
- `[tool.pytest.ini_options] testpaths`). `conftest-link` fires only when the
55
- conftest is inside a `isTestPath` directory to avoid O(n) fan-out from
56
- root-level conftests. `pyproject.toml` was also added to `shouldReadForGraph`
57
- so its content is available during the filesystem-backed build path. Together
58
- these raised Polar's average cohesion from 0.625 to 0.857 and reduced weak
59
- packets from 5 to 3.
60
-
61
- A second Polar field trial added `yaml-path-reference-link` (YAML/YML files
62
- → other config files referenced by explicit relative path). Resolution tries
63
- repo-root-relative first, then file-directory-relative. The extractor only
64
- fires for string values ending in `.yaml`, `.yml`, `.json`, or `.toml` that
65
- resolve to an existing repo file. In Polar, this produced 4 edges from
66
- `configs/benchmark.yaml` to its template files and raised `internal_edge_count`
67
- in the `experiments-domains` packet from 90 to 94.
68
-
69
- A third Polar field trial added `python-test-util-suite-link`, which chains
70
- `.py` files co-located in `utils/`, `helpers/`, or `support/` subdirectories
71
- within `isTestPath` directories (same bounded-suite pattern as the TypeScript
72
- type, JSON schema, and package-script suite links). `conftest.py` is excluded
73
- from the predicate. In Polar, this produced 2 intra-unit edges within the
74
- `tests-utils` packet, raising its `internal_edge_count` from 0 to 2 and
75
- eliminating it as a weak packet. Polar metrics improved from 0.857 to 1.000
76
- cohesion and 3 to 2 weak packets. The 2 remaining weak packets share genuinely
77
- isolated files (`.auditorignore`, `experiments/domains/__init__.py`,
78
- `experiments/summarize_results.py`) that cannot be linked without false
79
- positives; treat as the current floor. Note that intra-unit suite edges do not
80
- appear in `merge_edge_kind_counts` — their effect is visible in the packet's
81
- `internal_edge_count` and `unexplained_file_count` fields instead.
82
-
83
- Before treating a build as production-ready, verify the complete review loop in
84
- one real host:
85
-
86
- ```text
87
- audit-code prepare-dispatch --run-id <run_id> --artifacts-dir <artifacts_dir>
88
- worker reviews each packet prompt
89
- audit-code submit-packet ...
90
- audit-code merge-and-ingest --run-id <run_id> --artifacts-dir <artifacts_dir>
91
- audit-code validate
92
- ```
93
-
94
- On Windows, runtime validation runs package-manager shim commands such as
95
- `npm`, `npx`, `pnpm`, and `yarn` through the command shell so `.cmd` wrappers
96
- execute reliably. Keep that behavior covered when changing runtime command
97
- execution.
98
-
99
- If the final `audit-report.md` cannot be copied into the target repository
100
- because of local permissions, completion should remain successful and the
101
- artifact copy remains authoritative. Run `audit-code validate` against the
102
- artifact bundle before treating the run as complete.
103
-
104
- Then run `npm run verify:release` from a clean checkout.
105
-
106
33
  ## Architecture
107
34
 
108
35
  The system separates deterministic extraction from bounded LLM judgment:
@@ -122,6 +49,8 @@ Portability rules:
122
49
  - review work is attributable to files, lenses, passes, and tasks
123
50
  - coverage gaps are machine-detectable
124
51
 
52
+ `AuditTask` is the coverage identity; `AuditResult[]` is the ingestion contract.
53
+
125
54
  ## Adding language analyzers
126
55
 
127
56
  Language support should be adapter-based. A new analyzer should enrich shared
@@ -137,74 +66,41 @@ Preferred outputs:
137
66
  - graph edges with kind, direction, confidence, and reason
138
67
  - entrypoints and surfaces
139
68
  - test-to-source links
140
- - package/module ownership hints, including analyzer-supplied
141
- `ownership_roots` that become `analyzer-ownership-root-link` graph references
142
- - contract-suite links for small JSON Schema, workflow, package script, or
69
+ - package/module ownership hints, including analyzer-supplied `ownership_roots`
70
+ that become `analyzer-ownership-root-link` graph references
71
+ - contract-suite links for small JSON Schema, workflow, package-script, or
143
72
  TypeScript type suites when planner metrics show otherwise weak packets
144
73
  - external boundary hints
145
74
  - line counts and anchor summaries for large files
146
75
 
147
- Current analyzer priorities:
148
-
149
- - planner observability before additional ecosystem breadth
150
- - exercising the generic ownership-root input from analyzers or imported
151
- evidence
152
- - continued behavior-preserving extraction of high-concentration graph helpers
153
- - JS/TS compiler-backed resolution only after the current regex edges stay
154
- stable
155
- - Python deterministic support beyond the current local import, package/module,
156
- and pytest/unittest adjacency edges only where planner metrics show gaps
157
- - generic fallback from path patterns, ctags/tree-sitter, LSP output, or
158
- external analyzer results when available
159
-
160
- Keep deep analyzers optional. Repositories should still produce useful packets
76
+ Keep deep analyzers optional: a repository should still produce useful packets
161
77
  from manifests, paths, tests, and external analyzer results when a language has
162
- only fallback support.
163
-
164
- Command-backed analyzers should prove project intent before running. Prefer
165
- repo-local config checks, such as `eslint.config.*`, `.eslintrc*`, or
166
- `package.json` `eslintConfig`, over executing a globally installed tool and
78
+ only fallback support. Command-backed analyzers should prove project intent
79
+ before running — prefer repo-local config checks (`eslint.config.*`, `.eslintrc*`,
80
+ `package.json` `eslintConfig`) over executing a globally installed tool and
167
81
  parsing its no-config failure.
168
82
 
169
- Language-agnostic semantic affinity can be useful for ranking adjacent context,
170
- but it should be low authority. Do not let shared token frequency alone force
171
- packet merges; use it for `boundary_files` or candidate explanations unless a
83
+ Language-agnostic semantic affinity is useful for ranking adjacent context but
84
+ should stay low-authority: don't let shared token frequency alone force packet
85
+ merges; use it for `boundary_files` or candidate explanations unless a
172
86
  deterministic edge corroborates the relationship.
173
87
 
174
- ## Packetization work
175
-
176
- The current packetizer groups tasks across lenses and merges graph-connected
177
- task groups within line budgets. Plan metrics now record which graph edge kinds
178
- caused packet merges, which candidate edge kinds stayed as boundary context,
179
- and which packets remain weakly explained. Weak-packet diagnostics aggregate
180
- primary gap counts and unique file-extension counts, while bounded samples
181
- include representative file paths. Together those metrics let real or fixture
182
- runs point at the next deterministic extractor or analyzer-ownership
183
- improvement. The next phase is consolidation and carefully chosen deterministic
184
- depth:
185
-
186
- - use packet-quality observations to prioritize extractor gaps
187
- - keep manifest/project-file edge extraction isolated from packet planning code
188
- - use the generic ownership-root contract before adding more ecosystem-specific
189
- module formats
190
- - keep bounded suite edges as contract evidence, not as a generic
191
- same-directory merge rule
192
- - exercise the Python import, package layout, and test/source edges against
193
- fixture and real repositories before adding deeper Python framework handling
194
-
195
- Keep `AuditTask` as the coverage identity and `AuditResult[]` as the ingestion
196
- contract.
197
-
198
- ## File-splitting priorities
199
-
200
- The largest implementation files should be split conservatively and
201
- behavior-preservingly:
202
-
203
- - move CLI command families out of `src/cli.ts`
204
- - move language metadata tables out of file inventory logic
205
- - move graph manifest/project-file parsers out of `src/extractors/graph.ts`
206
- - split selective-deepening task builders by trigger type
207
- - keep packetization, recovery, and schema changes easier to review
208
-
209
- Run the focused tests for each area before and after a split, then run
210
- `npm test`.
88
+ ## Production readiness
89
+
90
+ Drive priorities from field trials, not speculation: run representative
91
+ repositories through planning, validate the bundle (`audit-code validate`), and
92
+ compare `audit_plan_metrics.json` (packet count, weak-packet count, cohesion,
93
+ merge/boundary edge kinds) across runs. Promote an extractor or planner change
94
+ when those metrics expose a deterministic gap and prefer improving shared
95
+ graph resolution or generic analyzer ownership roots before adding another
96
+ ecosystem-specific parser.
97
+
98
+ Before treating a build as production-ready, verify the full review loop in one
99
+ real host (`prepare-dispatch` → worker reviews each packet → `submit-packet` →
100
+ `merge-and-ingest` `validate`), then run `npm run verify:release` from a clean
101
+ checkout. On Windows, runtime validation runs package-manager shims (`npm`,
102
+ `npx`, `pnpm`, `yarn`) through the command shell so `.cmd` wrappers execute
103
+ reliably — keep that covered when changing runtime command execution. If the
104
+ final `audit-report.md` cannot be copied into the target repo due to local
105
+ permissions, completion still succeeds and the artifact copy is authoritative.
106
+ </content>
package/docs/history.md CHANGED
@@ -38,3 +38,29 @@ The old remediation baseline recorded fixes across:
38
38
 
39
39
  Current readiness is tracked in `docs/product.md`, `docs/operator-guide.md`,
40
40
  `docs/contracts.md`, `docs/release.md`, and `docs/development.md`.
41
+
42
+ ## Monorepo migration & drift reconciliation (2026-05 → 2026-06)
43
+
44
+ The auditor and remediator began as standalone repos (`auditor-lambda`,
45
+ `remediator-lambda`) and were merged into this npm-workspaces monorepo on a
46
+ shared `@audit-tools/shared` foundation. `providers/` and `quota/` had been
47
+ copy-pasted into both tools and forked in place; the ten resulting drift bugs
48
+ were all fixed by centralizing the forked logic into `shared` (one source of
49
+ truth). Durable decisions from that work:
50
+
51
+ - **Access scoping is JSON, not MCP.** `AccessDeclaration` rides on the step
52
+ contract, so it works with any host; the MCP servers stay compatibility
53
+ adapters over the same contract.
54
+ - **`--dangerously-skip-permissions` defaults ON for the remediator, OFF for the
55
+ auditor.** The remediator applies changes unattended and cannot pause; the
56
+ auditor is read-only. The asymmetry is intentional and the flag is overrideable.
57
+ - **The remediator's machine input is `audit-findings.json`, not the Markdown
58
+ report.** `audit-report.md` is human-facing; a Markdown file handed to the
59
+ remediator flows through the free-form LLM extractor, not a deterministic parse.
60
+ - **Prompts use one strict path** — no "or / unless / if-available" fallbacks.
61
+
62
+ Large files were then broken up as behaviour-preserving pure moves (`cli.ts` from
63
+ 4072 lines to a thin dispatcher plus `src/cli/*` handlers; `graph.ts`,
64
+ `reviewPackets.ts`, `internalExecutors.ts`, and the generated language table all
65
+ split out). The sprint-by-sprint handoff docs that tracked this work were removed
66
+ once shipped; this section is their durable residue.