auditor-lambda 0.9.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -9
- package/audit-code-wrapper-lib.mjs +19 -915
- package/dispatch/merge-results.mjs +1 -1
- package/dist/cli/auditStep.d.ts +1 -33
- package/dist/cli/dispatch.d.ts +47 -0
- package/dist/cli/dispatch.js +116 -1
- package/dist/cli/mergeAndIngestCommand.js +55 -8
- package/dist/cli/nextStepCommand.js +43 -15
- package/dist/cli/prompts.d.ts +2 -0
- package/dist/cli/prompts.js +9 -0
- package/dist/cli/reviewRun.js +1 -1
- package/dist/cli/runToCompletion.js +21 -8
- package/dist/cli/semanticReviewStep.js +12 -1
- package/dist/cli/steps.d.ts +15 -0
- package/dist/cli.js +1 -8
- package/dist/io/artifacts.d.ts +9 -1
- package/dist/io/artifacts.js +7 -0
- package/dist/io/runArtifacts.d.ts +14 -0
- package/dist/io/runArtifacts.js +23 -0
- package/dist/orchestrator/designReviewPrompt.d.ts +4 -1
- package/dist/orchestrator/designReviewPrompt.js +43 -2
- package/dist/orchestrator/executorResult.d.ts +25 -0
- package/dist/orchestrator/intakeExecutors.d.ts +19 -1
- package/dist/orchestrator/intakeExecutors.js +89 -3
- package/dist/orchestrator/nextStep.d.ts +1 -0
- package/dist/orchestrator/nextStep.js +1 -1
- package/dist/orchestrator/state.js +8 -1
- package/dist/providers/constants.d.ts +1 -1
- package/dist/providers/constants.js +1 -1
- package/dist/reporting/synthesis.d.ts +8 -0
- package/dist/reporting/synthesis.js +16 -1
- package/dist/supervisor/operatorHandoff.js +8 -1
- package/dist/types/auditScope.d.ts +16 -2
- package/dist/validation/sessionConfig.js +35 -0
- package/docs/contracts.md +0 -16
- package/docs/operator-guide.md +6 -8
- package/package.json +1 -1
- package/schemas/audit_findings.schema.json +1 -0
- package/scripts/postinstall.mjs +0 -174
- package/skills/audit-code/SKILL.md +17 -1
- package/skills/audit-code/audit-code.prompt.md +25 -0
- package/dist/mcp/server.d.ts +0 -72
- package/dist/mcp/server.js +0 -765
package/dist/mcp/server.js
DELETED
|
@@ -1,765 +0,0 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
import { spawn } from "node:child_process";
|
|
3
|
-
import { dirname, join, resolve } from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { loadArtifactBundle, AUDIT_REPORT_FILENAME } from "../io/artifacts.js";
|
|
6
|
-
import { readOptionalTextFile } from "@audit-tools/shared";
|
|
7
|
-
import { deriveAuditState } from "../orchestrator/state.js";
|
|
8
|
-
import { decideNextStep } from "../orchestrator/nextStep.js";
|
|
9
|
-
import { buildAuditCodeHandoff, } from "../supervisor/operatorHandoff.js";
|
|
10
|
-
import { readSessionConfigFile } from "../supervisor/sessionConfig.js";
|
|
11
|
-
import { resolveFreshSessionProviderName } from "../providers/index.js";
|
|
12
|
-
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
13
|
-
const packageRoot = resolve(moduleDir, "..", "..");
|
|
14
|
-
const wrapperPath = join(packageRoot, "audit-code.mjs");
|
|
15
|
-
const packageJsonPath = join(packageRoot, "package.json");
|
|
16
|
-
const PROTOCOL_VERSION = "2025-06-18";
|
|
17
|
-
const MAX_CONTENT_LENGTH_BYTES = 10 * 1024 * 1024;
|
|
18
|
-
function getFlag(argv, name) {
|
|
19
|
-
const index = argv.indexOf(name);
|
|
20
|
-
if (index < 0)
|
|
21
|
-
return undefined;
|
|
22
|
-
return argv[index + 1];
|
|
23
|
-
}
|
|
24
|
-
function hasValue(value) {
|
|
25
|
-
return typeof value === "string" && value.trim().length > 0;
|
|
26
|
-
}
|
|
27
|
-
function parseServerOptions(argv) {
|
|
28
|
-
const root = resolve(getFlag(argv, "--root") ?? ".");
|
|
29
|
-
const artifactsDir = resolve(getFlag(argv, "--artifacts-dir") ?? join(root, ".audit-artifacts"));
|
|
30
|
-
return { root, artifactsDir };
|
|
31
|
-
}
|
|
32
|
-
function parseObject(value) {
|
|
33
|
-
return value && typeof value === "object"
|
|
34
|
-
? value
|
|
35
|
-
: {};
|
|
36
|
-
}
|
|
37
|
-
function getToolContext(params, defaults) {
|
|
38
|
-
const root = hasValue(params?.root)
|
|
39
|
-
? resolve(params.root)
|
|
40
|
-
: defaults.root;
|
|
41
|
-
const artifactsDir = hasValue(params?.artifacts_dir)
|
|
42
|
-
? resolve(params.artifacts_dir)
|
|
43
|
-
: hasValue(params?.artifactsDir)
|
|
44
|
-
? resolve(params.artifactsDir)
|
|
45
|
-
: defaults.artifactsDir;
|
|
46
|
-
return { root, artifactsDir };
|
|
47
|
-
}
|
|
48
|
-
async function packageVersion() {
|
|
49
|
-
const parsed = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
50
|
-
return parsed.version ?? "0.0.0";
|
|
51
|
-
}
|
|
52
|
-
function writeMessage(payload) {
|
|
53
|
-
const body = Buffer.from(JSON.stringify(payload), "utf8");
|
|
54
|
-
const header = Buffer.from(`Content-Length: ${body.length}\r\n\r\n`, "utf8");
|
|
55
|
-
process.stdout.write(header);
|
|
56
|
-
process.stdout.write(body);
|
|
57
|
-
}
|
|
58
|
-
function success(id, result) {
|
|
59
|
-
return { jsonrpc: "2.0", id, result };
|
|
60
|
-
}
|
|
61
|
-
function failure(id, code, message, data) {
|
|
62
|
-
return {
|
|
63
|
-
jsonrpc: "2.0",
|
|
64
|
-
id,
|
|
65
|
-
error: {
|
|
66
|
-
code,
|
|
67
|
-
message,
|
|
68
|
-
data,
|
|
69
|
-
},
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
export function parseContentLength(headerBlock) {
|
|
73
|
-
const headers = headerBlock.split("\r\n");
|
|
74
|
-
const contentLengthHeader = headers.find((header) => header.toLowerCase().startsWith("content-length:"));
|
|
75
|
-
if (!contentLengthHeader) {
|
|
76
|
-
throw new Error("missing Content-Length");
|
|
77
|
-
}
|
|
78
|
-
const rawValue = contentLengthHeader.split(":")[1]?.trim();
|
|
79
|
-
const contentLength = Number(rawValue);
|
|
80
|
-
if (rawValue?.length === 0 ||
|
|
81
|
-
!Number.isInteger(contentLength) ||
|
|
82
|
-
contentLength < 0 ||
|
|
83
|
-
contentLength > MAX_CONTENT_LENGTH_BYTES) {
|
|
84
|
-
throw new Error("bad Content-Length");
|
|
85
|
-
}
|
|
86
|
-
return contentLength;
|
|
87
|
-
}
|
|
88
|
-
async function readOptionalJson(path) {
|
|
89
|
-
try {
|
|
90
|
-
return JSON.parse(await readFile(path, "utf8"));
|
|
91
|
-
}
|
|
92
|
-
catch {
|
|
93
|
-
return undefined;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
async function runWrapperCommand(args, options) {
|
|
97
|
-
return await new Promise((resolvePromise, rejectPromise) => {
|
|
98
|
-
const child = spawn(process.execPath, [
|
|
99
|
-
wrapperPath,
|
|
100
|
-
...args,
|
|
101
|
-
"--root",
|
|
102
|
-
options.root,
|
|
103
|
-
"--artifacts-dir",
|
|
104
|
-
options.artifactsDir,
|
|
105
|
-
], {
|
|
106
|
-
cwd: options.root,
|
|
107
|
-
env: process.env,
|
|
108
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
109
|
-
});
|
|
110
|
-
let stdout = "";
|
|
111
|
-
let stderr = "";
|
|
112
|
-
child.stdout.on("data", (chunk) => {
|
|
113
|
-
stdout += String(chunk);
|
|
114
|
-
});
|
|
115
|
-
child.stderr.on("data", (chunk) => {
|
|
116
|
-
stderr += String(chunk);
|
|
117
|
-
});
|
|
118
|
-
child.on("error", rejectPromise);
|
|
119
|
-
child.on("exit", (code) => {
|
|
120
|
-
resolvePromise({
|
|
121
|
-
code: code ?? 1,
|
|
122
|
-
stdout,
|
|
123
|
-
stderr,
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
const SUBPROCESS_STDERR_TAIL_CHARS = 2000;
|
|
129
|
-
async function parseCliJson(args, options, allowNonZero = false) {
|
|
130
|
-
const result = await runWrapperCommand(args, options);
|
|
131
|
-
const combined = result.stdout.trim() || result.stderr.trim();
|
|
132
|
-
if (!allowNonZero && result.code !== 0) {
|
|
133
|
-
throw new Error(combined || `Command failed with exit code ${result.code}.`);
|
|
134
|
-
}
|
|
135
|
-
if (combined.length === 0) {
|
|
136
|
-
throw new Error("Command completed without JSON output.");
|
|
137
|
-
}
|
|
138
|
-
// On a successful (or tolerated-nonzero) call we parse stdout for the JSON
|
|
139
|
-
// payload and otherwise discard stderr. Surface any captured stderr as a
|
|
140
|
-
// tail so subprocess diagnostics (warnings, structured stderr lines) are not
|
|
141
|
-
// lost when the command still succeeded.
|
|
142
|
-
const stderrTail = result.stderr.trim();
|
|
143
|
-
if (stderrTail.length > 0) {
|
|
144
|
-
process.stderr.write(`[audit-code] mcp: subprocess stderr: ${stderrTail.slice(-SUBPROCESS_STDERR_TAIL_CHARS)}\n`);
|
|
145
|
-
}
|
|
146
|
-
try {
|
|
147
|
-
return JSON.parse(result.stdout);
|
|
148
|
-
}
|
|
149
|
-
catch {
|
|
150
|
-
try {
|
|
151
|
-
return JSON.parse(result.stderr);
|
|
152
|
-
}
|
|
153
|
-
catch {
|
|
154
|
-
throw new Error(combined);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
async function getStatusPayload(context) {
|
|
159
|
-
const bundle = await loadArtifactBundle(context.artifactsDir);
|
|
160
|
-
const decision = decideNextStep(bundle);
|
|
161
|
-
const auditState = bundle.audit_state ?? deriveAuditState(bundle);
|
|
162
|
-
const rawSessionConfig = await readSessionConfigFile(context.artifactsDir);
|
|
163
|
-
const providerName = rawSessionConfig && typeof rawSessionConfig === "object"
|
|
164
|
-
? resolveFreshSessionProviderName(undefined, rawSessionConfig)
|
|
165
|
-
: null;
|
|
166
|
-
const handoff = buildAuditCodeHandoff({
|
|
167
|
-
root: context.root,
|
|
168
|
-
artifactsDir: context.artifactsDir,
|
|
169
|
-
state: auditState,
|
|
170
|
-
bundle,
|
|
171
|
-
providerName,
|
|
172
|
-
progressSummary: "Current artifact and handoff status.",
|
|
173
|
-
});
|
|
174
|
-
return {
|
|
175
|
-
repo_root: context.root,
|
|
176
|
-
artifacts_dir: context.artifactsDir,
|
|
177
|
-
audit_state: auditState,
|
|
178
|
-
selected_obligation: decision.selected_obligation,
|
|
179
|
-
selected_executor: decision.selected_executor,
|
|
180
|
-
next_likely_step: auditState.status === "complete" ? null : decision.selected_obligation,
|
|
181
|
-
handoff,
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
function asTextContent(value) {
|
|
185
|
-
return typeof value === "string" ? value : JSON.stringify(value);
|
|
186
|
-
}
|
|
187
|
-
function toolResult(value) {
|
|
188
|
-
return {
|
|
189
|
-
content: [
|
|
190
|
-
{
|
|
191
|
-
type: "text",
|
|
192
|
-
text: asTextContent(value),
|
|
193
|
-
},
|
|
194
|
-
],
|
|
195
|
-
structuredContent: value && typeof value === "object" ? value : { value },
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
export const resourceRegistry = [
|
|
199
|
-
{
|
|
200
|
-
uri: "audit-code://artifacts/current",
|
|
201
|
-
name: "current_artifacts",
|
|
202
|
-
description: "Current artifact bundle as JSON.",
|
|
203
|
-
mimeType: "application/json",
|
|
204
|
-
async read(context) {
|
|
205
|
-
const bundle = await loadArtifactBundle(context.artifactsDir);
|
|
206
|
-
return { mimeType: this.mimeType, text: JSON.stringify(bundle) };
|
|
207
|
-
},
|
|
208
|
-
},
|
|
209
|
-
{
|
|
210
|
-
uri: "audit-code://handoff/current",
|
|
211
|
-
name: "operator_handoff",
|
|
212
|
-
description: "Current operator handoff payload as JSON.",
|
|
213
|
-
mimeType: "application/json",
|
|
214
|
-
async read(context) {
|
|
215
|
-
const status = (await getStatusPayload(context)).handoff;
|
|
216
|
-
return { mimeType: this.mimeType, text: JSON.stringify(status) };
|
|
217
|
-
},
|
|
218
|
-
},
|
|
219
|
-
{
|
|
220
|
-
uri: "audit-code://install/guide",
|
|
221
|
-
name: "install_guide",
|
|
222
|
-
description: "Repo-local install guide for supported IDE hosts.",
|
|
223
|
-
mimeType: "text/markdown",
|
|
224
|
-
async read(context) {
|
|
225
|
-
const path = join(context.root, ".audit-code", "install", "GETTING-STARTED.md");
|
|
226
|
-
const guide = (await readOptionalTextFile(path)) ??
|
|
227
|
-
"Run `audit-code install` from the repository root to generate the repo-local setup guide.";
|
|
228
|
-
return { mimeType: this.mimeType, text: guide };
|
|
229
|
-
},
|
|
230
|
-
},
|
|
231
|
-
{
|
|
232
|
-
uri: "audit-code://report/current",
|
|
233
|
-
name: "audit_report",
|
|
234
|
-
description: "Current deterministic audit report if available.",
|
|
235
|
-
mimeType: "text/markdown",
|
|
236
|
-
async read(context) {
|
|
237
|
-
const report = (await readOptionalTextFile(join(context.artifactsDir, AUDIT_REPORT_FILENAME))) ??
|
|
238
|
-
(await readOptionalTextFile(join(context.root, AUDIT_REPORT_FILENAME))) ??
|
|
239
|
-
"The audit report has not been rendered yet.";
|
|
240
|
-
return { mimeType: this.mimeType, text: report };
|
|
241
|
-
},
|
|
242
|
-
},
|
|
243
|
-
];
|
|
244
|
-
async function readResource(uri, context) {
|
|
245
|
-
const entry = resourceRegistry.find((r) => r.uri === uri);
|
|
246
|
-
if (!entry) {
|
|
247
|
-
throw new Error(`Unknown resource URI: ${uri}`);
|
|
248
|
-
}
|
|
249
|
-
return entry.read(context);
|
|
250
|
-
}
|
|
251
|
-
function resourceListPayload() {
|
|
252
|
-
return resourceRegistry.map((entry) => ({
|
|
253
|
-
uri: entry.uri,
|
|
254
|
-
name: entry.name,
|
|
255
|
-
description: entry.description,
|
|
256
|
-
mimeType: entry.mimeType,
|
|
257
|
-
}));
|
|
258
|
-
}
|
|
259
|
-
export const promptRegistry = [
|
|
260
|
-
{
|
|
261
|
-
name: "audit-code",
|
|
262
|
-
description: "Start or continue the autonomous audit loop through the next-step machine.",
|
|
263
|
-
arguments: [],
|
|
264
|
-
render() {
|
|
265
|
-
return [
|
|
266
|
-
"Use `audit-code next-step` as the canonical interface to the backend wrapper.",
|
|
267
|
-
"1. Prefer running `audit-code next-step` directly from the repository root.",
|
|
268
|
-
"2. If this MCP adapter is your only available integration, call `start_audit` or `continue_audit`; both return the same one-step contract.",
|
|
269
|
-
"3. If the audit is blocked, inspect `audit-code://handoff/current`.",
|
|
270
|
-
" Do not read `audit-code://artifacts/current` unless explicitly needed for a specific task; it is massive and consumes your context window.",
|
|
271
|
-
"4. When the user provides additional evidence, call `import_results` or `import_runtime_updates`.",
|
|
272
|
-
].join("\n");
|
|
273
|
-
},
|
|
274
|
-
},
|
|
275
|
-
{
|
|
276
|
-
name: "review-task",
|
|
277
|
-
description: "Inspect one audit task with explain_task and the current artifacts before reviewing code.",
|
|
278
|
-
arguments: [
|
|
279
|
-
{
|
|
280
|
-
name: "task_id",
|
|
281
|
-
required: true,
|
|
282
|
-
description: "Audit task id to inspect.",
|
|
283
|
-
},
|
|
284
|
-
],
|
|
285
|
-
render(args) {
|
|
286
|
-
return [
|
|
287
|
-
`Use \`explain_task\` for task \`${String(args?.task_id ?? "")}\` before you inspect code manually.`,
|
|
288
|
-
"Do not read the full `audit-code://artifacts/current` bundle unless specifically needed, as it is massive.",
|
|
289
|
-
].join("\n");
|
|
290
|
-
},
|
|
291
|
-
},
|
|
292
|
-
{
|
|
293
|
-
name: "synthesize-report",
|
|
294
|
-
description: "Read the current audit report resource and summarize the highest-signal findings.",
|
|
295
|
-
arguments: [],
|
|
296
|
-
render() {
|
|
297
|
-
return [
|
|
298
|
-
"Read `audit-code://report/current`.",
|
|
299
|
-
"Summarize the final audit report as work blocks first, then highlight the most important risks and remediation priorities.",
|
|
300
|
-
].join("\n");
|
|
301
|
-
},
|
|
302
|
-
},
|
|
303
|
-
];
|
|
304
|
-
function promptDefinitions() {
|
|
305
|
-
return promptRegistry.map((entry) => ({
|
|
306
|
-
name: entry.name,
|
|
307
|
-
description: entry.description,
|
|
308
|
-
arguments: entry.arguments,
|
|
309
|
-
}));
|
|
310
|
-
}
|
|
311
|
-
function renderPrompt(name, args) {
|
|
312
|
-
const entry = promptRegistry.find((p) => p.name === name);
|
|
313
|
-
if (!entry) {
|
|
314
|
-
throw new Error(`Unknown prompt: ${name}`);
|
|
315
|
-
}
|
|
316
|
-
return entry.render(args);
|
|
317
|
-
}
|
|
318
|
-
async function runContinueAudit(context, extraArgs = ["next-step"]) {
|
|
319
|
-
const step = await parseCliJson(extraArgs, context);
|
|
320
|
-
if (!step || typeof step !== "object" || Array.isArray(step))
|
|
321
|
-
return step;
|
|
322
|
-
const s = step;
|
|
323
|
-
if (hasValue(s.prompt_path)) {
|
|
324
|
-
try {
|
|
325
|
-
s.prompt_content = await readFile(s.prompt_path, "utf8");
|
|
326
|
-
}
|
|
327
|
-
catch {
|
|
328
|
-
// ignore — prompt_path is a fallback for hosts that can read files
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
if (s.step_kind === "dispatch_review") {
|
|
332
|
-
const paths = s.artifact_paths;
|
|
333
|
-
if (hasValue(paths?.dispatch_plan)) {
|
|
334
|
-
const plan = await readOptionalJson(paths.dispatch_plan);
|
|
335
|
-
if (plan !== undefined)
|
|
336
|
-
s.dispatch_plan_entries = plan;
|
|
337
|
-
}
|
|
338
|
-
if (hasValue(paths?.dispatch_quota)) {
|
|
339
|
-
const quota = await readOptionalJson(paths.dispatch_quota);
|
|
340
|
-
if (quota !== undefined)
|
|
341
|
-
s.dispatch_quota = quota;
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
return s;
|
|
345
|
-
}
|
|
346
|
-
async function handleToolCall(name, params, defaults) {
|
|
347
|
-
const context = getToolContext(params, defaults);
|
|
348
|
-
switch (name) {
|
|
349
|
-
case "start_audit":
|
|
350
|
-
return toolResult(await runContinueAudit(context));
|
|
351
|
-
case "continue_audit":
|
|
352
|
-
return toolResult(await runContinueAudit(context));
|
|
353
|
-
case "get_status":
|
|
354
|
-
return toolResult(await getStatusPayload(context));
|
|
355
|
-
case "explain_task": {
|
|
356
|
-
if (!hasValue(params?.task_id) && !hasValue(params?.taskId)) {
|
|
357
|
-
throw new Error("explain_task requires task_id.");
|
|
358
|
-
}
|
|
359
|
-
const taskId = hasValue(params?.task_id)
|
|
360
|
-
? params.task_id
|
|
361
|
-
: params?.taskId;
|
|
362
|
-
return toolResult(await parseCliJson(["explain-task", taskId], context));
|
|
363
|
-
}
|
|
364
|
-
case "validate_artifacts":
|
|
365
|
-
return toolResult(await parseCliJson(["validate"], context, true));
|
|
366
|
-
case "import_results": {
|
|
367
|
-
if (!hasValue(params?.results_path) && !hasValue(params?.resultsPath)) {
|
|
368
|
-
throw new Error("import_results requires results_path.");
|
|
369
|
-
}
|
|
370
|
-
const resultsPath = hasValue(params?.results_path)
|
|
371
|
-
? params.results_path
|
|
372
|
-
: params?.resultsPath;
|
|
373
|
-
return toolResult(await parseCliJson(["--results", resolve(resultsPath)], context));
|
|
374
|
-
}
|
|
375
|
-
case "import_runtime_updates": {
|
|
376
|
-
if (!hasValue(params?.updates_path) && !hasValue(params?.updatesPath)) {
|
|
377
|
-
throw new Error("import_runtime_updates requires updates_path.");
|
|
378
|
-
}
|
|
379
|
-
const updatesPath = hasValue(params?.updates_path)
|
|
380
|
-
? params.updates_path
|
|
381
|
-
: params?.updatesPath;
|
|
382
|
-
return toolResult(await parseCliJson(["--updates", resolve(updatesPath)], context));
|
|
383
|
-
}
|
|
384
|
-
case "merge_and_ingest": {
|
|
385
|
-
const runId = hasValue(params?.run_id)
|
|
386
|
-
? params.run_id
|
|
387
|
-
: hasValue(params?.runId)
|
|
388
|
-
? params.runId
|
|
389
|
-
: undefined;
|
|
390
|
-
if (!runId)
|
|
391
|
-
throw new Error("merge_and_ingest requires run_id.");
|
|
392
|
-
return toolResult(await parseCliJson(["merge-and-ingest", "--run-id", runId], context, true));
|
|
393
|
-
}
|
|
394
|
-
case "report_capability": {
|
|
395
|
-
const extraArgs = [];
|
|
396
|
-
const canDispatch = params?.can_dispatch_subagents ?? params?.canDispatchSubagents;
|
|
397
|
-
if (canDispatch !== undefined) {
|
|
398
|
-
extraArgs.push("--host-can-dispatch-subagents", String(Boolean(canDispatch)));
|
|
399
|
-
}
|
|
400
|
-
const canRestrict = params?.can_restrict_subagent_tools ?? params?.canRestrictSubagentTools;
|
|
401
|
-
if (canRestrict !== undefined) {
|
|
402
|
-
extraArgs.push("--host-can-restrict-subagent-tools", String(Boolean(canRestrict)));
|
|
403
|
-
}
|
|
404
|
-
const canSelect = params?.can_select_subagent_model ?? params?.canSelectSubagentModel;
|
|
405
|
-
if (canSelect !== undefined) {
|
|
406
|
-
extraArgs.push("--host-can-select-subagent-model", String(Boolean(canSelect)));
|
|
407
|
-
}
|
|
408
|
-
const maxActiveSubagents = params?.max_active_subagents ?? params?.maxActiveSubagents;
|
|
409
|
-
if (maxActiveSubagents !== undefined) {
|
|
410
|
-
extraArgs.push("--host-max-active-subagents", String(maxActiveSubagents));
|
|
411
|
-
}
|
|
412
|
-
return toolResult(await runContinueAudit(context, ["next-step", ...extraArgs]));
|
|
413
|
-
}
|
|
414
|
-
default:
|
|
415
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
function toolDefinitions() {
|
|
419
|
-
return [
|
|
420
|
-
{
|
|
421
|
-
name: "start_audit",
|
|
422
|
-
description: "Compatibility adapter over audit-code next-step; returns one step contract.",
|
|
423
|
-
inputSchema: {
|
|
424
|
-
type: "object",
|
|
425
|
-
properties: {
|
|
426
|
-
root: { type: "string", description: "Repository root override." },
|
|
427
|
-
artifacts_dir: {
|
|
428
|
-
type: "string",
|
|
429
|
-
description: "Artifacts directory override.",
|
|
430
|
-
},
|
|
431
|
-
},
|
|
432
|
-
},
|
|
433
|
-
},
|
|
434
|
-
{
|
|
435
|
-
name: "get_status",
|
|
436
|
-
description: "Inspect the current artifact bundle and operator handoff without mutating state.",
|
|
437
|
-
inputSchema: {
|
|
438
|
-
type: "object",
|
|
439
|
-
properties: {
|
|
440
|
-
root: { type: "string", description: "Repository root override." },
|
|
441
|
-
artifacts_dir: {
|
|
442
|
-
type: "string",
|
|
443
|
-
description: "Artifacts directory override.",
|
|
444
|
-
},
|
|
445
|
-
},
|
|
446
|
-
},
|
|
447
|
-
},
|
|
448
|
-
{
|
|
449
|
-
name: "continue_audit",
|
|
450
|
-
description: "Compatibility adapter over audit-code next-step from the current artifacts directory.",
|
|
451
|
-
inputSchema: {
|
|
452
|
-
type: "object",
|
|
453
|
-
properties: {
|
|
454
|
-
root: { type: "string", description: "Repository root override." },
|
|
455
|
-
artifacts_dir: {
|
|
456
|
-
type: "string",
|
|
457
|
-
description: "Artifacts directory override.",
|
|
458
|
-
},
|
|
459
|
-
},
|
|
460
|
-
},
|
|
461
|
-
},
|
|
462
|
-
{
|
|
463
|
-
name: "explain_task",
|
|
464
|
-
description: "Resolve a task id into coverage scope, matching findings, and pending work.",
|
|
465
|
-
inputSchema: {
|
|
466
|
-
type: "object",
|
|
467
|
-
properties: {
|
|
468
|
-
task_id: {
|
|
469
|
-
type: "string",
|
|
470
|
-
description: "Task id to explain.",
|
|
471
|
-
},
|
|
472
|
-
root: { type: "string", description: "Repository root override." },
|
|
473
|
-
artifacts_dir: {
|
|
474
|
-
type: "string",
|
|
475
|
-
description: "Artifacts directory override.",
|
|
476
|
-
},
|
|
477
|
-
},
|
|
478
|
-
required: ["task_id"],
|
|
479
|
-
},
|
|
480
|
-
},
|
|
481
|
-
{
|
|
482
|
-
name: "validate_artifacts",
|
|
483
|
-
description: "Validate the current artifact bundle plus provider/session-config readiness.",
|
|
484
|
-
inputSchema: {
|
|
485
|
-
type: "object",
|
|
486
|
-
properties: {
|
|
487
|
-
root: { type: "string", description: "Repository root override." },
|
|
488
|
-
artifacts_dir: {
|
|
489
|
-
type: "string",
|
|
490
|
-
description: "Artifacts directory override.",
|
|
491
|
-
},
|
|
492
|
-
},
|
|
493
|
-
},
|
|
494
|
-
},
|
|
495
|
-
{
|
|
496
|
-
name: "import_results",
|
|
497
|
-
description: "Import structured audit results into the same backend wrapper flow.",
|
|
498
|
-
inputSchema: {
|
|
499
|
-
type: "object",
|
|
500
|
-
properties: {
|
|
501
|
-
results_path: {
|
|
502
|
-
type: "string",
|
|
503
|
-
description: "Path to an AuditResult JSON payload.",
|
|
504
|
-
},
|
|
505
|
-
root: { type: "string", description: "Repository root override." },
|
|
506
|
-
artifacts_dir: {
|
|
507
|
-
type: "string",
|
|
508
|
-
description: "Artifacts directory override.",
|
|
509
|
-
},
|
|
510
|
-
},
|
|
511
|
-
required: ["results_path"],
|
|
512
|
-
},
|
|
513
|
-
},
|
|
514
|
-
{
|
|
515
|
-
name: "import_runtime_updates",
|
|
516
|
-
description: "Import runtime validation evidence updates into the backend wrapper flow.",
|
|
517
|
-
inputSchema: {
|
|
518
|
-
type: "object",
|
|
519
|
-
properties: {
|
|
520
|
-
updates_path: {
|
|
521
|
-
type: "string",
|
|
522
|
-
description: "Path to a runtime validation update JSON payload.",
|
|
523
|
-
},
|
|
524
|
-
root: { type: "string", description: "Repository root override." },
|
|
525
|
-
artifacts_dir: {
|
|
526
|
-
type: "string",
|
|
527
|
-
description: "Artifacts directory override.",
|
|
528
|
-
},
|
|
529
|
-
},
|
|
530
|
-
required: ["updates_path"],
|
|
531
|
-
},
|
|
532
|
-
},
|
|
533
|
-
{
|
|
534
|
-
name: "merge_and_ingest",
|
|
535
|
-
description: "Merge completed packet submissions into the artifact bundle after all dispatch subagents finish.",
|
|
536
|
-
inputSchema: {
|
|
537
|
-
type: "object",
|
|
538
|
-
properties: {
|
|
539
|
-
run_id: {
|
|
540
|
-
type: "string",
|
|
541
|
-
description: "Review run ID from the dispatch_review step response.",
|
|
542
|
-
},
|
|
543
|
-
root: { type: "string", description: "Repository root override." },
|
|
544
|
-
artifacts_dir: {
|
|
545
|
-
type: "string",
|
|
546
|
-
description: "Artifacts directory override.",
|
|
547
|
-
},
|
|
548
|
-
},
|
|
549
|
-
required: ["run_id"],
|
|
550
|
-
},
|
|
551
|
-
},
|
|
552
|
-
{
|
|
553
|
-
name: "report_capability",
|
|
554
|
-
description: "Compatibility adapter that calls audit-code next-step with host subagent capability flags.",
|
|
555
|
-
inputSchema: {
|
|
556
|
-
type: "object",
|
|
557
|
-
properties: {
|
|
558
|
-
can_dispatch_subagents: {
|
|
559
|
-
type: "boolean",
|
|
560
|
-
description: "Whether this host can dispatch subagents (e.g. via the task tool).",
|
|
561
|
-
},
|
|
562
|
-
can_restrict_subagent_tools: {
|
|
563
|
-
type: "boolean",
|
|
564
|
-
description: "Whether this host can restrict tools per subagent.",
|
|
565
|
-
},
|
|
566
|
-
can_select_subagent_model: {
|
|
567
|
-
type: "boolean",
|
|
568
|
-
description: "Whether this host can select a model per subagent.",
|
|
569
|
-
},
|
|
570
|
-
max_active_subagents: {
|
|
571
|
-
type: "integer",
|
|
572
|
-
minimum: 1,
|
|
573
|
-
description: "Known hard cap on simultaneously active subagents for this host, if available.",
|
|
574
|
-
},
|
|
575
|
-
root: { type: "string", description: "Repository root override." },
|
|
576
|
-
artifacts_dir: {
|
|
577
|
-
type: "string",
|
|
578
|
-
description: "Artifacts directory override.",
|
|
579
|
-
},
|
|
580
|
-
},
|
|
581
|
-
required: ["can_dispatch_subagents"],
|
|
582
|
-
},
|
|
583
|
-
},
|
|
584
|
-
];
|
|
585
|
-
}
|
|
586
|
-
/**
|
|
587
|
-
* Extract zero or more complete Content-Length framed messages from a buffer.
|
|
588
|
-
* Returns an array of parsed body strings and the remaining unconsumed buffer.
|
|
589
|
-
* On framing errors, emits a framing error response via `emit` and resets the buffer.
|
|
590
|
-
*/
|
|
591
|
-
export function extractFrames(buffer, emit) {
|
|
592
|
-
const bodies = [];
|
|
593
|
-
let current = buffer;
|
|
594
|
-
while (true) {
|
|
595
|
-
const separator = current.indexOf("\r\n\r\n");
|
|
596
|
-
if (separator < 0) {
|
|
597
|
-
break;
|
|
598
|
-
}
|
|
599
|
-
let contentLength;
|
|
600
|
-
try {
|
|
601
|
-
const headerBlock = current.slice(0, separator).toString("utf8");
|
|
602
|
-
contentLength = parseContentLength(headerBlock);
|
|
603
|
-
}
|
|
604
|
-
catch (error) {
|
|
605
|
-
current = Buffer.alloc(0);
|
|
606
|
-
emit(failure(null, -32700, `Invalid MCP framing: ${error instanceof Error ? error.message : String(error)}.`));
|
|
607
|
-
break;
|
|
608
|
-
}
|
|
609
|
-
const frameLength = separator + 4 + contentLength;
|
|
610
|
-
if (current.length < frameLength) {
|
|
611
|
-
break;
|
|
612
|
-
}
|
|
613
|
-
bodies.push(current.slice(separator + 4, frameLength).toString("utf8"));
|
|
614
|
-
current = current.slice(frameLength);
|
|
615
|
-
}
|
|
616
|
-
return { bodies, remaining: current };
|
|
617
|
-
}
|
|
618
|
-
/**
|
|
619
|
-
* Dispatch a single JSON-RPC request and return the response(s) to send,
|
|
620
|
-
* plus updated shutdown state.
|
|
621
|
-
*/
|
|
622
|
-
export async function dispatchRequest(request, ctx) {
|
|
623
|
-
const responses = [];
|
|
624
|
-
let { shutdownRequested } = ctx;
|
|
625
|
-
if (!request.method) {
|
|
626
|
-
responses.push(failure(request.id ?? null, -32600, "Missing method."));
|
|
627
|
-
return { responses, shutdownRequested };
|
|
628
|
-
}
|
|
629
|
-
try {
|
|
630
|
-
switch (request.method) {
|
|
631
|
-
case "initialize": {
|
|
632
|
-
const requestedVersion = typeof request.params?.protocolVersion === "string"
|
|
633
|
-
? request.params.protocolVersion
|
|
634
|
-
: PROTOCOL_VERSION;
|
|
635
|
-
const negotiatedVersion = requestedVersion <= PROTOCOL_VERSION
|
|
636
|
-
? requestedVersion
|
|
637
|
-
: PROTOCOL_VERSION;
|
|
638
|
-
responses.push(success(request.id ?? null, {
|
|
639
|
-
protocolVersion: negotiatedVersion,
|
|
640
|
-
serverInfo: {
|
|
641
|
-
name: "audit-code",
|
|
642
|
-
version: ctx.version,
|
|
643
|
-
},
|
|
644
|
-
instructions: "Use audit-code next-step as the primary backend loop. These MCP tools are compatibility adapters that return the same one-step contract.",
|
|
645
|
-
capabilities: {
|
|
646
|
-
tools: { listChanged: false },
|
|
647
|
-
resources: { subscribe: false, listChanged: false },
|
|
648
|
-
prompts: { listChanged: false },
|
|
649
|
-
},
|
|
650
|
-
}));
|
|
651
|
-
break;
|
|
652
|
-
}
|
|
653
|
-
case "notifications/initialized":
|
|
654
|
-
break;
|
|
655
|
-
case "ping":
|
|
656
|
-
if (request.id !== undefined) {
|
|
657
|
-
responses.push(success(request.id, {}));
|
|
658
|
-
}
|
|
659
|
-
break;
|
|
660
|
-
case "tools/list":
|
|
661
|
-
responses.push(success(request.id ?? null, { tools: toolDefinitions() }));
|
|
662
|
-
break;
|
|
663
|
-
case "tools/call": {
|
|
664
|
-
const params = parseObject(request.params);
|
|
665
|
-
const toolName = params.name;
|
|
666
|
-
if (!hasValue(toolName)) {
|
|
667
|
-
throw new Error("tools/call requires a tool name.");
|
|
668
|
-
}
|
|
669
|
-
responses.push(success(request.id ?? null, await handleToolCall(toolName, parseObject(params.arguments), ctx.defaults)));
|
|
670
|
-
break;
|
|
671
|
-
}
|
|
672
|
-
case "resources/list":
|
|
673
|
-
responses.push(success(request.id ?? null, {
|
|
674
|
-
resources: resourceListPayload(),
|
|
675
|
-
}));
|
|
676
|
-
break;
|
|
677
|
-
case "resources/read": {
|
|
678
|
-
const params = parseObject(request.params);
|
|
679
|
-
if (!hasValue(params.uri)) {
|
|
680
|
-
throw new Error("resources/read requires uri.");
|
|
681
|
-
}
|
|
682
|
-
const resource = await readResource(params.uri, ctx.defaults);
|
|
683
|
-
responses.push(success(request.id ?? null, {
|
|
684
|
-
contents: [
|
|
685
|
-
{
|
|
686
|
-
uri: params.uri,
|
|
687
|
-
mimeType: resource.mimeType,
|
|
688
|
-
text: resource.text,
|
|
689
|
-
},
|
|
690
|
-
],
|
|
691
|
-
}));
|
|
692
|
-
break;
|
|
693
|
-
}
|
|
694
|
-
case "prompts/list":
|
|
695
|
-
responses.push(success(request.id ?? null, {
|
|
696
|
-
prompts: promptDefinitions(),
|
|
697
|
-
}));
|
|
698
|
-
break;
|
|
699
|
-
case "prompts/get": {
|
|
700
|
-
const params = parseObject(request.params);
|
|
701
|
-
if (!hasValue(params.name)) {
|
|
702
|
-
throw new Error("prompts/get requires name.");
|
|
703
|
-
}
|
|
704
|
-
responses.push(success(request.id ?? null, {
|
|
705
|
-
description: promptDefinitions().find((prompt) => prompt.name === params.name)?.description,
|
|
706
|
-
messages: [
|
|
707
|
-
{
|
|
708
|
-
role: "user",
|
|
709
|
-
content: {
|
|
710
|
-
type: "text",
|
|
711
|
-
text: renderPrompt(params.name, parseObject(params.arguments)),
|
|
712
|
-
},
|
|
713
|
-
},
|
|
714
|
-
],
|
|
715
|
-
}));
|
|
716
|
-
break;
|
|
717
|
-
}
|
|
718
|
-
case "shutdown":
|
|
719
|
-
shutdownRequested = true;
|
|
720
|
-
responses.push(success(request.id ?? null, {}));
|
|
721
|
-
break;
|
|
722
|
-
case "exit":
|
|
723
|
-
return { responses, shutdownRequested, exit: shutdownRequested ? 0 : 1 };
|
|
724
|
-
default:
|
|
725
|
-
responses.push(failure(request.id ?? null, -32601, `Unknown method: ${request.method}`));
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
catch (error) {
|
|
729
|
-
responses.push(failure(request.id ?? null, -32000, error instanceof Error ? error.message : String(error)));
|
|
730
|
-
}
|
|
731
|
-
return { responses, shutdownRequested };
|
|
732
|
-
}
|
|
733
|
-
export async function runAuditCodeMcpServer(argv) {
|
|
734
|
-
const defaults = parseServerOptions(argv);
|
|
735
|
-
const version = await packageVersion();
|
|
736
|
-
let shutdownRequested = false;
|
|
737
|
-
let buffer = Buffer.alloc(0);
|
|
738
|
-
process.stdin.on("data", async (chunk) => {
|
|
739
|
-
buffer = Buffer.concat([buffer, chunk]);
|
|
740
|
-
const { bodies, remaining } = extractFrames(buffer, writeMessage);
|
|
741
|
-
buffer = remaining;
|
|
742
|
-
for (const body of bodies) {
|
|
743
|
-
let request;
|
|
744
|
-
try {
|
|
745
|
-
request = JSON.parse(body);
|
|
746
|
-
}
|
|
747
|
-
catch (error) {
|
|
748
|
-
writeMessage(failure(null, -32700, "Invalid JSON-RPC payload.", error instanceof Error ? error.message : String(error)));
|
|
749
|
-
continue;
|
|
750
|
-
}
|
|
751
|
-
const result = await dispatchRequest(request, {
|
|
752
|
-
version,
|
|
753
|
-
defaults,
|
|
754
|
-
shutdownRequested,
|
|
755
|
-
});
|
|
756
|
-
shutdownRequested = result.shutdownRequested;
|
|
757
|
-
for (const response of result.responses) {
|
|
758
|
-
writeMessage(response);
|
|
759
|
-
}
|
|
760
|
-
if (result.exit !== undefined) {
|
|
761
|
-
process.exit(result.exit);
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
});
|
|
765
|
-
}
|