auditor-lambda 0.3.23 → 0.3.24

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.
@@ -605,6 +605,19 @@ function renderOpenCodePermissionConfig() {
605
605
  };
606
606
  }
607
607
 
608
+ const OPENCODE_MCP_COMMAND_TEMPLATE = [
609
+ '# audit-code',
610
+ '',
611
+ 'Use the auditor MCP tools as the primary interface to the audit workflow.',
612
+ '',
613
+ '1. Call `auditor_start_audit` to initialize and receive the first step.',
614
+ '2. Read the `prompt_content` field in the response and follow the instructions there.',
615
+ '3. When a step completes (not blocked), call `auditor_continue_audit` to advance.',
616
+ '4. Stop when the step instructions say to stop.',
617
+ '',
618
+ 'Do not run shell commands. Use only `auditor_*` MCP tools and the `task` tool for subagent dispatch.',
619
+ ].join('\n');
620
+
608
621
  function renderOpenCodeProjectConfig(root) {
609
622
  const launcher = replaceBackslashes(toRepoRelativePath(root, join(root, '.audit-code', 'install', MCP_LAUNCHER_FILENAME)));
610
623
  const auditPermission = renderOpenCodePermissionConfig();
@@ -625,10 +638,12 @@ function renderOpenCodeProjectConfig(root) {
625
638
  'Read-heavy audit orchestration agent for the /audit-code workflow.',
626
639
  tools: {
627
640
  'auditor*': true,
641
+ task: true,
628
642
  },
629
643
  permission: {
630
644
  ...auditPermission,
631
645
  question: 'allow',
646
+ task: 'allow',
632
647
  },
633
648
  },
634
649
  },
package/dist/cli.js CHANGED
@@ -523,10 +523,21 @@ function renderCapabilityCheckPrompt(params) {
523
523
  return [
524
524
  "# audit-code capability check",
525
525
  "",
526
- "Decide one thing from the active toolset: does this host expose a callable subagent/delegation tool for source-code review, such as Agent, Task, or an equivalent built-in subagent call?",
526
+ "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
527
  "",
528
528
  "Do not run shell commands to answer this. Do not inspect packet prompts, schemas, or backend command catalogs.",
529
529
  "",
530
+ "**If auditor MCP tools are available** (preferred — no shell required):",
531
+ "",
532
+ "Call `auditor_report_capability` with:",
533
+ "- `can_dispatch_subagents: true` if the `task` tool or equivalent subagent dispatch is available",
534
+ "- `can_dispatch_subagents: false` if not",
535
+ "- Optionally `can_restrict_subagent_tools: true` and/or `can_select_subagent_model: true`",
536
+ "",
537
+ "Read the `prompt_content` field in the tool response and follow it.",
538
+ "",
539
+ "**Fallback — if auditor MCP tools are not available:**",
540
+ "",
530
541
  "If callable subagents are available, run:",
531
542
  "",
532
543
  ` ${yesCommand}`,
@@ -550,30 +561,41 @@ function renderDispatchReviewPrompt(params) {
550
561
  const toolsLine = params.hostCanRestrictSubagentTools
551
562
  ? "Restrict review subagents to read/search plus the packet submit command named in their prompt. Do not give them source edit/write tools."
552
563
  : "Do not ask the user about per-subagent tool restrictions; this host did not report a callable restriction facility.";
553
- const fileLines = params.dispatchQuotaPath
564
+ const runId = params.activeReviewRun.run_id;
565
+ const dispatchDataLines = params.dispatchQuotaPath
554
566
  ? [
555
- "Dispatch is prepared. Read both of these files:",
567
+ "**If auditor MCP tools are available** (preferred):",
568
+ "",
569
+ "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.",
570
+ "",
571
+ "Use the `wave_size` from `dispatch_quota`. If `cooldown_until` is non-null, wait until that timestamp before starting the first wave.",
572
+ "",
573
+ "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.",
574
+ "",
575
+ "**Fallback — if auditor MCP tools are not available:** Read both of these files:",
556
576
  "",
557
577
  ` Dispatch plan: ${params.dispatchPlanPath}`,
558
578
  ` Dispatch quota: ${params.dispatchQuotaPath}`,
559
579
  "",
560
- "The quota file contains a `wave_size` field. Dispatch at most `wave_size` subagents at a time. If `cooldown_until` is non-null, wait until that timestamp before starting the first wave.",
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.",
580
+ "Apply the same wave logic from the quota file.",
563
581
  ]
564
582
  : [
565
- "Dispatch is prepared. Read only this dispatch plan JSON:",
583
+ "**If auditor MCP tools are available** (preferred):",
584
+ "",
585
+ "The dispatch plan entries are in the `dispatch_plan_entries` field of the tool response that returned this step.",
586
+ "",
587
+ "**Fallback — if auditor MCP tools are not available:** Read this dispatch plan JSON:",
566
588
  "",
567
589
  ` ${params.dispatchPlanPath}`,
568
590
  "",
569
- "Launch one host subagent for each entry in the plan.",
591
+ "Launch one subagent for each entry in the plan.",
570
592
  ];
571
593
  return [
572
594
  "# audit-code dispatch review",
573
595
  "",
574
- ...fileLines,
596
+ ...dispatchDataLines,
575
597
  "",
576
- "Pass each packet prompt path literally to its subagent; do not load packet prompt files into this orchestrator context.",
598
+ "Pass each `entry.prompt_path` literally to its subagent; do not load packet prompt files into this orchestrator context.",
577
599
  "",
578
600
  "Subagent prompt shape:",
579
601
  "",
@@ -584,7 +606,11 @@ function renderDispatchReviewPrompt(params) {
584
606
  "",
585
607
  "Each subagent must submit its packet through the submit command printed in its packet prompt and stop after successful submission.",
586
608
  "",
587
- "After all waves complete, run exactly:",
609
+ "**After all waves complete:**",
610
+ "",
611
+ "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.",
612
+ "",
613
+ "Fallback — if auditor MCP tools are not available, run exactly:",
588
614
  "",
589
615
  ` ${mergeCommand}`,
590
616
  "",
@@ -624,9 +650,11 @@ function renderPresentReportPrompt(finalReportPath) {
624
650
  "",
625
651
  "The deterministic audit is complete.",
626
652
  "",
627
- "Read this report and present the completed audit with work blocks first:",
628
- "",
653
+ "Read the final audit report from the `audit-code://report/current` MCP resource (or from the file at:",
629
654
  ` ${finalReportPath}`,
655
+ "if a Read tool is available).",
656
+ "",
657
+ "Present the completed audit with work blocks first.",
630
658
  "",
631
659
  "Do not run the orchestrator again for this completed audit.",
632
660
  "",
@@ -1231,8 +1259,13 @@ async function cmdNextStep(argv) {
1231
1259
  stepKind: "dispatch_review",
1232
1260
  status: "ready",
1233
1261
  runId: result.activeReviewRun.run_id,
1234
- allowedCommands: [mergeCommand, continueCommand],
1235
- stopCondition: "Dispatch every packet, merge-and-ingest once, then run next-step again.",
1262
+ allowedCommands: [
1263
+ "auditor_merge_and_ingest",
1264
+ "auditor_continue_audit",
1265
+ mergeCommand,
1266
+ continueCommand,
1267
+ ],
1268
+ stopCondition: "Dispatch every packet, call auditor_merge_and_ingest once, then call auditor_continue_audit.",
1236
1269
  repoRoot: root,
1237
1270
  artifactPaths: {
1238
1271
  dispatch_plan: dispatch.dispatch_plan_path,
@@ -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 parseCliJson([], context));
309
+ return toolResult(await runContinueAudit(context));
274
310
  case "continue_audit":
275
- return toolResult(await parseCliJson([], context));
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auditor-lambda",
3
- "version": "0.3.23",
3
+ "version": "0.3.24",
4
4
  "private": false,
5
5
  "description": "Portable hybrid code-auditing framework for arbitrary repositories.",
6
6
  "type": "module",
@@ -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',
@@ -183,7 +176,20 @@ function renderOpenCodePermissionConfig() {
183
176
  };
184
177
  }
185
178
 
186
- function mergeOpenCodeGlobalConfig(existing, promptBody) {
179
+ const OPENCODE_MCP_COMMAND_TEMPLATE = [
180
+ '# audit-code',
181
+ '',
182
+ 'Use the auditor MCP tools as the primary interface to the audit workflow.',
183
+ '',
184
+ '1. Call `auditor_start_audit` to initialize and receive the first step.',
185
+ '2. Read the `prompt_content` field in the response and follow the instructions there.',
186
+ '3. When a step completes (not blocked), call `auditor_continue_audit` to advance.',
187
+ '4. Stop when the step instructions say to stop.',
188
+ '',
189
+ 'Do not run shell commands. Use only `auditor_*` MCP tools and the `task` tool for subagent dispatch.',
190
+ ].join('\n');
191
+
192
+ function mergeOpenCodeGlobalConfig(existing) {
187
193
  const parsed = existing ? JSON.parse(existing) : {};
188
194
  const auditPermission = renderOpenCodePermissionConfig();
189
195
  const existingAuditor = objectValue(objectValue(parsed.agent).auditor);
@@ -194,7 +200,7 @@ function mergeOpenCodeGlobalConfig(existing, promptBody) {
194
200
  ? parsed.command
195
201
  : {}),
196
202
  'audit-code': {
197
- template: promptBody.trimStart(),
203
+ template: OPENCODE_MCP_COMMAND_TEMPLATE,
198
204
  description: 'Autonomous local loop code auditing',
199
205
  agent: 'auditor',
200
206
  subtask: false,
@@ -208,10 +214,15 @@ function mergeOpenCodeGlobalConfig(existing, promptBody) {
208
214
  auditor: {
209
215
  ...existingAuditor,
210
216
  description: 'Read-heavy audit orchestration agent for the /audit-code workflow.',
211
- permission: mergeOpenCodePermissionConfig(
212
- existingAuditor.permission,
213
- auditPermission,
214
- ),
217
+ tools: {
218
+ 'auditor*': true,
219
+ task: true,
220
+ },
221
+ permission: {
222
+ ...mergeOpenCodePermissionConfig(existingAuditor.permission, auditPermission),
223
+ question: 'allow',
224
+ task: 'allow',
225
+ },
215
226
  },
216
227
  },
217
228
  };
@@ -233,7 +244,6 @@ if (!promptSource || !skillSource) {
233
244
  process.exit(0);
234
245
  }
235
246
 
236
- const promptBody = splitFrontmatter(promptSource.toString('utf8')).body;
237
247
  const codexOpenAiAgentSource = readOptionalSource(codexOpenAiAgentSourceFile, 'Codex skill UI metadata');
238
248
 
239
249
  const installs = [
@@ -284,7 +294,7 @@ for (const install of installs) {
284
294
  const opencodeGlobalConfig = join(homedir(), '.config', 'opencode', 'opencode.json');
285
295
  try {
286
296
  const action = installMergedJson(opencodeGlobalConfig, (existing) =>
287
- mergeOpenCodeGlobalConfig(existing, promptBody),
297
+ mergeOpenCodeGlobalConfig(existing),
288
298
  );
289
299
  console.log(`audit-code: ${action} global OpenCode command in ${opencodeGlobalConfig}`);
290
300
  } catch (err) {