auditor-lambda 0.3.24 → 0.3.26
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 +26 -24
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +135 -6
- package/dist/mcp/server.js +9 -2
- package/dist/orchestrator/internalExecutors.js +6 -3
- package/dist/quota/limits.js +1 -1
- package/dist/quota/scheduler.js +2 -10
- package/package.json +1 -1
- package/scripts/postinstall.mjs +144 -18
|
@@ -542,7 +542,7 @@ const OPENCODE_AUDIT_EDIT_PERMISSION = {
|
|
|
542
542
|
};
|
|
543
543
|
|
|
544
544
|
const OPENCODE_AUDIT_BASH_PERMISSION = {
|
|
545
|
-
'*': '
|
|
545
|
+
'*': 'allow',
|
|
546
546
|
'audit-code run-to-completion*': 'deny',
|
|
547
547
|
'audit-code synthesize*': 'deny',
|
|
548
548
|
'audit-code cleanup*': 'deny',
|
|
@@ -588,10 +588,7 @@ function externalDirectoryPattern(path) {
|
|
|
588
588
|
}
|
|
589
589
|
|
|
590
590
|
function renderOpenCodeExternalDirectoryPermission() {
|
|
591
|
-
return {
|
|
592
|
-
[externalDirectoryPattern(repoRoot)]: 'allow',
|
|
593
|
-
[externalDirectoryPattern(dirname(process.execPath))]: 'allow',
|
|
594
|
-
};
|
|
591
|
+
return { '*': 'allow' };
|
|
595
592
|
}
|
|
596
593
|
|
|
597
594
|
function renderOpenCodePermissionConfig() {
|
|
@@ -611,15 +608,19 @@ const OPENCODE_MCP_COMMAND_TEMPLATE = [
|
|
|
611
608
|
'Use the auditor MCP tools as the primary interface to the audit workflow.',
|
|
612
609
|
'',
|
|
613
610
|
'1. Call `auditor_start_audit` to initialize and receive the first step.',
|
|
614
|
-
'2.
|
|
611
|
+
'2. Check `step_kind` in the response:',
|
|
612
|
+
' - If `step_kind` is `"capability_check"`: immediately call `auditor_report_capability` with `can_dispatch_subagents: true` and `can_select_subagent_model: true`. Do not run shell commands or inspect prompt_content for this step.',
|
|
613
|
+
' - Otherwise: read `prompt_content` and follow it.',
|
|
615
614
|
'3. When a step completes (not blocked), call `auditor_continue_audit` to advance.',
|
|
616
615
|
'4. Stop when the step instructions say to stop.',
|
|
617
616
|
'',
|
|
618
617
|
'Do not run shell commands. Use only `auditor_*` MCP tools and the `task` tool for subagent dispatch.',
|
|
618
|
+
'',
|
|
619
|
+
'If `auditor_start_audit` is not listed in your available tools, stop immediately and tell the user the auditor MCP server is not connected. Do not read local files as a fallback.',
|
|
619
620
|
].join('\n');
|
|
620
621
|
|
|
621
|
-
function renderOpenCodeProjectConfig(
|
|
622
|
-
const launcher =
|
|
622
|
+
function renderOpenCodeProjectConfig(_root) {
|
|
623
|
+
const launcher = `.audit-code/install/${MCP_LAUNCHER_FILENAME}`;
|
|
623
624
|
const auditPermission = renderOpenCodePermissionConfig();
|
|
624
625
|
return {
|
|
625
626
|
$schema: 'https://opencode.ai/config.json',
|
|
@@ -636,12 +637,9 @@ function renderOpenCodeProjectConfig(root) {
|
|
|
636
637
|
auditor: {
|
|
637
638
|
description:
|
|
638
639
|
'Read-heavy audit orchestration agent for the /audit-code workflow.',
|
|
639
|
-
tools: {
|
|
640
|
-
'auditor*': true,
|
|
641
|
-
task: true,
|
|
642
|
-
},
|
|
643
640
|
permission: {
|
|
644
641
|
...auditPermission,
|
|
642
|
+
'auditor_*': 'allow',
|
|
645
643
|
question: 'allow',
|
|
646
644
|
task: 'allow',
|
|
647
645
|
},
|
|
@@ -742,12 +740,10 @@ function assertOpenCodeAuditPermissionConfig(permissionConfig, label) {
|
|
|
742
740
|
}
|
|
743
741
|
const externalDirectory = permissionConfig?.external_directory;
|
|
744
742
|
if (!externalDirectory || typeof externalDirectory !== 'object' || Array.isArray(externalDirectory)) {
|
|
745
|
-
throw new Error(`OpenCode ${label}.external_directory must
|
|
743
|
+
throw new Error(`OpenCode ${label}.external_directory must set "*" to "allow". Run "audit-code install --host opencode".`);
|
|
746
744
|
}
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
throw new Error(`OpenCode ${label}.external_directory must allow ${pattern}. Run "audit-code install --host opencode".`);
|
|
750
|
-
}
|
|
745
|
+
if (externalDirectory['*'] !== 'allow') {
|
|
746
|
+
throw new Error(`OpenCode ${label}.external_directory must set "*" to "allow". Run "audit-code install --host opencode".`);
|
|
751
747
|
}
|
|
752
748
|
const edit = permissionConfig?.edit;
|
|
753
749
|
const bash = permissionConfig?.bash;
|
|
@@ -815,16 +811,22 @@ function buildMergedOpenCodeProjectConfig(existing, root) {
|
|
|
815
811
|
...objectValue(existing.mcp),
|
|
816
812
|
auditor: generated.mcp.auditor,
|
|
817
813
|
},
|
|
818
|
-
permission:
|
|
814
|
+
permission: {
|
|
815
|
+
...mergeOpenCodePermissionConfig(existing.permission, generated.permission),
|
|
816
|
+
external_directory: { '*': 'allow' },
|
|
817
|
+
},
|
|
819
818
|
agent: {
|
|
820
819
|
...objectValue(existing.agent),
|
|
821
820
|
auditor: {
|
|
822
821
|
...objectValue(objectValue(existing.agent).auditor),
|
|
823
822
|
...generated.agent.auditor,
|
|
824
|
-
permission:
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
823
|
+
permission: {
|
|
824
|
+
...mergeOpenCodePermissionConfig(
|
|
825
|
+
objectValue(objectValue(existing.agent).auditor).permission,
|
|
826
|
+
generated.agent.auditor.permission,
|
|
827
|
+
),
|
|
828
|
+
external_directory: { '*': 'allow' },
|
|
829
|
+
},
|
|
828
830
|
},
|
|
829
831
|
},
|
|
830
832
|
};
|
|
@@ -2007,8 +2009,8 @@ async function verifyInstalledBootstrap(argv) {
|
|
|
2007
2009
|
if (!Array.isArray(mcpCommand) || mcpCommand[0] !== 'node') {
|
|
2008
2010
|
throw new Error('OpenCode config must set mcp.auditor.command as a Node command array.');
|
|
2009
2011
|
}
|
|
2010
|
-
if (mcpCommand[1]
|
|
2011
|
-
throw new Error(`OpenCode config must
|
|
2012
|
+
if (!mcpCommand[1]?.includes(MCP_LAUNCHER_FILENAME)) {
|
|
2013
|
+
throw new Error(`OpenCode config must reference ${MCP_LAUNCHER_FILENAME}, got ${mcpCommand[1] ?? 'missing'}.`);
|
|
2012
2014
|
}
|
|
2013
2015
|
if (config?.mcp?.auditor?.type !== 'local') {
|
|
2014
2016
|
throw new Error(`OpenCode config must set mcp.auditor.type to "local", got ${config?.mcp?.auditor?.type ?? 'missing'}.`);
|
package/dist/cli.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ declare function getFlag(argv: string[], name: string, fallback?: string): strin
|
|
|
4
4
|
declare function hasFlag(argv: string[], name: string): boolean;
|
|
5
5
|
declare function getArtifactsDir(argv: string[]): string;
|
|
6
6
|
declare function getRootDir(argv: string[]): string;
|
|
7
|
+
declare function warnIfNotGitRepo(root: string): void;
|
|
7
8
|
declare function getBatchResultsDir(argv: string[]): string | undefined;
|
|
8
9
|
declare function getMaxRuns(argv: string[]): number;
|
|
9
10
|
declare function getAgentBatchSize(argv: string[], sessionConfig: SessionConfig): number;
|
|
@@ -36,6 +37,7 @@ export declare const cliTestUtils: {
|
|
|
36
37
|
getUiMode: typeof getUiMode;
|
|
37
38
|
looksLikeCliFlag: typeof looksLikeCliFlag;
|
|
38
39
|
countLines: typeof countLines;
|
|
40
|
+
warnIfNotGitRepo: typeof warnIfNotGitRepo;
|
|
39
41
|
};
|
|
40
42
|
export declare function runSample(argv?: string[]): Promise<void>;
|
|
41
43
|
export declare function runCli(argv: string[]): Promise<void>;
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
2
|
-
import { createReadStream } from "node:fs";
|
|
2
|
+
import { createReadStream, existsSync } from "node:fs";
|
|
3
3
|
import { Buffer } from "node:buffer";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
5
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
@@ -23,7 +23,7 @@ import { deriveAuditState } from "./orchestrator/state.js";
|
|
|
23
23
|
import { advanceAudit } from "./orchestrator/advance.js";
|
|
24
24
|
import { decideNextStep } from "./orchestrator/nextStep.js";
|
|
25
25
|
import { createFreshSessionProvider, resolveFreshSessionProviderName, } from "./providers/index.js";
|
|
26
|
-
import { appendRunLedgerEntry } from "./supervisor/runLedger.js";
|
|
26
|
+
import { appendRunLedgerEntry, loadRunLedger } from "./supervisor/runLedger.js";
|
|
27
27
|
import { buildAuditCodeHandoff, writeAuditCodeHandoffArtifacts, } from "./supervisor/operatorHandoff.js";
|
|
28
28
|
import { getSessionConfigPath, loadSessionConfig, readSessionConfigFile, } from "./supervisor/sessionConfig.js";
|
|
29
29
|
import { clearDispatchFiles, buildRunId, ensureSupervisorDirs, getRunPaths, writeDispatchBatchFiles, writeWorkerTaskFiles, } from "./io/runArtifacts.js";
|
|
@@ -154,6 +154,12 @@ function getArtifactsDir(argv) {
|
|
|
154
154
|
function getRootDir(argv) {
|
|
155
155
|
return resolveFlagPath(argv, "--root", DIRECT_CLI_DEFAULTS.rootDir);
|
|
156
156
|
}
|
|
157
|
+
function warnIfNotGitRepo(root) {
|
|
158
|
+
const gitEntry = join(root, ".git");
|
|
159
|
+
if (!existsSync(gitEntry)) {
|
|
160
|
+
console.warn(`Warning: target directory '${root}' does not appear to be a git repository. Diff-based signals will be unavailable.`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
157
163
|
function getBatchResultsDir(argv) {
|
|
158
164
|
const value = getFlag(argv, "--batch-results");
|
|
159
165
|
return value ? resolve(value) : undefined;
|
|
@@ -725,6 +731,7 @@ export const cliTestUtils = {
|
|
|
725
731
|
getUiMode,
|
|
726
732
|
looksLikeCliFlag,
|
|
727
733
|
countLines,
|
|
734
|
+
warnIfNotGitRepo,
|
|
728
735
|
};
|
|
729
736
|
async function maybeArchiveLegacyPendingResults(auditResultsPath) {
|
|
730
737
|
if (!auditResultsPath || basename(auditResultsPath) !== "worker_results_pending.json") {
|
|
@@ -954,6 +961,7 @@ export async function runSample(argv = process.argv) {
|
|
|
954
961
|
}
|
|
955
962
|
async function cmdAdvanceAudit(argv) {
|
|
956
963
|
const root = getRootDir(argv);
|
|
964
|
+
warnIfNotGitRepo(root);
|
|
957
965
|
const artifactsDir = getArtifactsDir(argv);
|
|
958
966
|
await cleanupStaleArtifactsDir(artifactsDir);
|
|
959
967
|
await mkdir(artifactsDir, { recursive: true });
|
|
@@ -1118,6 +1126,7 @@ async function runDeterministicForNextStep(params) {
|
|
|
1118
1126
|
}
|
|
1119
1127
|
async function cmdNextStep(argv) {
|
|
1120
1128
|
const root = getRootDir(argv);
|
|
1129
|
+
warnIfNotGitRepo(root);
|
|
1121
1130
|
const artifactsDir = getArtifactsDir(argv);
|
|
1122
1131
|
await mkdir(artifactsDir, { recursive: true });
|
|
1123
1132
|
await ensureSupervisorDirs(artifactsDir);
|
|
@@ -1288,6 +1297,7 @@ async function cmdNextStep(argv) {
|
|
|
1288
1297
|
}
|
|
1289
1298
|
async function cmdRunToCompletion(argv) {
|
|
1290
1299
|
const root = getRootDir(argv);
|
|
1300
|
+
warnIfNotGitRepo(root);
|
|
1291
1301
|
const artifactsDir = getArtifactsDir(argv);
|
|
1292
1302
|
await cleanupStaleArtifactsDir(artifactsDir);
|
|
1293
1303
|
await mkdir(artifactsDir, { recursive: true });
|
|
@@ -2205,7 +2215,6 @@ async function prepareDispatchArtifacts(params) {
|
|
|
2205
2215
|
const runId = params.runId;
|
|
2206
2216
|
const artifactsDir = params.artifactsDir;
|
|
2207
2217
|
const runDir = join(artifactsDir, "runs", runId);
|
|
2208
|
-
const tasksPath = join(runDir, "pending-audit-tasks.json");
|
|
2209
2218
|
const taskResultsDir = join(runDir, "task-results");
|
|
2210
2219
|
const dispatchPlanPath = join(runDir, "dispatch-plan.json");
|
|
2211
2220
|
let reviewRoot = params.root;
|
|
@@ -2218,8 +2227,13 @@ async function prepareDispatchArtifacts(params) {
|
|
|
2218
2227
|
throw error;
|
|
2219
2228
|
}
|
|
2220
2229
|
}
|
|
2221
|
-
const tasks = await readJsonFile(tasksPath);
|
|
2222
2230
|
const bundle = await loadArtifactBundle(artifactsDir);
|
|
2231
|
+
const tasksPath = join(runDir, "pending-audit-tasks.json");
|
|
2232
|
+
const tasks = await readJsonFile(tasksPath).catch((error) => {
|
|
2233
|
+
if (isFileMissingError(error))
|
|
2234
|
+
return buildPendingAuditTasks(bundle);
|
|
2235
|
+
throw error;
|
|
2236
|
+
});
|
|
2223
2237
|
const sessionConfig = params.sessionConfig ?? (await loadSessionConfig(artifactsDir).catch(() => ({})));
|
|
2224
2238
|
const lensDefsPath = join(packageRoot, "dispatch", "lens-definitions.json");
|
|
2225
2239
|
const lensDefs = await readJsonFile(lensDefsPath);
|
|
@@ -2737,6 +2751,8 @@ async function cmdMergeAndIngest(argv) {
|
|
|
2737
2751
|
preferredExecutor: "result_ingestion_executor",
|
|
2738
2752
|
auditResultsPath,
|
|
2739
2753
|
});
|
|
2754
|
+
const updatedPendingTasks = await addFileLineCountHints(workerTask.repo_root, buildPendingAuditTasks(result.updated_bundle));
|
|
2755
|
+
await writeJsonFile(tasksPath, updatedPendingTasks);
|
|
2740
2756
|
const workerResult = buildWorkerResult({
|
|
2741
2757
|
runId,
|
|
2742
2758
|
obligationId: workerTask.obligation_id,
|
|
@@ -2838,9 +2854,11 @@ async function cmdImportExternalAnalyzer(argv) {
|
|
|
2838
2854
|
}, null, 2));
|
|
2839
2855
|
}
|
|
2840
2856
|
async function cmdIntake(argv) {
|
|
2857
|
+
const root = getRootDir(argv);
|
|
2858
|
+
warnIfNotGitRepo(root);
|
|
2841
2859
|
const artifactsDir = getArtifactsDir(argv);
|
|
2842
2860
|
const result = await runAuditStep({
|
|
2843
|
-
root
|
|
2861
|
+
root,
|
|
2844
2862
|
artifactsDir,
|
|
2845
2863
|
preferredExecutor: "intake_executor",
|
|
2846
2864
|
});
|
|
@@ -3072,6 +3090,114 @@ async function cmdCleanup(argv) {
|
|
|
3072
3090
|
dry_run: dryRun,
|
|
3073
3091
|
}, null, 2));
|
|
3074
3092
|
}
|
|
3093
|
+
async function cmdStatus(argv) {
|
|
3094
|
+
const artifactsDir = getArtifactsDir(argv);
|
|
3095
|
+
const auditStatePath = join(artifactsDir, "audit_state.json");
|
|
3096
|
+
// 1. Read audit_state.json
|
|
3097
|
+
let auditState = null;
|
|
3098
|
+
try {
|
|
3099
|
+
auditState = await readJsonFile(auditStatePath);
|
|
3100
|
+
}
|
|
3101
|
+
catch (error) {
|
|
3102
|
+
if (!isFileMissingError(error)) {
|
|
3103
|
+
throw error;
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
if (!auditState) {
|
|
3107
|
+
console.error("No audit_state.json found; no active audit in this artifacts directory.");
|
|
3108
|
+
process.exitCode = 1;
|
|
3109
|
+
return;
|
|
3110
|
+
}
|
|
3111
|
+
// Build obligations summary: count by state
|
|
3112
|
+
const obligationStates = {
|
|
3113
|
+
missing: 0,
|
|
3114
|
+
present: 0,
|
|
3115
|
+
stale: 0,
|
|
3116
|
+
blocked: 0,
|
|
3117
|
+
satisfied: 0,
|
|
3118
|
+
};
|
|
3119
|
+
for (const obligation of auditState.obligations ?? []) {
|
|
3120
|
+
const state = obligation.state;
|
|
3121
|
+
if (state in obligationStates) {
|
|
3122
|
+
obligationStates[state]++;
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3125
|
+
// 2. Read run ledger for last N entries
|
|
3126
|
+
const ledger = await loadRunLedger(artifactsDir);
|
|
3127
|
+
const RECENT_RUN_LIMIT = 5;
|
|
3128
|
+
const recentRuns = ledger.runs
|
|
3129
|
+
.slice(-RECENT_RUN_LIMIT)
|
|
3130
|
+
.reverse()
|
|
3131
|
+
.map((entry) => ({
|
|
3132
|
+
run_id: entry.run_id,
|
|
3133
|
+
obligation_id: entry.obligation_id,
|
|
3134
|
+
status: entry.status,
|
|
3135
|
+
started_at: entry.started_at,
|
|
3136
|
+
}));
|
|
3137
|
+
// 3. Find the most recent run directory and read pending-audit-tasks.json
|
|
3138
|
+
let pendingTasksSummary = null;
|
|
3139
|
+
const runsDir = join(artifactsDir, "runs");
|
|
3140
|
+
let runDirs = [];
|
|
3141
|
+
try {
|
|
3142
|
+
const entries = await readdir(runsDir, { withFileTypes: true });
|
|
3143
|
+
runDirs = entries
|
|
3144
|
+
.filter((e) => e.isDirectory())
|
|
3145
|
+
.map((e) => e.name)
|
|
3146
|
+
.sort()
|
|
3147
|
+
.reverse();
|
|
3148
|
+
}
|
|
3149
|
+
catch {
|
|
3150
|
+
// runs directory may not exist yet
|
|
3151
|
+
}
|
|
3152
|
+
for (const runDirName of runDirs) {
|
|
3153
|
+
const runDir = join(runsDir, runDirName);
|
|
3154
|
+
const tasksPath = join(runDir, "pending-audit-tasks.json");
|
|
3155
|
+
let tasks = null;
|
|
3156
|
+
try {
|
|
3157
|
+
tasks = await readJsonFile(tasksPath);
|
|
3158
|
+
}
|
|
3159
|
+
catch {
|
|
3160
|
+
continue; // no pending-audit-tasks.json in this run dir — try previous
|
|
3161
|
+
}
|
|
3162
|
+
if (!Array.isArray(tasks))
|
|
3163
|
+
continue;
|
|
3164
|
+
// Count remaining: tasks without status "complete"
|
|
3165
|
+
const total = tasks.length;
|
|
3166
|
+
const remaining = tasks.filter((t) => t.status !== "complete").length;
|
|
3167
|
+
pendingTasksSummary = {
|
|
3168
|
+
run_id: runDirName,
|
|
3169
|
+
total,
|
|
3170
|
+
remaining,
|
|
3171
|
+
};
|
|
3172
|
+
break;
|
|
3173
|
+
}
|
|
3174
|
+
// 4. Surface failed-tasks.json from the most recent run that has one
|
|
3175
|
+
let failedTasks = null;
|
|
3176
|
+
for (const runDirName of runDirs) {
|
|
3177
|
+
const failedTasksPath = join(runsDir, runDirName, "failed-tasks.json");
|
|
3178
|
+
try {
|
|
3179
|
+
const raw = await readJsonFile(failedTasksPath);
|
|
3180
|
+
if (Array.isArray(raw) && raw.length > 0) {
|
|
3181
|
+
failedTasks = raw;
|
|
3182
|
+
break;
|
|
3183
|
+
}
|
|
3184
|
+
}
|
|
3185
|
+
catch {
|
|
3186
|
+
// Not present in this run dir — keep looking
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3189
|
+
console.log(JSON.stringify({
|
|
3190
|
+
artifacts_dir: artifactsDir,
|
|
3191
|
+
status: auditState.status,
|
|
3192
|
+
last_obligation: auditState.last_obligation ?? null,
|
|
3193
|
+
last_executor: auditState.last_executor ?? null,
|
|
3194
|
+
blockers: auditState.blockers ?? [],
|
|
3195
|
+
obligations_summary: obligationStates,
|
|
3196
|
+
recent_runs: recentRuns,
|
|
3197
|
+
pending_tasks: pendingTasksSummary,
|
|
3198
|
+
failed_tasks: failedTasks,
|
|
3199
|
+
}, null, 2));
|
|
3200
|
+
}
|
|
3075
3201
|
async function cmdMcp(argv) {
|
|
3076
3202
|
await runAuditCodeMcpServer(argv.slice(3));
|
|
3077
3203
|
}
|
|
@@ -3183,9 +3309,12 @@ async function main(argv) {
|
|
|
3183
3309
|
case "quota":
|
|
3184
3310
|
await cmdQuota(argv);
|
|
3185
3311
|
return;
|
|
3312
|
+
case "status":
|
|
3313
|
+
await cmdStatus(argv);
|
|
3314
|
+
return;
|
|
3186
3315
|
default:
|
|
3187
3316
|
console.error(`Unknown command: ${command}`);
|
|
3188
|
-
console.error("Available commands: sample-run, advance-audit, next-step, run-to-completion, worker-run, import-external-analyzer, intake, plan, ingest-results, explain-task, update-runtime-validation, validate, validate-results, requeue, synthesize, cleanup, mcp, prepare-dispatch, merge-and-ingest, submit-packet, validate-result, quota");
|
|
3317
|
+
console.error("Available commands: sample-run, advance-audit, next-step, run-to-completion, worker-run, import-external-analyzer, intake, plan, ingest-results, explain-task, update-runtime-validation, validate, validate-results, requeue, synthesize, cleanup, mcp, prepare-dispatch, merge-and-ingest, submit-packet, validate-result, quota, status");
|
|
3189
3318
|
process.exitCode = 1;
|
|
3190
3319
|
}
|
|
3191
3320
|
}
|
package/dist/mcp/server.js
CHANGED
|
@@ -577,9 +577,15 @@ export async function runAuditCodeMcpServer(argv) {
|
|
|
577
577
|
}
|
|
578
578
|
try {
|
|
579
579
|
switch (request.method) {
|
|
580
|
-
case "initialize":
|
|
580
|
+
case "initialize": {
|
|
581
|
+
const requestedVersion = typeof request.params?.protocolVersion === "string"
|
|
582
|
+
? request.params.protocolVersion
|
|
583
|
+
: PROTOCOL_VERSION;
|
|
584
|
+
const negotiatedVersion = requestedVersion <= PROTOCOL_VERSION
|
|
585
|
+
? requestedVersion
|
|
586
|
+
: PROTOCOL_VERSION;
|
|
581
587
|
writeMessage(success(request.id ?? null, {
|
|
582
|
-
protocolVersion:
|
|
588
|
+
protocolVersion: negotiatedVersion,
|
|
583
589
|
serverInfo: {
|
|
584
590
|
name: "audit-code",
|
|
585
591
|
version,
|
|
@@ -592,6 +598,7 @@ export async function runAuditCodeMcpServer(argv) {
|
|
|
592
598
|
},
|
|
593
599
|
}));
|
|
594
600
|
break;
|
|
601
|
+
}
|
|
595
602
|
case "notifications/initialized":
|
|
596
603
|
break;
|
|
597
604
|
case "ping":
|
|
@@ -272,7 +272,6 @@ export function runResultIngestionExecutor(bundle, results) {
|
|
|
272
272
|
const runtimeValidationReport = runtimeValidationTasks
|
|
273
273
|
? mergeRuntimeValidationReport(runtimeValidationTasks, bundle.runtime_validation_report)
|
|
274
274
|
: bundle.runtime_validation_report;
|
|
275
|
-
const requeuePayload = buildRequeuePayload(updatedCoverageMatrix, bundle.critical_flows, flowCoverage, bundle.external_analyzer_results);
|
|
276
275
|
const mergedResults = [...(bundle.audit_results ?? []), ...results];
|
|
277
276
|
const completedAuditTasks = updateAuditTaskStatuses(bundle.audit_tasks, mergedResults);
|
|
278
277
|
const baseUpdatedBundle = {
|
|
@@ -283,7 +282,6 @@ export function runResultIngestionExecutor(bundle, results) {
|
|
|
283
282
|
runtime_validation_report: runtimeValidationReport,
|
|
284
283
|
audit_results: mergedResults,
|
|
285
284
|
audit_tasks: completedAuditTasks,
|
|
286
|
-
requeue_tasks: requeuePayload.tasks,
|
|
287
285
|
audit_report: undefined,
|
|
288
286
|
};
|
|
289
287
|
const selectiveDeepening = appendSelectiveDeepeningTasks({
|
|
@@ -291,8 +289,13 @@ export function runResultIngestionExecutor(bundle, results) {
|
|
|
291
289
|
results: mergedResults,
|
|
292
290
|
runtimeValidationReport,
|
|
293
291
|
});
|
|
292
|
+
const requeuePayload = buildRequeuePayload(updatedCoverageMatrix, selectiveDeepening.bundle.critical_flows, selectiveDeepening.bundle.flow_coverage, selectiveDeepening.bundle.external_analyzer_results);
|
|
293
|
+
const finalBundle = {
|
|
294
|
+
...selectiveDeepening.bundle,
|
|
295
|
+
requeue_tasks: requeuePayload.tasks,
|
|
296
|
+
};
|
|
294
297
|
return {
|
|
295
|
-
updated:
|
|
298
|
+
updated: finalBundle,
|
|
296
299
|
artifacts_written: [
|
|
297
300
|
"coverage_matrix.json",
|
|
298
301
|
"flow_coverage.json",
|
package/dist/quota/limits.js
CHANGED
|
@@ -13,8 +13,8 @@ const KNOWN_MODEL_LIMITS = {
|
|
|
13
13
|
export function classifyProvider(providerName) {
|
|
14
14
|
switch (providerName) {
|
|
15
15
|
case "claude-code":
|
|
16
|
-
case "opencode":
|
|
17
16
|
return "hosted";
|
|
17
|
+
case "opencode":
|
|
18
18
|
case "local-subprocess":
|
|
19
19
|
return "local";
|
|
20
20
|
case "subprocess-template":
|
package/dist/quota/scheduler.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveLimits } from "./limits.js";
|
|
2
2
|
import { computeMaxSafeConcurrency } from "./state.js";
|
|
3
3
|
export function scheduleWave(options) {
|
|
4
4
|
const { providerName, sessionConfig, hostModel, requestedConcurrency, estimatedPacketTokens = 0, quotaStateEntry = null, } = options;
|
|
@@ -23,7 +23,6 @@ export function scheduleWave(options) {
|
|
|
23
23
|
}
|
|
24
24
|
const safetyMargin = quota.safety_margin ?? 0.8;
|
|
25
25
|
const halfLifeHours = quota.empirical_half_life_hours ?? 24;
|
|
26
|
-
const providerType = classifyProvider(providerName);
|
|
27
26
|
const { limits, source, confidence } = resolveLimits({ providerName, sessionConfig, hostModel });
|
|
28
27
|
let waveSize = requestedConcurrency;
|
|
29
28
|
let cooldownUntil = null;
|
|
@@ -50,14 +49,7 @@ export function scheduleWave(options) {
|
|
|
50
49
|
const learnedCap = computeMaxSafeConcurrency(quotaStateEntry, halfLifeHours);
|
|
51
50
|
waveSize = Math.min(waveSize, learnedCap);
|
|
52
51
|
}
|
|
53
|
-
|
|
54
|
-
// Unknown hosted provider with no learned data and no model-specific limits —
|
|
55
|
-
// be conservative. If the caller supplied RPM/TPM caps those already govern rate;
|
|
56
|
-
// this guard only triggers when we have no rate information at all.
|
|
57
|
-
const conservativeDefault = quota.unknown_hosted_concurrency ?? 1;
|
|
58
|
-
waveSize = Math.min(waveSize, conservativeDefault);
|
|
59
|
-
}
|
|
60
|
-
// Local providers with no learned data: use requestedConcurrency (no rate pressure)
|
|
52
|
+
// No learned data: use requestedConcurrency and let 429 outcomes train the cap
|
|
61
53
|
}
|
|
62
54
|
waveSize = Math.max(1, waveSize);
|
|
63
55
|
return {
|
package/package.json
CHANGED
package/scripts/postinstall.mjs
CHANGED
|
@@ -43,7 +43,7 @@ const OPENCODE_AUDIT_EDIT_PERMISSION = {
|
|
|
43
43
|
};
|
|
44
44
|
|
|
45
45
|
const OPENCODE_AUDIT_BASH_PERMISSION = {
|
|
46
|
-
'*': '
|
|
46
|
+
'*': 'allow',
|
|
47
47
|
'audit-code run-to-completion*': 'deny',
|
|
48
48
|
'audit-code synthesize*': 'deny',
|
|
49
49
|
'audit-code cleanup*': 'deny',
|
|
@@ -88,15 +88,116 @@ function replaceBackslashes(value) {
|
|
|
88
88
|
return value.replace(/\\/g, '/');
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
function
|
|
92
|
-
return
|
|
91
|
+
function renderOpenCodeExternalDirectoryPermission() {
|
|
92
|
+
return { '*': 'allow' };
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
function
|
|
96
|
-
return
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
95
|
+
function renderGlobalMcpLauncher(installedPkgRoot) {
|
|
96
|
+
return [
|
|
97
|
+
"import { access, readFile, appendFile } from 'node:fs/promises';",
|
|
98
|
+
"import { constants } from 'node:fs';",
|
|
99
|
+
"import { spawn } from 'node:child_process';",
|
|
100
|
+
"import { join } from 'node:path';",
|
|
101
|
+
"import { homedir } from 'node:os';",
|
|
102
|
+
'',
|
|
103
|
+
'const repoRoot = process.cwd();',
|
|
104
|
+
"const artifactsDir = join(repoRoot, '.audit-artifacts');",
|
|
105
|
+
`const globalPackageRoot = ${JSON.stringify(installedPkgRoot)};`,
|
|
106
|
+
"const logPath = join(homedir(), '.audit-code', 'mcp-server.log');",
|
|
107
|
+
'',
|
|
108
|
+
'async function log(msg) {',
|
|
109
|
+
' try {',
|
|
110
|
+
' const ts = new Date().toISOString();',
|
|
111
|
+
" await appendFile(logPath, `${ts} ${msg}\\n`, 'utf8');",
|
|
112
|
+
' } catch {',
|
|
113
|
+
' // ignore log failures',
|
|
114
|
+
' }',
|
|
115
|
+
'}',
|
|
116
|
+
'',
|
|
117
|
+
'async function exists(path) {',
|
|
118
|
+
' try {',
|
|
119
|
+
' await access(path, constants.F_OK);',
|
|
120
|
+
' return true;',
|
|
121
|
+
' } catch {',
|
|
122
|
+
' return false;',
|
|
123
|
+
' }',
|
|
124
|
+
'}',
|
|
125
|
+
'',
|
|
126
|
+
'function spawnForward(command, args) {',
|
|
127
|
+
' return new Promise((resolvePromise, rejectPromise) => {',
|
|
128
|
+
' const child = spawn(command, args, {',
|
|
129
|
+
' cwd: repoRoot,',
|
|
130
|
+
' env: process.env,',
|
|
131
|
+
" stdio: ['inherit', 'inherit', 'inherit'],",
|
|
132
|
+
' });',
|
|
133
|
+
" child.on('error', rejectPromise);",
|
|
134
|
+
" child.on('exit', (code) => resolvePromise(code ?? 1));",
|
|
135
|
+
' });',
|
|
136
|
+
'}',
|
|
137
|
+
'',
|
|
138
|
+
'async function tryCandidates() {',
|
|
139
|
+
" const localPackageEntrypoint = join(repoRoot, 'node_modules', 'auditor-lambda', 'audit-code.mjs');",
|
|
140
|
+
" const localBin = process.platform === 'win32'",
|
|
141
|
+
" ? join(repoRoot, 'node_modules', '.bin', 'audit-code.cmd')",
|
|
142
|
+
" : join(repoRoot, 'node_modules', '.bin', 'audit-code');",
|
|
143
|
+
" const repoPackageJsonPath = join(repoRoot, 'package.json');",
|
|
144
|
+
" const globalPackageEntrypoint = globalPackageRoot ? join(globalPackageRoot, 'audit-code.mjs') : null;",
|
|
145
|
+
" const sharedArgs = ['mcp', '--root', repoRoot, '--artifacts-dir', artifactsDir];",
|
|
146
|
+
'',
|
|
147
|
+
' if (await exists(localPackageEntrypoint)) {',
|
|
148
|
+
" await log(`launching local node_modules candidate: ${localPackageEntrypoint}`);",
|
|
149
|
+
' return await spawnForward(process.execPath, [localPackageEntrypoint, ...sharedArgs]);',
|
|
150
|
+
' }',
|
|
151
|
+
'',
|
|
152
|
+
" if (await exists(repoPackageJsonPath) && await exists(join(repoRoot, 'audit-code.mjs'))) {",
|
|
153
|
+
' try {',
|
|
154
|
+
" const packageJson = JSON.parse(await readFile(repoPackageJsonPath, 'utf8'));",
|
|
155
|
+
" if (packageJson?.name === 'auditor-lambda') {",
|
|
156
|
+
" await log(`launching repo-root candidate: ${join(repoRoot, 'audit-code.mjs')}`);",
|
|
157
|
+
" return await spawnForward(process.execPath, [join(repoRoot, 'audit-code.mjs'), ...sharedArgs]);",
|
|
158
|
+
' }',
|
|
159
|
+
' } catch {',
|
|
160
|
+
' // fall through to the next candidate',
|
|
161
|
+
' }',
|
|
162
|
+
' }',
|
|
163
|
+
'',
|
|
164
|
+
' if (globalPackageEntrypoint && await exists(globalPackageEntrypoint)) {',
|
|
165
|
+
" await log(`launching global candidate: ${globalPackageEntrypoint}`);",
|
|
166
|
+
' return await spawnForward(process.execPath, [globalPackageEntrypoint, ...sharedArgs]);',
|
|
167
|
+
' }',
|
|
168
|
+
'',
|
|
169
|
+
' if (await exists(localBin)) {',
|
|
170
|
+
" await log(`launching local bin candidate: ${localBin}`);",
|
|
171
|
+
' return await spawnForward(localBin, sharedArgs);',
|
|
172
|
+
' }',
|
|
173
|
+
'',
|
|
174
|
+
" const pathCandidate = process.platform === 'win32' ? 'audit-code.cmd' : 'audit-code';",
|
|
175
|
+
" await log(`trying PATH candidate: ${pathCandidate}`);",
|
|
176
|
+
' let exitCode = await spawnForward(pathCandidate, sharedArgs).catch(() => null);',
|
|
177
|
+
" if (typeof exitCode === 'number') {",
|
|
178
|
+
' return exitCode;',
|
|
179
|
+
' }',
|
|
180
|
+
'',
|
|
181
|
+
" exitCode = await spawnForward('npx', ['--no-install', 'audit-code', ...sharedArgs]).catch(() => null);",
|
|
182
|
+
" if (typeof exitCode === 'number') {",
|
|
183
|
+
' return exitCode;',
|
|
184
|
+
' }',
|
|
185
|
+
'',
|
|
186
|
+
" await log('ERROR: no candidate found');",
|
|
187
|
+
' throw new Error(',
|
|
188
|
+
" 'Unable to locate an audit-code executable. Install auditor-lambda globally or as a local dependency.',",
|
|
189
|
+
' );',
|
|
190
|
+
'}',
|
|
191
|
+
'',
|
|
192
|
+
"log(`run-mcp-server.mjs started: node=${process.execPath} cwd=${repoRoot} globalPkg=${globalPackageRoot}`).catch(() => {});",
|
|
193
|
+
'const code = await tryCandidates().catch(async (err) => {',
|
|
194
|
+
" await log(`FATAL: ${err.message}`);",
|
|
195
|
+
' process.stderr.write(err.message + "\\n");',
|
|
196
|
+
' return 1;',
|
|
197
|
+
'});',
|
|
198
|
+
'process.exitCode = code;',
|
|
199
|
+
'',
|
|
200
|
+
].join('\n');
|
|
100
201
|
}
|
|
101
202
|
|
|
102
203
|
function objectValue(value) {
|
|
@@ -182,17 +283,23 @@ const OPENCODE_MCP_COMMAND_TEMPLATE = [
|
|
|
182
283
|
'Use the auditor MCP tools as the primary interface to the audit workflow.',
|
|
183
284
|
'',
|
|
184
285
|
'1. Call `auditor_start_audit` to initialize and receive the first step.',
|
|
185
|
-
'2.
|
|
286
|
+
'2. Check `step_kind` in the response:',
|
|
287
|
+
' - If `step_kind` is `"capability_check"`: immediately call `auditor_report_capability` with `can_dispatch_subagents: true` and `can_select_subagent_model: true`. Do not run shell commands or inspect prompt_content for this step.',
|
|
288
|
+
' - Otherwise: read `prompt_content` and follow it.',
|
|
186
289
|
'3. When a step completes (not blocked), call `auditor_continue_audit` to advance.',
|
|
187
290
|
'4. Stop when the step instructions say to stop.',
|
|
188
291
|
'',
|
|
189
292
|
'Do not run shell commands. Use only `auditor_*` MCP tools and the `task` tool for subagent dispatch.',
|
|
293
|
+
'',
|
|
294
|
+
'If `auditor_start_audit` is not listed in your available tools, stop immediately and tell the user the auditor MCP server is not connected. Do not read local files as a fallback.',
|
|
190
295
|
].join('\n');
|
|
191
296
|
|
|
192
297
|
function mergeOpenCodeGlobalConfig(existing) {
|
|
193
298
|
const parsed = existing ? JSON.parse(existing) : {};
|
|
194
299
|
const auditPermission = renderOpenCodePermissionConfig();
|
|
195
300
|
const existingAuditor = objectValue(objectValue(parsed.agent).auditor);
|
|
301
|
+
const nodeExecPath = replaceBackslashes(process.execPath);
|
|
302
|
+
const pkgEntrypoint = replaceBackslashes(join(pkgRoot, 'audit-code.mjs'));
|
|
196
303
|
return {
|
|
197
304
|
...parsed,
|
|
198
305
|
command: {
|
|
@@ -206,7 +313,19 @@ function mergeOpenCodeGlobalConfig(existing) {
|
|
|
206
313
|
subtask: false,
|
|
207
314
|
},
|
|
208
315
|
},
|
|
209
|
-
|
|
316
|
+
mcp: {
|
|
317
|
+
...objectValue(parsed.mcp),
|
|
318
|
+
auditor: {
|
|
319
|
+
type: 'local',
|
|
320
|
+
command: [nodeExecPath, pkgEntrypoint, 'mcp'],
|
|
321
|
+
enabled: true,
|
|
322
|
+
timeout: 10000,
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
permission: {
|
|
326
|
+
...mergeOpenCodePermissionConfig(parsed.permission, auditPermission),
|
|
327
|
+
external_directory: { '*': 'allow' },
|
|
328
|
+
},
|
|
210
329
|
agent: {
|
|
211
330
|
...(parsed.agent && typeof parsed.agent === 'object' && !Array.isArray(parsed.agent)
|
|
212
331
|
? parsed.agent
|
|
@@ -214,12 +333,10 @@ function mergeOpenCodeGlobalConfig(existing) {
|
|
|
214
333
|
auditor: {
|
|
215
334
|
...existingAuditor,
|
|
216
335
|
description: 'Read-heavy audit orchestration agent for the /audit-code workflow.',
|
|
217
|
-
tools: {
|
|
218
|
-
'auditor*': true,
|
|
219
|
-
task: true,
|
|
220
|
-
},
|
|
221
336
|
permission: {
|
|
222
337
|
...mergeOpenCodePermissionConfig(existingAuditor.permission, auditPermission),
|
|
338
|
+
external_directory: { '*': 'allow' },
|
|
339
|
+
'auditor_*': 'allow',
|
|
223
340
|
question: 'allow',
|
|
224
341
|
task: 'allow',
|
|
225
342
|
},
|
|
@@ -290,15 +407,24 @@ for (const install of installs) {
|
|
|
290
407
|
}
|
|
291
408
|
}
|
|
292
409
|
|
|
293
|
-
// Install OpenCode
|
|
410
|
+
// Install global MCP launcher for OpenCode (and other hosts that support global config)
|
|
411
|
+
const globalMcpLauncherPath = join(homedir(), '.audit-code', 'run-mcp-server.mjs');
|
|
412
|
+
try {
|
|
413
|
+
const action = writeGeneratedFile(globalMcpLauncherPath, Buffer.from(renderGlobalMcpLauncher(pkgRoot)));
|
|
414
|
+
console.log(`audit-code: ${action} global MCP launcher at ${globalMcpLauncherPath}`);
|
|
415
|
+
} catch (err) {
|
|
416
|
+
console.warn(`audit-code: could not install global MCP launcher (${err.message})`);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Install OpenCode global command and MCP via merged config
|
|
294
420
|
const opencodeGlobalConfig = join(homedir(), '.config', 'opencode', 'opencode.json');
|
|
295
421
|
try {
|
|
296
422
|
const action = installMergedJson(opencodeGlobalConfig, (existing) =>
|
|
297
423
|
mergeOpenCodeGlobalConfig(existing),
|
|
298
424
|
);
|
|
299
|
-
console.log(`audit-code: ${action} global OpenCode
|
|
425
|
+
console.log(`audit-code: ${action} global OpenCode config in ${opencodeGlobalConfig}`);
|
|
300
426
|
} catch (err) {
|
|
301
|
-
console.warn(`audit-code: could not install global OpenCode
|
|
302
|
-
console.warn(` To install manually, add
|
|
427
|
+
console.warn(`audit-code: could not install global OpenCode config (${err.message})`);
|
|
428
|
+
console.warn(` To install manually, add the mcp.auditor and command["audit-code"] entries to:`);
|
|
303
429
|
console.warn(` ${opencodeGlobalConfig}`);
|
|
304
430
|
}
|