auditor-lambda 0.3.30 → 0.3.33
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 -1
- package/audit-code-wrapper-lib.mjs +208 -198
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +65 -101
- package/dist/extractors/risk.js +6 -4
- package/dist/io/artifacts.d.ts +2 -0
- package/dist/io/artifacts.js +1 -0
- package/dist/io/toolingManifest.d.ts +1 -0
- package/dist/io/toolingManifest.js +1 -1
- package/dist/mcp/server.d.ts +71 -0
- package/dist/mcp/server.js +261 -222
- package/dist/orchestrator/artifactFreshness.d.ts +4 -0
- package/dist/orchestrator/artifactFreshness.js +45 -0
- package/dist/orchestrator/artifactMetadata.js +2 -51
- package/dist/orchestrator/dependencyMap.js +14 -0
- package/dist/orchestrator/internalExecutors.js +8 -0
- package/dist/orchestrator/staleness.js +2 -46
- package/dist/orchestrator/state.js +1 -1
- package/dist/orchestrator/syntaxResolutionExecutor.js +121 -13
- package/dist/orchestrator/unitBuilder.js +2 -1
- package/dist/providers/spawnLoggedCommand.js +71 -18
- package/dist/providers/types.d.ts +5 -0
- package/dist/quota/scheduler.js +10 -2
- package/dist/quota/state.js +6 -2
- package/dist/supervisor/operatorHandoff.js +1 -1
- package/dist/types/externalAnalyzer.d.ts +10 -0
- package/dist/types/sessionConfig.d.ts +1 -0
- package/dist/types/workerSession.js +1 -2
- package/dist/validation/artifacts.js +36 -0
- package/dist/validation/sessionConfig.js +4 -0
- package/package.json +1 -1
- package/schemas/audit_task.schema.json +2 -2
- package/schemas/risk_register.schema.json +1 -1
- package/schemas/unit_manifest.schema.json +2 -1
- package/scripts/postinstall.mjs +10 -41
- package/skills/audit-code/audit-code.prompt.md +5 -0
package/dist/mcp/server.js
CHANGED
|
@@ -69,7 +69,7 @@ function failure(id, code, message, data) {
|
|
|
69
69
|
},
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
|
-
function parseContentLength(headerBlock) {
|
|
72
|
+
export function parseContentLength(headerBlock) {
|
|
73
73
|
const headers = headerBlock.split("\r\n");
|
|
74
74
|
const contentLengthHeader = headers.find((header) => header.toLowerCase().startsWith("content-length:"));
|
|
75
75
|
if (!contentLengthHeader) {
|
|
@@ -186,95 +186,127 @@ function toolResult(value) {
|
|
|
186
186
|
structuredContent: value && typeof value === "object" ? value : { value },
|
|
187
187
|
};
|
|
188
188
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
189
|
+
export const resourceRegistry = [
|
|
190
|
+
{
|
|
191
|
+
uri: "audit-code://artifacts/current",
|
|
192
|
+
name: "current_artifacts",
|
|
193
|
+
description: "Current artifact bundle as JSON.",
|
|
194
|
+
mimeType: "application/json",
|
|
195
|
+
async read(context) {
|
|
192
196
|
const bundle = await loadArtifactBundle(context.artifactsDir);
|
|
193
|
-
return {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
197
|
+
return { mimeType: this.mimeType, text: JSON.stringify(bundle) };
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
uri: "audit-code://handoff/current",
|
|
202
|
+
name: "operator_handoff",
|
|
203
|
+
description: "Current operator handoff payload as JSON.",
|
|
204
|
+
mimeType: "application/json",
|
|
205
|
+
async read(context) {
|
|
199
206
|
const status = (await getStatusPayload(context)).handoff;
|
|
200
|
-
return {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
207
|
+
return { mimeType: this.mimeType, text: JSON.stringify(status) };
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
uri: "audit-code://install/guide",
|
|
212
|
+
name: "install_guide",
|
|
213
|
+
description: "Repo-local install guide for supported IDE hosts.",
|
|
214
|
+
mimeType: "text/markdown",
|
|
215
|
+
async read(context) {
|
|
206
216
|
const path = join(context.root, ".audit-code", "install", "GETTING-STARTED.md");
|
|
207
217
|
const guide = (await readOptionalTextFile(path)) ??
|
|
208
218
|
"Run `audit-code install` from the repository root to generate the repo-local setup guide.";
|
|
209
|
-
return {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
219
|
+
return { mimeType: this.mimeType, text: guide };
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
uri: "audit-code://report/current",
|
|
224
|
+
name: "audit_report",
|
|
225
|
+
description: "Current deterministic audit report if available.",
|
|
226
|
+
mimeType: "text/markdown",
|
|
227
|
+
async read(context) {
|
|
215
228
|
const report = (await readOptionalTextFile(join(context.artifactsDir, "audit-report.md"))) ??
|
|
216
229
|
(await readOptionalTextFile(join(context.root, "audit-report.md"))) ??
|
|
217
230
|
"The audit report has not been rendered yet.";
|
|
218
|
-
return {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
231
|
+
return { mimeType: this.mimeType, text: report };
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
];
|
|
235
|
+
async function readResource(uri, context) {
|
|
236
|
+
const entry = resourceRegistry.find((r) => r.uri === uri);
|
|
237
|
+
if (!entry) {
|
|
238
|
+
throw new Error(`Unknown resource URI: ${uri}`);
|
|
225
239
|
}
|
|
240
|
+
return entry.read(context);
|
|
226
241
|
}
|
|
227
|
-
function
|
|
228
|
-
return
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
{
|
|
235
|
-
name: "review-task",
|
|
236
|
-
description: "Inspect one audit task with explain_task and the current artifacts before reviewing code.",
|
|
237
|
-
arguments: [
|
|
238
|
-
{
|
|
239
|
-
name: "task_id",
|
|
240
|
-
required: true,
|
|
241
|
-
description: "Audit task id to inspect.",
|
|
242
|
-
},
|
|
243
|
-
],
|
|
244
|
-
},
|
|
245
|
-
{
|
|
246
|
-
name: "synthesize-report",
|
|
247
|
-
description: "Read the current audit report resource and summarize the highest-signal findings.",
|
|
248
|
-
arguments: [],
|
|
249
|
-
},
|
|
250
|
-
];
|
|
242
|
+
function resourceListPayload() {
|
|
243
|
+
return resourceRegistry.map((entry) => ({
|
|
244
|
+
uri: entry.uri,
|
|
245
|
+
name: entry.name,
|
|
246
|
+
description: entry.description,
|
|
247
|
+
mimeType: entry.mimeType,
|
|
248
|
+
}));
|
|
251
249
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
250
|
+
export const promptRegistry = [
|
|
251
|
+
{
|
|
252
|
+
name: "audit-code",
|
|
253
|
+
description: "Start or continue the autonomous audit loop through the next-step machine.",
|
|
254
|
+
arguments: [],
|
|
255
|
+
render() {
|
|
255
256
|
return [
|
|
256
|
-
"Use
|
|
257
|
-
"1.
|
|
258
|
-
"2. If
|
|
257
|
+
"Use `audit-code next-step` as the canonical interface to the backend wrapper.",
|
|
258
|
+
"1. Prefer running `audit-code next-step` directly from the repository root.",
|
|
259
|
+
"2. If this MCP adapter is your only available integration, call `start_audit` or `continue_audit`; both return the same one-step contract.",
|
|
260
|
+
"3. If the audit is blocked, inspect `audit-code://handoff/current`.",
|
|
259
261
|
" Do not read `audit-code://artifacts/current` unless explicitly needed for a specific task; it is massive and consumes your context window.",
|
|
260
|
-
"
|
|
261
|
-
"4. Call `continue_audit` until the status is complete or explicitly blocked for operator input.",
|
|
262
|
+
"4. When the user provides additional evidence, call `import_results` or `import_runtime_updates`.",
|
|
262
263
|
].join("\n");
|
|
263
|
-
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
name: "review-task",
|
|
268
|
+
description: "Inspect one audit task with explain_task and the current artifacts before reviewing code.",
|
|
269
|
+
arguments: [
|
|
270
|
+
{
|
|
271
|
+
name: "task_id",
|
|
272
|
+
required: true,
|
|
273
|
+
description: "Audit task id to inspect.",
|
|
274
|
+
},
|
|
275
|
+
],
|
|
276
|
+
render(args) {
|
|
264
277
|
return [
|
|
265
278
|
`Use \`explain_task\` for task \`${String(args?.task_id ?? "")}\` before you inspect code manually.`,
|
|
266
279
|
"Do not read the full `audit-code://artifacts/current` bundle unless specifically needed, as it is massive.",
|
|
267
280
|
].join("\n");
|
|
268
|
-
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
name: "synthesize-report",
|
|
285
|
+
description: "Read the current audit report resource and summarize the highest-signal findings.",
|
|
286
|
+
arguments: [],
|
|
287
|
+
render() {
|
|
269
288
|
return [
|
|
270
289
|
"Read `audit-code://report/current`.",
|
|
271
290
|
"Summarize the final audit report as work blocks first, then highlight the most important risks and remediation priorities.",
|
|
272
291
|
].join("\n");
|
|
273
|
-
|
|
274
|
-
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
];
|
|
295
|
+
function promptDefinitions() {
|
|
296
|
+
return promptRegistry.map((entry) => ({
|
|
297
|
+
name: entry.name,
|
|
298
|
+
description: entry.description,
|
|
299
|
+
arguments: entry.arguments,
|
|
300
|
+
}));
|
|
301
|
+
}
|
|
302
|
+
function renderPrompt(name, args) {
|
|
303
|
+
const entry = promptRegistry.find((p) => p.name === name);
|
|
304
|
+
if (!entry) {
|
|
305
|
+
throw new Error(`Unknown prompt: ${name}`);
|
|
275
306
|
}
|
|
307
|
+
return entry.render(args);
|
|
276
308
|
}
|
|
277
|
-
async function runContinueAudit(context, extraArgs = []) {
|
|
309
|
+
async function runContinueAudit(context, extraArgs = ["next-step"]) {
|
|
278
310
|
const step = await parseCliJson(extraArgs, context);
|
|
279
311
|
if (!step || typeof step !== "object" || Array.isArray(step))
|
|
280
312
|
return step;
|
|
@@ -378,7 +410,7 @@ function toolDefinitions() {
|
|
|
378
410
|
return [
|
|
379
411
|
{
|
|
380
412
|
name: "start_audit",
|
|
381
|
-
description: "
|
|
413
|
+
description: "Compatibility adapter over audit-code next-step; returns one step contract.",
|
|
382
414
|
inputSchema: {
|
|
383
415
|
type: "object",
|
|
384
416
|
properties: {
|
|
@@ -406,7 +438,7 @@ function toolDefinitions() {
|
|
|
406
438
|
},
|
|
407
439
|
{
|
|
408
440
|
name: "continue_audit",
|
|
409
|
-
description: "
|
|
441
|
+
description: "Compatibility adapter over audit-code next-step from the current artifacts directory.",
|
|
410
442
|
inputSchema: {
|
|
411
443
|
type: "object",
|
|
412
444
|
properties: {
|
|
@@ -510,7 +542,7 @@ function toolDefinitions() {
|
|
|
510
542
|
},
|
|
511
543
|
{
|
|
512
544
|
name: "report_capability",
|
|
513
|
-
description: "
|
|
545
|
+
description: "Compatibility adapter that calls audit-code next-step with host subagent capability flags.",
|
|
514
546
|
inputSchema: {
|
|
515
547
|
type: "object",
|
|
516
548
|
properties: {
|
|
@@ -542,6 +574,153 @@ function toolDefinitions() {
|
|
|
542
574
|
},
|
|
543
575
|
];
|
|
544
576
|
}
|
|
577
|
+
/**
|
|
578
|
+
* Extract zero or more complete Content-Length framed messages from a buffer.
|
|
579
|
+
* Returns an array of parsed body strings and the remaining unconsumed buffer.
|
|
580
|
+
* On framing errors, emits a framing error response via `emit` and resets the buffer.
|
|
581
|
+
*/
|
|
582
|
+
export function extractFrames(buffer, emit) {
|
|
583
|
+
const bodies = [];
|
|
584
|
+
let current = buffer;
|
|
585
|
+
while (true) {
|
|
586
|
+
const separator = current.indexOf("\r\n\r\n");
|
|
587
|
+
if (separator < 0) {
|
|
588
|
+
break;
|
|
589
|
+
}
|
|
590
|
+
let contentLength;
|
|
591
|
+
try {
|
|
592
|
+
const headerBlock = current.slice(0, separator).toString("utf8");
|
|
593
|
+
contentLength = parseContentLength(headerBlock);
|
|
594
|
+
}
|
|
595
|
+
catch (error) {
|
|
596
|
+
current = Buffer.alloc(0);
|
|
597
|
+
emit(failure(null, -32700, `Invalid MCP framing: ${error instanceof Error ? error.message : String(error)}.`));
|
|
598
|
+
break;
|
|
599
|
+
}
|
|
600
|
+
const frameLength = separator + 4 + contentLength;
|
|
601
|
+
if (current.length < frameLength) {
|
|
602
|
+
break;
|
|
603
|
+
}
|
|
604
|
+
bodies.push(current.slice(separator + 4, frameLength).toString("utf8"));
|
|
605
|
+
current = current.slice(frameLength);
|
|
606
|
+
}
|
|
607
|
+
return { bodies, remaining: current };
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Dispatch a single JSON-RPC request and return the response(s) to send,
|
|
611
|
+
* plus updated shutdown state.
|
|
612
|
+
*/
|
|
613
|
+
export async function dispatchRequest(request, ctx) {
|
|
614
|
+
const responses = [];
|
|
615
|
+
let { shutdownRequested } = ctx;
|
|
616
|
+
if (!request.method) {
|
|
617
|
+
responses.push(failure(request.id ?? null, -32600, "Missing method."));
|
|
618
|
+
return { responses, shutdownRequested };
|
|
619
|
+
}
|
|
620
|
+
try {
|
|
621
|
+
switch (request.method) {
|
|
622
|
+
case "initialize": {
|
|
623
|
+
const requestedVersion = typeof request.params?.protocolVersion === "string"
|
|
624
|
+
? request.params.protocolVersion
|
|
625
|
+
: PROTOCOL_VERSION;
|
|
626
|
+
const negotiatedVersion = requestedVersion <= PROTOCOL_VERSION
|
|
627
|
+
? requestedVersion
|
|
628
|
+
: PROTOCOL_VERSION;
|
|
629
|
+
responses.push(success(request.id ?? null, {
|
|
630
|
+
protocolVersion: negotiatedVersion,
|
|
631
|
+
serverInfo: {
|
|
632
|
+
name: "audit-code",
|
|
633
|
+
version: ctx.version,
|
|
634
|
+
},
|
|
635
|
+
instructions: "Use audit-code next-step as the primary backend loop. These MCP tools are compatibility adapters that return the same one-step contract.",
|
|
636
|
+
capabilities: {
|
|
637
|
+
tools: { listChanged: false },
|
|
638
|
+
resources: { subscribe: false, listChanged: false },
|
|
639
|
+
prompts: { listChanged: false },
|
|
640
|
+
},
|
|
641
|
+
}));
|
|
642
|
+
break;
|
|
643
|
+
}
|
|
644
|
+
case "notifications/initialized":
|
|
645
|
+
break;
|
|
646
|
+
case "ping":
|
|
647
|
+
if (request.id !== undefined) {
|
|
648
|
+
responses.push(success(request.id, {}));
|
|
649
|
+
}
|
|
650
|
+
break;
|
|
651
|
+
case "tools/list":
|
|
652
|
+
responses.push(success(request.id ?? null, { tools: toolDefinitions() }));
|
|
653
|
+
break;
|
|
654
|
+
case "tools/call": {
|
|
655
|
+
const params = parseObject(request.params);
|
|
656
|
+
const toolName = params.name;
|
|
657
|
+
if (!hasValue(toolName)) {
|
|
658
|
+
throw new Error("tools/call requires a tool name.");
|
|
659
|
+
}
|
|
660
|
+
responses.push(success(request.id ?? null, await handleToolCall(toolName, parseObject(params.arguments), ctx.defaults)));
|
|
661
|
+
break;
|
|
662
|
+
}
|
|
663
|
+
case "resources/list":
|
|
664
|
+
responses.push(success(request.id ?? null, {
|
|
665
|
+
resources: resourceListPayload(),
|
|
666
|
+
}));
|
|
667
|
+
break;
|
|
668
|
+
case "resources/read": {
|
|
669
|
+
const params = parseObject(request.params);
|
|
670
|
+
if (!hasValue(params.uri)) {
|
|
671
|
+
throw new Error("resources/read requires uri.");
|
|
672
|
+
}
|
|
673
|
+
const resource = await readResource(params.uri, ctx.defaults);
|
|
674
|
+
responses.push(success(request.id ?? null, {
|
|
675
|
+
contents: [
|
|
676
|
+
{
|
|
677
|
+
uri: params.uri,
|
|
678
|
+
mimeType: resource.mimeType,
|
|
679
|
+
text: resource.text,
|
|
680
|
+
},
|
|
681
|
+
],
|
|
682
|
+
}));
|
|
683
|
+
break;
|
|
684
|
+
}
|
|
685
|
+
case "prompts/list":
|
|
686
|
+
responses.push(success(request.id ?? null, {
|
|
687
|
+
prompts: promptDefinitions(),
|
|
688
|
+
}));
|
|
689
|
+
break;
|
|
690
|
+
case "prompts/get": {
|
|
691
|
+
const params = parseObject(request.params);
|
|
692
|
+
if (!hasValue(params.name)) {
|
|
693
|
+
throw new Error("prompts/get requires name.");
|
|
694
|
+
}
|
|
695
|
+
responses.push(success(request.id ?? null, {
|
|
696
|
+
description: promptDefinitions().find((prompt) => prompt.name === params.name)?.description,
|
|
697
|
+
messages: [
|
|
698
|
+
{
|
|
699
|
+
role: "user",
|
|
700
|
+
content: {
|
|
701
|
+
type: "text",
|
|
702
|
+
text: renderPrompt(params.name, parseObject(params.arguments)),
|
|
703
|
+
},
|
|
704
|
+
},
|
|
705
|
+
],
|
|
706
|
+
}));
|
|
707
|
+
break;
|
|
708
|
+
}
|
|
709
|
+
case "shutdown":
|
|
710
|
+
shutdownRequested = true;
|
|
711
|
+
responses.push(success(request.id ?? null, {}));
|
|
712
|
+
break;
|
|
713
|
+
case "exit":
|
|
714
|
+
return { responses, shutdownRequested, exit: shutdownRequested ? 0 : 1 };
|
|
715
|
+
default:
|
|
716
|
+
responses.push(failure(request.id ?? null, -32601, `Unknown method: ${request.method}`));
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
catch (error) {
|
|
720
|
+
responses.push(failure(request.id ?? null, -32000, error instanceof Error ? error.message : String(error)));
|
|
721
|
+
}
|
|
722
|
+
return { responses, shutdownRequested };
|
|
723
|
+
}
|
|
545
724
|
export async function runAuditCodeMcpServer(argv) {
|
|
546
725
|
const defaults = parseServerOptions(argv);
|
|
547
726
|
const version = await packageVersion();
|
|
@@ -549,29 +728,9 @@ export async function runAuditCodeMcpServer(argv) {
|
|
|
549
728
|
let buffer = Buffer.alloc(0);
|
|
550
729
|
process.stdin.on("data", async (chunk) => {
|
|
551
730
|
buffer = Buffer.concat([buffer, chunk]);
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
return;
|
|
556
|
-
}
|
|
557
|
-
let contentLength;
|
|
558
|
-
try {
|
|
559
|
-
const headerBlock = buffer.slice(0, separator).toString("utf8");
|
|
560
|
-
contentLength = parseContentLength(headerBlock);
|
|
561
|
-
}
|
|
562
|
-
catch (error) {
|
|
563
|
-
buffer = Buffer.alloc(0);
|
|
564
|
-
writeMessage(failure(null, -32700, `Invalid MCP framing: ${error instanceof Error ? error.message : String(error)}.`));
|
|
565
|
-
return;
|
|
566
|
-
}
|
|
567
|
-
const frameLength = separator + 4 + contentLength;
|
|
568
|
-
if (buffer.length < frameLength) {
|
|
569
|
-
return;
|
|
570
|
-
}
|
|
571
|
-
const body = buffer
|
|
572
|
-
.slice(separator + 4, frameLength)
|
|
573
|
-
.toString("utf8");
|
|
574
|
-
buffer = buffer.slice(frameLength);
|
|
731
|
+
const { bodies, remaining } = extractFrames(buffer, writeMessage);
|
|
732
|
+
buffer = remaining;
|
|
733
|
+
for (const body of bodies) {
|
|
575
734
|
let request;
|
|
576
735
|
try {
|
|
577
736
|
request = JSON.parse(body);
|
|
@@ -580,137 +739,17 @@ export async function runAuditCodeMcpServer(argv) {
|
|
|
580
739
|
writeMessage(failure(null, -32700, "Invalid JSON-RPC payload.", error instanceof Error ? error.message : String(error)));
|
|
581
740
|
continue;
|
|
582
741
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
? request.params.protocolVersion
|
|
592
|
-
: PROTOCOL_VERSION;
|
|
593
|
-
const negotiatedVersion = requestedVersion <= PROTOCOL_VERSION
|
|
594
|
-
? requestedVersion
|
|
595
|
-
: PROTOCOL_VERSION;
|
|
596
|
-
writeMessage(success(request.id ?? null, {
|
|
597
|
-
protocolVersion: negotiatedVersion,
|
|
598
|
-
serverInfo: {
|
|
599
|
-
name: "audit-code",
|
|
600
|
-
version,
|
|
601
|
-
},
|
|
602
|
-
instructions: "Use the audit-code MCP tools as the primary interface to the backend wrapper. Prefer start_audit, get_status, continue_audit, and the audit-code resources over ad hoc shell commands.",
|
|
603
|
-
capabilities: {
|
|
604
|
-
tools: { listChanged: false },
|
|
605
|
-
resources: { subscribe: false, listChanged: false },
|
|
606
|
-
prompts: { listChanged: false },
|
|
607
|
-
},
|
|
608
|
-
}));
|
|
609
|
-
break;
|
|
610
|
-
}
|
|
611
|
-
case "notifications/initialized":
|
|
612
|
-
break;
|
|
613
|
-
case "ping":
|
|
614
|
-
if (request.id !== undefined) {
|
|
615
|
-
writeMessage(success(request.id, {}));
|
|
616
|
-
}
|
|
617
|
-
break;
|
|
618
|
-
case "tools/list":
|
|
619
|
-
writeMessage(success(request.id ?? null, { tools: toolDefinitions() }));
|
|
620
|
-
break;
|
|
621
|
-
case "tools/call": {
|
|
622
|
-
const params = parseObject(request.params);
|
|
623
|
-
const toolName = params.name;
|
|
624
|
-
if (!hasValue(toolName)) {
|
|
625
|
-
throw new Error("tools/call requires a tool name.");
|
|
626
|
-
}
|
|
627
|
-
writeMessage(success(request.id ?? null, await handleToolCall(toolName, parseObject(params.arguments), defaults)));
|
|
628
|
-
break;
|
|
629
|
-
}
|
|
630
|
-
case "resources/list":
|
|
631
|
-
writeMessage(success(request.id ?? null, {
|
|
632
|
-
resources: [
|
|
633
|
-
{
|
|
634
|
-
uri: "audit-code://artifacts/current",
|
|
635
|
-
name: "current_artifacts",
|
|
636
|
-
description: "Current artifact bundle as JSON.",
|
|
637
|
-
mimeType: "application/json",
|
|
638
|
-
},
|
|
639
|
-
{
|
|
640
|
-
uri: "audit-code://handoff/current",
|
|
641
|
-
name: "operator_handoff",
|
|
642
|
-
description: "Current operator handoff payload as JSON.",
|
|
643
|
-
mimeType: "application/json",
|
|
644
|
-
},
|
|
645
|
-
{
|
|
646
|
-
uri: "audit-code://install/guide",
|
|
647
|
-
name: "install_guide",
|
|
648
|
-
description: "Repo-local install guide for supported IDE hosts.",
|
|
649
|
-
mimeType: "text/markdown",
|
|
650
|
-
},
|
|
651
|
-
{
|
|
652
|
-
uri: "audit-code://report/current",
|
|
653
|
-
name: "audit_report",
|
|
654
|
-
description: "Current deterministic audit report if available.",
|
|
655
|
-
mimeType: "text/markdown",
|
|
656
|
-
},
|
|
657
|
-
],
|
|
658
|
-
}));
|
|
659
|
-
break;
|
|
660
|
-
case "resources/read": {
|
|
661
|
-
const params = parseObject(request.params);
|
|
662
|
-
if (!hasValue(params.uri)) {
|
|
663
|
-
throw new Error("resources/read requires uri.");
|
|
664
|
-
}
|
|
665
|
-
const resource = await readResource(params.uri, defaults);
|
|
666
|
-
writeMessage(success(request.id ?? null, {
|
|
667
|
-
contents: [
|
|
668
|
-
{
|
|
669
|
-
uri: params.uri,
|
|
670
|
-
mimeType: resource.mimeType,
|
|
671
|
-
text: resource.text,
|
|
672
|
-
},
|
|
673
|
-
],
|
|
674
|
-
}));
|
|
675
|
-
break;
|
|
676
|
-
}
|
|
677
|
-
case "prompts/list":
|
|
678
|
-
writeMessage(success(request.id ?? null, {
|
|
679
|
-
prompts: promptDefinitions(),
|
|
680
|
-
}));
|
|
681
|
-
break;
|
|
682
|
-
case "prompts/get": {
|
|
683
|
-
const params = parseObject(request.params);
|
|
684
|
-
if (!hasValue(params.name)) {
|
|
685
|
-
throw new Error("prompts/get requires name.");
|
|
686
|
-
}
|
|
687
|
-
writeMessage(success(request.id ?? null, {
|
|
688
|
-
description: promptDefinitions().find((prompt) => prompt.name === params.name)?.description,
|
|
689
|
-
messages: [
|
|
690
|
-
{
|
|
691
|
-
role: "user",
|
|
692
|
-
content: {
|
|
693
|
-
type: "text",
|
|
694
|
-
text: renderPrompt(params.name, parseObject(params.arguments)),
|
|
695
|
-
},
|
|
696
|
-
},
|
|
697
|
-
],
|
|
698
|
-
}));
|
|
699
|
-
break;
|
|
700
|
-
}
|
|
701
|
-
case "shutdown":
|
|
702
|
-
shutdownRequested = true;
|
|
703
|
-
writeMessage(success(request.id ?? null, {}));
|
|
704
|
-
break;
|
|
705
|
-
case "exit":
|
|
706
|
-
process.exit(shutdownRequested ? 0 : 1);
|
|
707
|
-
break;
|
|
708
|
-
default:
|
|
709
|
-
writeMessage(failure(request.id ?? null, -32601, `Unknown method: ${request.method}`));
|
|
710
|
-
}
|
|
742
|
+
const result = await dispatchRequest(request, {
|
|
743
|
+
version,
|
|
744
|
+
defaults,
|
|
745
|
+
shutdownRequested,
|
|
746
|
+
});
|
|
747
|
+
shutdownRequested = result.shutdownRequested;
|
|
748
|
+
for (const response of result.responses) {
|
|
749
|
+
writeMessage(response);
|
|
711
750
|
}
|
|
712
|
-
|
|
713
|
-
|
|
751
|
+
if (result.exit !== undefined) {
|
|
752
|
+
process.exit(result.exit);
|
|
714
753
|
}
|
|
715
754
|
}
|
|
716
755
|
});
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function stableStringify(value: unknown): string;
|
|
2
|
+
export declare function normalizeForMetadataHash(artifactName: string, value: unknown): unknown;
|
|
3
|
+
export declare function hashArtifactValue(artifactName: string, value: unknown): string;
|
|
4
|
+
export declare function buildReverseDependencyMap(): Record<string, string[]>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { ARTIFACT_DEPENDENCY_MAP } from "./dependencyMap.js";
|
|
3
|
+
export function stableStringify(value) {
|
|
4
|
+
if (value === undefined) {
|
|
5
|
+
return "null";
|
|
6
|
+
}
|
|
7
|
+
if (value === null || typeof value !== "object") {
|
|
8
|
+
return JSON.stringify(value);
|
|
9
|
+
}
|
|
10
|
+
if (Array.isArray(value)) {
|
|
11
|
+
return `[${value.map((item) => stableStringify(item ?? null)).join(",")}]`;
|
|
12
|
+
}
|
|
13
|
+
const entries = Object.entries(value)
|
|
14
|
+
.filter(([, item]) => item !== undefined)
|
|
15
|
+
.sort(([a], [b]) => a.localeCompare(b));
|
|
16
|
+
return `{${entries.map(([key, item]) => `${JSON.stringify(key)}:${stableStringify(item)}`).join(",")}}`;
|
|
17
|
+
}
|
|
18
|
+
export function normalizeForMetadataHash(artifactName, value) {
|
|
19
|
+
if ((artifactName === "repo_manifest.json" ||
|
|
20
|
+
artifactName === "tooling_manifest.json") &&
|
|
21
|
+
value &&
|
|
22
|
+
typeof value === "object" &&
|
|
23
|
+
!Array.isArray(value)) {
|
|
24
|
+
const record = value;
|
|
25
|
+
const { generated_at: _generatedAt, ...rest } = record;
|
|
26
|
+
return rest;
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
export function hashArtifactValue(artifactName, value) {
|
|
31
|
+
return createHash("sha256")
|
|
32
|
+
.update(stableStringify(normalizeForMetadataHash(artifactName, value)))
|
|
33
|
+
.digest("hex");
|
|
34
|
+
}
|
|
35
|
+
export function buildReverseDependencyMap() {
|
|
36
|
+
const reverse = {};
|
|
37
|
+
for (const [upstream, downstreamList] of Object.entries(ARTIFACT_DEPENDENCY_MAP)) {
|
|
38
|
+
reverse[upstream] ??= [];
|
|
39
|
+
for (const downstream of downstreamList) {
|
|
40
|
+
reverse[downstream] ??= [];
|
|
41
|
+
reverse[downstream].push(upstream);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return reverse;
|
|
45
|
+
}
|
|
@@ -1,54 +1,5 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { ARTIFACT_DEPENDENCY_MAP } from "./dependencyMap.js";
|
|
3
1
|
import { getArtifactValue } from "../io/artifacts.js";
|
|
4
|
-
|
|
5
|
-
if (value === undefined) {
|
|
6
|
-
return "null";
|
|
7
|
-
}
|
|
8
|
-
if (value === null || typeof value !== "object") {
|
|
9
|
-
return JSON.stringify(value);
|
|
10
|
-
}
|
|
11
|
-
if (Array.isArray(value)) {
|
|
12
|
-
return `[${value.map((item) => stableStringify(item ?? null)).join(",")}]`;
|
|
13
|
-
}
|
|
14
|
-
const entries = Object.entries(value)
|
|
15
|
-
.filter(([, item]) => item !== undefined)
|
|
16
|
-
.sort(([a], [b]) => a.localeCompare(b));
|
|
17
|
-
return `{${entries.map(([key, item]) => `${JSON.stringify(key)}:${stableStringify(item)}`).join(",")}}`;
|
|
18
|
-
}
|
|
19
|
-
function hashValue(value) {
|
|
20
|
-
return createHash("sha256").update(stableStringify(value)).digest("hex");
|
|
21
|
-
}
|
|
22
|
-
function normalizeForMetadataHash(artifactName, value) {
|
|
23
|
-
if (artifactName === "repo_manifest.json" &&
|
|
24
|
-
value &&
|
|
25
|
-
typeof value === "object" &&
|
|
26
|
-
!Array.isArray(value)) {
|
|
27
|
-
const record = value;
|
|
28
|
-
const { generated_at: _generatedAt, ...rest } = record;
|
|
29
|
-
return rest;
|
|
30
|
-
}
|
|
31
|
-
if (artifactName === "tooling_manifest.json" &&
|
|
32
|
-
value &&
|
|
33
|
-
typeof value === "object" &&
|
|
34
|
-
!Array.isArray(value)) {
|
|
35
|
-
const record = value;
|
|
36
|
-
const { generated_at: _generatedAt, ...rest } = record;
|
|
37
|
-
return rest;
|
|
38
|
-
}
|
|
39
|
-
return value;
|
|
40
|
-
}
|
|
41
|
-
function buildReverseDependencyMap() {
|
|
42
|
-
const reverse = {};
|
|
43
|
-
for (const [upstream, downstreamList] of Object.entries(ARTIFACT_DEPENDENCY_MAP)) {
|
|
44
|
-
reverse[upstream] ??= [];
|
|
45
|
-
for (const downstream of downstreamList) {
|
|
46
|
-
reverse[downstream] ??= [];
|
|
47
|
-
reverse[downstream].push(upstream);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return reverse;
|
|
51
|
-
}
|
|
2
|
+
import { buildReverseDependencyMap, hashArtifactValue, stableStringify, } from "./artifactFreshness.js";
|
|
52
3
|
const REVERSE_DEPENDENCY_MAP = buildReverseDependencyMap();
|
|
53
4
|
function computeDependencyFirstOrder(artifactNames) {
|
|
54
5
|
const target = new Set(artifactNames);
|
|
@@ -97,7 +48,7 @@ export function computeArtifactMetadata(bundle, previous, updatedArtifacts = [])
|
|
|
97
48
|
artifacts[artifactName] = previousEntry;
|
|
98
49
|
continue;
|
|
99
50
|
}
|
|
100
|
-
const contentHash =
|
|
51
|
+
const contentHash = hashArtifactValue(artifactName, value);
|
|
101
52
|
const dependencyRevisions = Object.fromEntries((REVERSE_DEPENDENCY_MAP[artifactName] ?? [])
|
|
102
53
|
.filter((dependencyName) => dependencyName !== "artifact_metadata.json")
|
|
103
54
|
.sort()
|