auditor-lambda 0.3.32 → 0.3.34

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.
Files changed (40) hide show
  1. package/README.md +2 -1
  2. package/audit-code-wrapper-lib.mjs +30 -28
  3. package/dist/cli.d.ts +5 -0
  4. package/dist/cli.js +55 -123
  5. package/dist/mcp/server.js +11 -11
  6. package/dist/orchestrator/reviewPackets.d.ts +3 -0
  7. package/dist/orchestrator/reviewPackets.js +13 -2
  8. package/dist/quota/compositeQuotaSource.d.ts +7 -0
  9. package/dist/quota/compositeQuotaSource.js +20 -0
  10. package/dist/quota/errorParsers/claudeCodeErrorParser.d.ts +6 -0
  11. package/dist/quota/errorParsers/claudeCodeErrorParser.js +39 -0
  12. package/dist/quota/errorParsers/genericErrorParser.d.ts +9 -0
  13. package/dist/quota/errorParsers/genericErrorParser.js +7 -0
  14. package/dist/quota/errorParsers/index.d.ts +5 -0
  15. package/dist/quota/errorParsers/index.js +12 -0
  16. package/dist/quota/errorParsing.d.ts +7 -0
  17. package/dist/quota/errorParsing.js +69 -0
  18. package/dist/quota/fileLock.d.ts +6 -0
  19. package/dist/quota/fileLock.js +64 -0
  20. package/dist/quota/index.d.ts +11 -1
  21. package/dist/quota/index.js +7 -1
  22. package/dist/quota/learnedQuotaSource.d.ts +7 -0
  23. package/dist/quota/learnedQuotaSource.js +25 -0
  24. package/dist/quota/probe.d.ts +1 -4
  25. package/dist/quota/probe.js +1 -4
  26. package/dist/quota/quotaSource.d.ts +12 -0
  27. package/dist/quota/quotaSource.js +1 -0
  28. package/dist/quota/scheduler.d.ts +5 -1
  29. package/dist/quota/scheduler.js +51 -9
  30. package/dist/quota/slidingWindow.d.ts +4 -0
  31. package/dist/quota/slidingWindow.js +28 -0
  32. package/dist/quota/state.d.ts +3 -0
  33. package/dist/quota/state.js +57 -14
  34. package/dist/quota/types.d.ts +11 -2
  35. package/dist/supervisor/operatorHandoff.js +1 -1
  36. package/dist/types/sessionConfig.d.ts +3 -0
  37. package/dist/validation/sessionConfig.js +4 -0
  38. package/package.json +1 -1
  39. package/schemas/dispatch_quota.schema.json +23 -2
  40. package/skills/audit-code/audit-code.prompt.md +5 -0
package/README.md CHANGED
@@ -138,7 +138,8 @@ audit-code next-step
138
138
 
139
139
  This writes `.audit-artifacts/steps/current-step.json` and
140
140
  `.audit-artifacts/steps/current-prompt.md`; hosts should follow only the
141
- returned step prompt.
141
+ returned step prompt. MCP tools are compatibility adapters over this same
142
+ `next-step` contract rather than a separate orchestration path.
142
143
 
143
144
  For an operator-side artifact consistency check:
144
145
 
@@ -580,16 +580,17 @@ function replaceBackslashes(value) {
580
580
  function renderVSCodeAgentFile() {
581
581
  return [
582
582
  '---',
583
- 'description: Plan and orchestrate /audit-code with the installed auditor MCP server before making code changes.',
583
+ 'description: Plan and orchestrate /audit-code through the next-step machine before making code changes.',
584
584
  '---',
585
585
  '',
586
586
  '# Auditor Agent',
587
587
  '',
588
- 'Use the installed auditor MCP server as the primary integration surface for the audit workflow.',
588
+ 'Use `audit-code next-step` as the primary integration surface for the audit workflow. The installed auditor MCP server is a compatibility adapter over the same step contract.',
589
589
  '',
590
590
  'When the user asks to run or continue `/audit-code`:',
591
591
  '',
592
- '- call the `start_audit`, `get_status`, and `continue_audit` MCP tools instead of reconstructing backend state manually',
592
+ '- run `audit-code next-step` directly when shell access is available',
593
+ '- if MCP is the only available integration, call `start_audit`, `get_status`, and `continue_audit`; those tools return the same one-step contract',
593
594
  '- read `audit-code://handoff/current` and `audit-code://artifacts/current` when the audit blocks or you need current context',
594
595
  '- prefer imported audit results and runtime updates over ad hoc manual state edits',
595
596
  '- treat the deterministic audit report as the final source of truth once the audit completes',
@@ -615,7 +616,7 @@ function renderCodexMcpSetupGuide(root) {
615
616
  `args = ["${replaceBackslashes(toRepoRelativePath(root, join(root, '.audit-code', 'install', MCP_LAUNCHER_FILENAME)))}"]`,
616
617
  '```',
617
618
  '',
618
- 'Once the server is registered, ask Codex to use the `auditor` MCP tools to start or continue the audit.',
619
+ 'Prefer `audit-code next-step` directly. Use the registered `auditor` MCP tools only when shell access is unavailable; they return the same one-step contract.',
619
620
  '',
620
621
  ].join('\n');
621
622
  }
@@ -626,9 +627,9 @@ function renderCodexAutomationRecipe() {
626
627
  '',
627
628
  'Suggested recurring task:',
628
629
  '',
629
- '- Prompt: Re-run the autonomous audit workflow for this repository. Use the installed auditor MCP tools, summarize only new or regressed findings, and stop once the deterministic report is current.',
630
+ '- Prompt: Re-run the autonomous audit workflow for this repository with `audit-code next-step`, summarize only new or regressed findings, and stop once the deterministic report is current.',
630
631
  '- Cadence: daily on active branches or before release cut-offs',
631
- '- Inputs: repository root plus the installed `auditor` MCP server',
632
+ '- Inputs: repository root; the installed `auditor` MCP server is optional compatibility plumbing',
632
633
  '',
633
634
  'Use this recipe as a starting point for a Codex automation once the local workflow is stable in your environment.',
634
635
  '',
@@ -706,16 +707,17 @@ function renderOpenCodePermissionConfig() {
706
707
  const OPENCODE_MCP_COMMAND_TEMPLATE = [
707
708
  '# audit-code',
708
709
  '',
709
- 'Use the auditor MCP tools as the primary interface to the audit workflow.',
710
+ 'Use `audit-code next-step` as the primary interface to the audit workflow.',
710
711
  '',
711
- '1. Call `auditor_start_audit` to initialize and receive the first step.',
712
- '2. Read `prompt_content` in the response and follow it.',
713
- '3. When a step completes (not blocked), call `auditor_continue_audit` to advance.',
714
- '4. Stop when the step instructions say to stop.',
712
+ '1. Run `audit-code next-step` directly when shell access is available.',
713
+ '2. If MCP is your only available interface, call `auditor_start_audit` or `auditor_continue_audit`; both return the same one-step contract.',
714
+ '3. Read `prompt_content` in the response and follow it.',
715
+ '4. When a step completes (not blocked), run `audit-code next-step` again or call `auditor_continue_audit` as the compatibility adapter.',
716
+ '5. Stop when the step instructions say to stop.',
715
717
  '',
716
- 'Do not run shell commands. Use only `auditor_*` MCP tools and the `task` tool for subagent dispatch.',
718
+ 'Use the `task` tool or equivalent for subagent dispatch when the step tells you to fan out review work.',
717
719
  '',
718
- '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.',
720
+ 'If neither shell access nor `auditor_start_audit` is available, stop and report that no next-step interface is connected.',
719
721
  ].join('\n');
720
722
 
721
723
  function renderOpenCodeProjectConfig(_root) {
@@ -939,7 +941,7 @@ function renderClaudeDesktopProjectTemplate() {
939
941
  'Suggested project instructions:',
940
942
  '',
941
943
  '- Treat `/audit-code` as the canonical autonomous audit workflow for this repository.',
942
- '- Prefer the installed auditor MCP tools over recreating state manually.',
944
+ '- Prefer `audit-code next-step`; use the installed auditor MCP tools only as a compatibility adapter over the same step contract.',
943
945
  '- Read the operator handoff and artifact resources before asking for more context.',
944
946
  '- Present the final deterministic audit report as work blocks first.',
945
947
  '',
@@ -951,7 +953,7 @@ function renderClaudeDesktopProjectTemplate() {
951
953
  '',
952
954
  'Starter prompt:',
953
955
  '',
954
- '> Start `/audit-code` for this repository using the installed auditor MCP tools. Continue until the audit is complete or blocked for operator input, and summarize the current handoff status before you stop.',
956
+ '> Start `/audit-code` for this repository using `audit-code next-step`. Continue until the audit is complete or blocked for operator input, and summarize the current handoff status before you stop.',
955
957
  '',
956
958
  ].join('\n');
957
959
  }
@@ -981,7 +983,7 @@ function renderAntigravityPlanningGuide(root) {
981
983
  '',
982
984
  '1. Open Antigravity in Planning mode.',
983
985
  '2. Load the repo-local prompt asset or the AGENTS instructions before starting the audit conversation.',
984
- '3. Ask Antigravity to use the installed auditor MCP server for structured state inspection and continuation.',
986
+ '3. Ask Antigravity to use `audit-code next-step` directly. The installed auditor MCP server is available only as compatibility plumbing when direct shell access is unavailable.',
985
987
  '4. Review Antigravity artifacts before accepting major code changes or imported evidence.',
986
988
  '',
987
989
  'Recommended repo-local paths:',
@@ -1266,9 +1268,9 @@ async function buildClaudeDesktopBundle(root, results) {
1266
1268
  name: 'auditor-lambda',
1267
1269
  display_name: 'Auditor Lambda',
1268
1270
  version: packageVersion,
1269
- description: 'Local MCP bundle for the /audit-code autonomous audit workflow.',
1271
+ description: 'Compatibility MCP bundle for the /audit-code autonomous audit workflow.',
1270
1272
  long_description:
1271
- 'Runs the auditor-lambda MCP server locally so Claude Desktop can start, inspect, and continue repository audits with structured tools, resources, and prompts.',
1273
+ 'Runs a local compatibility MCP adapter whose start and continue tools return the same audit-code next-step contract as the direct CLI loop.',
1272
1274
  author: {
1273
1275
  name: 'auditor-lambda',
1274
1276
  url: 'https://github.com/OhOkThisIsFine/auditor-lambda',
@@ -1390,7 +1392,7 @@ const INSTALL_HOST_DEFINITIONS = {
1390
1392
  support_level: 'supported',
1391
1393
  setup_kind: 'local-mcp-bundle',
1392
1394
  summary:
1393
- 'Install the generated local MCP bundle in Claude Desktop, then use the project template and prompt asset as supporting context.',
1395
+ 'Install the generated local MCP compatibility bundle in Claude Desktop, then use the project template and prompt asset as supporting context.',
1394
1396
  primary_path_key: 'claudeDesktopDxtPath',
1395
1397
  supporting_path_keys: [
1396
1398
  'claudeDesktopMcpbPath',
@@ -1400,7 +1402,7 @@ const INSTALL_HOST_DEFINITIONS = {
1400
1402
  ],
1401
1403
  steps: [
1402
1404
  'Open Claude Desktop Settings and install the generated `.dxt` bundle.',
1403
- 'Configure the repository root when prompted so the bundle can launch the local auditor MCP server.',
1405
+ 'Configure the repository root when prompted so the bundle can launch the local auditor MCP adapter.',
1404
1406
  'Use the project template and prompt asset to kick off `/audit-code` in conversation.',
1405
1407
  ],
1406
1408
  profile: {
@@ -1492,7 +1494,7 @@ const INSTALL_HOST_DEFINITIONS = {
1492
1494
  support_level: 'supported',
1493
1495
  setup_kind: 'global-command+project-mcp',
1494
1496
  summary:
1495
- 'Use the global OpenCode `/audit-code` command installed by npm plus generated project MCP and permission wiring.',
1497
+ 'Use the global OpenCode `/audit-code` command installed by npm plus generated project permissions; MCP is compatibility-only.',
1496
1498
  primary_path_key: 'opencodeConfigPath',
1497
1499
  supporting_path_keys: [
1498
1500
  'agentsInstructionsPath',
@@ -1501,7 +1503,7 @@ const INSTALL_HOST_DEFINITIONS = {
1501
1503
  steps: [
1502
1504
  'Open this repository in OpenCode.',
1503
1505
  'Use the global `/audit-code` command installed by `npm install -g auditor-lambda`.',
1504
- 'Let OpenCode load the generated `opencode.json` for the auditor MCP server and project permissions only.',
1506
+ 'Let OpenCode load the generated `opencode.json` for project permissions; the global command drives `audit-code next-step` directly.',
1505
1507
  ],
1506
1508
  profile: {
1507
1509
  writeOpenCode: true,
@@ -1519,7 +1521,7 @@ const INSTALL_HOST_DEFINITIONS = {
1519
1521
  assertOpenCodeAuditPermissionConfig(config?.permission, 'permission');
1520
1522
  assertOpenCodeAuditPermissionConfig(config?.agent?.auditor?.permission, 'agent.auditor.permission');
1521
1523
  return {
1522
- summary: 'OpenCode project config has audit permissions; MCP server and /audit-code command are supplied by the global npm-installed config.',
1524
+ summary: 'OpenCode project config has audit permissions; /audit-code is supplied by the global npm-installed config.',
1523
1525
  path: assetPaths.opencodeConfigPath,
1524
1526
  };
1525
1527
  });
@@ -1531,7 +1533,7 @@ const INSTALL_HOST_DEFINITIONS = {
1531
1533
  support_level: 'supported',
1532
1534
  setup_kind: 'prompt+agent+mcp',
1533
1535
  summary:
1534
- 'Use the generated prompt file, custom agent, and workspace MCP configuration for the cleanest VS Code integration.',
1536
+ 'Use the generated prompt file and custom agent for next-step-first VS Code integration; workspace MCP is compatibility-only.',
1535
1537
  primary_path_key: 'vscodePromptPath',
1536
1538
  supporting_path_keys: [
1537
1539
  'vscodeAgentPath',
@@ -1540,8 +1542,8 @@ const INSTALL_HOST_DEFINITIONS = {
1540
1542
  ],
1541
1543
  steps: [
1542
1544
  'Open this repository in VS Code with Copilot.',
1543
- 'Allow VS Code to discover the workspace MCP server from `.vscode/mcp.json`.',
1544
- 'Invoke `/audit-code` in chat and use the custom auditor agent when you want the dedicated orchestration persona.',
1545
+ 'Invoke `/audit-code` from the generated prompt or chat so the workflow calls `audit-code next-step` directly.',
1546
+ 'Use the workspace MCP adapter only when direct shell access is unavailable.',
1545
1547
  ],
1546
1548
  profile: {
1547
1549
  writeVSCode: true,
@@ -1587,7 +1589,7 @@ const INSTALL_HOST_DEFINITIONS = {
1587
1589
  support_level: 'supported',
1588
1590
  setup_kind: 'agent-skill+gemini-command+planning-guide+mcp-ready',
1589
1591
  summary:
1590
- 'Uses the project-scoped .agent/skills/audit-code/SKILL.md skill, the .gemini/commands/audit-code.toml slash command, the planning guide, and AGENTS instructions. The shared MCP launcher is available for structured tool calls.',
1592
+ 'Uses the project-scoped .agent/skills/audit-code/SKILL.md skill, the .gemini/commands/audit-code.toml slash command, the planning guide, and AGENTS instructions. The shared MCP launcher is compatibility-only.',
1591
1593
  primary_path_key: 'antigravitySkillPath',
1592
1594
  supporting_path_keys: [
1593
1595
  'geminiCommandPath',
@@ -1600,7 +1602,7 @@ const INSTALL_HOST_DEFINITIONS = {
1600
1602
  'Open this repository in Antigravity.',
1601
1603
  'The audit-code skill is automatically discovered from .agent/skills/audit-code/SKILL.md.',
1602
1604
  'The /audit-code slash command is also available from .gemini/commands/audit-code.toml.',
1603
- 'Use the shared auditor MCP server when Antigravity needs structured audit state instead of free-form shell guesses.',
1605
+ 'Use `audit-code next-step` directly; use the shared auditor MCP launcher only when direct shell access is unavailable.',
1604
1606
  ],
1605
1607
  profile: {
1606
1608
  writeAntigravity: true,
package/dist/cli.d.ts CHANGED
@@ -2,6 +2,11 @@ import type { SessionConfig } from "./types/sessionConfig.js";
2
2
  type UiMode = "visible" | "headless";
3
3
  declare function getFlag(argv: string[], name: string, fallback?: string): string | undefined;
4
4
  declare function hasFlag(argv: string[], name: string): boolean;
5
+ export declare function resolveHostDispatchCapability(options: {
6
+ explicit?: boolean;
7
+ sessionConfig: SessionConfig;
8
+ env?: NodeJS.ProcessEnv;
9
+ }): boolean;
5
10
  declare function getArtifactsDir(argv: string[]): string;
6
11
  declare function getRootDir(argv: string[]): string;
7
12
  declare function warnIfNotGitRepo(root: string): void;
package/dist/cli.js CHANGED
@@ -28,11 +28,11 @@ import { buildAuditCodeHandoff, writeAuditCodeHandoffArtifacts, } from "./superv
28
28
  import { getSessionConfigPath, loadSessionConfig, readSessionConfigFile, } from "./supervisor/sessionConfig.js";
29
29
  import { clearDispatchFiles, buildRunId, ensureSupervisorDirs, getRunPaths, writeDispatchBatchFiles, writeWorkerTaskFiles, } from "./io/runArtifacts.js";
30
30
  import { renderWorkerPrompt } from "./prompts/renderWorkerPrompt.js";
31
- import { buildReviewPackets, orderTasksForPacketReview, } from "./orchestrator/reviewPackets.js";
31
+ import { buildReviewPackets, orderTasksForPacketReview, estimateTaskGroupTokens, } from "./orchestrator/reviewPackets.js";
32
32
  import { buildFileAnchorSummary, } from "./orchestrator/fileAnchors.js";
33
33
  import { LOCAL_SUBPROCESS_PROVIDER_NAME } from "./providers/constants.js";
34
34
  import { runAuditCodeMcpServer } from "./mcp/server.js";
35
- import { scheduleWave, buildProviderModelKey, readQuotaState, recordWaveOutcome, resolveLimits, resolveHostActiveSubagentLimit, probeProvider, computeMaxSafeConcurrency, getQuotaStatePath, } from "./quota/index.js";
35
+ import { scheduleWave, buildProviderModelKey, readQuotaState, recordWaveOutcome, resolveLimits, resolveHostActiveSubagentLimit, probeProvider, computeMaxSafeConcurrency, getQuotaStatePath, detectRateLimitError, computeCooldownUntil, runSlidingWindow, LearnedQuotaSource, CompositeQuotaSource, } from "./quota/index.js";
36
36
  const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
37
37
  const ADVANCE_AUDIT_CONTRACT_VERSION = "audit-code/v1alpha1";
38
38
  const WORKER_RESULT_CONTRACT_VERSION = "audit-code-worker-result/v1alpha1";
@@ -87,6 +87,22 @@ function getOptionalBooleanFlag(argv, name) {
87
87
  }
88
88
  throw new Error(`${name} must be either true or false.`);
89
89
  }
90
+ function optionalBooleanEnv(value) {
91
+ if (value === "true")
92
+ return true;
93
+ if (value === "false")
94
+ return false;
95
+ return undefined;
96
+ }
97
+ export function resolveHostDispatchCapability(options) {
98
+ if (options.explicit !== undefined) {
99
+ return options.explicit;
100
+ }
101
+ if (options.sessionConfig.host_can_dispatch_subagents !== undefined) {
102
+ return options.sessionConfig.host_can_dispatch_subagents;
103
+ }
104
+ return optionalBooleanEnv((options.env ?? process.env).AUDIT_CODE_HOST_CAN_DISPATCH) ?? true;
105
+ }
90
106
  function toBase64Url(value) {
91
107
  return Buffer.from(value, "utf8").toString("base64url");
92
108
  }
@@ -212,18 +228,6 @@ function getQuotaProbeMode(argv, sessionConfig) {
212
228
  return raw;
213
229
  return "auto";
214
230
  }
215
- function detectRateLimitError(errorText) {
216
- const lower = errorText.toLowerCase();
217
- return lower.includes("429") || lower.includes("rate limit") || lower.includes("rate_limit");
218
- }
219
- function defaultCooldownUntil(resetAtHeader) {
220
- if (resetAtHeader) {
221
- const t = new Date(resetAtHeader).getTime();
222
- if (!Number.isNaN(t))
223
- return new Date(t).toISOString();
224
- }
225
- return new Date(Date.now() + 60_000).toISOString();
226
- }
227
231
  function resolveRunProviderName(argv, sessionConfig) {
228
232
  return resolveFreshSessionProviderName(getExplicitProvider(argv), sessionConfig);
229
233
  }
@@ -535,50 +539,6 @@ function mergeAndIngestCommand(artifactsDir, runId) {
535
539
  artifactsDir,
536
540
  ]);
537
541
  }
538
- function renderCapabilityCheckPrompt(params) {
539
- const yesCommand = nextStepCommand(params.root, params.artifactsDir, [
540
- "--host-can-dispatch-subagents",
541
- "true",
542
- ]);
543
- const noCommand = nextStepCommand(params.root, params.artifactsDir, [
544
- "--host-can-dispatch-subagents",
545
- "false",
546
- ]);
547
- return [
548
- "# audit-code capability check",
549
- "",
550
- "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?",
551
- "",
552
- "Do not run shell commands to answer this. Do not inspect packet prompts, schemas, or backend command catalogs.",
553
- "",
554
- "**If auditor MCP tools are available** (preferred — no shell required):",
555
- "",
556
- "Call `auditor_report_capability` with:",
557
- "- `can_dispatch_subagents: true` if the `task` tool or equivalent subagent dispatch is available",
558
- "- `can_dispatch_subagents: false` if not",
559
- "- Optionally `can_restrict_subagent_tools: true` and/or `can_select_subagent_model: true`",
560
- "- If the host documents or exposes a hard cap on simultaneously active subagents, include `max_active_subagents`.",
561
- "",
562
- "Read the `prompt_content` field in the tool response and follow it.",
563
- "",
564
- "**Fallback — if auditor MCP tools are not available:**",
565
- "",
566
- "If callable subagents are available, run:",
567
- "",
568
- ` ${yesCommand}`,
569
- "",
570
- "If callable subagents are not available, run:",
571
- "",
572
- ` ${noCommand}`,
573
- "",
574
- "If the host can also restrict tools per subagent or select models per subagent, add the matching `--host-can-restrict-subagent-tools true` or `--host-can-select-subagent-model true` flags to the same command. Omit those flags when unsure.",
575
- "",
576
- "If the host has a known active-subagent ceiling, add `--host-max-active-subagents <n>` to the same command. For Codex Desktop, use 6.",
577
- "",
578
- "After the command writes the next step, read and follow only its `prompt_path`.",
579
- "",
580
- ].join("\n");
581
- }
582
542
  function renderDispatchReviewPrompt(params) {
583
543
  const mergeCommand = mergeAndIngestCommand(params.artifactsDir, params.activeReviewRun.run_id);
584
544
  const continueCommand = nextStepCommand(params.root, params.artifactsDir);
@@ -588,32 +548,21 @@ function renderDispatchReviewPrompt(params) {
588
548
  const toolsLine = params.hostCanRestrictSubagentTools
589
549
  ? "Restrict review subagents to read/search plus the packet submit command named in their prompt. Do not give them source edit/write tools."
590
550
  : "Do not ask the user about per-subagent tool restrictions; this host did not report a callable restriction facility.";
591
- const runId = params.activeReviewRun.run_id;
592
551
  const dispatchDataLines = params.dispatchQuotaPath
593
552
  ? [
594
- "**If auditor MCP tools are available** (preferred):",
553
+ "Read these generated files unless the current tool response already included equivalent `dispatch_plan_entries` and `dispatch_quota` fields:",
595
554
  "",
596
- "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.",
555
+ ` Dispatch plan: ${params.dispatchPlanPath}`,
556
+ ` Dispatch quota: ${params.dispatchQuotaPath}`,
597
557
  "",
598
- "Use the `wave_size` from `dispatch_quota`. If `cooldown_until` is non-null, wait until that timestamp before starting the first wave.",
558
+ "Use the `wave_size` from the quota data. If `cooldown_until` is non-null, wait until that timestamp before starting the first wave.",
599
559
  "",
600
- "`dispatch_quota.host_concurrency_limit` records any detected hard host cap that contributed to `wave_size`.",
560
+ "`host_concurrency_limit` records any detected hard host cap that contributed to `wave_size`.",
601
561
  "",
602
562
  "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.",
603
- "",
604
- "**Fallback — if auditor MCP tools are not available:** Read both of these files:",
605
- "",
606
- ` Dispatch plan: ${params.dispatchPlanPath}`,
607
- ` Dispatch quota: ${params.dispatchQuotaPath}`,
608
- "",
609
- "Apply the same wave logic from the quota file.",
610
563
  ]
611
564
  : [
612
- "**If auditor MCP tools are available** (preferred):",
613
- "",
614
- "The dispatch plan entries are in the `dispatch_plan_entries` field of the tool response that returned this step.",
615
- "",
616
- "**Fallback — if auditor MCP tools are not available:** Read this dispatch plan JSON:",
565
+ "Read this generated dispatch plan unless the current tool response already included equivalent `dispatch_plan_entries`:",
617
566
  "",
618
567
  ` ${params.dispatchPlanPath}`,
619
568
  "",
@@ -637,9 +586,7 @@ function renderDispatchReviewPrompt(params) {
637
586
  "",
638
587
  "**After all waves complete:**",
639
588
  "",
640
- "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.",
641
- "",
642
- "Fallback — if auditor MCP tools are not available, run exactly:",
589
+ "Run exactly:",
643
590
  "",
644
591
  ` ${mergeCommand}`,
645
592
  "",
@@ -1185,6 +1132,10 @@ async function cmdNextStep(argv) {
1185
1132
  console.log(JSON.stringify(step, null, 2));
1186
1133
  return;
1187
1134
  }
1135
+ const hostCanDispatch = resolveHostDispatchCapability({
1136
+ explicit: hostCanDispatchSubagents,
1137
+ sessionConfig,
1138
+ });
1188
1139
  const result = await runDeterministicForNextStep({
1189
1140
  root,
1190
1141
  artifactsDir,
@@ -1226,35 +1177,7 @@ async function cmdNextStep(argv) {
1226
1177
  console.log(JSON.stringify(step, null, 2));
1227
1178
  return;
1228
1179
  }
1229
- if (hostCanDispatchSubagents === undefined) {
1230
- const yesCommand = nextStepCommand(root, artifactsDir, [
1231
- "--host-can-dispatch-subagents",
1232
- "true",
1233
- ]);
1234
- const noCommand = nextStepCommand(root, artifactsDir, [
1235
- "--host-can-dispatch-subagents",
1236
- "false",
1237
- ]);
1238
- const step = await writeCurrentStep({
1239
- artifactsDir,
1240
- stepKind: "capability_check",
1241
- status: "ready",
1242
- runId: result.activeReviewRun.run_id,
1243
- allowedCommands: [yesCommand, noCommand],
1244
- stopCondition: "Run exactly one next-step command with an explicit host dispatch capability.",
1245
- repoRoot: root,
1246
- artifactPaths: {
1247
- active_review_task: result.activeReviewRun.task_path,
1248
- active_review_prompt: result.activeReviewRun.prompt_path,
1249
- pending_audit_tasks: result.activeReviewRun.pending_audit_tasks_path ?? null,
1250
- single_task_prompt: join(artifactsDir, "dispatch", "current-single-task-prompt.md"),
1251
- },
1252
- prompt: renderCapabilityCheckPrompt({ root, artifactsDir }),
1253
- });
1254
- console.log(JSON.stringify(step, null, 2));
1255
- return;
1256
- }
1257
- if (!hostCanDispatchSubagents) {
1180
+ if (!hostCanDispatch) {
1258
1181
  const singleTaskPromptPath = join(artifactsDir, "dispatch", "current-single-task-prompt.md");
1259
1182
  const workerCommand = renderCommand(result.activeReviewRun.worker_command);
1260
1183
  const step = await writeCurrentStep({
@@ -1299,7 +1222,7 @@ async function cmdNextStep(argv) {
1299
1222
  mergeCommand,
1300
1223
  continueCommand,
1301
1224
  ],
1302
- stopCondition: "Dispatch every packet, call auditor_merge_and_ingest once, then call auditor_continue_audit.",
1225
+ stopCondition: "Dispatch every packet, run merge-and-ingest once, then run next-step.",
1303
1226
  repoRoot: root,
1304
1227
  artifactPaths: {
1305
1228
  dispatch_plan: dispatch.dispatch_plan_path,
@@ -1487,11 +1410,15 @@ async function cmdRunToCompletion(argv) {
1487
1410
  const quotaState = await readQuotaState();
1488
1411
  const providerModelKey = buildProviderModelKey(provider.name, hostModel);
1489
1412
  const quotaStateEntry = quotaState.entries[providerModelKey] ?? null;
1413
+ const allCandidateTasks = buildPendingAuditTasks(bundle);
1414
+ const candidateGroups = chunkArray(allCandidateTasks.slice(0, parallelWorkers * agentBatchSize), agentBatchSize);
1415
+ const slotTokenEstimates = candidateGroups.map((g) => estimateTaskGroupTokens(g));
1490
1416
  const waveSchedule = scheduleWave({
1491
1417
  providerName: resolveFreshSessionProviderName(getExplicitProvider(argv), sessionConfig),
1492
1418
  sessionConfig,
1493
1419
  hostModel,
1494
1420
  requestedConcurrency: parallelWorkers,
1421
+ estimatedSlotTokens: slotTokenEstimates,
1495
1422
  quotaStateEntry,
1496
1423
  });
1497
1424
  const waveSize = waveSchedule.wave_size;
@@ -1503,8 +1430,7 @@ async function cmdRunToCompletion(argv) {
1503
1430
  await new Promise((r) => setTimeout(r, cappedWait));
1504
1431
  }
1505
1432
  }
1506
- const allPendingTasks = buildPendingAuditTasks(bundle);
1507
- const taskGroups = chunkArray(allPendingTasks.slice(0, waveSize * agentBatchSize), agentBatchSize);
1433
+ const taskGroups = candidateGroups.slice(0, waveSize);
1508
1434
  const workerSlots = [];
1509
1435
  for (const rawGroup of taskGroups) {
1510
1436
  const group = await addFileLineCountHints(root, rawGroup);
@@ -1543,7 +1469,7 @@ async function cmdRunToCompletion(argv) {
1543
1469
  pending_audit_tasks_path: slot.pendingTasksPath,
1544
1470
  })), workerSlots.flatMap((slot) => slot.group));
1545
1471
  const parallelStartedAt = new Date().toISOString();
1546
- const launchResults = await Promise.allSettled(workerSlots.map((slot) => provider.launch({
1472
+ const { results: launchResults } = await runSlidingWindow(workerSlots.map((slot) => () => provider.launch({
1547
1473
  repoRoot: root,
1548
1474
  runId: slot.runId,
1549
1475
  obligationId,
@@ -1554,7 +1480,7 @@ async function cmdRunToCompletion(argv) {
1554
1480
  stderrPath: slot.paths.stderrPath,
1555
1481
  uiMode,
1556
1482
  timeoutMs,
1557
- })));
1483
+ })), waveSize);
1558
1484
  const launchErrorsByRunId = new Map();
1559
1485
  for (let index = 0; index < launchResults.length; index++) {
1560
1486
  const outcome = launchResults[index];
@@ -1666,12 +1592,14 @@ async function cmdRunToCompletion(argv) {
1666
1592
  }
1667
1593
  // Record outcome for adaptive learning (best-effort — never blocks dispatch)
1668
1594
  {
1669
- const hasRateLimit = batchErrors.some(detectRateLimitError);
1595
+ const rateLimitResults = batchErrors.map((e) => detectRateLimitError(e));
1596
+ const rateLimitHit = rateLimitResults.find((r) => r.isRateLimited);
1597
+ const retryAfterMs = rateLimitHit?.retryAfterMs ?? null;
1670
1598
  await recordWaveOutcome(providerModelKey, {
1671
1599
  concurrency: workerSlots.length,
1672
- estimated_tokens: waveSize * agentBatchSize * 900,
1673
- outcome: hasRateLimit ? "rate_limited" : batchErrors.length > 0 ? "timeout" : "success",
1674
- cooldown_until: hasRateLimit ? defaultCooldownUntil(null) : null,
1600
+ estimated_tokens: slotTokenEstimates.slice(0, workerSlots.length).reduce((a, b) => a + b, 0),
1601
+ outcome: rateLimitHit ? "rate_limited" : batchErrors.length > 0 ? "timeout" : "success",
1602
+ cooldown_until: rateLimitHit ? computeCooldownUntil(retryAfterMs) : null,
1675
1603
  }, sessionConfig.quota?.empirical_half_life_hours ?? 24).catch(() => undefined);
1676
1604
  }
1677
1605
  if (batchErrors.length > 0) {
@@ -2520,12 +2448,10 @@ async function prepareDispatchArtifacts(params) {
2520
2448
  });
2521
2449
  // Compute and write dispatch-quota.json
2522
2450
  const hostModel = params.hostModel ?? null;
2523
- const avgPacketTokens = plan.length > 0
2524
- ? Math.floor(plan.reduce((s, p) => s + p.complexity.estimated_tokens, 0) / plan.length)
2525
- : 0;
2451
+ const perPacketTokens = plan.map((p) => p.complexity.estimated_tokens);
2526
2452
  const quotaProviderName = resolveFreshSessionProviderName(undefined, sessionConfig);
2527
2453
  const quotaProviderKey = buildProviderModelKey(quotaProviderName, hostModel);
2528
- const quotaState = await readQuotaState().catch(() => ({ version: 1, entries: {} }));
2454
+ const quotaState = await readQuotaState().catch(() => ({ version: 2, entries: {} }));
2529
2455
  const quotaStateEntry = quotaState.entries[quotaProviderKey] ?? null;
2530
2456
  const hostConcurrencyLimit = resolveHostActiveSubagentLimit({
2531
2457
  explicitLimit: params.hostActiveSubagentLimit,
@@ -2536,12 +2462,12 @@ async function prepareDispatchArtifacts(params) {
2536
2462
  sessionConfig,
2537
2463
  hostModel,
2538
2464
  requestedConcurrency: sessionConfig.parallel_workers ?? plan.length,
2539
- estimatedPacketTokens: avgPacketTokens,
2465
+ estimatedSlotTokens: perPacketTokens,
2540
2466
  quotaStateEntry,
2541
2467
  hostConcurrencyLimit,
2542
2468
  });
2543
2469
  const dispatchQuota = {
2544
- contract_version: "audit-code-dispatch-quota/v1alpha1",
2470
+ contract_version: "audit-code-dispatch-quota/v1alpha2",
2545
2471
  run_id: runId,
2546
2472
  model: hostModel,
2547
2473
  resolved_limits: waveSchedule.resolved_limits,
@@ -2551,6 +2477,8 @@ async function prepareDispatchArtifacts(params) {
2551
2477
  wave_size: waveSchedule.wave_size,
2552
2478
  estimated_wave_tokens: waveSchedule.estimated_wave_tokens,
2553
2479
  cooldown_until: waveSchedule.cooldown_until,
2480
+ quota_source_snapshot: waveSchedule.quota_source_snapshot ?? null,
2481
+ backoff_state: null,
2554
2482
  };
2555
2483
  const dispatchQuotaPath = join(runDir, "dispatch-quota.json");
2556
2484
  await writeJsonFile(dispatchQuotaPath, dispatchQuota);
@@ -3277,13 +3205,15 @@ async function cmdQuota(argv) {
3277
3205
  const providerModelKey = buildProviderModelKey(providerName, hostModel);
3278
3206
  const { limits, source, confidence } = resolveLimits({ providerName, sessionConfig, hostModel });
3279
3207
  const probeResult = await probeProvider(providerName, probeMode);
3280
- const quotaState = await readQuotaState().catch(() => ({ version: 1, entries: {} }));
3208
+ const quotaState = await readQuotaState().catch(() => ({ version: 2, entries: {} }));
3281
3209
  const quotaStateEntry = quotaState.entries[providerModelKey] ?? null;
3282
3210
  const halfLifeHours = sessionConfig.quota?.empirical_half_life_hours ?? 24;
3283
3211
  const hostConcurrencyLimit = resolveHostActiveSubagentLimit({
3284
3212
  explicitLimit: getHostMaxActiveSubagents(argv),
3285
3213
  sessionConfig,
3286
3214
  });
3215
+ const quotaSource = new CompositeQuotaSource([new LearnedQuotaSource(halfLifeHours)]);
3216
+ const quotaSourceSnapshot = await quotaSource.queryCurrentUsage(providerModelKey).catch(() => null);
3287
3217
  const waveSchedule = scheduleWave({
3288
3218
  providerName,
3289
3219
  sessionConfig,
@@ -3291,6 +3221,7 @@ async function cmdQuota(argv) {
3291
3221
  requestedConcurrency: sessionConfig.parallel_workers ?? 1,
3292
3222
  quotaStateEntry,
3293
3223
  hostConcurrencyLimit,
3224
+ quotaSourceSnapshot,
3294
3225
  });
3295
3226
  console.log(JSON.stringify({
3296
3227
  provider: providerName,
@@ -3308,6 +3239,7 @@ async function cmdQuota(argv) {
3308
3239
  last_429_at: quotaStateEntry.last_429_at,
3309
3240
  }
3310
3241
  : null,
3242
+ quota_source_snapshot: quotaSourceSnapshot,
3311
3243
  wave_schedule: waveSchedule,
3312
3244
  quota_state_path: getQuotaStatePath(),
3313
3245
  }, null, 2));
@@ -250,16 +250,16 @@ function resourceListPayload() {
250
250
  export const promptRegistry = [
251
251
  {
252
252
  name: "audit-code",
253
- description: "Start or continue the autonomous audit loop through the auditor MCP tools.",
253
+ description: "Start or continue the autonomous audit loop through the next-step machine.",
254
254
  arguments: [],
255
255
  render() {
256
256
  return [
257
- "Use the auditor MCP tools as the primary interface to the backend wrapper.",
258
- "1. Call `start_audit`.",
259
- "2. If the audit is blocked, inspect `audit-code://handoff/current`.",
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`.",
260
261
  " Do not read `audit-code://artifacts/current` unless explicitly needed for a specific task; it is massive and consumes your context window.",
261
- "3. When the user provides additional evidence, call `import_results` or `import_runtime_updates`.",
262
- "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`.",
263
263
  ].join("\n");
264
264
  },
265
265
  },
@@ -306,7 +306,7 @@ function renderPrompt(name, args) {
306
306
  }
307
307
  return entry.render(args);
308
308
  }
309
- async function runContinueAudit(context, extraArgs = []) {
309
+ async function runContinueAudit(context, extraArgs = ["next-step"]) {
310
310
  const step = await parseCliJson(extraArgs, context);
311
311
  if (!step || typeof step !== "object" || Array.isArray(step))
312
312
  return step;
@@ -410,7 +410,7 @@ function toolDefinitions() {
410
410
  return [
411
411
  {
412
412
  name: "start_audit",
413
- description: "Start the audit wrapper and advance until completion or blocked operator handoff.",
413
+ description: "Compatibility adapter over audit-code next-step; returns one step contract.",
414
414
  inputSchema: {
415
415
  type: "object",
416
416
  properties: {
@@ -438,7 +438,7 @@ function toolDefinitions() {
438
438
  },
439
439
  {
440
440
  name: "continue_audit",
441
- description: "Continue the audit wrapper from the current artifacts directory.",
441
+ description: "Compatibility adapter over audit-code next-step from the current artifacts directory.",
442
442
  inputSchema: {
443
443
  type: "object",
444
444
  properties: {
@@ -542,7 +542,7 @@ function toolDefinitions() {
542
542
  },
543
543
  {
544
544
  name: "report_capability",
545
- 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.",
545
+ description: "Compatibility adapter that calls audit-code next-step with host subagent capability flags.",
546
546
  inputSchema: {
547
547
  type: "object",
548
548
  properties: {
@@ -632,7 +632,7 @@ export async function dispatchRequest(request, ctx) {
632
632
  name: "audit-code",
633
633
  version: ctx.version,
634
634
  },
635
- 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.",
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
636
  capabilities: {
637
637
  tools: { listChanged: false },
638
638
  resources: { subscribe: false, listChanged: false },
@@ -1,6 +1,9 @@
1
1
  import type { AuditTask } from "../types.js";
2
2
  import type { AuditPlanMetrics, ReviewPacket } from "../types/reviewPlanning.js";
3
3
  import type { GraphBundle } from "../types/graph.js";
4
+ export declare const ESTIMATED_TOKENS_PER_LINE = 4;
5
+ export declare const ESTIMATED_PACKET_PROMPT_TOKENS = 900;
6
+ export declare function estimateTaskGroupTokens(tasks: AuditTask[]): number;
4
7
  export interface BuildReviewPacketOptions {
5
8
  graphBundle?: GraphBundle;
6
9
  lineIndex?: Record<string, number>;