auditor-lambda 0.3.40 → 0.5.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/audit-code-wrapper-lib.mjs +20 -2
- package/dist/cli/args.d.ts +59 -0
- package/dist/cli/args.js +244 -0
- package/dist/cli/dispatch.d.ts +80 -0
- package/dist/cli/dispatch.js +532 -0
- package/dist/cli/prompts.d.ts +37 -0
- package/dist/cli/prompts.js +225 -0
- package/dist/cli/steps.d.ts +29 -0
- package/dist/cli/steps.js +30 -0
- package/dist/cli/waveManifest.d.ts +40 -0
- package/dist/cli/waveManifest.js +41 -0
- package/dist/cli/workerResult.d.ts +18 -0
- package/dist/cli/workerResult.js +42 -0
- package/dist/cli.d.ts +2 -22
- package/dist/cli.js +442 -975
- package/dist/extractors/analyzers/css.d.ts +2 -0
- package/dist/extractors/analyzers/css.js +101 -0
- package/dist/extractors/analyzers/html.d.ts +2 -0
- package/dist/extractors/analyzers/html.js +92 -0
- package/dist/extractors/analyzers/merge.d.ts +14 -0
- package/dist/extractors/analyzers/merge.js +85 -0
- package/dist/extractors/analyzers/python.d.ts +2 -0
- package/dist/extractors/analyzers/python.js +104 -0
- package/dist/extractors/analyzers/registry.d.ts +33 -0
- package/dist/extractors/analyzers/registry.js +100 -0
- package/dist/extractors/analyzers/resourceUrl.d.ts +7 -0
- package/dist/extractors/analyzers/resourceUrl.js +25 -0
- package/dist/extractors/analyzers/sql.d.ts +2 -0
- package/dist/extractors/analyzers/sql.js +19 -0
- package/dist/extractors/analyzers/treeSitter.d.ts +34 -0
- package/dist/extractors/analyzers/treeSitter.js +111 -0
- package/dist/extractors/analyzers/types.d.ts +53 -0
- package/dist/extractors/analyzers/typescript.d.ts +2 -0
- package/dist/extractors/analyzers/typescript.js +257 -0
- package/dist/extractors/browserExtension.d.ts +1 -3
- package/dist/extractors/browserExtension.js +2 -2
- package/dist/extractors/designAssessment.d.ts +1 -3
- package/dist/extractors/disposition.d.ts +2 -1
- package/dist/extractors/disposition.js +11 -1
- package/dist/extractors/flows.d.ts +1 -3
- package/dist/extractors/flows.js +2 -2
- package/dist/extractors/graph.d.ts +2 -2
- package/dist/extractors/graph.js +171 -327
- package/dist/extractors/graphManifestEdges.d.ts +1 -1
- package/dist/extractors/graphPathUtils.d.ts +1 -1
- package/dist/extractors/graphPythonImports.d.ts +18 -0
- package/dist/extractors/graphPythonImports.js +362 -0
- package/dist/extractors/pathPatterns.d.ts +6 -0
- package/dist/extractors/pathPatterns.js +8 -0
- package/dist/extractors/risk.d.ts +1 -2
- package/dist/extractors/surfaces.d.ts +1 -3
- package/dist/extractors/surfaces.js +2 -2
- package/dist/io/artifacts.d.ts +12 -5
- package/dist/io/artifacts.js +13 -1
- package/dist/io/runArtifacts.js +1 -1
- package/dist/mcp/server.js +1 -1
- package/dist/orchestrator/advance.d.ts +21 -0
- package/dist/orchestrator/advance.js +69 -7
- package/dist/orchestrator/auditTaskUtils.d.ts +4 -0
- package/dist/orchestrator/auditTaskUtils.js +27 -0
- package/dist/orchestrator/dependencyMap.js +27 -0
- package/dist/orchestrator/edgeReasoning.d.ts +39 -0
- package/dist/orchestrator/edgeReasoning.js +125 -0
- package/dist/orchestrator/executors.js +11 -1
- package/dist/orchestrator/fileAnchors.d.ts +1 -1
- package/dist/orchestrator/fileIntegrity.d.ts +7 -0
- package/dist/orchestrator/fileIntegrity.js +41 -0
- package/dist/orchestrator/flowCoverage.d.ts +1 -1
- package/dist/orchestrator/flowPlanning.d.ts +1 -1
- package/dist/orchestrator/flowRequeue.d.ts +1 -1
- package/dist/orchestrator/graphEnrichmentExecutor.d.ts +29 -0
- package/dist/orchestrator/graphEnrichmentExecutor.js +196 -0
- package/dist/orchestrator/internalExecutors.d.ts +13 -2
- package/dist/orchestrator/internalExecutors.js +112 -16
- package/dist/orchestrator/localCommands.js +6 -25
- package/dist/orchestrator/nextStep.d.ts +2 -1
- package/dist/orchestrator/nextStep.js +3 -1
- package/dist/orchestrator/planning.d.ts +1 -1
- package/dist/orchestrator/requeueCommand.d.ts +1 -1
- package/dist/orchestrator/reviewPackets.d.ts +37 -4
- package/dist/orchestrator/reviewPackets.js +113 -158
- package/dist/orchestrator/runtimeValidation.d.ts +1 -1
- package/dist/orchestrator/runtimeValidation.js +4 -31
- package/dist/orchestrator/scope.d.ts +62 -0
- package/dist/orchestrator/scope.js +227 -0
- package/dist/orchestrator/state.js +2 -0
- package/dist/orchestrator/taskBuilder.d.ts +1 -1
- package/dist/orchestrator/taskBuilder.js +1 -12
- package/dist/orchestrator/unionFind.d.ts +7 -0
- package/dist/orchestrator/unionFind.js +32 -0
- package/dist/orchestrator/unitBuilder.d.ts +2 -2
- package/dist/orchestrator/unitBuilder.js +4 -18
- package/dist/prompts/renderWorkerPrompt.js +18 -1
- package/dist/providers/claudeCodeProvider.d.ts +4 -4
- package/dist/providers/claudeCodeProvider.js +9 -3
- package/dist/providers/constants.d.ts +1 -1
- package/dist/providers/constants.js +1 -1
- package/dist/providers/index.d.ts +1 -2
- package/dist/providers/index.js +5 -4
- package/dist/providers/localSubprocessProvider.d.ts +2 -2
- package/dist/providers/localSubprocessProvider.js +1 -1
- package/dist/providers/opencodeProvider.d.ts +4 -4
- package/dist/providers/opencodeProvider.js +7 -2
- package/dist/providers/spawnLoggedCommand.d.ts +3 -1
- package/dist/providers/spawnLoggedCommand.js +21 -0
- package/dist/providers/subprocessTemplateProvider.d.ts +4 -4
- package/dist/providers/subprocessTemplateProvider.js +8 -3
- package/dist/providers/vscodeTaskProvider.d.ts +3 -4
- package/dist/providers/vscodeTaskProvider.js +2 -2
- package/dist/quota/discoveredLimits.js +1 -1
- package/dist/quota/hostLimits.d.ts +1 -2
- package/dist/quota/hostLimits.js +4 -46
- package/dist/quota/index.d.ts +18 -15
- package/dist/quota/index.js +4 -9
- package/dist/quota/scheduler.d.ts +1 -3
- package/dist/quota/scheduler.js +1 -2
- package/dist/reporting/synthesis.d.ts +37 -3
- package/dist/reporting/synthesis.js +97 -16
- package/dist/reporting/synthesisNarrativePrompt.d.ts +7 -0
- package/dist/reporting/synthesisNarrativePrompt.js +60 -0
- package/dist/reporting/workBlocks.d.ts +2 -11
- package/dist/supervisor/operatorHandoff.js +1 -1
- package/dist/supervisor/runLedger.d.ts +1 -1
- package/dist/supervisor/runLedger.js +2 -2
- package/dist/supervisor/sessionConfig.d.ts +8 -1
- package/dist/supervisor/sessionConfig.js +22 -3
- package/dist/types/analyzerCapability.d.ts +16 -0
- package/dist/types/auditScope.d.ts +43 -0
- package/dist/types/auditScope.js +14 -0
- package/dist/types/reviewPlanning.d.ts +1 -1
- package/dist/types/synthesisNarrative.d.ts +7 -0
- package/dist/types/synthesisNarrative.js +5 -0
- package/dist/types/workerSession.d.ts +6 -0
- package/dist/types.d.ts +2 -19
- package/dist/validation/artifacts.d.ts +1 -1
- package/dist/validation/artifacts.js +10 -1
- package/dist/validation/auditResults.d.ts +1 -1
- package/dist/validation/auditResults.js +1 -1
- package/dist/validation/sessionConfig.d.ts +2 -3
- package/dist/validation/sessionConfig.js +25 -3
- package/package.json +7 -3
- package/schemas/analyzer_capability.schema.json +47 -0
- package/schemas/audit_findings.schema.json +141 -0
- package/schemas/finding.schema.json +2 -1
- package/schemas/graph_bundle.schema.json +5 -0
- package/schemas/scope.schema.json +46 -0
- package/scripts/postinstall.mjs +0 -1
- package/dist/io/json.d.ts +0 -10
- package/dist/io/json.js +0 -142
- package/dist/providers/types.d.ts +0 -33
- package/dist/quota/compositeQuotaSource.d.ts +0 -7
- package/dist/quota/compositeQuotaSource.js +0 -20
- package/dist/quota/errorParsers/claudeCodeErrorParser.d.ts +0 -6
- package/dist/quota/errorParsers/claudeCodeErrorParser.js +0 -39
- package/dist/quota/errorParsers/genericErrorParser.d.ts +0 -9
- package/dist/quota/errorParsers/genericErrorParser.js +0 -7
- package/dist/quota/errorParsers/index.d.ts +0 -5
- package/dist/quota/errorParsers/index.js +0 -12
- package/dist/quota/errorParsing.d.ts +0 -7
- package/dist/quota/errorParsing.js +0 -69
- package/dist/quota/fileLock.d.ts +0 -6
- package/dist/quota/fileLock.js +0 -64
- package/dist/quota/learnedQuotaSource.d.ts +0 -7
- package/dist/quota/learnedQuotaSource.js +0 -25
- package/dist/quota/limits.d.ts +0 -16
- package/dist/quota/limits.js +0 -77
- package/dist/quota/quotaSource.d.ts +0 -12
- package/dist/quota/slidingWindow.d.ts +0 -4
- package/dist/quota/slidingWindow.js +0 -28
- package/dist/quota/state.d.ts +0 -15
- package/dist/quota/state.js +0 -148
- package/dist/quota/types.d.ts +0 -67
- package/dist/quota/types.js +0 -1
- package/dist/reporting/rootCause.d.ts +0 -10
- package/dist/reporting/rootCause.js +0 -146
- package/dist/types/disposition.d.ts +0 -9
- package/dist/types/disposition.js +0 -1
- package/dist/types/flows.d.ts +0 -17
- package/dist/types/flows.js +0 -1
- package/dist/types/graph.d.ts +0 -22
- package/dist/types/graph.js +0 -1
- package/dist/types/risk.d.ts +0 -9
- package/dist/types/risk.js +0 -1
- package/dist/types/runLedger.d.ts +0 -17
- package/dist/types/runLedger.js +0 -6
- package/dist/types/sessionConfig.d.ts +0 -79
- package/dist/types/sessionConfig.js +0 -15
- package/dist/types/surfaces.d.ts +0 -15
- package/dist/types/surfaces.js +0 -1
- package/dist/validation/basic.d.ts +0 -13
- package/dist/validation/basic.js +0 -46
- /package/dist/{providers → extractors/analyzers}/types.js +0 -0
- /package/dist/{quota/quotaSource.js → types/analyzerCapability.js} +0 -0
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { mkdir, readFile, readdir, rename, rm, unlink, writeFile } from "node:fs/promises";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { createHash } from "node:crypto";
|
|
5
|
-
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
6
4
|
import { fileURLToPath } from "node:url";
|
|
7
5
|
import { buildRepoManifest } from "./extractors/fileInventory.js";
|
|
8
6
|
import { buildFileDisposition } from "./extractors/disposition.js";
|
|
@@ -13,244 +11,47 @@ import { buildFlowCoverage } from "./orchestrator/flowCoverage.js";
|
|
|
13
11
|
import { buildRuntimeValidationTasks, } from "./orchestrator/runtimeValidation.js";
|
|
14
12
|
import { initializeCoverageFromPlan } from "./orchestrator/planning.js";
|
|
15
13
|
import { loadArtifactBundle, writeCoreArtifacts, promoteFinalAuditReport, } from "./io/artifacts.js";
|
|
16
|
-
import { isFileMissingError, readJsonFile, writeJsonFile } from "
|
|
14
|
+
import { isFileMissingError, readJsonFile, writeJsonFile, prefixValidationIssues, RunLogger } from "@audit-tools/shared";
|
|
17
15
|
import { validateArtifactBundle } from "./validation/artifacts.js";
|
|
18
16
|
import { validateAuditResults, formatAuditResultIssues, } from "./validation/auditResults.js";
|
|
19
|
-
import { prefixValidationIssues } from "./validation/basic.js";
|
|
20
17
|
import { validateConfiguredProviderEnvironment, validateSessionConfig, } from "./validation/sessionConfig.js";
|
|
21
18
|
import { buildAuditReportModel, renderAuditReportMarkdown, } from "./reporting/synthesis.js";
|
|
22
19
|
import { deriveAuditState } from "./orchestrator/state.js";
|
|
23
20
|
import { advanceAudit } from "./orchestrator/advance.js";
|
|
21
|
+
import { checkFileIntegrity } from "./orchestrator/fileIntegrity.js";
|
|
24
22
|
import { decideNextStep } from "./orchestrator/nextStep.js";
|
|
23
|
+
import { collectLowConfidenceEdges, buildEdgeReasoningPrompt, edgeReasoningContentHash, } from "./orchestrator/edgeReasoning.js";
|
|
25
24
|
import { renderDesignReviewPrompt } from "./orchestrator/designReviewPrompt.js";
|
|
25
|
+
import { renderSynthesisNarrativePrompt } from "./reporting/synthesisNarrativePrompt.js";
|
|
26
26
|
import { createFreshSessionProvider, resolveFreshSessionProviderName, } from "./providers/index.js";
|
|
27
27
|
import { appendRunLedgerEntry, loadRunLedger } from "./supervisor/runLedger.js";
|
|
28
28
|
import { buildAuditCodeHandoff, writeAuditCodeHandoffArtifacts, } from "./supervisor/operatorHandoff.js";
|
|
29
|
-
import { getSessionConfigPath, loadSessionConfig, readSessionConfigFile, } from "./supervisor/sessionConfig.js";
|
|
29
|
+
import { getSessionConfigPath, loadSessionConfig, persistAnalyzerSettings, readSessionConfigFile, } from "./supervisor/sessionConfig.js";
|
|
30
|
+
import { resolveAnalyzerPlan, needsInstallDecision, } from "./extractors/analyzers/registry.js";
|
|
31
|
+
import { buildPathLookup } from "./extractors/graph.js";
|
|
32
|
+
import { buildDispositionMap } from "./extractors/disposition.js";
|
|
30
33
|
import { clearDispatchFiles, buildRunId, ensureSupervisorDirs, getRunPaths, writeDispatchBatchFiles, writeWorkerTaskFiles, } from "./io/runArtifacts.js";
|
|
31
34
|
import { renderWorkerPrompt } from "./prompts/renderWorkerPrompt.js";
|
|
32
|
-
import {
|
|
33
|
-
import { buildFileAnchorSummary, } from "./orchestrator/fileAnchors.js";
|
|
35
|
+
import { estimateTaskGroupTokens, sizeIndexFromManifest, } from "./orchestrator/reviewPackets.js";
|
|
34
36
|
import { LOCAL_SUBPROCESS_PROVIDER_NAME } from "./providers/constants.js";
|
|
35
37
|
import { runAuditCodeMcpServer } from "./mcp/server.js";
|
|
36
|
-
import { scheduleWave, buildProviderModelKey, readQuotaState, recordWaveOutcome, resolveLimits, resolveHostActiveSubagentLimit, probeProvider, computeMaxSafeConcurrency, getQuotaStatePath, detectRateLimitError, computeCooldownUntil, runSlidingWindow, LearnedQuotaSource, CompositeQuotaSource, lookupDiscoveredLimits, updateDiscoveredLimits, mergeDiscoveredLimits, getHeaderExtractorForProvider, } from "./quota/index.js";
|
|
38
|
+
import { scheduleWave, buildProviderModelKey, readQuotaState, recordWaveOutcome, resolveLimits, resolveHostActiveSubagentLimit, probeProvider, computeMaxSafeConcurrency, getQuotaStatePath, detectRateLimitError, computeCooldownUntil, runSlidingWindow, LearnedQuotaSource, CompositeQuotaSource, lookupDiscoveredLimits, updateDiscoveredLimits, mergeDiscoveredLimits, getHeaderExtractorForProvider, setQuotaStateDir, } from "./quota/index.js";
|
|
39
|
+
// Re-exports from extracted modules
|
|
40
|
+
export { resolveHostDispatchCapability, DIRECT_CLI_DEFAULTS, getFlag, hasFlag, getOptionalBooleanFlag, getArtifactsDir, getRootDir, getBatchResultsDir, getMaxRuns, getAgentBatchSize, getParallelWorkers, getTimeoutMs, chunkArray, getUiMode, looksLikeCliFlag, countLines, warnIfNotGitRepo, } from "./cli/args.js";
|
|
41
|
+
import { DIRECT_CLI_DEFAULTS, getFlag, hasFlag, getOptionalBooleanFlag, fromBase64Url, renderCommand, summarizeLaunchExit, taskResultPath, readStdinText, getArtifactsDir, getRootDir, warnIfNotGitRepo, getBatchResultsDir, getMaxRuns, getAgentBatchSize, getParallelWorkers, getTimeoutMs, getExplicitProvider, getHostModel, getHostMaxActiveSubagents, getQuotaProbeMode, resolveRunProviderName, chunkArray, getUiMode, looksLikeCliFlag, resolveHostDispatchCapability, countLines, listBatchResultFiles, } from "./cli/args.js";
|
|
42
|
+
import { nextStepCommand, mergeAndIngestCommand, renderDispatchReviewPrompt, renderSingleTaskFallbackStepPrompt, renderPresentReportPrompt, renderAnalyzerInstallPrompt, renderEdgeReasoningStepPrompt, renderEdgeReasoningDispatchPrompt, renderBlockedStepPrompt, } from "./cli/prompts.js";
|
|
43
|
+
import { writeCurrentStep, } from "./cli/steps.js";
|
|
44
|
+
import { WORKER_RESULT_CONTRACT_VERSION, buildWorkerResult, persistWorkerRunArtifacts, isWorkerResult, buildWorkerFailureBlocker, formatAuditResultValidationError, } from "./cli/workerResult.js";
|
|
45
|
+
import { DISPATCH_RESULT_MAP_FILENAME, ACTIVE_DISPATCH_FILENAME, resolveRunScopedArg, loadDispatchResultMap, entriesByTaskId, buildPendingAuditTasks, prepareDispatchArtifacts, } from "./cli/dispatch.js";
|
|
46
|
+
import { readWaveManifest, writeWaveManifest, removeWaveManifest, buildWaveSlotEntry, } from "./cli/waveManifest.js";
|
|
37
47
|
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
38
48
|
const ADVANCE_AUDIT_CONTRACT_VERSION = "audit-code/v1alpha1";
|
|
39
|
-
const WORKER_RESULT_CONTRACT_VERSION = "audit-code-worker-result/v1alpha1";
|
|
40
|
-
const STEP_CONTRACT_VERSION = "audit-code-step/v1alpha1";
|
|
41
|
-
const LARGE_FILE_PACKET_TARGET_LINES = 2500;
|
|
42
|
-
const SMALL_MODEL_HINT_MAX_LINES = 500;
|
|
43
|
-
const SMALL_MODEL_HINT_MAX_ESTIMATED_TOKENS = 3000;
|
|
44
|
-
const DEEP_MODEL_HINT_MIN_ESTIMATED_TOKENS = 9000;
|
|
45
|
-
const DIRECT_CLI_DEFAULTS = {
|
|
46
|
-
rootDir: ".",
|
|
47
|
-
artifactsDir: ".artifacts",
|
|
48
|
-
maxRuns: 1000,
|
|
49
|
-
agentBatchSize: 6,
|
|
50
|
-
parallelWorkers: 1,
|
|
51
|
-
timeoutMs: 30 * 60 * 1000, // 30 minutes
|
|
52
|
-
uiMode: "headless",
|
|
53
|
-
};
|
|
54
|
-
// Keep the sample-run payload explicit so the demo command is deterministic.
|
|
55
49
|
const SAMPLE_REPO_FILES = [
|
|
56
50
|
{ path: "src/api/auth.ts", size_bytes: 1240, hash: "abc123" },
|
|
57
51
|
{ path: "src/lib/session.ts", size_bytes: 980, hash: "def456" },
|
|
58
52
|
{ path: "infra/deploy.yml", size_bytes: 420, hash: "ghi789" },
|
|
59
53
|
{ path: "docs/notes.md", size_bytes: 300, hash: "doc111" },
|
|
60
54
|
];
|
|
61
|
-
function isLongFlagToken(value) {
|
|
62
|
-
return typeof value === "string" && value.startsWith("--");
|
|
63
|
-
}
|
|
64
|
-
// Read a long-form CLI flag while treating a following flag token as "missing".
|
|
65
|
-
function getFlag(argv, name, fallback) {
|
|
66
|
-
const index = argv.indexOf(name);
|
|
67
|
-
if (index < 0)
|
|
68
|
-
return fallback;
|
|
69
|
-
const candidate = argv[index + 1];
|
|
70
|
-
if (!candidate || isLongFlagToken(candidate))
|
|
71
|
-
return fallback;
|
|
72
|
-
return candidate;
|
|
73
|
-
}
|
|
74
|
-
// Boolean flags only care whether the token is present at all.
|
|
75
|
-
function hasFlag(argv, name) {
|
|
76
|
-
return argv.includes(name);
|
|
77
|
-
}
|
|
78
|
-
function getOptionalBooleanFlag(argv, name) {
|
|
79
|
-
const raw = getFlag(argv, name);
|
|
80
|
-
if (raw === undefined) {
|
|
81
|
-
return undefined;
|
|
82
|
-
}
|
|
83
|
-
if (raw === "true") {
|
|
84
|
-
return true;
|
|
85
|
-
}
|
|
86
|
-
if (raw === "false") {
|
|
87
|
-
return false;
|
|
88
|
-
}
|
|
89
|
-
throw new Error(`${name} must be either true or false.`);
|
|
90
|
-
}
|
|
91
|
-
function optionalBooleanEnv(value) {
|
|
92
|
-
if (value === "true")
|
|
93
|
-
return true;
|
|
94
|
-
if (value === "false")
|
|
95
|
-
return false;
|
|
96
|
-
return undefined;
|
|
97
|
-
}
|
|
98
|
-
export function resolveHostDispatchCapability(options) {
|
|
99
|
-
if (options.explicit !== undefined) {
|
|
100
|
-
return options.explicit;
|
|
101
|
-
}
|
|
102
|
-
if (options.sessionConfig.host_can_dispatch_subagents !== undefined) {
|
|
103
|
-
return options.sessionConfig.host_can_dispatch_subagents;
|
|
104
|
-
}
|
|
105
|
-
return optionalBooleanEnv((options.env ?? process.env).AUDIT_CODE_HOST_CAN_DISPATCH) ?? true;
|
|
106
|
-
}
|
|
107
|
-
function toBase64Url(value) {
|
|
108
|
-
return Buffer.from(value, "utf8").toString("base64url");
|
|
109
|
-
}
|
|
110
|
-
function fromBase64Url(value) {
|
|
111
|
-
return Buffer.from(value, "base64url").toString("utf8");
|
|
112
|
-
}
|
|
113
|
-
function digestId(value) {
|
|
114
|
-
return createHash("sha256").update(value).digest("hex").slice(0, 12);
|
|
115
|
-
}
|
|
116
|
-
function safeArtifactStem(value) {
|
|
117
|
-
const sanitized = value
|
|
118
|
-
.replace(/[^a-zA-Z0-9_-]+/g, "_")
|
|
119
|
-
.replace(/^_+|_+$/g, "")
|
|
120
|
-
.slice(0, 80);
|
|
121
|
-
return sanitized.length > 0 ? sanitized : "artifact";
|
|
122
|
-
}
|
|
123
|
-
function artifactNameForId(value, extension) {
|
|
124
|
-
return `${safeArtifactStem(value)}_${digestId(value)}.${extension}`;
|
|
125
|
-
}
|
|
126
|
-
function quoteCommandArg(value) {
|
|
127
|
-
return /[\s"]/u.test(value) ? `"${value.replace(/"/g, '\\"')}"` : value;
|
|
128
|
-
}
|
|
129
|
-
function renderCommand(argv) {
|
|
130
|
-
return argv.map((item) => quoteCommandArg(item)).join(" ");
|
|
131
|
-
}
|
|
132
|
-
function summarizeLaunchExit(result) {
|
|
133
|
-
if (result.accepted !== false && !result.error) {
|
|
134
|
-
return null;
|
|
135
|
-
}
|
|
136
|
-
const parts = [
|
|
137
|
-
result.signal
|
|
138
|
-
? `signal ${result.signal}`
|
|
139
|
-
: `exit code ${result.exitCode ?? "unknown"}`,
|
|
140
|
-
result.command ? `command: ${result.command}` : null,
|
|
141
|
-
result.stdoutPath ? `stdout: ${result.stdoutPath}` : null,
|
|
142
|
-
result.stderrPath ? `stderr: ${result.stderrPath}` : null,
|
|
143
|
-
result.error ?? null,
|
|
144
|
-
].filter((part) => Boolean(part));
|
|
145
|
-
return parts.join("; ");
|
|
146
|
-
}
|
|
147
|
-
function taskResultPath(taskResultsDir, taskId) {
|
|
148
|
-
return join(taskResultsDir, artifactNameForId(taskId, "json"));
|
|
149
|
-
}
|
|
150
|
-
function packetPromptPath(taskResultsDir, packetId) {
|
|
151
|
-
return join(taskResultsDir, artifactNameForId(packetId, "prompt.md"));
|
|
152
|
-
}
|
|
153
|
-
async function readStdinText() {
|
|
154
|
-
if (process.stdin.isTTY) {
|
|
155
|
-
return "";
|
|
156
|
-
}
|
|
157
|
-
return await new Promise((resolveInput, reject) => {
|
|
158
|
-
let input = "";
|
|
159
|
-
process.stdin.setEncoding("utf8");
|
|
160
|
-
process.stdin.on("data", (chunk) => {
|
|
161
|
-
input += chunk;
|
|
162
|
-
});
|
|
163
|
-
process.stdin.on("end", () => resolveInput(input));
|
|
164
|
-
process.stdin.on("error", reject);
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
function resolveFlagPath(argv, name, fallback) {
|
|
168
|
-
return resolve(getFlag(argv, name, fallback));
|
|
169
|
-
}
|
|
170
|
-
function normalizePositiveInteger(value) {
|
|
171
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
172
|
-
return undefined;
|
|
173
|
-
}
|
|
174
|
-
return Math.floor(value);
|
|
175
|
-
}
|
|
176
|
-
function parsePositiveIntegerFlag(argv, name) {
|
|
177
|
-
const raw = getFlag(argv, name);
|
|
178
|
-
if (raw === undefined) {
|
|
179
|
-
return undefined;
|
|
180
|
-
}
|
|
181
|
-
return normalizePositiveInteger(Number(raw));
|
|
182
|
-
}
|
|
183
|
-
function getArtifactsDir(argv) {
|
|
184
|
-
return resolveFlagPath(argv, "--artifacts-dir", DIRECT_CLI_DEFAULTS.artifactsDir);
|
|
185
|
-
}
|
|
186
|
-
function getRootDir(argv) {
|
|
187
|
-
return resolveFlagPath(argv, "--root", DIRECT_CLI_DEFAULTS.rootDir);
|
|
188
|
-
}
|
|
189
|
-
function warnIfNotGitRepo(root) {
|
|
190
|
-
const gitEntry = join(root, ".git");
|
|
191
|
-
if (!existsSync(gitEntry)) {
|
|
192
|
-
console.warn(`Warning: target directory '${root}' does not appear to be a git repository. Diff-based signals will be unavailable.`);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
function getBatchResultsDir(argv) {
|
|
196
|
-
const value = getFlag(argv, "--batch-results");
|
|
197
|
-
return value ? resolve(value) : undefined;
|
|
198
|
-
}
|
|
199
|
-
function getMaxRuns(argv) {
|
|
200
|
-
return parsePositiveIntegerFlag(argv, "--max-runs") ?? DIRECT_CLI_DEFAULTS.maxRuns;
|
|
201
|
-
}
|
|
202
|
-
function getAgentBatchSize(argv, sessionConfig) {
|
|
203
|
-
return (parsePositiveIntegerFlag(argv, "--agent-batch-size") ??
|
|
204
|
-
normalizePositiveInteger(sessionConfig.agent_task_batch_size) ??
|
|
205
|
-
DIRECT_CLI_DEFAULTS.agentBatchSize);
|
|
206
|
-
}
|
|
207
|
-
function getParallelWorkers(argv, sessionConfig) {
|
|
208
|
-
return (parsePositiveIntegerFlag(argv, "--parallel") ??
|
|
209
|
-
normalizePositiveInteger(sessionConfig.parallel_workers) ??
|
|
210
|
-
DIRECT_CLI_DEFAULTS.parallelWorkers);
|
|
211
|
-
}
|
|
212
|
-
function getTimeoutMs(argv, sessionConfig) {
|
|
213
|
-
return (parsePositiveIntegerFlag(argv, "--timeout") ??
|
|
214
|
-
normalizePositiveInteger(sessionConfig.timeout_ms) ??
|
|
215
|
-
DIRECT_CLI_DEFAULTS.timeoutMs);
|
|
216
|
-
}
|
|
217
|
-
function getExplicitProvider(argv) {
|
|
218
|
-
return getFlag(argv, "--provider");
|
|
219
|
-
}
|
|
220
|
-
function getHostModel(argv) {
|
|
221
|
-
return getFlag(argv, "--host-model") ?? null;
|
|
222
|
-
}
|
|
223
|
-
function getHostMaxActiveSubagents(argv) {
|
|
224
|
-
return parsePositiveIntegerFlag(argv, "--host-max-active-subagents") ?? null;
|
|
225
|
-
}
|
|
226
|
-
function getQuotaProbeMode(argv, sessionConfig) {
|
|
227
|
-
const raw = getFlag(argv, "--quota-probe") ?? sessionConfig.quota?.probe ?? "auto";
|
|
228
|
-
if (raw === "auto" || raw === "never" || raw === "force")
|
|
229
|
-
return raw;
|
|
230
|
-
return "auto";
|
|
231
|
-
}
|
|
232
|
-
function resolveRunProviderName(argv, sessionConfig) {
|
|
233
|
-
return resolveFreshSessionProviderName(getExplicitProvider(argv), sessionConfig);
|
|
234
|
-
}
|
|
235
|
-
function chunkArray(arr, size) {
|
|
236
|
-
const chunkSize = normalizePositiveInteger(size);
|
|
237
|
-
if (chunkSize === undefined) {
|
|
238
|
-
throw new Error("chunkArray size must be a positive integer.");
|
|
239
|
-
}
|
|
240
|
-
const chunks = [];
|
|
241
|
-
for (let i = 0; i < arr.length; i += chunkSize) {
|
|
242
|
-
chunks.push(arr.slice(i, i + chunkSize));
|
|
243
|
-
}
|
|
244
|
-
return chunks;
|
|
245
|
-
}
|
|
246
|
-
function getUiMode(argv, fallback = DIRECT_CLI_DEFAULTS.uiMode) {
|
|
247
|
-
const raw = getFlag(argv, "--ui");
|
|
248
|
-
if (raw === "visible")
|
|
249
|
-
return "visible";
|
|
250
|
-
if (raw === "headless")
|
|
251
|
-
return "headless";
|
|
252
|
-
return fallback;
|
|
253
|
-
}
|
|
254
55
|
function buildEnvelope(params) {
|
|
255
56
|
return {
|
|
256
57
|
contract_version: ADVANCE_AUDIT_CONTRACT_VERSION,
|
|
@@ -312,30 +113,6 @@ function buildBlockedAuditState(params) {
|
|
|
312
113
|
: item),
|
|
313
114
|
};
|
|
314
115
|
}
|
|
315
|
-
async function countLines(path) {
|
|
316
|
-
return new Promise((resolve, reject) => {
|
|
317
|
-
let lines = 0;
|
|
318
|
-
let byteCount = 0;
|
|
319
|
-
let lastByte = -1;
|
|
320
|
-
const stream = createReadStream(path);
|
|
321
|
-
stream.on("data", (chunk) => {
|
|
322
|
-
const buffer = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
323
|
-
byteCount += buffer.length;
|
|
324
|
-
for (let i = 0; i < buffer.length; ++i) {
|
|
325
|
-
if (buffer[i] === 10)
|
|
326
|
-
lines++;
|
|
327
|
-
lastByte = buffer[i];
|
|
328
|
-
}
|
|
329
|
-
});
|
|
330
|
-
stream.on("end", () => {
|
|
331
|
-
if (byteCount === 0)
|
|
332
|
-
return resolve(0);
|
|
333
|
-
// Files not ending with \n have one final line not counted above
|
|
334
|
-
resolve(lastByte !== 10 ? lines + 1 : lines);
|
|
335
|
-
});
|
|
336
|
-
stream.on("error", reject);
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
116
|
async function buildLineIndex(root, repoManifest) {
|
|
340
117
|
const entries = [];
|
|
341
118
|
const batchSize = 25;
|
|
@@ -368,26 +145,6 @@ async function buildLineIndexForPaths(root, paths) {
|
|
|
368
145
|
}));
|
|
369
146
|
return Object.fromEntries(entries);
|
|
370
147
|
}
|
|
371
|
-
async function listBatchResultFiles(batchDir) {
|
|
372
|
-
const entries = await readdir(batchDir, { withFileTypes: true });
|
|
373
|
-
const files = entries
|
|
374
|
-
.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".json"))
|
|
375
|
-
.map((entry) => join(batchDir, entry.name))
|
|
376
|
-
.sort((a, b) => a.localeCompare(b));
|
|
377
|
-
if (files.length === 0) {
|
|
378
|
-
throw new Error(`No JSON audit result files found in ${batchDir}.`);
|
|
379
|
-
}
|
|
380
|
-
return files;
|
|
381
|
-
}
|
|
382
|
-
function buildPendingAuditTasks(bundle) {
|
|
383
|
-
const completedTaskIds = new Set((bundle.audit_results ?? []).map((result) => result.task_id));
|
|
384
|
-
const pendingTasks = (bundle.audit_tasks ?? []).filter((task) => task.status !== "complete" && !completedTaskIds.has(task.task_id));
|
|
385
|
-
const lineIndex = Object.fromEntries(pendingTasks.flatMap((task) => Object.entries(task.file_line_counts ?? {})));
|
|
386
|
-
return orderTasksForPacketReview(pendingTasks, {
|
|
387
|
-
graphBundle: bundle.graph_bundle,
|
|
388
|
-
lineIndex,
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
148
|
async function addFileLineCountHints(root, tasks) {
|
|
392
149
|
const lineIndex = await buildLineIndexForPaths(root, tasks.flatMap((task) => task.file_paths));
|
|
393
150
|
return tasks.map((task) => ({
|
|
@@ -477,6 +234,11 @@ async function ensureSemanticReviewRun(params) {
|
|
|
477
234
|
const pendingTasks = await addFileLineCountHints(params.root, buildPendingAuditTasks(params.bundle));
|
|
478
235
|
const pendingTasksPath = join(paths.runDir, "pending-audit-tasks.json");
|
|
479
236
|
const auditResultsPath = join(paths.runDir, "audit-results.json");
|
|
237
|
+
const taskReadPaths = new Set();
|
|
238
|
+
for (const pt of pendingTasks) {
|
|
239
|
+
for (const fp of pt.file_paths)
|
|
240
|
+
taskReadPaths.add(fp);
|
|
241
|
+
}
|
|
480
242
|
const task = {
|
|
481
243
|
contract_version: "audit-code-worker/v1alpha1",
|
|
482
244
|
run_id: runId,
|
|
@@ -496,6 +258,10 @@ async function ensureSemanticReviewRun(params) {
|
|
|
496
258
|
pending_audit_tasks_path: pendingTasksPath,
|
|
497
259
|
timeout_ms: params.timeoutMs,
|
|
498
260
|
max_retries: 0,
|
|
261
|
+
access: {
|
|
262
|
+
read_paths: [...taskReadPaths],
|
|
263
|
+
write_paths: [auditResultsPath, paths.resultPath],
|
|
264
|
+
},
|
|
499
265
|
};
|
|
500
266
|
const prompt = renderWorkerPrompt(task);
|
|
501
267
|
await writeWorkerTaskFiles(task, prompt, paths, params.artifactsDir, pendingTasks);
|
|
@@ -519,174 +285,6 @@ async function ensureSemanticReviewRun(params) {
|
|
|
519
285
|
});
|
|
520
286
|
return { state: blockedState, bundle: blockedBundle, activeReviewRun };
|
|
521
287
|
}
|
|
522
|
-
function nextStepCommand(root, artifactsDir, extraArgs = []) {
|
|
523
|
-
return renderCommand([
|
|
524
|
-
"audit-code",
|
|
525
|
-
"next-step",
|
|
526
|
-
"--root",
|
|
527
|
-
root,
|
|
528
|
-
"--artifacts-dir",
|
|
529
|
-
artifactsDir,
|
|
530
|
-
...extraArgs,
|
|
531
|
-
]);
|
|
532
|
-
}
|
|
533
|
-
function mergeAndIngestCommand(artifactsDir, runId) {
|
|
534
|
-
return renderCommand([
|
|
535
|
-
"audit-code",
|
|
536
|
-
"merge-and-ingest",
|
|
537
|
-
"--run-id",
|
|
538
|
-
runId,
|
|
539
|
-
"--artifacts-dir",
|
|
540
|
-
artifactsDir,
|
|
541
|
-
]);
|
|
542
|
-
}
|
|
543
|
-
function renderDispatchReviewPrompt(params) {
|
|
544
|
-
const mergeCommand = mergeAndIngestCommand(params.artifactsDir, params.activeReviewRun.run_id);
|
|
545
|
-
const continueCommand = nextStepCommand(params.root, params.artifactsDir);
|
|
546
|
-
const modelLine = params.hostCanSelectSubagentModel
|
|
547
|
-
? "When launching each subagent, map `entry.model_hint.tier` (`small`, `standard`, `deep`) to an available host model without asking the user for model names."
|
|
548
|
-
: "Ignore `entry.model_hint`; this host did not report per-subagent model selection.";
|
|
549
|
-
const toolsLine = params.hostCanRestrictSubagentTools
|
|
550
|
-
? "Restrict review subagents to read/search plus the packet submit command named in their prompt. Do not give them source edit/write tools."
|
|
551
|
-
: "Do not ask the user about per-subagent tool restrictions; this host did not report a callable restriction facility.";
|
|
552
|
-
const dispatchDataLines = params.dispatchQuotaPath
|
|
553
|
-
? [
|
|
554
|
-
"Read these generated files unless the current tool response already included equivalent `dispatch_plan_entries` and `dispatch_quota` fields:",
|
|
555
|
-
"",
|
|
556
|
-
` Dispatch plan: ${params.dispatchPlanPath}`,
|
|
557
|
-
` Dispatch quota: ${params.dispatchQuotaPath}`,
|
|
558
|
-
"",
|
|
559
|
-
"Use the `wave_size` from the quota data. If `cooldown_until` is non-null, wait until that timestamp before starting the first wave.",
|
|
560
|
-
"",
|
|
561
|
-
"`host_concurrency_limit` records any detected hard host cap that contributed to `wave_size`.",
|
|
562
|
-
"",
|
|
563
|
-
"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.",
|
|
564
|
-
]
|
|
565
|
-
: [
|
|
566
|
-
"Read this generated dispatch plan unless the current tool response already included equivalent `dispatch_plan_entries`:",
|
|
567
|
-
"",
|
|
568
|
-
` ${params.dispatchPlanPath}`,
|
|
569
|
-
"",
|
|
570
|
-
"Launch one subagent for each entry in the plan.",
|
|
571
|
-
];
|
|
572
|
-
return [
|
|
573
|
-
"# audit-code dispatch review",
|
|
574
|
-
"",
|
|
575
|
-
...dispatchDataLines,
|
|
576
|
-
"",
|
|
577
|
-
"Pass each `entry.prompt_path` literally to its subagent; do not load packet prompt files into this orchestrator context.",
|
|
578
|
-
"",
|
|
579
|
-
"Subagent prompt shape:",
|
|
580
|
-
"",
|
|
581
|
-
' Read and follow the audit instructions in: <entry.prompt_path>',
|
|
582
|
-
"",
|
|
583
|
-
modelLine,
|
|
584
|
-
toolsLine,
|
|
585
|
-
"",
|
|
586
|
-
"Each subagent must submit its packet through the submit command printed in its packet prompt and stop after successful submission.",
|
|
587
|
-
"",
|
|
588
|
-
"**After all waves complete:**",
|
|
589
|
-
"",
|
|
590
|
-
"Run exactly:",
|
|
591
|
-
"",
|
|
592
|
-
` ${mergeCommand}`,
|
|
593
|
-
"",
|
|
594
|
-
"If merge-and-ingest fails, stop and report the exact command and error output. Do not manually merge results or edit audit state.",
|
|
595
|
-
"",
|
|
596
|
-
"If merge-and-ingest succeeds, run:",
|
|
597
|
-
"",
|
|
598
|
-
` ${continueCommand}`,
|
|
599
|
-
"",
|
|
600
|
-
"Read and follow only the new step prompt path returned by that command.",
|
|
601
|
-
"",
|
|
602
|
-
].join("\n");
|
|
603
|
-
}
|
|
604
|
-
function renderSingleTaskFallbackStepPrompt(params) {
|
|
605
|
-
return [
|
|
606
|
-
"# audit-code single-task fallback step",
|
|
607
|
-
"",
|
|
608
|
-
"Use this step only because the host reported no callable subagent facility.",
|
|
609
|
-
"",
|
|
610
|
-
"Read and follow exactly this generated single-task prompt:",
|
|
611
|
-
"",
|
|
612
|
-
` ${params.singleTaskPromptPath}`,
|
|
613
|
-
"",
|
|
614
|
-
"Complete exactly one AuditResult for the task named there, write the JSON array to the prompt's audit_results_path, run the exact worker_command from that prompt, then stop.",
|
|
615
|
-
"",
|
|
616
|
-
"Do not run dispatch commands, do not prepare packets, do not run next-step again in this turn, and do not read a report after the worker command.",
|
|
617
|
-
"",
|
|
618
|
-
"The only backend command allowed after writing the result is:",
|
|
619
|
-
"",
|
|
620
|
-
` ${renderCommand(params.activeReviewRun.worker_command)}`,
|
|
621
|
-
"",
|
|
622
|
-
].join("\n");
|
|
623
|
-
}
|
|
624
|
-
function renderPresentReportPrompt(finalReportPath) {
|
|
625
|
-
return [
|
|
626
|
-
"# audit-code present report",
|
|
627
|
-
"",
|
|
628
|
-
"The deterministic audit is complete.",
|
|
629
|
-
"",
|
|
630
|
-
"Read the final audit report from the `audit-code://report/current` MCP resource (or from the file at:",
|
|
631
|
-
` ${finalReportPath}`,
|
|
632
|
-
"if a Read tool is available).",
|
|
633
|
-
"",
|
|
634
|
-
"Present the completed audit with work blocks first.",
|
|
635
|
-
"",
|
|
636
|
-
"Do not run the orchestrator again for this completed audit.",
|
|
637
|
-
"",
|
|
638
|
-
].join("\n");
|
|
639
|
-
}
|
|
640
|
-
function renderBlockedStepPrompt(reason) {
|
|
641
|
-
return [
|
|
642
|
-
"# audit-code blocked",
|
|
643
|
-
"",
|
|
644
|
-
"The audit cannot continue automatically from this step.",
|
|
645
|
-
"",
|
|
646
|
-
"Report this blocker verbatim and stop:",
|
|
647
|
-
"",
|
|
648
|
-
reason,
|
|
649
|
-
"",
|
|
650
|
-
].join("\n");
|
|
651
|
-
}
|
|
652
|
-
async function writeCurrentStep(params) {
|
|
653
|
-
const stepsDir = join(params.artifactsDir, "steps");
|
|
654
|
-
await mkdir(stepsDir, { recursive: true });
|
|
655
|
-
const promptPath = join(stepsDir, "current-prompt.md");
|
|
656
|
-
const stepPath = join(stepsDir, "current-step.json");
|
|
657
|
-
await writeFile(promptPath, params.prompt, "utf8");
|
|
658
|
-
const step = {
|
|
659
|
-
contract_version: STEP_CONTRACT_VERSION,
|
|
660
|
-
step_kind: params.stepKind,
|
|
661
|
-
prompt_path: promptPath,
|
|
662
|
-
status: params.status,
|
|
663
|
-
run_id: params.runId,
|
|
664
|
-
allowed_commands: params.allowedCommands,
|
|
665
|
-
stop_condition: params.stopCondition,
|
|
666
|
-
repo_root: params.repoRoot,
|
|
667
|
-
artifacts_dir: params.artifactsDir,
|
|
668
|
-
artifact_paths: {
|
|
669
|
-
current_step: stepPath,
|
|
670
|
-
current_prompt: promptPath,
|
|
671
|
-
...params.artifactPaths,
|
|
672
|
-
},
|
|
673
|
-
};
|
|
674
|
-
await writeJsonFile(stepPath, step);
|
|
675
|
-
return step;
|
|
676
|
-
}
|
|
677
|
-
function formatAuditResultValidationError(issues) {
|
|
678
|
-
return (`audit-results validation failed with ${issues.length} error(s):\n` +
|
|
679
|
-
formatAuditResultIssues(issues));
|
|
680
|
-
}
|
|
681
|
-
function buildWorkerFailureBlocker(workerResult) {
|
|
682
|
-
const details = workerResult.errors.filter((error) => error.trim().length > 0);
|
|
683
|
-
return details.length > 0
|
|
684
|
-
? `${workerResult.summary} ${details.join(" ")}`
|
|
685
|
-
: workerResult.summary;
|
|
686
|
-
}
|
|
687
|
-
function looksLikeCliFlag(value) {
|
|
688
|
-
return isLongFlagToken(value);
|
|
689
|
-
}
|
|
690
288
|
export const cliTestUtils = {
|
|
691
289
|
defaults: DIRECT_CLI_DEFAULTS,
|
|
692
290
|
getFlag,
|
|
@@ -720,9 +318,15 @@ async function maybeArchiveLegacyPendingResults(auditResultsPath) {
|
|
|
720
318
|
}
|
|
721
319
|
async function runAuditStep(options) {
|
|
722
320
|
const bundle = await loadArtifactBundle(options.artifactsDir);
|
|
321
|
+
const runLogger = new RunLogger(join(options.artifactsDir, "run.log.jsonl"), {
|
|
322
|
+
enabled: options.runLog ?? true,
|
|
323
|
+
});
|
|
723
324
|
const lineIndex = bundle.repo_manifest
|
|
724
325
|
? await buildLineIndex(options.root, bundle.repo_manifest)
|
|
725
326
|
: undefined;
|
|
327
|
+
const sizeIndex = bundle.repo_manifest
|
|
328
|
+
? sizeIndexFromManifest(bundle.repo_manifest)
|
|
329
|
+
: undefined;
|
|
726
330
|
if (looksLikeCliFlag(options.auditResultsPath)) {
|
|
727
331
|
throw new Error(`Invalid audit results path '${options.auditResultsPath}'. This looks like a CLI flag rather than a file path.`);
|
|
728
332
|
}
|
|
@@ -750,13 +354,27 @@ async function runAuditStep(options) {
|
|
|
750
354
|
const externalAnalyzerResults = options.externalAnalyzerPath
|
|
751
355
|
? await readJsonFile(options.externalAnalyzerPath)
|
|
752
356
|
: undefined;
|
|
357
|
+
const narrativeResults = options.narrativeResultsPath
|
|
358
|
+
? await readJsonFile(options.narrativeResultsPath)
|
|
359
|
+
: undefined;
|
|
360
|
+
const edgeReasoningResults = options.edgeReasoningResultsPath
|
|
361
|
+
? await readJsonFile(options.edgeReasoningResultsPath)
|
|
362
|
+
: undefined;
|
|
753
363
|
const result = await advanceAudit(bundle, {
|
|
754
364
|
root: options.root,
|
|
755
365
|
lineIndex,
|
|
366
|
+
sizeIndex,
|
|
756
367
|
auditResults: auditResults,
|
|
757
368
|
runtimeValidationUpdates,
|
|
758
369
|
externalAnalyzerResults,
|
|
370
|
+
narrativeResults,
|
|
371
|
+
edgeReasoningResults,
|
|
372
|
+
analyzers: options.analyzers,
|
|
373
|
+
graphLlmEdgeReasoning: options.graphLlmEdgeReasoning,
|
|
374
|
+
since: options.since,
|
|
759
375
|
preferredExecutor: options.preferredExecutor,
|
|
376
|
+
opentoken: options.opentoken,
|
|
377
|
+
runLogger,
|
|
760
378
|
});
|
|
761
379
|
await writeCoreArtifacts(options.artifactsDir, result.updated_bundle);
|
|
762
380
|
const archivedPendingResults = await maybeArchiveLegacyPendingResults(options.auditResultsPath);
|
|
@@ -805,29 +423,6 @@ async function ingestBatchAuditResults(options) {
|
|
|
805
423
|
next_likely_step: state.status === "complete" ? null : decision.selected_obligation,
|
|
806
424
|
};
|
|
807
425
|
}
|
|
808
|
-
function buildWorkerResult(params) {
|
|
809
|
-
return {
|
|
810
|
-
contract_version: WORKER_RESULT_CONTRACT_VERSION,
|
|
811
|
-
run_id: params.runId,
|
|
812
|
-
obligation_id: params.obligationId,
|
|
813
|
-
status: params.status,
|
|
814
|
-
progress_made: params.progressMade,
|
|
815
|
-
selected_executor: params.selectedExecutor,
|
|
816
|
-
artifacts_written: params.artifactsWritten,
|
|
817
|
-
summary: params.summary,
|
|
818
|
-
next_likely_step: params.nextLikelyStep,
|
|
819
|
-
errors: params.errors,
|
|
820
|
-
};
|
|
821
|
-
}
|
|
822
|
-
async function persistWorkerRunArtifacts(paths, workerResult, executionMode) {
|
|
823
|
-
await writeJsonFile(paths.resultPath, workerResult);
|
|
824
|
-
await writeJsonFile(paths.statusPath, {
|
|
825
|
-
run_id: workerResult.run_id,
|
|
826
|
-
status: workerResult.status,
|
|
827
|
-
execution_mode: executionMode,
|
|
828
|
-
result_path: paths.resultPath,
|
|
829
|
-
});
|
|
830
|
-
}
|
|
831
426
|
async function persistConfigErrorHandoff(params) {
|
|
832
427
|
const bundle = await loadArtifactBundle(params.artifactsDir);
|
|
833
428
|
const blockedState = buildBlockedAuditState({
|
|
@@ -850,12 +445,6 @@ async function persistConfigErrorHandoff(params) {
|
|
|
850
445
|
});
|
|
851
446
|
await writeAuditCodeHandoffArtifacts(handoff);
|
|
852
447
|
}
|
|
853
|
-
function isWorkerResult(value) {
|
|
854
|
-
return (typeof value === "object" &&
|
|
855
|
-
value !== null &&
|
|
856
|
-
value.contract_version ===
|
|
857
|
-
WORKER_RESULT_CONTRACT_VERSION);
|
|
858
|
-
}
|
|
859
448
|
export async function runSample(argv = process.argv) {
|
|
860
449
|
const repoManifest = buildRepoManifest("sample-repo", SAMPLE_REPO_FILES);
|
|
861
450
|
const disposition = buildFileDisposition(repoManifest);
|
|
@@ -990,6 +579,11 @@ async function cmdAdvanceAudit(argv) {
|
|
|
990
579
|
auditResultsPath: getFlag(argv, "--results"),
|
|
991
580
|
runtimeUpdatesPath: getFlag(argv, "--updates"),
|
|
992
581
|
externalAnalyzerPath,
|
|
582
|
+
analyzers: sessionConfig.analyzers,
|
|
583
|
+
graphLlmEdgeReasoning: sessionConfig.graph?.llm_edge_reasoning,
|
|
584
|
+
since: getFlag(argv, "--since"),
|
|
585
|
+
opentoken: sessionConfig.opentoken?.enabled,
|
|
586
|
+
runLog: sessionConfig.observability?.run_log,
|
|
993
587
|
});
|
|
994
588
|
if (result.selected_executor !== "agent") {
|
|
995
589
|
await clearDispatchFiles(artifactsDir);
|
|
@@ -1013,6 +607,7 @@ async function cmdAdvanceAudit(argv) {
|
|
|
1013
607
|
}
|
|
1014
608
|
async function runDeterministicForNextStep(params) {
|
|
1015
609
|
let lastSummary = "";
|
|
610
|
+
let analyzers = params.analyzers;
|
|
1016
611
|
for (let index = 0; index < params.maxRuns; index++) {
|
|
1017
612
|
const bundle = await loadArtifactBundle(params.artifactsDir);
|
|
1018
613
|
const decision = decideNextStep(bundle);
|
|
@@ -1039,6 +634,105 @@ async function runDeterministicForNextStep(params) {
|
|
|
1039
634
|
: join(params.artifactsDir, "audit-report.md"),
|
|
1040
635
|
};
|
|
1041
636
|
}
|
|
637
|
+
if (index === 0 && bundle.repo_manifest) {
|
|
638
|
+
const pendingTasks = buildPendingAuditTasks(bundle);
|
|
639
|
+
const taskFiles = new Set();
|
|
640
|
+
for (const task of pendingTasks) {
|
|
641
|
+
for (const fp of Object.keys(task.file_line_counts ?? {}))
|
|
642
|
+
taskFiles.add(fp);
|
|
643
|
+
}
|
|
644
|
+
if (taskFiles.size > 0) {
|
|
645
|
+
const integrity = await checkFileIntegrity(params.root, bundle.repo_manifest, [...taskFiles]);
|
|
646
|
+
if (!integrity.is_clean) {
|
|
647
|
+
console.log(`File integrity check: ${integrity.changed_files.length} changed, ${integrity.missing_files.length} missing — re-running intake.`);
|
|
648
|
+
await advanceAudit(bundle, { root: params.root, preferredExecutor: "intake_executor", opentoken: params.opentoken });
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if (decision.selected_executor === "graph_enrichment_executor") {
|
|
654
|
+
const includedFiles = bundle.repo_manifest
|
|
655
|
+
? [
|
|
656
|
+
...new Set(buildPathLookup(bundle.repo_manifest, buildDispositionMap(bundle.file_disposition)).values()),
|
|
657
|
+
]
|
|
658
|
+
: [];
|
|
659
|
+
const plan = resolveAnalyzerPlan(params.root, analyzers, includedFiles);
|
|
660
|
+
const unresolved = plan.filter(needsInstallDecision);
|
|
661
|
+
if (unresolved.length > 0) {
|
|
662
|
+
const decisionsPath = join(params.artifactsDir, "incoming", "analyzer-decisions.json");
|
|
663
|
+
let decisions;
|
|
664
|
+
try {
|
|
665
|
+
decisions = await readJsonFile(decisionsPath);
|
|
666
|
+
}
|
|
667
|
+
catch (error) {
|
|
668
|
+
if (!isFileMissingError(error))
|
|
669
|
+
throw error;
|
|
670
|
+
}
|
|
671
|
+
if (decisions && typeof decisions === "object") {
|
|
672
|
+
const settings = {};
|
|
673
|
+
for (const [id, value] of Object.entries(decisions)) {
|
|
674
|
+
if (value === "ephemeral" ||
|
|
675
|
+
value === "permanent" ||
|
|
676
|
+
value === "skip" ||
|
|
677
|
+
value === "repo" ||
|
|
678
|
+
value === "auto") {
|
|
679
|
+
settings[id] = value;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
if (Object.keys(settings).length > 0) {
|
|
683
|
+
const merged = await persistAnalyzerSettings(params.artifactsDir, settings);
|
|
684
|
+
analyzers = merged.analyzers;
|
|
685
|
+
}
|
|
686
|
+
await unlink(decisionsPath).catch(() => { });
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
return {
|
|
690
|
+
kind: "analyzer_install",
|
|
691
|
+
state,
|
|
692
|
+
bundle,
|
|
693
|
+
unresolved,
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
// Phase 4B — optional edge-reasoning producing turn. Once analyzer installs
|
|
697
|
+
// are resolved, if the flag is on and the floor carries low-confidence
|
|
698
|
+
// (< 0.65) edges, emit one bounded host turn (subagent dispatch or a single
|
|
699
|
+
// host step) to produce reason rewrites, then re-run. The enrichment
|
|
700
|
+
// executor applies the host-supplied rewrites in the SAME advanceAudit call
|
|
701
|
+
// that merges analyzer edges and writes analyzer_capability, so graph_bundle
|
|
702
|
+
// and its marker stay revision-consistent (no staleness loop). Flag off or
|
|
703
|
+
// no candidates → fall through and run the executor with no rewrites.
|
|
704
|
+
if (params.graphLlmEdgeReasoning === true && bundle.graph_bundle) {
|
|
705
|
+
const candidates = collectLowConfidenceEdges(bundle.graph_bundle);
|
|
706
|
+
if (candidates.length > 0) {
|
|
707
|
+
const edgeReasoningResultsPath = join(params.artifactsDir, "incoming", "edge-reasoning.json");
|
|
708
|
+
let edgeReasoningResults;
|
|
709
|
+
try {
|
|
710
|
+
edgeReasoningResults = await readJsonFile(edgeReasoningResultsPath);
|
|
711
|
+
}
|
|
712
|
+
catch (error) {
|
|
713
|
+
if (!isFileMissingError(error))
|
|
714
|
+
throw error;
|
|
715
|
+
}
|
|
716
|
+
if (edgeReasoningResults) {
|
|
717
|
+
await runAuditStep({
|
|
718
|
+
root: params.root,
|
|
719
|
+
artifactsDir: params.artifactsDir,
|
|
720
|
+
analyzers,
|
|
721
|
+
graphLlmEdgeReasoning: true,
|
|
722
|
+
edgeReasoningResultsPath,
|
|
723
|
+
since: params.since,
|
|
724
|
+
opentoken: params.opentoken,
|
|
725
|
+
});
|
|
726
|
+
await unlink(edgeReasoningResultsPath).catch(() => { });
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
729
|
+
return { kind: "edge_reasoning", state, bundle, candidates };
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
// No undecided installs (and no pending edge reasoning): fall through to run
|
|
733
|
+
// the executor below (it installs for ephemeral/permanent, uses repo/cache,
|
|
734
|
+
// skips the rest).
|
|
735
|
+
}
|
|
1042
736
|
if (decision.selected_executor === "design_review") {
|
|
1043
737
|
const findingsPath = join(params.artifactsDir, "incoming", "design-review-findings.json");
|
|
1044
738
|
let reviewFindings;
|
|
@@ -1065,6 +759,36 @@ async function runDeterministicForNextStep(params) {
|
|
|
1065
759
|
bundle,
|
|
1066
760
|
};
|
|
1067
761
|
}
|
|
762
|
+
if (decision.selected_executor === "synthesis_narrative_executor") {
|
|
763
|
+
const narrativePath = join(params.artifactsDir, "incoming", "synthesis-narrative.json");
|
|
764
|
+
let narrativeResults;
|
|
765
|
+
try {
|
|
766
|
+
narrativeResults = await readJsonFile(narrativePath);
|
|
767
|
+
}
|
|
768
|
+
catch (error) {
|
|
769
|
+
if (!isFileMissingError(error))
|
|
770
|
+
throw error;
|
|
771
|
+
}
|
|
772
|
+
if (narrativeResults) {
|
|
773
|
+
await runAuditStep({
|
|
774
|
+
root: params.root,
|
|
775
|
+
artifactsDir: params.artifactsDir,
|
|
776
|
+
preferredExecutor: "synthesis_narrative_executor",
|
|
777
|
+
narrativeResultsPath: narrativePath,
|
|
778
|
+
opentoken: params.opentoken,
|
|
779
|
+
});
|
|
780
|
+
await unlink(narrativePath).catch(() => { });
|
|
781
|
+
continue;
|
|
782
|
+
}
|
|
783
|
+
if (params.narrativeEnabled) {
|
|
784
|
+
return {
|
|
785
|
+
kind: "synthesis_narrative",
|
|
786
|
+
state,
|
|
787
|
+
bundle,
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
// Narrative disabled: fall through so the deterministic omit runs below.
|
|
791
|
+
}
|
|
1068
792
|
if (decision.selected_executor === "agent") {
|
|
1069
793
|
return {
|
|
1070
794
|
kind: "semantic_review",
|
|
@@ -1095,11 +819,45 @@ async function runDeterministicForNextStep(params) {
|
|
|
1095
819
|
reason: lastSummary || decision.reason,
|
|
1096
820
|
};
|
|
1097
821
|
}
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
822
|
+
let result;
|
|
823
|
+
try {
|
|
824
|
+
result = await runAuditStep({
|
|
825
|
+
root: params.root,
|
|
826
|
+
artifactsDir: params.artifactsDir,
|
|
827
|
+
analyzers,
|
|
828
|
+
graphLlmEdgeReasoning: params.graphLlmEdgeReasoning,
|
|
829
|
+
since: params.since,
|
|
830
|
+
opentoken: params.opentoken,
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
catch (error) {
|
|
834
|
+
const current = await loadArtifactBundle(params.artifactsDir);
|
|
835
|
+
const currentState = deriveAuditState(current);
|
|
836
|
+
currentState.last_executor = decision.selected_executor ?? undefined;
|
|
837
|
+
currentState.last_obligation = decision.selected_obligation ?? undefined;
|
|
838
|
+
await writeCoreArtifacts(params.artifactsDir, { ...current, audit_state: currentState });
|
|
839
|
+
await writeJsonFile(join(params.artifactsDir, "steps", "deterministic-progress.json"), {
|
|
840
|
+
iteration: index + 1,
|
|
841
|
+
max_runs: params.maxRuns,
|
|
842
|
+
last_executor: decision.selected_executor,
|
|
843
|
+
last_obligation: decision.selected_obligation,
|
|
844
|
+
prior_summary: lastSummary || null,
|
|
845
|
+
error: error instanceof Error ? error.message : String(error),
|
|
846
|
+
timestamp: new Date().toISOString(),
|
|
847
|
+
});
|
|
848
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
849
|
+
throw new Error(`Deterministic executor ${decision.selected_executor} failed on obligation ${decision.selected_obligation} (iteration ${index + 1}/${params.maxRuns}, prior progress: ${lastSummary || "none"}): ${detail}`, { cause: error instanceof Error ? error : undefined });
|
|
850
|
+
}
|
|
1102
851
|
lastSummary = result.progress_summary;
|
|
852
|
+
await writeJsonFile(join(params.artifactsDir, "steps", "deterministic-progress.json"), {
|
|
853
|
+
iteration: index + 1,
|
|
854
|
+
max_runs: params.maxRuns,
|
|
855
|
+
last_executor: result.selected_executor,
|
|
856
|
+
last_obligation: decision.selected_obligation,
|
|
857
|
+
progress_made: result.progress_made,
|
|
858
|
+
summary: result.progress_summary,
|
|
859
|
+
timestamp: new Date().toISOString(),
|
|
860
|
+
});
|
|
1103
861
|
if (result.selected_executor !== "agent") {
|
|
1104
862
|
await clearDispatchFiles(params.artifactsDir);
|
|
1105
863
|
}
|
|
@@ -1169,6 +927,11 @@ async function cmdNextStep(argv) {
|
|
|
1169
927
|
selfCliPath: resolve(argv[1] ?? process.argv[1] ?? ""),
|
|
1170
928
|
timeoutMs: getTimeoutMs(argv, sessionConfig),
|
|
1171
929
|
maxRuns: getMaxRuns(argv),
|
|
930
|
+
opentoken: sessionConfig.opentoken?.enabled,
|
|
931
|
+
narrativeEnabled: sessionConfig.synthesis?.narrative !== false,
|
|
932
|
+
analyzers: sessionConfig.analyzers,
|
|
933
|
+
graphLlmEdgeReasoning: sessionConfig.graph?.llm_edge_reasoning,
|
|
934
|
+
since: getFlag(argv, "--since"),
|
|
1172
935
|
});
|
|
1173
936
|
if (result.kind === "complete") {
|
|
1174
937
|
const step = await writeCurrentStep({
|
|
@@ -1236,6 +999,130 @@ async function cmdNextStep(argv) {
|
|
|
1236
999
|
console.log(JSON.stringify(step, null, 2));
|
|
1237
1000
|
return;
|
|
1238
1001
|
}
|
|
1002
|
+
if (result.kind === "analyzer_install") {
|
|
1003
|
+
const decisionsPath = join(artifactsDir, "incoming", "analyzer-decisions.json");
|
|
1004
|
+
await mkdir(join(artifactsDir, "incoming"), { recursive: true });
|
|
1005
|
+
const continueCommand = nextStepCommand(root, artifactsDir);
|
|
1006
|
+
const step = await writeCurrentStep({
|
|
1007
|
+
artifactsDir,
|
|
1008
|
+
stepKind: "analyzer_install",
|
|
1009
|
+
status: "ready",
|
|
1010
|
+
runId: null,
|
|
1011
|
+
allowedCommands: [continueCommand],
|
|
1012
|
+
stopCondition: "Write analyzer install decisions to the results path, then run next-step.",
|
|
1013
|
+
repoRoot: root,
|
|
1014
|
+
artifactPaths: {
|
|
1015
|
+
analyzer_decisions: decisionsPath,
|
|
1016
|
+
},
|
|
1017
|
+
prompt: renderAnalyzerInstallPrompt({
|
|
1018
|
+
unresolved: result.unresolved,
|
|
1019
|
+
decisionsPath,
|
|
1020
|
+
continueCommand,
|
|
1021
|
+
}),
|
|
1022
|
+
});
|
|
1023
|
+
console.log(JSON.stringify(step, null, 2));
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
if (result.kind === "edge_reasoning") {
|
|
1027
|
+
await mkdir(join(artifactsDir, "incoming"), { recursive: true });
|
|
1028
|
+
const edgeReasoningResultsPath = join(artifactsDir, "incoming", "edge-reasoning.json");
|
|
1029
|
+
const continueCommand = nextStepCommand(root, artifactsDir);
|
|
1030
|
+
const basePrompt = buildEdgeReasoningPrompt(result.candidates);
|
|
1031
|
+
const contentHash = edgeReasoningContentHash(result.candidates);
|
|
1032
|
+
if (hostCanDispatch) {
|
|
1033
|
+
// Dispatch path: isolate the (potentially large) edge-list prompt in a file
|
|
1034
|
+
// and have the host fan it out to one subagent, mirroring the packet review
|
|
1035
|
+
// dispatch contract. The subagent writes the rewrites file; next-step applies.
|
|
1036
|
+
const edgeReasoningPromptPath = join(artifactsDir, "incoming", "edge-reasoning-prompt.md");
|
|
1037
|
+
await writeFile(edgeReasoningPromptPath, basePrompt, "utf8");
|
|
1038
|
+
const step = await writeCurrentStep({
|
|
1039
|
+
artifactsDir,
|
|
1040
|
+
stepKind: "edge_reasoning_dispatch",
|
|
1041
|
+
status: "ready",
|
|
1042
|
+
runId: null,
|
|
1043
|
+
allowedCommands: [continueCommand],
|
|
1044
|
+
stopCondition: "Dispatch one subagent to write the edge-reasoning rewrites, then run next-step.",
|
|
1045
|
+
repoRoot: root,
|
|
1046
|
+
artifactPaths: {
|
|
1047
|
+
edge_reasoning_prompt: edgeReasoningPromptPath,
|
|
1048
|
+
edge_reasoning_results: edgeReasoningResultsPath,
|
|
1049
|
+
},
|
|
1050
|
+
prompt: renderEdgeReasoningDispatchPrompt({
|
|
1051
|
+
promptPath: edgeReasoningPromptPath,
|
|
1052
|
+
resultsPath: edgeReasoningResultsPath,
|
|
1053
|
+
continueCommand,
|
|
1054
|
+
contentHash,
|
|
1055
|
+
candidateCount: result.candidates.length,
|
|
1056
|
+
}),
|
|
1057
|
+
access: {
|
|
1058
|
+
read_paths: [edgeReasoningPromptPath],
|
|
1059
|
+
write_paths: [edgeReasoningResultsPath],
|
|
1060
|
+
},
|
|
1061
|
+
});
|
|
1062
|
+
console.log(JSON.stringify(step, null, 2));
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
// One-step fallback (no callable subagent facility): the host produces the
|
|
1066
|
+
// rewrites itself in a single bounded turn, mirroring the narrative step.
|
|
1067
|
+
const step = await writeCurrentStep({
|
|
1068
|
+
artifactsDir,
|
|
1069
|
+
stepKind: "edge_reasoning",
|
|
1070
|
+
status: "ready",
|
|
1071
|
+
runId: null,
|
|
1072
|
+
allowedCommands: [continueCommand],
|
|
1073
|
+
stopCondition: "Write the edge-reasoning rewrites to the results path, then run next-step.",
|
|
1074
|
+
repoRoot: root,
|
|
1075
|
+
artifactPaths: {
|
|
1076
|
+
edge_reasoning_results: edgeReasoningResultsPath,
|
|
1077
|
+
},
|
|
1078
|
+
prompt: renderEdgeReasoningStepPrompt({
|
|
1079
|
+
basePrompt,
|
|
1080
|
+
resultsPath: edgeReasoningResultsPath,
|
|
1081
|
+
continueCommand,
|
|
1082
|
+
contentHash,
|
|
1083
|
+
}),
|
|
1084
|
+
access: {
|
|
1085
|
+
read_paths: [],
|
|
1086
|
+
write_paths: [edgeReasoningResultsPath],
|
|
1087
|
+
},
|
|
1088
|
+
});
|
|
1089
|
+
console.log(JSON.stringify(step, null, 2));
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
if (result.kind === "synthesis_narrative") {
|
|
1093
|
+
const narrativeResultsPath = join(artifactsDir, "incoming", "synthesis-narrative.json");
|
|
1094
|
+
await mkdir(join(artifactsDir, "incoming"), { recursive: true });
|
|
1095
|
+
const continueCommand = nextStepCommand(root, artifactsDir);
|
|
1096
|
+
const basePrompt = result.bundle.audit_findings
|
|
1097
|
+
? renderSynthesisNarrativePrompt(result.bundle.audit_findings)
|
|
1098
|
+
: "# Synthesis narrative\n\nNo findings report is available; write an empty themes array.";
|
|
1099
|
+
const fullPrompt = [
|
|
1100
|
+
basePrompt,
|
|
1101
|
+
"## Results path",
|
|
1102
|
+
"",
|
|
1103
|
+
"Write the SynthesisNarrative JSON object to:",
|
|
1104
|
+
"",
|
|
1105
|
+
` ${narrativeResultsPath}`,
|
|
1106
|
+
"",
|
|
1107
|
+
`Then run: ${continueCommand}`,
|
|
1108
|
+
"",
|
|
1109
|
+
].join("\n");
|
|
1110
|
+
const step = await writeCurrentStep({
|
|
1111
|
+
artifactsDir,
|
|
1112
|
+
stepKind: "synthesis_narrative",
|
|
1113
|
+
status: "ready",
|
|
1114
|
+
runId: null,
|
|
1115
|
+
allowedCommands: [continueCommand],
|
|
1116
|
+
stopCondition: "Write the synthesis narrative to the results path, then run next-step.",
|
|
1117
|
+
repoRoot: root,
|
|
1118
|
+
artifactPaths: {
|
|
1119
|
+
synthesis_narrative_results: narrativeResultsPath,
|
|
1120
|
+
},
|
|
1121
|
+
prompt: fullPrompt,
|
|
1122
|
+
});
|
|
1123
|
+
console.log(JSON.stringify(step, null, 2));
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1239
1126
|
if (!hostCanDispatch) {
|
|
1240
1127
|
const singleTaskPromptPath = join(artifactsDir, "dispatch", "current-single-task-prompt.md");
|
|
1241
1128
|
const workerCommand = renderCommand(result.activeReviewRun.worker_command);
|
|
@@ -1258,11 +1145,16 @@ async function cmdNextStep(argv) {
|
|
|
1258
1145
|
singleTaskPromptPath,
|
|
1259
1146
|
activeReviewRun: result.activeReviewRun,
|
|
1260
1147
|
}),
|
|
1148
|
+
access: {
|
|
1149
|
+
read_paths: [singleTaskPromptPath],
|
|
1150
|
+
write_paths: [result.activeReviewRun.audit_results_path],
|
|
1151
|
+
},
|
|
1261
1152
|
});
|
|
1262
1153
|
console.log(JSON.stringify(step, null, 2));
|
|
1263
1154
|
return;
|
|
1264
1155
|
}
|
|
1265
1156
|
const dispatch = await prepareDispatchArtifacts({
|
|
1157
|
+
packageRoot,
|
|
1266
1158
|
runId: result.activeReviewRun.run_id,
|
|
1267
1159
|
artifactsDir,
|
|
1268
1160
|
root,
|
|
@@ -1299,6 +1191,13 @@ async function cmdNextStep(argv) {
|
|
|
1299
1191
|
hostCanRestrictSubagentTools,
|
|
1300
1192
|
hostCanSelectSubagentModel,
|
|
1301
1193
|
}),
|
|
1194
|
+
access: {
|
|
1195
|
+
read_paths: [
|
|
1196
|
+
dispatch.dispatch_plan_path,
|
|
1197
|
+
...(dispatch.dispatch_quota_path ? [dispatch.dispatch_quota_path] : []),
|
|
1198
|
+
],
|
|
1199
|
+
write_paths: [],
|
|
1200
|
+
},
|
|
1302
1201
|
});
|
|
1303
1202
|
console.log(JSON.stringify(step, null, 2));
|
|
1304
1203
|
}
|
|
@@ -1349,6 +1248,38 @@ async function cmdRunToCompletion(argv) {
|
|
|
1349
1248
|
while (runCount < maxRuns) {
|
|
1350
1249
|
const bundle = await loadArtifactBundle(artifactsDir);
|
|
1351
1250
|
const decision = decideNextStep(bundle);
|
|
1251
|
+
// Resume interrupted parallel wave: ingest any results that workers
|
|
1252
|
+
// wrote before the previous process exited.
|
|
1253
|
+
const priorWave = await readWaveManifest(artifactsDir);
|
|
1254
|
+
if (priorWave) {
|
|
1255
|
+
process.stderr.write(`[audit-code] Recovering interrupted wave (${priorWave.slots.length} slot(s), obligation ${priorWave.obligation_id}).\n`);
|
|
1256
|
+
let recoveredProgress = false;
|
|
1257
|
+
for (const entry of priorWave.slots) {
|
|
1258
|
+
try {
|
|
1259
|
+
const results = await readJsonFile(entry.audit_results_path);
|
|
1260
|
+
if (!results || results.length === 0)
|
|
1261
|
+
continue;
|
|
1262
|
+
const stepResult = await runAuditStep({
|
|
1263
|
+
root,
|
|
1264
|
+
artifactsDir,
|
|
1265
|
+
preferredExecutor: "result_ingestion_executor",
|
|
1266
|
+
auditResultsPath: entry.audit_results_path,
|
|
1267
|
+
});
|
|
1268
|
+
if (stepResult.progress_made) {
|
|
1269
|
+
recoveredProgress = true;
|
|
1270
|
+
anyProgress = true;
|
|
1271
|
+
for (const a of stepResult.artifacts_written)
|
|
1272
|
+
artifactsWritten.add(a);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
catch {
|
|
1276
|
+
process.stderr.write(`[audit-code] Skipping unreadable results for ${entry.run_id}.\n`);
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
await removeWaveManifest(artifactsDir);
|
|
1280
|
+
if (recoveredProgress)
|
|
1281
|
+
continue;
|
|
1282
|
+
}
|
|
1352
1283
|
if (decision.selected_executor === "agent" &&
|
|
1353
1284
|
bundle.audit_tasks?.some((t) => t.tags?.includes("selective_deepening") &&
|
|
1354
1285
|
t.status !== "complete") &&
|
|
@@ -1402,6 +1333,11 @@ async function cmdRunToCompletion(argv) {
|
|
|
1402
1333
|
const blockPendingTasks = await addFileLineCountHints(root, buildPendingAuditTasks(bundle));
|
|
1403
1334
|
const blockPendingTasksPath = join(blockPaths.runDir, "pending-audit-tasks.json");
|
|
1404
1335
|
const blockAuditResultsPath = join(blockPaths.runDir, "audit-results.json");
|
|
1336
|
+
const blockReadPaths = new Set();
|
|
1337
|
+
for (const pt of blockPendingTasks) {
|
|
1338
|
+
for (const fp of pt.file_paths)
|
|
1339
|
+
blockReadPaths.add(fp);
|
|
1340
|
+
}
|
|
1405
1341
|
const blockTask = {
|
|
1406
1342
|
contract_version: "audit-code-worker/v1alpha1",
|
|
1407
1343
|
run_id: blockRunId,
|
|
@@ -1421,6 +1357,10 @@ async function cmdRunToCompletion(argv) {
|
|
|
1421
1357
|
pending_audit_tasks_path: blockPendingTasksPath,
|
|
1422
1358
|
timeout_ms: timeoutMs,
|
|
1423
1359
|
max_retries: 0,
|
|
1360
|
+
access: {
|
|
1361
|
+
read_paths: [...blockReadPaths],
|
|
1362
|
+
write_paths: [blockAuditResultsPath, blockPaths.resultPath],
|
|
1363
|
+
},
|
|
1424
1364
|
};
|
|
1425
1365
|
const blockPrompt = renderWorkerPrompt(blockTask);
|
|
1426
1366
|
await writeWorkerTaskFiles(blockTask, blockPrompt, blockPaths, artifactsDir, blockPendingTasks);
|
|
@@ -1484,7 +1424,8 @@ async function cmdRunToCompletion(argv) {
|
|
|
1484
1424
|
const quotaStateEntry = quotaState.entries[providerModelKey] ?? null;
|
|
1485
1425
|
const allCandidateTasks = buildPendingAuditTasks(bundle);
|
|
1486
1426
|
const candidateGroups = chunkArray(allCandidateTasks.slice(0, parallelWorkers * agentBatchSize), agentBatchSize);
|
|
1487
|
-
const
|
|
1427
|
+
const candidateSizeIndex = sizeIndexFromManifest(bundle.repo_manifest);
|
|
1428
|
+
const slotTokenEstimates = candidateGroups.map((g) => estimateTaskGroupTokens(g, candidateSizeIndex));
|
|
1488
1429
|
const providerLimits = await provider.queryLimits?.(hostModel)
|
|
1489
1430
|
.then((r) => r ? { ...r, source: "provider_query" } : null)
|
|
1490
1431
|
.catch(() => null)
|
|
@@ -1526,6 +1467,11 @@ async function cmdRunToCompletion(argv) {
|
|
|
1526
1467
|
const slotPaths = getRunPaths(artifactsDir, slotRunId);
|
|
1527
1468
|
const slotAuditResultsPath = join(slotPaths.runDir, "audit-results.json");
|
|
1528
1469
|
const slotPendingTasksPath = join(slotPaths.runDir, "pending-audit-tasks.json");
|
|
1470
|
+
const slotReadPaths = new Set();
|
|
1471
|
+
for (const t of group) {
|
|
1472
|
+
for (const fp of t.file_paths)
|
|
1473
|
+
slotReadPaths.add(fp);
|
|
1474
|
+
}
|
|
1529
1475
|
const slotTask = {
|
|
1530
1476
|
contract_version: "audit-code-worker/v1alpha1",
|
|
1531
1477
|
run_id: slotRunId,
|
|
@@ -1540,6 +1486,10 @@ async function cmdRunToCompletion(argv) {
|
|
|
1540
1486
|
worker_command_mode: "deferred",
|
|
1541
1487
|
timeout_ms: timeoutMs,
|
|
1542
1488
|
max_retries: 0,
|
|
1489
|
+
access: {
|
|
1490
|
+
read_paths: [...slotReadPaths],
|
|
1491
|
+
write_paths: [slotAuditResultsPath, slotPaths.resultPath],
|
|
1492
|
+
},
|
|
1543
1493
|
};
|
|
1544
1494
|
const slotPrompt = renderWorkerPrompt(slotTask);
|
|
1545
1495
|
await writeWorkerTaskFiles(slotTask, slotPrompt, slotPaths, artifactsDir, group, { updateDispatch: false });
|
|
@@ -1556,6 +1506,12 @@ async function cmdRunToCompletion(argv) {
|
|
|
1556
1506
|
pending_audit_tasks_path: slot.pendingTasksPath,
|
|
1557
1507
|
})), workerSlots.flatMap((slot) => slot.group));
|
|
1558
1508
|
const parallelStartedAt = new Date().toISOString();
|
|
1509
|
+
await writeWaveManifest(artifactsDir, {
|
|
1510
|
+
obligation_id: obligationId ?? "unknown",
|
|
1511
|
+
started_at: parallelStartedAt,
|
|
1512
|
+
pid: process.pid,
|
|
1513
|
+
slots: workerSlots.map(buildWaveSlotEntry),
|
|
1514
|
+
});
|
|
1559
1515
|
const { results: launchResults } = await runSlidingWindow(workerSlots.map((slot) => () => provider.launch({
|
|
1560
1516
|
repoRoot: root,
|
|
1561
1517
|
runId: slot.runId,
|
|
@@ -1710,6 +1666,7 @@ async function cmdRunToCompletion(argv) {
|
|
|
1710
1666
|
}
|
|
1711
1667
|
}
|
|
1712
1668
|
}
|
|
1669
|
+
await removeWaveManifest(artifactsDir);
|
|
1713
1670
|
if (batchErrors.length > 0) {
|
|
1714
1671
|
const bundleAfter = await loadArtifactBundle(artifactsDir);
|
|
1715
1672
|
const blockedState = buildBlockedAuditState({
|
|
@@ -1774,6 +1731,8 @@ async function cmdRunToCompletion(argv) {
|
|
|
1774
1731
|
auditResultsPath,
|
|
1775
1732
|
runtimeUpdatesPath,
|
|
1776
1733
|
externalAnalyzerPath,
|
|
1734
|
+
analyzers: sessionConfig.analyzers,
|
|
1735
|
+
since: getFlag(argv, "--since"),
|
|
1777
1736
|
});
|
|
1778
1737
|
workerResult = {
|
|
1779
1738
|
contract_version: WORKER_RESULT_CONTRACT_VERSION,
|
|
@@ -1884,6 +1843,13 @@ async function cmdRunToCompletion(argv) {
|
|
|
1884
1843
|
const providerAuditResultsPath = preferredExecutor === "agent"
|
|
1885
1844
|
? join(paths.runDir, "audit-results.json")
|
|
1886
1845
|
: auditResultsPath;
|
|
1846
|
+
const providerReadPaths = new Set();
|
|
1847
|
+
if (pendingAuditTasks) {
|
|
1848
|
+
for (const pt of pendingAuditTasks) {
|
|
1849
|
+
for (const fp of pt.file_paths)
|
|
1850
|
+
providerReadPaths.add(fp);
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1887
1853
|
const task = {
|
|
1888
1854
|
contract_version: "audit-code-worker/v1alpha1",
|
|
1889
1855
|
run_id: runId,
|
|
@@ -1905,6 +1871,10 @@ async function cmdRunToCompletion(argv) {
|
|
|
1905
1871
|
external_analyzer_results_path: externalAnalyzerPath,
|
|
1906
1872
|
timeout_ms: timeoutMs,
|
|
1907
1873
|
max_retries: 0,
|
|
1874
|
+
access: providerReadPaths.size > 0 ? {
|
|
1875
|
+
read_paths: [...providerReadPaths],
|
|
1876
|
+
write_paths: [providerAuditResultsPath ?? paths.resultPath, paths.resultPath],
|
|
1877
|
+
} : undefined,
|
|
1908
1878
|
};
|
|
1909
1879
|
const prompt = renderWorkerPrompt(task);
|
|
1910
1880
|
await writeWorkerTaskFiles(task, prompt, paths, artifactsDir, pendingAuditTasks);
|
|
@@ -2141,520 +2111,12 @@ async function cmdWorkerRun(argv) {
|
|
|
2141
2111
|
process.exitCode = 1;
|
|
2142
2112
|
}
|
|
2143
2113
|
}
|
|
2144
|
-
const DISPATCH_RESULT_MAP_FILENAME = "dispatch-result-map.json";
|
|
2145
|
-
const ACTIVE_DISPATCH_FILENAME = "active-dispatch.json";
|
|
2146
|
-
function dispatchResultMapPath(runDir) {
|
|
2147
|
-
return join(runDir, DISPATCH_RESULT_MAP_FILENAME);
|
|
2148
|
-
}
|
|
2149
|
-
function resolveRunScopedArg(argv, rawFlag, b64Flag) {
|
|
2150
|
-
const raw = getFlag(argv, rawFlag);
|
|
2151
|
-
const encoded = getFlag(argv, b64Flag);
|
|
2152
|
-
return raw ?? (encoded ? fromBase64Url(encoded) : undefined);
|
|
2153
|
-
}
|
|
2154
|
-
async function loadDispatchResultMap(runDir) {
|
|
2155
|
-
try {
|
|
2156
|
-
return await readJsonFile(dispatchResultMapPath(runDir));
|
|
2157
|
-
}
|
|
2158
|
-
catch (error) {
|
|
2159
|
-
if (!isFileMissingError(error)) {
|
|
2160
|
-
throw error;
|
|
2161
|
-
}
|
|
2162
|
-
return null;
|
|
2163
|
-
}
|
|
2164
|
-
}
|
|
2165
|
-
function entriesByTaskId(entries) {
|
|
2166
|
-
return new Map(entries.map((entry) => [entry.task_id, entry]));
|
|
2167
|
-
}
|
|
2168
|
-
function isIsolatedLargeFilePacket(packet) {
|
|
2169
|
-
return (packet.file_paths.length === 1 &&
|
|
2170
|
-
packet.total_lines > LARGE_FILE_PACKET_TARGET_LINES);
|
|
2171
|
-
}
|
|
2172
|
-
function buildDispatchComplexity(packet, largeFileMode) {
|
|
2173
|
-
return {
|
|
2174
|
-
priority: packet.priority,
|
|
2175
|
-
task_count: packet.task_ids.length,
|
|
2176
|
-
file_count: packet.file_paths.length,
|
|
2177
|
-
total_lines: packet.total_lines,
|
|
2178
|
-
estimated_tokens: packet.estimated_tokens,
|
|
2179
|
-
lenses: packet.lenses,
|
|
2180
|
-
tags: packet.tags ?? [],
|
|
2181
|
-
large_file_mode: largeFileMode,
|
|
2182
|
-
};
|
|
2183
|
-
}
|
|
2184
|
-
function buildDispatchModelHint(complexity) {
|
|
2185
|
-
const deepReasons = [];
|
|
2186
|
-
if (complexity.priority === "high")
|
|
2187
|
-
deepReasons.push("high_priority");
|
|
2188
|
-
if (complexity.large_file_mode)
|
|
2189
|
-
deepReasons.push("isolated_large_file");
|
|
2190
|
-
if (complexity.estimated_tokens >= DEEP_MODEL_HINT_MIN_ESTIMATED_TOKENS) {
|
|
2191
|
-
deepReasons.push("high_estimated_tokens");
|
|
2192
|
-
}
|
|
2193
|
-
if (complexity.tags.some((tag) => tag === "critical_flow" || tag.startsWith("critical_flow:"))) {
|
|
2194
|
-
deepReasons.push("critical_flow");
|
|
2195
|
-
}
|
|
2196
|
-
if (complexity.tags.some((tag) => tag === "external_analyzer_signal" || tag.startsWith("external_tool:"))) {
|
|
2197
|
-
deepReasons.push("external_analyzer_signal");
|
|
2198
|
-
}
|
|
2199
|
-
if (complexity.tags.includes("lens_verification")) {
|
|
2200
|
-
deepReasons.push("lens_verification");
|
|
2201
|
-
}
|
|
2202
|
-
if (deepReasons.length > 0) {
|
|
2203
|
-
return { tier: "deep", reasons: deepReasons };
|
|
2204
|
-
}
|
|
2205
|
-
const sensitiveLenses = new Set(["security", "data_integrity", "reliability"]);
|
|
2206
|
-
const hasSensitiveLens = complexity.lenses.some((lens) => sensitiveLenses.has(lens));
|
|
2207
|
-
if (complexity.priority === "low" &&
|
|
2208
|
-
complexity.total_lines <= SMALL_MODEL_HINT_MAX_LINES &&
|
|
2209
|
-
complexity.estimated_tokens <= SMALL_MODEL_HINT_MAX_ESTIMATED_TOKENS &&
|
|
2210
|
-
!hasSensitiveLens &&
|
|
2211
|
-
complexity.tags.length === 0) {
|
|
2212
|
-
return { tier: "small", reasons: ["small_low_priority_packet"] };
|
|
2213
|
-
}
|
|
2214
|
-
const reasons = [];
|
|
2215
|
-
if (complexity.priority === "medium")
|
|
2216
|
-
reasons.push("medium_priority");
|
|
2217
|
-
if (hasSensitiveLens)
|
|
2218
|
-
reasons.push("sensitive_lens");
|
|
2219
|
-
if (complexity.total_lines > SMALL_MODEL_HINT_MAX_LINES) {
|
|
2220
|
-
reasons.push("moderate_size");
|
|
2221
|
-
}
|
|
2222
|
-
return {
|
|
2223
|
-
tier: "standard",
|
|
2224
|
-
reasons: reasons.length > 0 ? reasons : ["default_review_packet"],
|
|
2225
|
-
};
|
|
2226
|
-
}
|
|
2227
|
-
function withinRoot(root, path) {
|
|
2228
|
-
const rootPath = resolve(root);
|
|
2229
|
-
const absolutePath = resolve(rootPath, path);
|
|
2230
|
-
const relativePath = relative(rootPath, absolutePath);
|
|
2231
|
-
if (relativePath.startsWith("..") || isAbsolute(relativePath)) {
|
|
2232
|
-
throw new Error(`Path '${path}' escapes repository root '${rootPath}'.`);
|
|
2233
|
-
}
|
|
2234
|
-
return absolutePath;
|
|
2235
|
-
}
|
|
2236
|
-
function renderAnchorPreview(summary, anchorPath) {
|
|
2237
|
-
const preview = summary.anchors.slice(0, 24).map((anchor) => {
|
|
2238
|
-
const location = anchor.line ? `${summary.path}:${anchor.line}` : summary.path;
|
|
2239
|
-
const detail = anchor.detail ? ` - ${anchor.detail}` : "";
|
|
2240
|
-
return `- ${location} [${anchor.kind}] ${anchor.name}${detail}`;
|
|
2241
|
-
});
|
|
2242
|
-
return [
|
|
2243
|
-
"## Large File Review Mode",
|
|
2244
|
-
"This packet is intentionally isolated because it covers one large file.",
|
|
2245
|
-
"Use targeted reads/searches within this file, guided by the mechanical anchors.",
|
|
2246
|
-
"Do not read unrelated files unless a finding cannot be evidenced without a direct boundary check.",
|
|
2247
|
-
`Anchor file: ${anchorPath}`,
|
|
2248
|
-
`Anchor counts: symbols=${summary.counts.symbols}, routes=${summary.counts.routes}, keywords=${summary.counts.keywords}, graph_edges=${summary.counts.graph_edges}, analyzer_signals=${summary.counts.analyzer_signals}, omitted=${summary.omitted_anchor_count}`,
|
|
2249
|
-
"Anchor preview:",
|
|
2250
|
-
...(preview.length > 0 ? preview : ["- no anchors extracted beyond file boundaries"]),
|
|
2251
|
-
"",
|
|
2252
|
-
];
|
|
2253
|
-
}
|
|
2254
|
-
function formatPacketConfidence(value) {
|
|
2255
|
-
return typeof value === "number" && Number.isFinite(value)
|
|
2256
|
-
? value.toFixed(2)
|
|
2257
|
-
: "n/a";
|
|
2258
|
-
}
|
|
2259
|
-
function renderPacketGraphContext(packet) {
|
|
2260
|
-
const hasContext = (packet.entrypoints?.length ?? 0) > 0 ||
|
|
2261
|
-
(packet.key_edges?.length ?? 0) > 0 ||
|
|
2262
|
-
(packet.boundary_files?.length ?? 0) > 0 ||
|
|
2263
|
-
packet.quality !== undefined;
|
|
2264
|
-
if (!hasContext) {
|
|
2265
|
-
return [];
|
|
2266
|
-
}
|
|
2267
|
-
const lines = ["## Packet graph context"];
|
|
2268
|
-
if (packet.entrypoints?.length) {
|
|
2269
|
-
lines.push("Entrypoints:");
|
|
2270
|
-
lines.push(...packet.entrypoints.map((entrypoint) => `- ${entrypoint}`));
|
|
2271
|
-
}
|
|
2272
|
-
if (packet.key_edges?.length) {
|
|
2273
|
-
lines.push("Key internal edges:");
|
|
2274
|
-
lines.push(...packet.key_edges.map((edge) => {
|
|
2275
|
-
const kind = edge.kind ? ` [${edge.kind}]` : "";
|
|
2276
|
-
const reason = edge.reason ? ` - ${edge.reason}` : "";
|
|
2277
|
-
return `- ${edge.from} -> ${edge.to}${kind} confidence=${formatPacketConfidence(edge.confidence)}${reason}`;
|
|
2278
|
-
}));
|
|
2279
|
-
}
|
|
2280
|
-
if (packet.boundary_files?.length) {
|
|
2281
|
-
lines.push("Boundary files to check only when evidence crosses the packet:");
|
|
2282
|
-
lines.push(...packet.boundary_files.map((path) => `- ${path}`));
|
|
2283
|
-
}
|
|
2284
|
-
if (packet.quality) {
|
|
2285
|
-
lines.push(`Quality: cohesion=${packet.quality.cohesion_score}, internal_edges=${packet.quality.internal_edge_count}, boundary_edges=${packet.quality.boundary_edge_count}, unexplained_files=${packet.quality.unexplained_file_count}`);
|
|
2286
|
-
}
|
|
2287
|
-
lines.push("");
|
|
2288
|
-
return lines;
|
|
2289
|
-
}
|
|
2290
|
-
async function prepareDispatchArtifacts(params) {
|
|
2291
|
-
const runId = params.runId;
|
|
2292
|
-
const artifactsDir = params.artifactsDir;
|
|
2293
|
-
const runDir = join(artifactsDir, "runs", runId);
|
|
2294
|
-
const taskResultsDir = join(runDir, "task-results");
|
|
2295
|
-
const dispatchPlanPath = join(runDir, "dispatch-plan.json");
|
|
2296
|
-
let reviewRoot = params.root;
|
|
2297
|
-
try {
|
|
2298
|
-
const workerTask = await readJsonFile(join(runDir, "task.json"));
|
|
2299
|
-
reviewRoot ??= workerTask.repo_root;
|
|
2300
|
-
}
|
|
2301
|
-
catch (error) {
|
|
2302
|
-
if (!isFileMissingError(error)) {
|
|
2303
|
-
throw error;
|
|
2304
|
-
}
|
|
2305
|
-
}
|
|
2306
|
-
const bundle = await loadArtifactBundle(artifactsDir);
|
|
2307
|
-
const tasksPath = join(runDir, "pending-audit-tasks.json");
|
|
2308
|
-
const tasks = await readJsonFile(tasksPath).catch((error) => {
|
|
2309
|
-
if (isFileMissingError(error))
|
|
2310
|
-
return buildPendingAuditTasks(bundle);
|
|
2311
|
-
throw error;
|
|
2312
|
-
});
|
|
2313
|
-
const sessionConfig = params.sessionConfig ?? (await loadSessionConfig(artifactsDir).catch(() => ({})));
|
|
2314
|
-
const lensDefsPath = join(packageRoot, "dispatch", "lens-definitions.json");
|
|
2315
|
-
const lensDefs = await readJsonFile(lensDefsPath);
|
|
2316
|
-
await mkdir(taskResultsDir, { recursive: true });
|
|
2317
|
-
// On resume: skip tasks whose result files already exist from a prior dispatch.
|
|
2318
|
-
const priorResultTaskIds = new Set();
|
|
2319
|
-
for (const task of tasks) {
|
|
2320
|
-
if (existsSync(taskResultPath(taskResultsDir, task.task_id))) {
|
|
2321
|
-
priorResultTaskIds.add(task.task_id);
|
|
2322
|
-
}
|
|
2323
|
-
}
|
|
2324
|
-
const dispatchTasks = priorResultTaskIds.size > 0
|
|
2325
|
-
? tasks.filter((task) => !priorResultTaskIds.has(task.task_id))
|
|
2326
|
-
: tasks;
|
|
2327
|
-
const lineIndex = Object.fromEntries(dispatchTasks.flatMap((task) => Object.entries(task.file_line_counts ?? {})));
|
|
2328
|
-
const orderedTasks = orderTasksForPacketReview(dispatchTasks, {
|
|
2329
|
-
graphBundle: bundle.graph_bundle,
|
|
2330
|
-
lineIndex,
|
|
2331
|
-
});
|
|
2332
|
-
const packets = buildReviewPackets(orderedTasks, {
|
|
2333
|
-
graphBundle: bundle.graph_bundle,
|
|
2334
|
-
lineIndex,
|
|
2335
|
-
});
|
|
2336
|
-
const tasksById = new Map(orderedTasks.map((task) => [task.task_id, task]));
|
|
2337
|
-
const resultPathByTaskId = new Map(orderedTasks.map((task) => [
|
|
2338
|
-
task.task_id,
|
|
2339
|
-
taskResultPath(taskResultsDir, task.task_id),
|
|
2340
|
-
]));
|
|
2341
|
-
const resultPathSet = new Set(resultPathByTaskId.values());
|
|
2342
|
-
if (resultPathSet.size !== resultPathByTaskId.size) {
|
|
2343
|
-
throw new Error("prepare-dispatch generated duplicate result paths; task ids must be uniquely addressable.");
|
|
2344
|
-
}
|
|
2345
|
-
const plan = [];
|
|
2346
|
-
const resultMapEntries = [];
|
|
2347
|
-
for (const task of tasks) {
|
|
2348
|
-
if (priorResultTaskIds.has(task.task_id)) {
|
|
2349
|
-
resultMapEntries.push({
|
|
2350
|
-
packet_id: "__prior_dispatch__",
|
|
2351
|
-
task_id: task.task_id,
|
|
2352
|
-
result_path: taskResultPath(taskResultsDir, task.task_id),
|
|
2353
|
-
});
|
|
2354
|
-
}
|
|
2355
|
-
}
|
|
2356
|
-
let largestPacketId = null;
|
|
2357
|
-
let largestLines = 0;
|
|
2358
|
-
let largestEstimatedTokens = 0;
|
|
2359
|
-
const warnings = [];
|
|
2360
|
-
for (const packet of packets) {
|
|
2361
|
-
const promptPath = packetPromptPath(taskResultsDir, packet.packet_id);
|
|
2362
|
-
const packetTasks = packet.task_ids
|
|
2363
|
-
.map((taskId) => tasksById.get(taskId))
|
|
2364
|
-
.filter((task) => task !== undefined);
|
|
2365
|
-
if (packet.total_lines > largestLines) {
|
|
2366
|
-
largestLines = packet.total_lines;
|
|
2367
|
-
largestEstimatedTokens = packet.estimated_tokens;
|
|
2368
|
-
largestPacketId = packet.packet_id;
|
|
2369
|
-
}
|
|
2370
|
-
const largeFileMode = isIsolatedLargeFilePacket(packet);
|
|
2371
|
-
if (packet.total_lines > LARGE_FILE_PACKET_TARGET_LINES && !largeFileMode) {
|
|
2372
|
-
warnings.push({
|
|
2373
|
-
code: "large_packet",
|
|
2374
|
-
message: `large packet ${packet.packet_id} (~${packet.total_lines} lines) may hit quota limits`,
|
|
2375
|
-
});
|
|
2376
|
-
}
|
|
2377
|
-
for (const task of packetTasks) {
|
|
2378
|
-
if (!lensDefs[task.lens]) {
|
|
2379
|
-
warnings.push({
|
|
2380
|
-
code: "missing_lens_definition",
|
|
2381
|
-
message: `no lens definition for '${task.lens}' (task ${task.task_id})`,
|
|
2382
|
-
});
|
|
2383
|
-
}
|
|
2384
|
-
}
|
|
2385
|
-
const fileList = packet.file_paths.map((path) => {
|
|
2386
|
-
const lines = packet.file_line_counts[path] ?? 0;
|
|
2387
|
-
return `- ${path} (${lines} lines)`;
|
|
2388
|
-
}).join("\n");
|
|
2389
|
-
let anchorPath = null;
|
|
2390
|
-
let anchorSummary = null;
|
|
2391
|
-
if (largeFileMode) {
|
|
2392
|
-
const filePath = packet.file_paths[0];
|
|
2393
|
-
if (!reviewRoot) {
|
|
2394
|
-
warnings.push({
|
|
2395
|
-
code: "large_file_anchor_unavailable",
|
|
2396
|
-
message: `large single-file packet ${packet.packet_id} has no repo root available for anchor extraction`,
|
|
2397
|
-
});
|
|
2398
|
-
}
|
|
2399
|
-
else {
|
|
2400
|
-
try {
|
|
2401
|
-
const totalLines = packet.file_line_counts[filePath] ?? packet.total_lines;
|
|
2402
|
-
const content = await readFile(withinRoot(reviewRoot, filePath), "utf8");
|
|
2403
|
-
anchorSummary = buildFileAnchorSummary({
|
|
2404
|
-
path: filePath,
|
|
2405
|
-
content,
|
|
2406
|
-
totalLines,
|
|
2407
|
-
graphBundle: bundle.graph_bundle,
|
|
2408
|
-
externalAnalyzerResults: bundle.external_analyzer_results,
|
|
2409
|
-
});
|
|
2410
|
-
anchorPath = join(taskResultsDir, artifactNameForId(packet.packet_id, "anchors.json"));
|
|
2411
|
-
await writeJsonFile(anchorPath, anchorSummary);
|
|
2412
|
-
}
|
|
2413
|
-
catch (error) {
|
|
2414
|
-
warnings.push({
|
|
2415
|
-
code: "large_file_anchor_failed",
|
|
2416
|
-
message: `large single-file packet ${packet.packet_id} could not be anchored mechanically: ` +
|
|
2417
|
-
(error instanceof Error ? error.message : String(error)),
|
|
2418
|
-
});
|
|
2419
|
-
}
|
|
2420
|
-
}
|
|
2421
|
-
}
|
|
2422
|
-
const largeFileSection = anchorSummary && anchorPath
|
|
2423
|
-
? renderAnchorPreview(anchorSummary, anchorPath)
|
|
2424
|
-
: largeFileMode
|
|
2425
|
-
? [
|
|
2426
|
-
"## Large File Review Mode",
|
|
2427
|
-
"This packet is intentionally isolated because it covers one large file.",
|
|
2428
|
-
"Use targeted reads/searches within this file only.",
|
|
2429
|
-
"No mechanical anchor file was available, so rely on targeted symbol and keyword searches before reading broad ranges.",
|
|
2430
|
-
"",
|
|
2431
|
-
]
|
|
2432
|
-
: [];
|
|
2433
|
-
const taskSections = packetTasks.flatMap((task) => {
|
|
2434
|
-
const lensDef = lensDefs[task.lens];
|
|
2435
|
-
const inputLines = task.inputs
|
|
2436
|
-
? Object.entries(task.inputs)
|
|
2437
|
-
.sort(([a], [b]) => a.localeCompare(b))
|
|
2438
|
-
.map(([key, value]) => `input.${key}: ${value}`)
|
|
2439
|
-
: [];
|
|
2440
|
-
const isLensVerification = task.tags?.includes("lens_verification") ?? false;
|
|
2441
|
-
const coverageTemplate = task.file_paths.map((path) => ({
|
|
2442
|
-
path,
|
|
2443
|
-
total_lines: task.file_line_counts?.[path] ?? lineIndex[path] ?? 0,
|
|
2444
|
-
}));
|
|
2445
|
-
return [
|
|
2446
|
-
`### ${task.task_id}`,
|
|
2447
|
-
`unit_id: ${task.unit_id}`,
|
|
2448
|
-
`pass_id: ${task.pass_id}`,
|
|
2449
|
-
`lens: ${task.lens}`,
|
|
2450
|
-
...(task.tags?.length ? [`tags: ${task.tags.join(", ")}`] : []),
|
|
2451
|
-
...inputLines,
|
|
2452
|
-
`rationale: ${task.rationale}`,
|
|
2453
|
-
"",
|
|
2454
|
-
`Lens guidance: ${lensDef?.description ?? task.lens}`,
|
|
2455
|
-
`Do NOT report: ${lensDef?.do_not_report ?? "N/A"}`,
|
|
2456
|
-
...(isLensVerification
|
|
2457
|
-
? [
|
|
2458
|
-
"",
|
|
2459
|
-
"Lens verification mode: review the prior result summary in the rationale and use only targeted source checks.",
|
|
2460
|
-
"Do not redo every packet and do not write direct findings for this task.",
|
|
2461
|
-
"Return findings: [] plus verification metadata. Include followup_tasks only for bounded, specific re-review packets.",
|
|
2462
|
-
]
|
|
2463
|
-
: []),
|
|
2464
|
-
"",
|
|
2465
|
-
"file_coverage (copy exactly into your AuditResult for this task):",
|
|
2466
|
-
"```json",
|
|
2467
|
-
JSON.stringify(coverageTemplate),
|
|
2468
|
-
"```",
|
|
2469
|
-
"",
|
|
2470
|
-
];
|
|
2471
|
-
});
|
|
2472
|
-
const submitCommand = `"${process.execPath}" "${join(packageRoot, "audit-code.mjs")}" submit-packet ` +
|
|
2473
|
-
`--run-id-b64 ${toBase64Url(runId)} ` +
|
|
2474
|
-
`--packet-id-b64 ${toBase64Url(packet.packet_id)} ` +
|
|
2475
|
-
`--artifacts-dir-b64 ${toBase64Url(artifactsDir)}`;
|
|
2476
|
-
const complexity = buildDispatchComplexity(packet, largeFileMode);
|
|
2477
|
-
for (const task of packetTasks) {
|
|
2478
|
-
resultMapEntries.push({
|
|
2479
|
-
packet_id: packet.packet_id,
|
|
2480
|
-
task_id: task.task_id,
|
|
2481
|
-
result_path: resultPathByTaskId.get(task.task_id),
|
|
2482
|
-
});
|
|
2483
|
-
}
|
|
2484
|
-
const prompt = [
|
|
2485
|
-
"You are a code auditor. Review this packet once, then submit exactly one result per listed task.",
|
|
2486
|
-
"",
|
|
2487
|
-
"## Packet",
|
|
2488
|
-
`packet_id: ${packet.packet_id}`,
|
|
2489
|
-
`task_count: ${packet.task_ids.length}`,
|
|
2490
|
-
`lenses: ${packet.lenses.join(", ")}`,
|
|
2491
|
-
`estimated_tokens: ${packet.estimated_tokens}`,
|
|
2492
|
-
"",
|
|
2493
|
-
"## Files to read",
|
|
2494
|
-
largeFileMode
|
|
2495
|
-
? "Use targeted Read/Grep calls. Paths are repo-relative from the current working directory."
|
|
2496
|
-
: "Use your Read tool. Paths are repo-relative from the current working directory.",
|
|
2497
|
-
"Prefer host Read/Grep tools. On native Windows, do not use Unix pipelines like `grep ... | head`; if shell search is unavoidable, use `Select-String` as a fallback.",
|
|
2498
|
-
fileList,
|
|
2499
|
-
"",
|
|
2500
|
-
...renderPacketGraphContext(packet),
|
|
2501
|
-
...largeFileSection,
|
|
2502
|
-
"## Tasks",
|
|
2503
|
-
...taskSections,
|
|
2504
|
-
"## Output",
|
|
2505
|
-
"Do not write files directly. Do not use a Write tool, create temp files, edit source files,",
|
|
2506
|
-
"remediate findings, create extra task results, or run unrelated audits.",
|
|
2507
|
-
"Produce one JSON array containing exactly one AuditResult object for each listed task.",
|
|
2508
|
-
"",
|
|
2509
|
-
"Required AuditResult fields:",
|
|
2510
|
-
" task_id copy from the task metadata",
|
|
2511
|
-
" unit_id copy from the task metadata",
|
|
2512
|
-
" pass_id copy from the task metadata",
|
|
2513
|
-
" lens copy from the task metadata",
|
|
2514
|
-
" file_coverage [{path, total_lines}] - copy the exact template from each task section above",
|
|
2515
|
-
" findings [] or array of finding objects",
|
|
2516
|
-
"",
|
|
2517
|
-
"Lens verification tasks:",
|
|
2518
|
-
" tasks tagged lens_verification must use findings: [] and include verification:",
|
|
2519
|
-
" {verified: boolean, needs_followup: boolean, concerns?: string[],",
|
|
2520
|
-
" coverage_concerns?: string[], confidence_concerns?: string[],",
|
|
2521
|
-
" followup_tasks?: AuditTask[]}.",
|
|
2522
|
-
" Follow-up AuditTask suggestions must stay bounded to files in this packet and use the same lens.",
|
|
2523
|
-
"",
|
|
2524
|
-
"Each finding object:",
|
|
2525
|
-
" id unique ID, e.g. \"COR-001\"",
|
|
2526
|
-
" title short title",
|
|
2527
|
-
" category specific finding category, such as missing-validation or command-execution",
|
|
2528
|
-
" severity critical|high|medium|low|info",
|
|
2529
|
-
" confidence high|medium|low",
|
|
2530
|
-
" lens must match the task lens exactly",
|
|
2531
|
-
" summary 1-2 sentence description",
|
|
2532
|
-
" affected_files [{path, line_start?, line_end?, symbol?}] - objects, not strings; min 1 entry",
|
|
2533
|
-
" evidence [\"path/to/file.ts:42 - description of what you see there\"] - min 1 entry",
|
|
2534
|
-
"",
|
|
2535
|
-
"Constraints:",
|
|
2536
|
-
"1. line_end must not exceed the file's actual line count.",
|
|
2537
|
-
"2. affected_files entries are objects with a path key, not plain strings.",
|
|
2538
|
-
"3. Only reference files from the packet unless a finding genuinely crosses a boundary.",
|
|
2539
|
-
"4. findings: [] is correct when you find nothing genuine.",
|
|
2540
|
-
"",
|
|
2541
|
-
"## Submit",
|
|
2542
|
-
"Pipe the JSON array on stdin to this command:",
|
|
2543
|
-
` ${submitCommand}`,
|
|
2544
|
-
"",
|
|
2545
|
-
"The command validates and writes the packet-owned result files. Exit 0 means accepted.",
|
|
2546
|
-
"Non-zero: read the errors, fix the JSON, and run the same submit command again. Retry up to 3 times.",
|
|
2547
|
-
"",
|
|
2548
|
-
"## Final response",
|
|
2549
|
-
`After the submit command succeeds, reply exactly: valid: ${packet.packet_id}, findings=<total finding count>`,
|
|
2550
|
-
].join("\n");
|
|
2551
|
-
await writeFile(promptPath, prompt, "utf8");
|
|
2552
|
-
plan.push({
|
|
2553
|
-
packet_id: packet.packet_id,
|
|
2554
|
-
description: `Audit ${packet.file_paths.length} file(s), ${packet.task_ids.length} task(s), ${packet.lenses.length} lens(es) (~${packet.total_lines} lines)` +
|
|
2555
|
-
(largeFileMode ? " [isolated large-file mode]" : ""),
|
|
2556
|
-
prompt_path: promptPath,
|
|
2557
|
-
complexity,
|
|
2558
|
-
model_hint: buildDispatchModelHint(complexity),
|
|
2559
|
-
});
|
|
2560
|
-
}
|
|
2561
|
-
await writeJsonFile(dispatchPlanPath, plan);
|
|
2562
|
-
await writeJsonFile(dispatchResultMapPath(runDir), {
|
|
2563
|
-
contract_version: "audit-code-dispatch-results/v1alpha1",
|
|
2564
|
-
run_id: runId,
|
|
2565
|
-
entries: resultMapEntries,
|
|
2566
|
-
});
|
|
2567
|
-
// Compute and write dispatch-quota.json
|
|
2568
|
-
const hostModel = params.hostModel ?? null;
|
|
2569
|
-
const perPacketTokens = plan.map((p) => p.complexity.estimated_tokens);
|
|
2570
|
-
const quotaProviderName = resolveFreshSessionProviderName(undefined, sessionConfig);
|
|
2571
|
-
const quotaProviderKey = buildProviderModelKey(quotaProviderName, hostModel);
|
|
2572
|
-
const quotaState = await readQuotaState().catch(() => ({ version: 2, entries: {} }));
|
|
2573
|
-
const quotaStateEntry = quotaState.entries[quotaProviderKey] ?? null;
|
|
2574
|
-
const hostConcurrencyLimit = resolveHostActiveSubagentLimit({
|
|
2575
|
-
explicitLimit: params.hostActiveSubagentLimit,
|
|
2576
|
-
sessionConfig,
|
|
2577
|
-
});
|
|
2578
|
-
const dispatchCachedLimits = await lookupDiscoveredLimits(quotaProviderKey).catch(() => null);
|
|
2579
|
-
const waveSchedule = scheduleWave({
|
|
2580
|
-
providerName: quotaProviderName,
|
|
2581
|
-
sessionConfig,
|
|
2582
|
-
hostModel,
|
|
2583
|
-
requestedConcurrency: sessionConfig.parallel_workers ?? plan.length,
|
|
2584
|
-
estimatedSlotTokens: perPacketTokens,
|
|
2585
|
-
quotaStateEntry,
|
|
2586
|
-
hostConcurrencyLimit,
|
|
2587
|
-
discoveredLimits: dispatchCachedLimits,
|
|
2588
|
-
});
|
|
2589
|
-
const dispatchQuota = {
|
|
2590
|
-
contract_version: "audit-code-dispatch-quota/v1alpha2",
|
|
2591
|
-
run_id: runId,
|
|
2592
|
-
model: hostModel,
|
|
2593
|
-
resolved_limits: waveSchedule.resolved_limits,
|
|
2594
|
-
confidence: waveSchedule.confidence,
|
|
2595
|
-
source: waveSchedule.source,
|
|
2596
|
-
host_concurrency_limit: waveSchedule.host_concurrency_limit,
|
|
2597
|
-
wave_size: waveSchedule.wave_size,
|
|
2598
|
-
estimated_wave_tokens: waveSchedule.estimated_wave_tokens,
|
|
2599
|
-
cooldown_until: waveSchedule.cooldown_until,
|
|
2600
|
-
quota_source_snapshot: waveSchedule.quota_source_snapshot ?? null,
|
|
2601
|
-
backoff_state: null,
|
|
2602
|
-
};
|
|
2603
|
-
const dispatchQuotaPath = join(runDir, "dispatch-quota.json");
|
|
2604
|
-
await writeJsonFile(dispatchQuotaPath, dispatchQuota);
|
|
2605
|
-
// Warn about packets that exceed the context budget only when we have reliable limit
|
|
2606
|
-
// information (confidence medium/high). Low-confidence limits are conservative defaults
|
|
2607
|
-
// and would produce misleading warnings since the real context window is unknown.
|
|
2608
|
-
if (waveSchedule.confidence !== "low") {
|
|
2609
|
-
const contextBudget = waveSchedule.resolved_limits.context_tokens - waveSchedule.resolved_limits.output_tokens;
|
|
2610
|
-
for (const p of plan) {
|
|
2611
|
-
if (p.complexity.estimated_tokens > contextBudget) {
|
|
2612
|
-
warnings.push({
|
|
2613
|
-
code: "oversized_packet",
|
|
2614
|
-
message: `Packet ${p.packet_id} estimated tokens (${p.complexity.estimated_tokens}) exceed ` +
|
|
2615
|
-
`context budget (${contextBudget}). This packet may fail at dispatch. ` +
|
|
2616
|
-
`Set quota.default_context_tokens or quota.models in session-config.json to override.`,
|
|
2617
|
-
});
|
|
2618
|
-
}
|
|
2619
|
-
}
|
|
2620
|
-
}
|
|
2621
|
-
const warningsPath = warnings.length > 0
|
|
2622
|
-
? join(runDir, "dispatch-warnings.json")
|
|
2623
|
-
: null;
|
|
2624
|
-
if (warningsPath) {
|
|
2625
|
-
await writeJsonFile(warningsPath, warnings);
|
|
2626
|
-
}
|
|
2627
|
-
const activeDispatch = {
|
|
2628
|
-
run_id: runId,
|
|
2629
|
-
created_at: new Date().toISOString(),
|
|
2630
|
-
packet_count: plan.length,
|
|
2631
|
-
task_count: orderedTasks.length,
|
|
2632
|
-
status: "active",
|
|
2633
|
-
};
|
|
2634
|
-
await writeJsonFile(join(artifactsDir, ACTIVE_DISPATCH_FILENAME), activeDispatch);
|
|
2635
|
-
return {
|
|
2636
|
-
run_id: runId,
|
|
2637
|
-
dispatch_plan_path: dispatchPlanPath,
|
|
2638
|
-
dispatch_quota_path: dispatchQuotaPath,
|
|
2639
|
-
packet_count: plan.length,
|
|
2640
|
-
task_count: orderedTasks.length,
|
|
2641
|
-
skipped_task_count: priorResultTaskIds.size,
|
|
2642
|
-
largest_packet: largestPacketId
|
|
2643
|
-
? {
|
|
2644
|
-
packet_id: largestPacketId,
|
|
2645
|
-
total_lines: largestLines,
|
|
2646
|
-
estimated_tokens: largestEstimatedTokens,
|
|
2647
|
-
}
|
|
2648
|
-
: null,
|
|
2649
|
-
warning_count: warnings.length,
|
|
2650
|
-
dispatch_warnings_path: warningsPath,
|
|
2651
|
-
};
|
|
2652
|
-
}
|
|
2653
2114
|
async function cmdPrepareDispatch(argv) {
|
|
2654
2115
|
const runId = getFlag(argv, "--run-id");
|
|
2655
2116
|
if (!runId)
|
|
2656
2117
|
throw new Error("prepare-dispatch requires --run-id <run_id>");
|
|
2657
2118
|
const result = await prepareDispatchArtifacts({
|
|
2119
|
+
packageRoot,
|
|
2658
2120
|
runId,
|
|
2659
2121
|
artifactsDir: getArtifactsDir(argv),
|
|
2660
2122
|
root: getFlag(argv, "--root") ? getRootDir(argv) : undefined,
|
|
@@ -3104,7 +2566,11 @@ async function cmdIntake(argv) {
|
|
|
3104
2566
|
}
|
|
3105
2567
|
async function cmdPlan(argv) {
|
|
3106
2568
|
const artifactsDir = getArtifactsDir(argv);
|
|
3107
|
-
const result = await runAuditStep({
|
|
2569
|
+
const result = await runAuditStep({
|
|
2570
|
+
root: getRootDir(argv),
|
|
2571
|
+
artifactsDir,
|
|
2572
|
+
since: getFlag(argv, "--since"),
|
|
2573
|
+
});
|
|
3108
2574
|
console.log(JSON.stringify({
|
|
3109
2575
|
artifacts_dir: artifactsDir,
|
|
3110
2576
|
selected_executor: result.selected_executor,
|
|
@@ -3551,6 +3017,7 @@ async function cmdDispatchStatus(argv) {
|
|
|
3551
3017
|
}, null, 2));
|
|
3552
3018
|
}
|
|
3553
3019
|
async function main(argv) {
|
|
3020
|
+
setQuotaStateDir(join(homedir(), ".audit-code"));
|
|
3554
3021
|
const command = argv[2] ?? "sample-run";
|
|
3555
3022
|
switch (command) {
|
|
3556
3023
|
case "sample-run":
|