auditor-lambda 0.3.23 → 0.3.25
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 +39 -22
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +183 -21
- package/dist/mcp/server.js +119 -4
- 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 +142 -28
|
@@ -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() {
|
|
@@ -605,8 +602,25 @@ function renderOpenCodePermissionConfig() {
|
|
|
605
602
|
};
|
|
606
603
|
}
|
|
607
604
|
|
|
608
|
-
|
|
609
|
-
|
|
605
|
+
const OPENCODE_MCP_COMMAND_TEMPLATE = [
|
|
606
|
+
'# audit-code',
|
|
607
|
+
'',
|
|
608
|
+
'Use the auditor MCP tools as the primary interface to the audit workflow.',
|
|
609
|
+
'',
|
|
610
|
+
'1. Call `auditor_start_audit` to initialize and receive the first step.',
|
|
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.',
|
|
614
|
+
'3. When a step completes (not blocked), call `auditor_continue_audit` to advance.',
|
|
615
|
+
'4. Stop when the step instructions say to stop.',
|
|
616
|
+
'',
|
|
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.',
|
|
620
|
+
].join('\n');
|
|
621
|
+
|
|
622
|
+
function renderOpenCodeProjectConfig(_root) {
|
|
623
|
+
const launcher = `.audit-code/install/${MCP_LAUNCHER_FILENAME}`;
|
|
610
624
|
const auditPermission = renderOpenCodePermissionConfig();
|
|
611
625
|
return {
|
|
612
626
|
$schema: 'https://opencode.ai/config.json',
|
|
@@ -623,12 +637,11 @@ function renderOpenCodeProjectConfig(root) {
|
|
|
623
637
|
auditor: {
|
|
624
638
|
description:
|
|
625
639
|
'Read-heavy audit orchestration agent for the /audit-code workflow.',
|
|
626
|
-
tools: {
|
|
627
|
-
'auditor*': true,
|
|
628
|
-
},
|
|
629
640
|
permission: {
|
|
630
641
|
...auditPermission,
|
|
642
|
+
'auditor_*': 'allow',
|
|
631
643
|
question: 'allow',
|
|
644
|
+
task: 'allow',
|
|
632
645
|
},
|
|
633
646
|
},
|
|
634
647
|
},
|
|
@@ -727,12 +740,10 @@ function assertOpenCodeAuditPermissionConfig(permissionConfig, label) {
|
|
|
727
740
|
}
|
|
728
741
|
const externalDirectory = permissionConfig?.external_directory;
|
|
729
742
|
if (!externalDirectory || typeof externalDirectory !== 'object' || Array.isArray(externalDirectory)) {
|
|
730
|
-
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".`);
|
|
731
744
|
}
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
throw new Error(`OpenCode ${label}.external_directory must allow ${pattern}. Run "audit-code install --host opencode".`);
|
|
735
|
-
}
|
|
745
|
+
if (externalDirectory['*'] !== 'allow') {
|
|
746
|
+
throw new Error(`OpenCode ${label}.external_directory must set "*" to "allow". Run "audit-code install --host opencode".`);
|
|
736
747
|
}
|
|
737
748
|
const edit = permissionConfig?.edit;
|
|
738
749
|
const bash = permissionConfig?.bash;
|
|
@@ -800,16 +811,22 @@ function buildMergedOpenCodeProjectConfig(existing, root) {
|
|
|
800
811
|
...objectValue(existing.mcp),
|
|
801
812
|
auditor: generated.mcp.auditor,
|
|
802
813
|
},
|
|
803
|
-
permission:
|
|
814
|
+
permission: {
|
|
815
|
+
...mergeOpenCodePermissionConfig(existing.permission, generated.permission),
|
|
816
|
+
external_directory: { '*': 'allow' },
|
|
817
|
+
},
|
|
804
818
|
agent: {
|
|
805
819
|
...objectValue(existing.agent),
|
|
806
820
|
auditor: {
|
|
807
821
|
...objectValue(objectValue(existing.agent).auditor),
|
|
808
822
|
...generated.agent.auditor,
|
|
809
|
-
permission:
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
823
|
+
permission: {
|
|
824
|
+
...mergeOpenCodePermissionConfig(
|
|
825
|
+
objectValue(objectValue(existing.agent).auditor).permission,
|
|
826
|
+
generated.agent.auditor.permission,
|
|
827
|
+
),
|
|
828
|
+
external_directory: { '*': 'allow' },
|
|
829
|
+
},
|
|
813
830
|
},
|
|
814
831
|
},
|
|
815
832
|
};
|
|
@@ -1992,8 +2009,8 @@ async function verifyInstalledBootstrap(argv) {
|
|
|
1992
2009
|
if (!Array.isArray(mcpCommand) || mcpCommand[0] !== 'node') {
|
|
1993
2010
|
throw new Error('OpenCode config must set mcp.auditor.command as a Node command array.');
|
|
1994
2011
|
}
|
|
1995
|
-
if (mcpCommand[1]
|
|
1996
|
-
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'}.`);
|
|
1997
2014
|
}
|
|
1998
2015
|
if (config?.mcp?.auditor?.type !== 'local') {
|
|
1999
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;
|
|
@@ -523,10 +529,21 @@ function renderCapabilityCheckPrompt(params) {
|
|
|
523
529
|
return [
|
|
524
530
|
"# audit-code capability check",
|
|
525
531
|
"",
|
|
526
|
-
"Decide one thing from the active toolset: does this host expose a callable subagent/delegation tool for source-code review, such as Agent,
|
|
532
|
+
"Decide one thing from the active toolset: does this host expose a callable subagent/delegation tool for source-code review, such as `task`, Agent, or an equivalent built-in subagent call?",
|
|
527
533
|
"",
|
|
528
534
|
"Do not run shell commands to answer this. Do not inspect packet prompts, schemas, or backend command catalogs.",
|
|
529
535
|
"",
|
|
536
|
+
"**If auditor MCP tools are available** (preferred — no shell required):",
|
|
537
|
+
"",
|
|
538
|
+
"Call `auditor_report_capability` with:",
|
|
539
|
+
"- `can_dispatch_subagents: true` if the `task` tool or equivalent subagent dispatch is available",
|
|
540
|
+
"- `can_dispatch_subagents: false` if not",
|
|
541
|
+
"- Optionally `can_restrict_subagent_tools: true` and/or `can_select_subagent_model: true`",
|
|
542
|
+
"",
|
|
543
|
+
"Read the `prompt_content` field in the tool response and follow it.",
|
|
544
|
+
"",
|
|
545
|
+
"**Fallback — if auditor MCP tools are not available:**",
|
|
546
|
+
"",
|
|
530
547
|
"If callable subagents are available, run:",
|
|
531
548
|
"",
|
|
532
549
|
` ${yesCommand}`,
|
|
@@ -550,30 +567,41 @@ function renderDispatchReviewPrompt(params) {
|
|
|
550
567
|
const toolsLine = params.hostCanRestrictSubagentTools
|
|
551
568
|
? "Restrict review subagents to read/search plus the packet submit command named in their prompt. Do not give them source edit/write tools."
|
|
552
569
|
: "Do not ask the user about per-subagent tool restrictions; this host did not report a callable restriction facility.";
|
|
553
|
-
const
|
|
570
|
+
const runId = params.activeReviewRun.run_id;
|
|
571
|
+
const dispatchDataLines = params.dispatchQuotaPath
|
|
554
572
|
? [
|
|
555
|
-
"
|
|
573
|
+
"**If auditor MCP tools are available** (preferred):",
|
|
574
|
+
"",
|
|
575
|
+
"The dispatch plan entries are in the `dispatch_plan_entries` field of the tool response that returned this step. The wave schedule is in the `dispatch_quota` field.",
|
|
576
|
+
"",
|
|
577
|
+
"Use the `wave_size` from `dispatch_quota`. If `cooldown_until` is non-null, wait until that timestamp before starting the first wave.",
|
|
578
|
+
"",
|
|
579
|
+
"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.",
|
|
580
|
+
"",
|
|
581
|
+
"**Fallback — if auditor MCP tools are not available:** Read both of these files:",
|
|
556
582
|
"",
|
|
557
583
|
` Dispatch plan: ${params.dispatchPlanPath}`,
|
|
558
584
|
` Dispatch quota: ${params.dispatchQuotaPath}`,
|
|
559
585
|
"",
|
|
560
|
-
"
|
|
561
|
-
"",
|
|
562
|
-
"For each wave: launch up to `wave_size` subagents in parallel (one per plan entry), wait for all of them to finish, then start the next wave. Repeat until all entries are dispatched.",
|
|
586
|
+
"Apply the same wave logic from the quota file.",
|
|
563
587
|
]
|
|
564
588
|
: [
|
|
565
|
-
"
|
|
589
|
+
"**If auditor MCP tools are available** (preferred):",
|
|
590
|
+
"",
|
|
591
|
+
"The dispatch plan entries are in the `dispatch_plan_entries` field of the tool response that returned this step.",
|
|
592
|
+
"",
|
|
593
|
+
"**Fallback — if auditor MCP tools are not available:** Read this dispatch plan JSON:",
|
|
566
594
|
"",
|
|
567
595
|
` ${params.dispatchPlanPath}`,
|
|
568
596
|
"",
|
|
569
|
-
"Launch one
|
|
597
|
+
"Launch one subagent for each entry in the plan.",
|
|
570
598
|
];
|
|
571
599
|
return [
|
|
572
600
|
"# audit-code dispatch review",
|
|
573
601
|
"",
|
|
574
|
-
...
|
|
602
|
+
...dispatchDataLines,
|
|
575
603
|
"",
|
|
576
|
-
"Pass each
|
|
604
|
+
"Pass each `entry.prompt_path` literally to its subagent; do not load packet prompt files into this orchestrator context.",
|
|
577
605
|
"",
|
|
578
606
|
"Subagent prompt shape:",
|
|
579
607
|
"",
|
|
@@ -584,7 +612,11 @@ function renderDispatchReviewPrompt(params) {
|
|
|
584
612
|
"",
|
|
585
613
|
"Each subagent must submit its packet through the submit command printed in its packet prompt and stop after successful submission.",
|
|
586
614
|
"",
|
|
587
|
-
"After all waves complete
|
|
615
|
+
"**After all waves complete:**",
|
|
616
|
+
"",
|
|
617
|
+
"If auditor MCP tools are available, call `auditor_merge_and_ingest` with `{ run_id: \"" + runId + "\" }`, then call `auditor_continue_audit` and follow the `prompt_content` in the response.",
|
|
618
|
+
"",
|
|
619
|
+
"Fallback — if auditor MCP tools are not available, run exactly:",
|
|
588
620
|
"",
|
|
589
621
|
` ${mergeCommand}`,
|
|
590
622
|
"",
|
|
@@ -624,9 +656,11 @@ function renderPresentReportPrompt(finalReportPath) {
|
|
|
624
656
|
"",
|
|
625
657
|
"The deterministic audit is complete.",
|
|
626
658
|
"",
|
|
627
|
-
"Read
|
|
628
|
-
"",
|
|
659
|
+
"Read the final audit report from the `audit-code://report/current` MCP resource (or from the file at:",
|
|
629
660
|
` ${finalReportPath}`,
|
|
661
|
+
"if a Read tool is available).",
|
|
662
|
+
"",
|
|
663
|
+
"Present the completed audit with work blocks first.",
|
|
630
664
|
"",
|
|
631
665
|
"Do not run the orchestrator again for this completed audit.",
|
|
632
666
|
"",
|
|
@@ -697,6 +731,7 @@ export const cliTestUtils = {
|
|
|
697
731
|
getUiMode,
|
|
698
732
|
looksLikeCliFlag,
|
|
699
733
|
countLines,
|
|
734
|
+
warnIfNotGitRepo,
|
|
700
735
|
};
|
|
701
736
|
async function maybeArchiveLegacyPendingResults(auditResultsPath) {
|
|
702
737
|
if (!auditResultsPath || basename(auditResultsPath) !== "worker_results_pending.json") {
|
|
@@ -926,6 +961,7 @@ export async function runSample(argv = process.argv) {
|
|
|
926
961
|
}
|
|
927
962
|
async function cmdAdvanceAudit(argv) {
|
|
928
963
|
const root = getRootDir(argv);
|
|
964
|
+
warnIfNotGitRepo(root);
|
|
929
965
|
const artifactsDir = getArtifactsDir(argv);
|
|
930
966
|
await cleanupStaleArtifactsDir(artifactsDir);
|
|
931
967
|
await mkdir(artifactsDir, { recursive: true });
|
|
@@ -1090,6 +1126,7 @@ async function runDeterministicForNextStep(params) {
|
|
|
1090
1126
|
}
|
|
1091
1127
|
async function cmdNextStep(argv) {
|
|
1092
1128
|
const root = getRootDir(argv);
|
|
1129
|
+
warnIfNotGitRepo(root);
|
|
1093
1130
|
const artifactsDir = getArtifactsDir(argv);
|
|
1094
1131
|
await mkdir(artifactsDir, { recursive: true });
|
|
1095
1132
|
await ensureSupervisorDirs(artifactsDir);
|
|
@@ -1231,8 +1268,13 @@ async function cmdNextStep(argv) {
|
|
|
1231
1268
|
stepKind: "dispatch_review",
|
|
1232
1269
|
status: "ready",
|
|
1233
1270
|
runId: result.activeReviewRun.run_id,
|
|
1234
|
-
allowedCommands: [
|
|
1235
|
-
|
|
1271
|
+
allowedCommands: [
|
|
1272
|
+
"auditor_merge_and_ingest",
|
|
1273
|
+
"auditor_continue_audit",
|
|
1274
|
+
mergeCommand,
|
|
1275
|
+
continueCommand,
|
|
1276
|
+
],
|
|
1277
|
+
stopCondition: "Dispatch every packet, call auditor_merge_and_ingest once, then call auditor_continue_audit.",
|
|
1236
1278
|
repoRoot: root,
|
|
1237
1279
|
artifactPaths: {
|
|
1238
1280
|
dispatch_plan: dispatch.dispatch_plan_path,
|
|
@@ -1255,6 +1297,7 @@ async function cmdNextStep(argv) {
|
|
|
1255
1297
|
}
|
|
1256
1298
|
async function cmdRunToCompletion(argv) {
|
|
1257
1299
|
const root = getRootDir(argv);
|
|
1300
|
+
warnIfNotGitRepo(root);
|
|
1258
1301
|
const artifactsDir = getArtifactsDir(argv);
|
|
1259
1302
|
await cleanupStaleArtifactsDir(artifactsDir);
|
|
1260
1303
|
await mkdir(artifactsDir, { recursive: true });
|
|
@@ -2172,7 +2215,6 @@ async function prepareDispatchArtifacts(params) {
|
|
|
2172
2215
|
const runId = params.runId;
|
|
2173
2216
|
const artifactsDir = params.artifactsDir;
|
|
2174
2217
|
const runDir = join(artifactsDir, "runs", runId);
|
|
2175
|
-
const tasksPath = join(runDir, "pending-audit-tasks.json");
|
|
2176
2218
|
const taskResultsDir = join(runDir, "task-results");
|
|
2177
2219
|
const dispatchPlanPath = join(runDir, "dispatch-plan.json");
|
|
2178
2220
|
let reviewRoot = params.root;
|
|
@@ -2185,8 +2227,13 @@ async function prepareDispatchArtifacts(params) {
|
|
|
2185
2227
|
throw error;
|
|
2186
2228
|
}
|
|
2187
2229
|
}
|
|
2188
|
-
const tasks = await readJsonFile(tasksPath);
|
|
2189
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
|
+
});
|
|
2190
2237
|
const sessionConfig = params.sessionConfig ?? (await loadSessionConfig(artifactsDir).catch(() => ({})));
|
|
2191
2238
|
const lensDefsPath = join(packageRoot, "dispatch", "lens-definitions.json");
|
|
2192
2239
|
const lensDefs = await readJsonFile(lensDefsPath);
|
|
@@ -2704,6 +2751,8 @@ async function cmdMergeAndIngest(argv) {
|
|
|
2704
2751
|
preferredExecutor: "result_ingestion_executor",
|
|
2705
2752
|
auditResultsPath,
|
|
2706
2753
|
});
|
|
2754
|
+
const updatedPendingTasks = await addFileLineCountHints(workerTask.repo_root, buildPendingAuditTasks(result.updated_bundle));
|
|
2755
|
+
await writeJsonFile(tasksPath, updatedPendingTasks);
|
|
2707
2756
|
const workerResult = buildWorkerResult({
|
|
2708
2757
|
runId,
|
|
2709
2758
|
obligationId: workerTask.obligation_id,
|
|
@@ -2805,9 +2854,11 @@ async function cmdImportExternalAnalyzer(argv) {
|
|
|
2805
2854
|
}, null, 2));
|
|
2806
2855
|
}
|
|
2807
2856
|
async function cmdIntake(argv) {
|
|
2857
|
+
const root = getRootDir(argv);
|
|
2858
|
+
warnIfNotGitRepo(root);
|
|
2808
2859
|
const artifactsDir = getArtifactsDir(argv);
|
|
2809
2860
|
const result = await runAuditStep({
|
|
2810
|
-
root
|
|
2861
|
+
root,
|
|
2811
2862
|
artifactsDir,
|
|
2812
2863
|
preferredExecutor: "intake_executor",
|
|
2813
2864
|
});
|
|
@@ -3039,6 +3090,114 @@ async function cmdCleanup(argv) {
|
|
|
3039
3090
|
dry_run: dryRun,
|
|
3040
3091
|
}, null, 2));
|
|
3041
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
|
+
}
|
|
3042
3201
|
async function cmdMcp(argv) {
|
|
3043
3202
|
await runAuditCodeMcpServer(argv.slice(3));
|
|
3044
3203
|
}
|
|
@@ -3150,9 +3309,12 @@ async function main(argv) {
|
|
|
3150
3309
|
case "quota":
|
|
3151
3310
|
await cmdQuota(argv);
|
|
3152
3311
|
return;
|
|
3312
|
+
case "status":
|
|
3313
|
+
await cmdStatus(argv);
|
|
3314
|
+
return;
|
|
3153
3315
|
default:
|
|
3154
3316
|
console.error(`Unknown command: ${command}`);
|
|
3155
|
-
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");
|
|
3156
3318
|
process.exitCode = 1;
|
|
3157
3319
|
}
|
|
3158
3320
|
}
|
package/dist/mcp/server.js
CHANGED
|
@@ -85,6 +85,14 @@ function parseContentLength(headerBlock) {
|
|
|
85
85
|
}
|
|
86
86
|
return contentLength;
|
|
87
87
|
}
|
|
88
|
+
async function readOptionalJson(path) {
|
|
89
|
+
try {
|
|
90
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
88
96
|
async function runWrapperCommand(args, options) {
|
|
89
97
|
return await new Promise((resolvePromise, rejectPromise) => {
|
|
90
98
|
const child = spawn(process.execPath, [
|
|
@@ -266,13 +274,41 @@ function renderPrompt(name, args) {
|
|
|
266
274
|
throw new Error(`Unknown prompt: ${name}`);
|
|
267
275
|
}
|
|
268
276
|
}
|
|
277
|
+
async function runContinueAudit(context, extraArgs = []) {
|
|
278
|
+
const step = await parseCliJson(extraArgs, context);
|
|
279
|
+
if (!step || typeof step !== "object" || Array.isArray(step))
|
|
280
|
+
return step;
|
|
281
|
+
const s = step;
|
|
282
|
+
if (hasValue(s.prompt_path)) {
|
|
283
|
+
try {
|
|
284
|
+
s.prompt_content = await readFile(s.prompt_path, "utf8");
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
// ignore — prompt_path is a fallback for hosts that can read files
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (s.step_kind === "dispatch_review") {
|
|
291
|
+
const paths = s.artifact_paths;
|
|
292
|
+
if (hasValue(paths?.dispatch_plan)) {
|
|
293
|
+
const plan = await readOptionalJson(paths.dispatch_plan);
|
|
294
|
+
if (plan !== undefined)
|
|
295
|
+
s.dispatch_plan_entries = plan;
|
|
296
|
+
}
|
|
297
|
+
if (hasValue(paths?.dispatch_quota)) {
|
|
298
|
+
const quota = await readOptionalJson(paths.dispatch_quota);
|
|
299
|
+
if (quota !== undefined)
|
|
300
|
+
s.dispatch_quota = quota;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return s;
|
|
304
|
+
}
|
|
269
305
|
async function handleToolCall(name, params, defaults) {
|
|
270
306
|
const context = getToolContext(params, defaults);
|
|
271
307
|
switch (name) {
|
|
272
308
|
case "start_audit":
|
|
273
|
-
return toolResult(await
|
|
309
|
+
return toolResult(await runContinueAudit(context));
|
|
274
310
|
case "continue_audit":
|
|
275
|
-
return toolResult(await
|
|
311
|
+
return toolResult(await runContinueAudit(context));
|
|
276
312
|
case "get_status":
|
|
277
313
|
return toolResult(await getStatusPayload(context));
|
|
278
314
|
case "explain_task": {
|
|
@@ -304,6 +340,32 @@ async function handleToolCall(name, params, defaults) {
|
|
|
304
340
|
: params?.updatesPath;
|
|
305
341
|
return toolResult(await parseCliJson(["--updates", resolve(updatesPath)], context));
|
|
306
342
|
}
|
|
343
|
+
case "merge_and_ingest": {
|
|
344
|
+
const runId = hasValue(params?.run_id)
|
|
345
|
+
? params.run_id
|
|
346
|
+
: hasValue(params?.runId)
|
|
347
|
+
? params.runId
|
|
348
|
+
: undefined;
|
|
349
|
+
if (!runId)
|
|
350
|
+
throw new Error("merge_and_ingest requires run_id.");
|
|
351
|
+
return toolResult(await parseCliJson(["merge-and-ingest", "--run-id", runId], context, true));
|
|
352
|
+
}
|
|
353
|
+
case "report_capability": {
|
|
354
|
+
const extraArgs = [];
|
|
355
|
+
const canDispatch = params?.can_dispatch_subagents ?? params?.canDispatchSubagents;
|
|
356
|
+
if (canDispatch !== undefined) {
|
|
357
|
+
extraArgs.push("--host-can-dispatch-subagents", String(Boolean(canDispatch)));
|
|
358
|
+
}
|
|
359
|
+
const canRestrict = params?.can_restrict_subagent_tools ?? params?.canRestrictSubagentTools;
|
|
360
|
+
if (canRestrict !== undefined) {
|
|
361
|
+
extraArgs.push("--host-can-restrict-subagent-tools", String(Boolean(canRestrict)));
|
|
362
|
+
}
|
|
363
|
+
const canSelect = params?.can_select_subagent_model ?? params?.canSelectSubagentModel;
|
|
364
|
+
if (canSelect !== undefined) {
|
|
365
|
+
extraArgs.push("--host-can-select-subagent-model", String(Boolean(canSelect)));
|
|
366
|
+
}
|
|
367
|
+
return toolResult(await runContinueAudit(context, ["next-step", ...extraArgs]));
|
|
368
|
+
}
|
|
307
369
|
default:
|
|
308
370
|
throw new Error(`Unknown tool: ${name}`);
|
|
309
371
|
}
|
|
@@ -423,6 +485,52 @@ function toolDefinitions() {
|
|
|
423
485
|
required: ["updates_path"],
|
|
424
486
|
},
|
|
425
487
|
},
|
|
488
|
+
{
|
|
489
|
+
name: "merge_and_ingest",
|
|
490
|
+
description: "Merge completed packet submissions into the artifact bundle after all dispatch subagents finish.",
|
|
491
|
+
inputSchema: {
|
|
492
|
+
type: "object",
|
|
493
|
+
properties: {
|
|
494
|
+
run_id: {
|
|
495
|
+
type: "string",
|
|
496
|
+
description: "Review run ID from the dispatch_review step response.",
|
|
497
|
+
},
|
|
498
|
+
root: { type: "string", description: "Repository root override." },
|
|
499
|
+
artifacts_dir: {
|
|
500
|
+
type: "string",
|
|
501
|
+
description: "Artifacts directory override.",
|
|
502
|
+
},
|
|
503
|
+
},
|
|
504
|
+
required: ["run_id"],
|
|
505
|
+
},
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
name: "report_capability",
|
|
509
|
+
description: "Report host subagent dispatch capability and advance to the next step. Call this instead of running audit-code next-step from the shell during a capability_check step.",
|
|
510
|
+
inputSchema: {
|
|
511
|
+
type: "object",
|
|
512
|
+
properties: {
|
|
513
|
+
can_dispatch_subagents: {
|
|
514
|
+
type: "boolean",
|
|
515
|
+
description: "Whether this host can dispatch subagents (e.g. via the task tool).",
|
|
516
|
+
},
|
|
517
|
+
can_restrict_subagent_tools: {
|
|
518
|
+
type: "boolean",
|
|
519
|
+
description: "Whether this host can restrict tools per subagent.",
|
|
520
|
+
},
|
|
521
|
+
can_select_subagent_model: {
|
|
522
|
+
type: "boolean",
|
|
523
|
+
description: "Whether this host can select a model per subagent.",
|
|
524
|
+
},
|
|
525
|
+
root: { type: "string", description: "Repository root override." },
|
|
526
|
+
artifacts_dir: {
|
|
527
|
+
type: "string",
|
|
528
|
+
description: "Artifacts directory override.",
|
|
529
|
+
},
|
|
530
|
+
},
|
|
531
|
+
required: ["can_dispatch_subagents"],
|
|
532
|
+
},
|
|
533
|
+
},
|
|
426
534
|
];
|
|
427
535
|
}
|
|
428
536
|
export async function runAuditCodeMcpServer(argv) {
|
|
@@ -469,9 +577,15 @@ export async function runAuditCodeMcpServer(argv) {
|
|
|
469
577
|
}
|
|
470
578
|
try {
|
|
471
579
|
switch (request.method) {
|
|
472
|
-
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;
|
|
473
587
|
writeMessage(success(request.id ?? null, {
|
|
474
|
-
protocolVersion:
|
|
588
|
+
protocolVersion: negotiatedVersion,
|
|
475
589
|
serverInfo: {
|
|
476
590
|
name: "audit-code",
|
|
477
591
|
version,
|
|
@@ -484,6 +598,7 @@ export async function runAuditCodeMcpServer(argv) {
|
|
|
484
598
|
},
|
|
485
599
|
}));
|
|
486
600
|
break;
|
|
601
|
+
}
|
|
487
602
|
case "notifications/initialized":
|
|
488
603
|
break;
|
|
489
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
|
@@ -35,13 +35,6 @@ function writeGeneratedFile(path, content) {
|
|
|
35
35
|
return action;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
function splitFrontmatter(text) {
|
|
39
|
-
const normalized = text.replace(/\r\n/g, '\n');
|
|
40
|
-
const match = normalized.match(/^---\n([\s\S]*?)\n---\n?/u);
|
|
41
|
-
if (!match) return { body: normalized };
|
|
42
|
-
return { body: normalized.slice(match[0].length) };
|
|
43
|
-
}
|
|
44
|
-
|
|
45
38
|
const OPENCODE_AUDIT_EDIT_PERMISSION = {
|
|
46
39
|
'*': 'ask',
|
|
47
40
|
'.audit-code/**': 'allow',
|
|
@@ -50,7 +43,7 @@ const OPENCODE_AUDIT_EDIT_PERMISSION = {
|
|
|
50
43
|
};
|
|
51
44
|
|
|
52
45
|
const OPENCODE_AUDIT_BASH_PERMISSION = {
|
|
53
|
-
'*': '
|
|
46
|
+
'*': 'allow',
|
|
54
47
|
'audit-code run-to-completion*': 'deny',
|
|
55
48
|
'audit-code synthesize*': 'deny',
|
|
56
49
|
'audit-code cleanup*': 'deny',
|
|
@@ -95,15 +88,94 @@ function replaceBackslashes(value) {
|
|
|
95
88
|
return value.replace(/\\/g, '/');
|
|
96
89
|
}
|
|
97
90
|
|
|
98
|
-
function
|
|
99
|
-
return
|
|
91
|
+
function renderOpenCodeExternalDirectoryPermission() {
|
|
92
|
+
return { '*': 'allow' };
|
|
100
93
|
}
|
|
101
94
|
|
|
102
|
-
function
|
|
103
|
-
return
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
95
|
+
function renderGlobalMcpLauncher(installedPkgRoot) {
|
|
96
|
+
return [
|
|
97
|
+
"import { access, readFile } from 'node:fs/promises';",
|
|
98
|
+
"import { constants } from 'node:fs';",
|
|
99
|
+
"import { spawn } from 'node:child_process';",
|
|
100
|
+
"import { join } from 'node:path';",
|
|
101
|
+
'',
|
|
102
|
+
'const repoRoot = process.cwd();',
|
|
103
|
+
"const artifactsDir = join(repoRoot, '.audit-artifacts');",
|
|
104
|
+
`const globalPackageRoot = ${JSON.stringify(installedPkgRoot)};`,
|
|
105
|
+
'',
|
|
106
|
+
'async function exists(path) {',
|
|
107
|
+
' try {',
|
|
108
|
+
' await access(path, constants.F_OK);',
|
|
109
|
+
' return true;',
|
|
110
|
+
' } catch {',
|
|
111
|
+
' return false;',
|
|
112
|
+
' }',
|
|
113
|
+
'}',
|
|
114
|
+
'',
|
|
115
|
+
'function spawnForward(command, args) {',
|
|
116
|
+
' return new Promise((resolvePromise, rejectPromise) => {',
|
|
117
|
+
' const child = spawn(command, args, {',
|
|
118
|
+
' cwd: repoRoot,',
|
|
119
|
+
' env: process.env,',
|
|
120
|
+
" stdio: ['inherit', 'inherit', 'inherit'],",
|
|
121
|
+
' });',
|
|
122
|
+
" child.on('error', rejectPromise);",
|
|
123
|
+
" child.on('exit', (code) => resolvePromise(code ?? 1));",
|
|
124
|
+
' });',
|
|
125
|
+
'}',
|
|
126
|
+
'',
|
|
127
|
+
'async function tryCandidates() {',
|
|
128
|
+
" const localPackageEntrypoint = join(repoRoot, 'node_modules', 'auditor-lambda', 'audit-code.mjs');",
|
|
129
|
+
" const localBin = process.platform === 'win32'",
|
|
130
|
+
" ? join(repoRoot, 'node_modules', '.bin', 'audit-code.cmd')",
|
|
131
|
+
" : join(repoRoot, 'node_modules', '.bin', 'audit-code');",
|
|
132
|
+
" const repoPackageJsonPath = join(repoRoot, 'package.json');",
|
|
133
|
+
" const globalPackageEntrypoint = globalPackageRoot ? join(globalPackageRoot, 'audit-code.mjs') : null;",
|
|
134
|
+
" const sharedArgs = ['mcp', '--root', repoRoot, '--artifacts-dir', artifactsDir];",
|
|
135
|
+
'',
|
|
136
|
+
' if (await exists(localPackageEntrypoint)) {',
|
|
137
|
+
' return await spawnForward(process.execPath, [localPackageEntrypoint, ...sharedArgs]);',
|
|
138
|
+
' }',
|
|
139
|
+
'',
|
|
140
|
+
" if (await exists(repoPackageJsonPath) && await exists(join(repoRoot, 'audit-code.mjs'))) {",
|
|
141
|
+
' try {',
|
|
142
|
+
" const packageJson = JSON.parse(await readFile(repoPackageJsonPath, 'utf8'));",
|
|
143
|
+
" if (packageJson?.name === 'auditor-lambda') {",
|
|
144
|
+
" return await spawnForward(process.execPath, [join(repoRoot, 'audit-code.mjs'), ...sharedArgs]);",
|
|
145
|
+
' }',
|
|
146
|
+
' } catch {',
|
|
147
|
+
' // fall through to the next candidate',
|
|
148
|
+
' }',
|
|
149
|
+
' }',
|
|
150
|
+
'',
|
|
151
|
+
' if (globalPackageEntrypoint && await exists(globalPackageEntrypoint)) {',
|
|
152
|
+
' return await spawnForward(process.execPath, [globalPackageEntrypoint, ...sharedArgs]);',
|
|
153
|
+
' }',
|
|
154
|
+
'',
|
|
155
|
+
' if (await exists(localBin)) {',
|
|
156
|
+
' return await spawnForward(localBin, sharedArgs);',
|
|
157
|
+
' }',
|
|
158
|
+
'',
|
|
159
|
+
" const pathCandidate = process.platform === 'win32' ? 'audit-code.cmd' : 'audit-code';",
|
|
160
|
+
' let exitCode = await spawnForward(pathCandidate, sharedArgs).catch(() => null);',
|
|
161
|
+
" if (typeof exitCode === 'number') {",
|
|
162
|
+
' return exitCode;',
|
|
163
|
+
' }',
|
|
164
|
+
'',
|
|
165
|
+
" exitCode = await spawnForward('npx', ['--no-install', 'audit-code', ...sharedArgs]).catch(() => null);",
|
|
166
|
+
" if (typeof exitCode === 'number') {",
|
|
167
|
+
' return exitCode;',
|
|
168
|
+
' }',
|
|
169
|
+
'',
|
|
170
|
+
' throw new Error(',
|
|
171
|
+
" 'Unable to locate an audit-code executable. Install auditor-lambda globally or as a local dependency.',",
|
|
172
|
+
' );',
|
|
173
|
+
'}',
|
|
174
|
+
'',
|
|
175
|
+
'const code = await tryCandidates();',
|
|
176
|
+
'process.exitCode = code;',
|
|
177
|
+
'',
|
|
178
|
+
].join('\n');
|
|
107
179
|
}
|
|
108
180
|
|
|
109
181
|
function objectValue(value) {
|
|
@@ -183,10 +255,29 @@ function renderOpenCodePermissionConfig() {
|
|
|
183
255
|
};
|
|
184
256
|
}
|
|
185
257
|
|
|
186
|
-
|
|
258
|
+
const OPENCODE_MCP_COMMAND_TEMPLATE = [
|
|
259
|
+
'# audit-code',
|
|
260
|
+
'',
|
|
261
|
+
'Use the auditor MCP tools as the primary interface to the audit workflow.',
|
|
262
|
+
'',
|
|
263
|
+
'1. Call `auditor_start_audit` to initialize and receive the first step.',
|
|
264
|
+
'2. Check `step_kind` in the response:',
|
|
265
|
+
' - 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.',
|
|
266
|
+
' - Otherwise: read `prompt_content` and follow it.',
|
|
267
|
+
'3. When a step completes (not blocked), call `auditor_continue_audit` to advance.',
|
|
268
|
+
'4. Stop when the step instructions say to stop.',
|
|
269
|
+
'',
|
|
270
|
+
'Do not run shell commands. Use only `auditor_*` MCP tools and the `task` tool for subagent dispatch.',
|
|
271
|
+
'',
|
|
272
|
+
'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.',
|
|
273
|
+
].join('\n');
|
|
274
|
+
|
|
275
|
+
function mergeOpenCodeGlobalConfig(existing) {
|
|
187
276
|
const parsed = existing ? JSON.parse(existing) : {};
|
|
188
277
|
const auditPermission = renderOpenCodePermissionConfig();
|
|
189
278
|
const existingAuditor = objectValue(objectValue(parsed.agent).auditor);
|
|
279
|
+
const globalLauncherPath = replaceBackslashes(join(homedir(), '.audit-code', 'run-mcp-server.mjs'));
|
|
280
|
+
const nodeExecPath = replaceBackslashes(process.execPath);
|
|
190
281
|
return {
|
|
191
282
|
...parsed,
|
|
192
283
|
command: {
|
|
@@ -194,13 +285,25 @@ function mergeOpenCodeGlobalConfig(existing, promptBody) {
|
|
|
194
285
|
? parsed.command
|
|
195
286
|
: {}),
|
|
196
287
|
'audit-code': {
|
|
197
|
-
template:
|
|
288
|
+
template: OPENCODE_MCP_COMMAND_TEMPLATE,
|
|
198
289
|
description: 'Autonomous local loop code auditing',
|
|
199
290
|
agent: 'auditor',
|
|
200
291
|
subtask: false,
|
|
201
292
|
},
|
|
202
293
|
},
|
|
203
|
-
|
|
294
|
+
mcp: {
|
|
295
|
+
...objectValue(parsed.mcp),
|
|
296
|
+
auditor: {
|
|
297
|
+
type: 'local',
|
|
298
|
+
command: [nodeExecPath, globalLauncherPath],
|
|
299
|
+
enabled: true,
|
|
300
|
+
timeout: 10000,
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
permission: {
|
|
304
|
+
...mergeOpenCodePermissionConfig(parsed.permission, auditPermission),
|
|
305
|
+
external_directory: { '*': 'allow' },
|
|
306
|
+
},
|
|
204
307
|
agent: {
|
|
205
308
|
...(parsed.agent && typeof parsed.agent === 'object' && !Array.isArray(parsed.agent)
|
|
206
309
|
? parsed.agent
|
|
@@ -208,10 +311,13 @@ function mergeOpenCodeGlobalConfig(existing, promptBody) {
|
|
|
208
311
|
auditor: {
|
|
209
312
|
...existingAuditor,
|
|
210
313
|
description: 'Read-heavy audit orchestration agent for the /audit-code workflow.',
|
|
211
|
-
permission:
|
|
212
|
-
existingAuditor.permission,
|
|
213
|
-
|
|
214
|
-
|
|
314
|
+
permission: {
|
|
315
|
+
...mergeOpenCodePermissionConfig(existingAuditor.permission, auditPermission),
|
|
316
|
+
external_directory: { '*': 'allow' },
|
|
317
|
+
'auditor_*': 'allow',
|
|
318
|
+
question: 'allow',
|
|
319
|
+
task: 'allow',
|
|
320
|
+
},
|
|
215
321
|
},
|
|
216
322
|
},
|
|
217
323
|
};
|
|
@@ -233,7 +339,6 @@ if (!promptSource || !skillSource) {
|
|
|
233
339
|
process.exit(0);
|
|
234
340
|
}
|
|
235
341
|
|
|
236
|
-
const promptBody = splitFrontmatter(promptSource.toString('utf8')).body;
|
|
237
342
|
const codexOpenAiAgentSource = readOptionalSource(codexOpenAiAgentSourceFile, 'Codex skill UI metadata');
|
|
238
343
|
|
|
239
344
|
const installs = [
|
|
@@ -280,15 +385,24 @@ for (const install of installs) {
|
|
|
280
385
|
}
|
|
281
386
|
}
|
|
282
387
|
|
|
283
|
-
// Install OpenCode
|
|
388
|
+
// Install global MCP launcher for OpenCode (and other hosts that support global config)
|
|
389
|
+
const globalMcpLauncherPath = join(homedir(), '.audit-code', 'run-mcp-server.mjs');
|
|
390
|
+
try {
|
|
391
|
+
const action = writeGeneratedFile(globalMcpLauncherPath, Buffer.from(renderGlobalMcpLauncher(pkgRoot)));
|
|
392
|
+
console.log(`audit-code: ${action} global MCP launcher at ${globalMcpLauncherPath}`);
|
|
393
|
+
} catch (err) {
|
|
394
|
+
console.warn(`audit-code: could not install global MCP launcher (${err.message})`);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Install OpenCode global command and MCP via merged config
|
|
284
398
|
const opencodeGlobalConfig = join(homedir(), '.config', 'opencode', 'opencode.json');
|
|
285
399
|
try {
|
|
286
400
|
const action = installMergedJson(opencodeGlobalConfig, (existing) =>
|
|
287
|
-
mergeOpenCodeGlobalConfig(existing
|
|
401
|
+
mergeOpenCodeGlobalConfig(existing),
|
|
288
402
|
);
|
|
289
|
-
console.log(`audit-code: ${action} global OpenCode
|
|
403
|
+
console.log(`audit-code: ${action} global OpenCode config in ${opencodeGlobalConfig}`);
|
|
290
404
|
} catch (err) {
|
|
291
|
-
console.warn(`audit-code: could not install global OpenCode
|
|
292
|
-
console.warn(` To install manually, add
|
|
405
|
+
console.warn(`audit-code: could not install global OpenCode config (${err.message})`);
|
|
406
|
+
console.warn(` To install manually, add the mcp.auditor and command["audit-code"] entries to:`);
|
|
293
407
|
console.warn(` ${opencodeGlobalConfig}`);
|
|
294
408
|
}
|