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.
- package/README.md +0 -21
- package/audit-code-wrapper-lib.mjs +44 -1
- package/dist/cli/args.d.ts +1 -0
- package/dist/cli/args.js +8 -0
- package/dist/cli/auditStep.js +7 -1
- package/dist/cli/dispatch.js +14 -3
- package/dist/cli/nextStepCommand.js +37 -0
- package/dist/cli/prompts.js +2 -0
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +22 -15
- package/dist/extractors/fileInventory.js +15 -2
- package/dist/extractors/graph.js +12 -2
- package/dist/io/artifacts.d.ts +3 -1
- package/dist/io/artifacts.js +18 -2
- package/dist/orchestrator/advance.js +2 -1
- package/dist/orchestrator/artifactFreshness.js +12 -2
- package/dist/orchestrator/artifactMetadata.d.ts +1 -0
- package/dist/orchestrator/artifactMetadata.js +15 -0
- package/dist/orchestrator/autoFixExecutor.d.ts +1 -1
- package/dist/orchestrator/autoFixExecutor.js +10 -0
- package/dist/orchestrator/executorResult.d.ts +12 -0
- package/dist/orchestrator/executorResult.js +1 -0
- package/dist/orchestrator/fileIntegrity.d.ts +1 -0
- package/dist/orchestrator/fileIntegrity.js +12 -3
- package/dist/orchestrator/flowRequeue.js +1 -14
- package/dist/orchestrator/graphEnrichmentExecutor.d.ts +1 -1
- package/dist/orchestrator/graphEnrichmentExecutor.js +3 -1
- package/dist/orchestrator/internalExecutors.d.ts +1 -18
- package/dist/orchestrator/internalExecutors.js +1 -158
- package/dist/orchestrator/reviewPacketGraph.d.ts +31 -0
- package/dist/orchestrator/reviewPacketGraph.js +691 -0
- package/dist/orchestrator/reviewPacketSizing.d.ts +25 -0
- package/dist/orchestrator/reviewPacketSizing.js +60 -0
- package/dist/orchestrator/reviewPackets.d.ts +3 -28
- package/dist/orchestrator/reviewPackets.js +6 -740
- package/dist/orchestrator/runtimeCommand.d.ts +11 -0
- package/dist/orchestrator/runtimeCommand.js +79 -0
- package/dist/orchestrator/scope.js +1 -1
- package/dist/orchestrator/syntaxResolutionExecutor.d.ts +1 -1
- package/dist/orchestrator/synthesisExecutors.d.ts +12 -0
- package/dist/orchestrator/synthesisExecutors.js +90 -0
- package/dist/orchestrator.js +1 -4
- package/dist/quota/index.d.ts +1 -1
- package/dist/quota/index.js +1 -1
- package/dist/types/workerSession.d.ts +1 -3
- package/dist/types.d.ts +6 -0
- package/dist/types.js +20 -1
- package/docs/development.md +35 -139
- package/docs/history.md +26 -0
- package/docs/product.md +41 -108
- package/package.json +1 -1
- package/schemas/audit_findings.schema.json +3 -2
- package/schemas/dispatch_quota.schema.json +2 -0
- package/schemas/external_analyzer_results.schema.json +2 -2
- package/schemas/repo_manifest.schema.json +1 -1
- 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`
|
|
@@ -2,7 +2,7 @@ import { access, cp, mkdir, open, readFile, readdir, stat, unlink, writeFile } f
|
|
|
2
2
|
import { constants } from 'node:fs';
|
|
3
3
|
import { spawn } from 'node:child_process';
|
|
4
4
|
import { createRequire } from 'node:module';
|
|
5
|
-
import { dirname, join, relative, resolve } from 'node:path';
|
|
5
|
+
import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
|
|
8
8
|
const repoRoot = dirname(fileURLToPath(import.meta.url));
|
|
@@ -247,11 +247,54 @@ async function acquireBuildLock() {
|
|
|
247
247
|
}
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
+
// Pure, testable core of the build preflight. `sharedManifestPath` is the
|
|
251
|
+
// resolved path of @audit-tools/shared's package.json (or null if it could not
|
|
252
|
+
// be resolved at all); `checkoutRoot` is the root this wrapper belongs to.
|
|
253
|
+
export function assertWorkspaceInstalled({ checkoutRoot, sharedManifestPath }) {
|
|
254
|
+
if (!sharedManifestPath) {
|
|
255
|
+
throw new Error(
|
|
256
|
+
'Dependencies are not installed for this checkout. Run `npm install` from ' +
|
|
257
|
+
'the repository root, then retry — building from source needs node_modules ' +
|
|
258
|
+
'(including the @audit-tools/shared workspace link).',
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const relToCheckout = relative(checkoutRoot, sharedManifestPath);
|
|
263
|
+
if (relToCheckout.startsWith('..') || isAbsolute(relToCheckout)) {
|
|
264
|
+
throw new Error(
|
|
265
|
+
`@audit-tools/shared resolved to ${sharedManifestPath}, outside this ` +
|
|
266
|
+
`checkout (${checkoutRoot}). node_modules was never installed here — ` +
|
|
267
|
+
'common in a fresh git worktree — so building would typecheck against ' +
|
|
268
|
+
"another checkout's stale dist and report phantom \"missing export\" " +
|
|
269
|
+
"errors. Run `npm install` from this checkout's root.",
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Catches the common fresh-checkout trap before `npm run build` runs: with no
|
|
275
|
+
// local node_modules, Node/tsc resolve @audit-tools/shared against a different
|
|
276
|
+
// checkout (e.g. the main repo when running inside a git worktree).
|
|
277
|
+
async function preflightWorkspace() {
|
|
278
|
+
const requireFromHere = createRequire(import.meta.url);
|
|
279
|
+
let sharedManifestPath = null;
|
|
280
|
+
try {
|
|
281
|
+
sharedManifestPath = requireFromHere.resolve('@audit-tools/shared/package.json');
|
|
282
|
+
} catch {
|
|
283
|
+
sharedManifestPath = null;
|
|
284
|
+
}
|
|
285
|
+
assertWorkspaceInstalled({
|
|
286
|
+
checkoutRoot: resolve(repoRoot, '..', '..'),
|
|
287
|
+
sharedManifestPath,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
250
291
|
async function ensureBuilt() {
|
|
251
292
|
if (!(await shouldBuildDist())) {
|
|
252
293
|
return;
|
|
253
294
|
}
|
|
254
295
|
|
|
296
|
+
await preflightWorkspace();
|
|
297
|
+
|
|
255
298
|
const lockHandle = await acquireBuildLock();
|
|
256
299
|
if (!lockHandle) {
|
|
257
300
|
return;
|
package/dist/cli/args.d.ts
CHANGED
|
@@ -35,6 +35,7 @@ export declare function summarizeLaunchExit(result: {
|
|
|
35
35
|
error?: string;
|
|
36
36
|
}): string | null;
|
|
37
37
|
export declare function taskResultPath(taskResultsDir: string, taskId: string): string;
|
|
38
|
+
export declare function isCanonicalResultFilename(filename: string): boolean;
|
|
38
39
|
export declare function packetPromptPath(taskResultsDir: string, packetId: string): string;
|
|
39
40
|
export declare function readStdinText(): Promise<string>;
|
|
40
41
|
export declare function normalizePositiveInteger(value: unknown): number | undefined;
|
package/dist/cli/args.js
CHANGED
|
@@ -101,6 +101,14 @@ export function summarizeLaunchExit(result) {
|
|
|
101
101
|
export function taskResultPath(taskResultsDir, taskId) {
|
|
102
102
|
return join(taskResultsDir, artifactNameForId(taskId, "json"));
|
|
103
103
|
}
|
|
104
|
+
const CANONICAL_RESULT_FILENAME = /_[0-9a-f]{12}\.json$/i;
|
|
105
|
+
// True when `filename` matches the canonical per-task result naming produced by
|
|
106
|
+
// artifactNameForId (stem + "_" + 12-hex sha256 digest + ".json"). Lets
|
|
107
|
+
// merge-and-ingest tell legitimate prior-round results apart from genuinely
|
|
108
|
+
// stray files (e.g. packet-23-results.json) left in task-results/.
|
|
109
|
+
export function isCanonicalResultFilename(filename) {
|
|
110
|
+
return CANONICAL_RESULT_FILENAME.test(filename);
|
|
111
|
+
}
|
|
104
112
|
export function packetPromptPath(taskResultsDir, packetId) {
|
|
105
113
|
return join(taskResultsDir, artifactNameForId(packetId, "prompt.md"));
|
|
106
114
|
}
|
package/dist/cli/auditStep.js
CHANGED
|
@@ -84,7 +84,13 @@ export async function runAuditStep(options) {
|
|
|
84
84
|
opentoken: options.opentoken,
|
|
85
85
|
runLogger,
|
|
86
86
|
});
|
|
87
|
-
|
|
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/dispatch.js
CHANGED
|
@@ -8,7 +8,7 @@ import { orderTasksForPacketReview, buildReviewPackets, sizeIndexFromManifest, }
|
|
|
8
8
|
import { buildFileAnchorSummary } from "../orchestrator/fileAnchors.js";
|
|
9
9
|
import { resolveFreshSessionProviderName } from "../providers/index.js";
|
|
10
10
|
import { loadSessionConfig } from "../supervisor/sessionConfig.js";
|
|
11
|
-
import { scheduleWave, buildProviderModelKey, readQuotaState, resolveHostActiveSubagentLimit, lookupDiscoveredLimits, mergeDiscoveredLimits, } from "../quota/index.js";
|
|
11
|
+
import { scheduleWave, buildProviderModelKey, resolveHostModel, readQuotaState, resolveHostActiveSubagentLimit, lookupDiscoveredLimits, mergeDiscoveredLimits, } from "../quota/index.js";
|
|
12
12
|
import { taskResultPath, packetPromptPath, artifactNameForId, toBase64Url, fromBase64Url, getFlag, } from "./args.js";
|
|
13
13
|
export const LARGE_FILE_PACKET_TARGET_LINES = 2500;
|
|
14
14
|
export const SMALL_MODEL_HINT_MAX_LINES = 500;
|
|
@@ -391,7 +391,9 @@ export async function prepareDispatchArtifacts(params) {
|
|
|
391
391
|
...taskSections,
|
|
392
392
|
"## Output",
|
|
393
393
|
"Do not write files directly. Do not use a Write tool, create temp files, edit source files,",
|
|
394
|
-
"remediate findings,
|
|
394
|
+
"remediate findings, run unrelated audits, or write any result file yourself (e.g.",
|
|
395
|
+
"packet-*-result.json / audit_result_*.json) — the submit-packet command below is the only",
|
|
396
|
+
"way to record results, and it writes them inside the artifacts directory for you.",
|
|
395
397
|
"Produce one JSON array containing exactly one AuditResult object for each listed task.",
|
|
396
398
|
"",
|
|
397
399
|
"Required AuditResult fields:",
|
|
@@ -453,9 +455,18 @@ export async function prepareDispatchArtifacts(params) {
|
|
|
453
455
|
run_id: runId,
|
|
454
456
|
entries: resultMapEntries,
|
|
455
457
|
});
|
|
456
|
-
const hostModel = params.hostModel ?? null;
|
|
457
458
|
const perPacketTokens = plan.map((p) => p.complexity.estimated_tokens);
|
|
458
459
|
const quotaProviderName = resolveFreshSessionProviderName(undefined, sessionConfig);
|
|
460
|
+
// Resolve the host model (explicit/CLI override → block_quota.host_model → env
|
|
461
|
+
// → per-provider default) so per-model quota detection engages with realistic
|
|
462
|
+
// limits instead of the conservative unknown-model floor. params.hostModel
|
|
463
|
+
// carries any caller/CLI override.
|
|
464
|
+
const hostModel = resolveHostModel({
|
|
465
|
+
providerName: quotaProviderName,
|
|
466
|
+
sessionConfig,
|
|
467
|
+
explicitModel: params.hostModel,
|
|
468
|
+
envVar: "AUDIT_CODE_HOST_MODEL",
|
|
469
|
+
});
|
|
459
470
|
const quotaProviderKey = buildProviderModelKey(quotaProviderName, hostModel);
|
|
460
471
|
const quotaState = await readQuotaState().catch(() => ({ version: 2, entries: {} }));
|
|
461
472
|
const quotaStateEntry = quotaState.entries[quotaProviderKey] ?? null;
|
|
@@ -3,6 +3,7 @@ import { join, resolve } from "node:path";
|
|
|
3
3
|
import { isFileMissingError, readJsonFile, writeJsonFile, } from "@audit-tools/shared";
|
|
4
4
|
import { loadArtifactBundle, promoteFinalAuditReport, writeCoreArtifacts, AUDIT_REPORT_FILENAME, } from "../io/artifacts.js";
|
|
5
5
|
import { advanceAudit } from "../orchestrator/advance.js";
|
|
6
|
+
import { computeArtifactStateSignature } from "../orchestrator/artifactMetadata.js";
|
|
6
7
|
import { decideNextStep } from "../orchestrator/nextStep.js";
|
|
7
8
|
import { deriveAuditState } from "../orchestrator/state.js";
|
|
8
9
|
import { checkFileIntegrity } from "../orchestrator/fileIntegrity.js";
|
|
@@ -25,6 +26,15 @@ import { getArtifactsDir, getFlag, getHostMaxActiveSubagents, getMaxRuns, getOpt
|
|
|
25
26
|
async function runDeterministicForNextStep(params) {
|
|
26
27
|
let lastSummary = "";
|
|
27
28
|
let analyzers = params.analyzers;
|
|
29
|
+
// Finalization thrashing guard. A converging run produces a (mostly) new
|
|
30
|
+
// artifact state each iteration, so the iteration count tracks the number of
|
|
31
|
+
// distinct states closely (a few idempotent passes are normal). When
|
|
32
|
+
// iterations outrun distinct states by this tolerance, deterministic executors
|
|
33
|
+
// are revisiting states (a staleness ping-pong, e.g. runtime_validation <->
|
|
34
|
+
// synthesis) rather than progressing — stop instead of spinning to maxRuns.
|
|
35
|
+
const FINALIZATION_CYCLE_TOLERANCE = 16;
|
|
36
|
+
const seenStateSignatures = new Set();
|
|
37
|
+
const obligationTrail = [];
|
|
28
38
|
for (let index = 0; index < params.maxRuns; index++) {
|
|
29
39
|
const bundle = await loadArtifactBundle(params.artifactsDir);
|
|
30
40
|
const decision = decideNextStep(bundle);
|
|
@@ -286,6 +296,33 @@ async function runDeterministicForNextStep(params) {
|
|
|
286
296
|
reason: result.progress_summary,
|
|
287
297
|
};
|
|
288
298
|
}
|
|
299
|
+
// Finalization cycle guard. If this iteration returned the audit to an
|
|
300
|
+
// artifact state already produced this run, the deterministic loop is
|
|
301
|
+
// thrashing (no net progress) rather than converging. The canonical outputs
|
|
302
|
+
// are already rendered, so stop and surface the cycling obligations instead
|
|
303
|
+
// of spinning to maxRuns and crashing.
|
|
304
|
+
obligationTrail.push(decision.selected_obligation ?? "unknown");
|
|
305
|
+
seenStateSignatures.add(computeArtifactStateSignature(result.updated_bundle));
|
|
306
|
+
if (index + 1 - seenStateSignatures.size >= FINALIZATION_CYCLE_TOLERANCE) {
|
|
307
|
+
const cycle = Array.from(new Set(obligationTrail.slice(-FINALIZATION_CYCLE_TOLERANCE)));
|
|
308
|
+
await writeJsonFile(join(params.artifactsDir, "steps", "deterministic-progress.json"), {
|
|
309
|
+
iteration: index + 1,
|
|
310
|
+
max_runs: params.maxRuns,
|
|
311
|
+
cycle_detected: true,
|
|
312
|
+
cycling_obligations: cycle,
|
|
313
|
+
summary: "Finalization kept revisiting prior artifact states without net " +
|
|
314
|
+
`progress; stopping. Cycling obligations: ${cycle.join(" -> ")}.`,
|
|
315
|
+
timestamp: new Date().toISOString(),
|
|
316
|
+
});
|
|
317
|
+
return {
|
|
318
|
+
kind: "blocked",
|
|
319
|
+
state: result.audit_state,
|
|
320
|
+
bundle: result.updated_bundle,
|
|
321
|
+
reason: "Finalization is not converging: deterministic executors kept revisiting " +
|
|
322
|
+
`prior artifact states (${cycle.join(" -> ")}). The report has been ` +
|
|
323
|
+
"rendered; review whether these obligations are erroneously invalidating each other.",
|
|
324
|
+
};
|
|
325
|
+
}
|
|
289
326
|
}
|
|
290
327
|
const bundle = await loadArtifactBundle(params.artifactsDir);
|
|
291
328
|
const state = deriveAuditState(bundle);
|
package/dist/cli/prompts.js
CHANGED
|
@@ -68,6 +68,8 @@ export function renderDispatchReviewPrompt(params) {
|
|
|
68
68
|
"`host_concurrency_limit` records any detected hard host cap that contributed to `wave_size`.",
|
|
69
69
|
"",
|
|
70
70
|
"For each wave: use the `task` tool (or equivalent subagent dispatch) to launch up to `wave_size` subagents in parallel (one per entry), wait for all to finish, then start the next wave.",
|
|
71
|
+
"",
|
|
72
|
+
'If a subagent reports a host session/usage limit (e.g. "hit your session limit · resets <time>") instead of submitting its result, do not immediately re-dispatch it: run merge-and-ingest with the results you did get, then wait until the stated reset time before running next-step to re-dispatch the remaining packets. Re-dispatching into an active limit just loses the wave.',
|
|
71
73
|
]
|
|
72
74
|
: [
|
|
73
75
|
"Read this generated dispatch plan:",
|
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,9 +24,7 @@ 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
|
-
|
|
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
|
-
import { DIRECT_CLI_DEFAULTS, getFlag, hasFlag, fromBase64Url, taskResultPath, readStdinText, getArtifactsDir, getRootDir, warnIfNotGitRepo, getBatchResultsDir, getMaxRuns, getAgentBatchSize, getParallelWorkers, getTimeoutMs, getExplicitProvider, getHostModel, getHostMaxActiveSubagents, getQuotaProbeMode, resolveRunProviderName, chunkArray, getUiMode, looksLikeCliFlag, countLines, } from "./cli/args.js";
|
|
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";
|
|
32
30
|
import { buildLineIndex, buildLineIndexForPaths, addFileLineCountHints, } from "./cli/lineIndex.js";
|
|
@@ -510,20 +508,29 @@ async function cmdMergeAndIngest(argv) {
|
|
|
510
508
|
const fallbackByTaskId = new Map();
|
|
511
509
|
for (const filename of files) {
|
|
512
510
|
const filePath = resolve(join(taskResultsDir, filename));
|
|
513
|
-
if (
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
511
|
+
if (expectedPaths.has(filePath))
|
|
512
|
+
continue;
|
|
513
|
+
// Not part of this round's plan. Still read it so a current task can be
|
|
514
|
+
// recovered by task_id (e.g. a subagent wrote a valid result under a
|
|
515
|
+
// non-assigned name).
|
|
516
|
+
try {
|
|
517
|
+
const raw = await readFile(filePath, "utf8");
|
|
518
|
+
const parsed = JSON.parse(raw);
|
|
519
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
520
|
+
const tid = typeof parsed.task_id === "string"
|
|
521
|
+
? String(parsed.task_id) : undefined;
|
|
522
|
+
if (tid && !fallbackByTaskId.has(tid)) {
|
|
523
|
+
fallbackByTaskId.set(tid, parsed);
|
|
524
524
|
}
|
|
525
525
|
}
|
|
526
|
-
|
|
526
|
+
}
|
|
527
|
+
catch { /* not parseable — skip */ }
|
|
528
|
+
// Only genuinely stray files are "spurious". Canonical per-task result files
|
|
529
|
+
// (<stem>_<digest>.json) left by prior deepening rounds in the same
|
|
530
|
+
// task-results/ dir are legitimate and must not inflate the count or bury
|
|
531
|
+
// the real stray-file signal (3 -> 191 over a run before this fix).
|
|
532
|
+
if (!isCanonicalResultFilename(filename)) {
|
|
533
|
+
spuriousFileCount++;
|
|
527
534
|
process.stderr.write(`[merge-and-ingest] Warning: unexpected file in task-results/: ${filename}\n`);
|
|
528
535
|
}
|
|
529
536
|
}
|
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
import { normalizeExtractorPath } from "./pathPatterns.js";
|
|
2
2
|
import { LANGUAGE_BY_EXTENSION } from "./languageMap.generated.js";
|
|
3
|
+
// The generated linguist map resolves a few common extensions to obscure
|
|
4
|
+
// languages that outrank the everyday one (".md" -> GCC machine description,
|
|
5
|
+
// ".yml"/".yaml" -> MiniYAML). These overrides win over the generated map so the
|
|
6
|
+
// file inventory does not mislabel ordinary docs/config. Keep this list small
|
|
7
|
+
// and limited to extensions whose generated mapping is demonstrably wrong.
|
|
8
|
+
const EXTENSION_LANGUAGE_OVERRIDES = {
|
|
9
|
+
md: "markdown",
|
|
10
|
+
markdown: "markdown",
|
|
11
|
+
yaml: "yaml",
|
|
12
|
+
yml: "yaml",
|
|
13
|
+
};
|
|
3
14
|
function inferLanguage(path) {
|
|
4
15
|
const normalized = normalizeExtractorPath(path);
|
|
5
16
|
const base = normalized.split("/").pop() ?? normalized;
|
|
6
|
-
const extension = base.includes(".") ? base.split(".").pop() ?? "" : "";
|
|
7
|
-
return
|
|
17
|
+
const extension = (base.includes(".") ? base.split(".").pop() ?? "" : "").toLowerCase();
|
|
18
|
+
return (EXTENSION_LANGUAGE_OVERRIDES[extension] ??
|
|
19
|
+
LANGUAGE_BY_EXTENSION[extension] ??
|
|
20
|
+
"unknown");
|
|
8
21
|
}
|
|
9
22
|
export function buildRepoManifest(repositoryName, files) {
|
|
10
23
|
return {
|
package/dist/extractors/graph.js
CHANGED
|
@@ -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 = {}) {
|
package/dist/io/artifacts.d.ts
CHANGED
|
@@ -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
|
|
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;
|
package/dist/io/artifacts.js
CHANGED
|
@@ -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(
|
|
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,
|
|
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
|
|
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,4 +1,5 @@
|
|
|
1
1
|
import type { ArtifactMetadataManifest } from "../types/artifactMetadata.js";
|
|
2
2
|
import type { ArtifactBundle } from "../io/artifacts.js";
|
|
3
3
|
export declare function present(bundle: ArtifactBundle, artifactName: string): boolean;
|
|
4
|
+
export declare function computeArtifactStateSignature(bundle: ArtifactBundle): string;
|
|
4
5
|
export declare function computeArtifactMetadata(bundle: ArtifactBundle, previous?: ArtifactMetadataManifest, updatedArtifacts?: Iterable<string>): ArtifactMetadataManifest;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { getArtifactValue } from "../io/artifacts.js";
|
|
2
3
|
import { buildReverseDependencyMap, hashArtifactValue, stableStringify, } from "./artifactFreshness.js";
|
|
3
4
|
const REVERSE_DEPENDENCY_MAP = buildReverseDependencyMap();
|
|
@@ -31,6 +32,20 @@ export function present(bundle, artifactName) {
|
|
|
31
32
|
const value = getArtifactValue(bundle, artifactName);
|
|
32
33
|
return value !== undefined && value !== null;
|
|
33
34
|
}
|
|
35
|
+
// Stable signature of the overall artifact state, keyed on per-artifact CONTENT
|
|
36
|
+
// hashes — deliberately NOT revisions, which only ever increment. A
|
|
37
|
+
// deterministic advance loop that revisits a signature it already produced this
|
|
38
|
+
// run is cycling (e.g. a runtime_validation <-> synthesis staleness ping-pong);
|
|
39
|
+
// the content-hash basis catches that even while revisions churn underneath.
|
|
40
|
+
export function computeArtifactStateSignature(bundle) {
|
|
41
|
+
const metadata = bundle.artifact_metadata;
|
|
42
|
+
if (!metadata)
|
|
43
|
+
return "no-metadata";
|
|
44
|
+
const entries = Object.entries(metadata.artifacts)
|
|
45
|
+
.map(([name, entry]) => `${name}:${entry.content_hash}`)
|
|
46
|
+
.sort();
|
|
47
|
+
return createHash("sha256").update(entries.join("\n")).digest("hex");
|
|
48
|
+
}
|
|
34
49
|
export function computeArtifactMetadata(bundle, previous, updatedArtifacts = []) {
|
|
35
50
|
const artifacts = {};
|
|
36
51
|
const updated = new Set(updatedArtifacts);
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { ArtifactBundle } from "../io/artifacts.js";
|
|
2
|
-
import type { ExecutorRunResult } from "./
|
|
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
|
-
|
|
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
|
-
|
|
47
|
+
io_errors: ioErrors,
|
|
48
|
+
is_clean: changed.length === 0 && missing.length === 0 && ioErrors.length === 0,
|
|
40
49
|
};
|
|
41
50
|
}
|
|
@@ -1,17 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
return [
|
|
3
|
-
"correctness",
|
|
4
|
-
"architecture",
|
|
5
|
-
"maintainability",
|
|
6
|
-
"security",
|
|
7
|
-
"reliability",
|
|
8
|
-
"performance",
|
|
9
|
-
"data_integrity",
|
|
10
|
-
"tests",
|
|
11
|
-
"operability",
|
|
12
|
-
"config_deployment",
|
|
13
|
-
].includes(String(value));
|
|
14
|
-
}
|
|
1
|
+
import { isLens } from "../types.js";
|
|
15
2
|
function getExternalSignalPaths(externalAnalyzerResults) {
|
|
16
3
|
const results = Array.isArray(externalAnalyzerResults?.results)
|
|
17
4
|
? externalAnalyzerResults.results
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ArtifactBundle } from "../io/artifacts.js";
|
|
2
|
-
import type { ExecutorRunResult } from "./
|
|
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";
|