auditor-lambda 0.6.3 → 0.6.5

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.
@@ -1,4 +1,4 @@
1
- import type { SessionConfig, DispatchModelHint } from "@audit-tools/shared";
1
+ import type { ProviderRateLimits, SessionConfig, DispatchModelHint } from "@audit-tools/shared";
2
2
  import type { ArtifactBundle } from "../io/artifacts.js";
3
3
  import type { AuditTask } from "../types.js";
4
4
  export declare const LARGE_FILE_PACKET_TARGET_LINES = 2500;
@@ -76,5 +76,6 @@ export declare function prepareDispatchArtifacts(params: {
76
76
  root?: string;
77
77
  sessionConfig?: SessionConfig;
78
78
  hostModel?: string | null;
79
+ queryLimits?: (model: string | null) => Promise<ProviderRateLimits | null>;
79
80
  hostActiveSubagentLimit?: number | null;
80
81
  }): Promise<PrepareDispatchResult>;
@@ -2,12 +2,13 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
2
  import { existsSync } from "node:fs";
3
3
  import { isAbsolute, join, relative, resolve } from "node:path";
4
4
  import { isFileMissingError, readJsonFile, writeJsonFile } from "@audit-tools/shared";
5
+ import { buildQuotaSource } from "@audit-tools/shared/quota/compositeQuotaSource";
5
6
  import { loadArtifactBundle } from "../io/artifacts.js";
6
7
  import { orderTasksForPacketReview, buildReviewPackets, sizeIndexFromManifest, } from "../orchestrator/reviewPackets.js";
7
8
  import { buildFileAnchorSummary } from "../orchestrator/fileAnchors.js";
8
9
  import { resolveFreshSessionProviderName } from "../providers/index.js";
9
10
  import { loadSessionConfig } from "../supervisor/sessionConfig.js";
10
- import { scheduleWave, buildProviderModelKey, readQuotaState, resolveHostActiveSubagentLimit, lookupDiscoveredLimits, } from "../quota/index.js";
11
+ import { scheduleWave, buildProviderModelKey, readQuotaState, resolveHostActiveSubagentLimit, lookupDiscoveredLimits, mergeDiscoveredLimits, } from "../quota/index.js";
11
12
  import { taskResultPath, packetPromptPath, artifactNameForId, toBase64Url, fromBase64Url, getFlag, } from "./args.js";
12
13
  export const LARGE_FILE_PACKET_TARGET_LINES = 2500;
13
14
  export const SMALL_MODEL_HINT_MAX_LINES = 500;
@@ -462,7 +463,15 @@ export async function prepareDispatchArtifacts(params) {
462
463
  explicitLimit: params.hostActiveSubagentLimit,
463
464
  sessionConfig,
464
465
  });
466
+ const providerLimits = await params.queryLimits?.(hostModel)
467
+ .then((r) => r ? { ...r, source: "provider_query" } : null)
468
+ .catch(() => null)
469
+ ?? null;
465
470
  const dispatchCachedLimits = await lookupDiscoveredLimits(quotaProviderKey).catch(() => null);
471
+ const discoveredLimits = mergeDiscoveredLimits(providerLimits, dispatchCachedLimits);
472
+ const halfLifeHours = sessionConfig.quota?.empirical_half_life_hours ?? 24;
473
+ const quotaSource = buildQuotaSource({ halfLifeHours });
474
+ const quotaSourceSnapshot = await quotaSource.queryCurrentUsage(quotaProviderKey).catch(() => null);
466
475
  const waveSchedule = scheduleWave({
467
476
  providerName: quotaProviderName,
468
477
  sessionConfig,
@@ -471,7 +480,8 @@ export async function prepareDispatchArtifacts(params) {
471
480
  estimatedSlotTokens: perPacketTokens,
472
481
  quotaStateEntry,
473
482
  hostConcurrencyLimit,
474
- discoveredLimits: dispatchCachedLimits,
483
+ discoveredLimits,
484
+ quotaSourceSnapshot,
475
485
  });
476
486
  const dispatchQuota = {
477
487
  contract_version: "audit-code-dispatch-quota/v1alpha2",
package/dist/cli.js CHANGED
@@ -12,6 +12,7 @@ import { buildRuntimeValidationTasks, } from "./orchestrator/runtimeValidation.j
12
12
  import { initializeCoverageFromPlan } from "./orchestrator/planning.js";
13
13
  import { loadArtifactBundle, writeCoreArtifacts, promoteFinalAuditReport, AUDIT_REPORT_FILENAME, } from "./io/artifacts.js";
14
14
  import { isFileMissingError, readJsonFile, writeJsonFile, prefixValidationIssues, RunLogger } from "@audit-tools/shared";
15
+ import { buildQuotaSource } from "@audit-tools/shared/quota/compositeQuotaSource";
15
16
  import { validateArtifactBundle } from "./validation/artifacts.js";
16
17
  import { validateAuditResults, formatAuditResultIssues, } from "./validation/auditResults.js";
17
18
  import { validateConfiguredProviderEnvironment, validateSessionConfig, } from "./validation/sessionConfig.js";
@@ -35,7 +36,7 @@ import { renderWorkerPrompt } from "./prompts/renderWorkerPrompt.js";
35
36
  import { estimateTaskGroupTokens, sizeIndexFromManifest, } from "./orchestrator/reviewPackets.js";
36
37
  import { LOCAL_SUBPROCESS_PROVIDER_NAME } from "./providers/constants.js";
37
38
  import { runAuditCodeMcpServer } from "./mcp/server.js";
38
- import { scheduleWave, buildProviderModelKey, readQuotaState, recordWaveOutcome, resolveLimits, resolveHostActiveSubagentLimit, probeProvider, computeMaxSafeConcurrency, getQuotaStatePath, detectRateLimitError, computeCooldownUntil, runSlidingWindow, LearnedQuotaSource, CompositeQuotaSource, lookupDiscoveredLimits, updateDiscoveredLimits, mergeDiscoveredLimits, getHeaderExtractorForProvider, setQuotaStateDir, } from "./quota/index.js";
39
+ import { scheduleWave, buildProviderModelKey, readQuotaState, recordWaveOutcome, resolveLimits, resolveHostActiveSubagentLimit, probeProvider, computeMaxSafeConcurrency, getQuotaStatePath, detectRateLimitError, computeCooldownUntil, runSlidingWindow, lookupDiscoveredLimits, updateDiscoveredLimits, mergeDiscoveredLimits, getHeaderExtractorForProvider, setQuotaStateDir, } from "./quota/index.js";
39
40
  // Re-exports from extracted modules
40
41
  export { resolveHostDispatchCapability, DIRECT_CLI_DEFAULTS, getFlag, hasFlag, getOptionalBooleanFlag, getArtifactsDir, getRootDir, getBatchResultsDir, getMaxRuns, getAgentBatchSize, getParallelWorkers, getTimeoutMs, chunkArray, getUiMode, looksLikeCliFlag, countLines, warnIfNotGitRepo, } from "./cli/args.js";
41
42
  import { DIRECT_CLI_DEFAULTS, getFlag, hasFlag, getOptionalBooleanFlag, fromBase64Url, renderCommand, summarizeLaunchExit, taskResultPath, readStdinText, getArtifactsDir, getRootDir, warnIfNotGitRepo, getBatchResultsDir, getMaxRuns, getAgentBatchSize, getParallelWorkers, getTimeoutMs, getExplicitProvider, getHostModel, getHostMaxActiveSubagents, getQuotaProbeMode, resolveRunProviderName, chunkArray, getUiMode, looksLikeCliFlag, resolveHostDispatchCapability, countLines, listBatchResultFiles, } from "./cli/args.js";
@@ -915,11 +916,16 @@ async function renderSemanticReviewStep(params) {
915
916
  },
916
917
  });
917
918
  }
919
+ const sessionConfig = await loadSessionConfig(artifactsDir).catch(() => ({}));
920
+ const provider = createFreshSessionProvider(undefined, sessionConfig);
918
921
  const dispatch = await prepareDispatchArtifacts({
919
922
  packageRoot,
920
923
  runId: activeReviewRun.run_id,
921
924
  artifactsDir,
922
925
  root,
926
+ sessionConfig,
927
+ hostModel: sessionConfig.block_quota?.host_model ?? null,
928
+ queryLimits: provider.queryLimits?.bind(provider),
923
929
  hostActiveSubagentLimit: params.hostMaxActiveSubagents,
924
930
  });
925
931
  const mergeCommand = mergeAndIngestCommand(artifactsDir, activeReviewRun.run_id);
@@ -1473,7 +1479,7 @@ async function cmdRunToCompletion(argv) {
1473
1479
  const cachedLimits = await lookupDiscoveredLimits(providerModelKey).catch(() => null);
1474
1480
  const discoveredLimits = mergeDiscoveredLimits(providerLimits, cachedLimits);
1475
1481
  const halfLifeHours = sessionConfig.quota?.empirical_half_life_hours ?? 24;
1476
- const quotaSource = new CompositeQuotaSource([new LearnedQuotaSource(halfLifeHours)]);
1482
+ const quotaSource = buildQuotaSource({ halfLifeHours });
1477
1483
  const quotaSourceSnapshot = await quotaSource.queryCurrentUsage(providerModelKey).catch(() => null);
1478
1484
  const hostConcurrencyLimit = resolveHostActiveSubagentLimit({
1479
1485
  sessionConfig,
@@ -2155,12 +2161,18 @@ async function cmdPrepareDispatch(argv) {
2155
2161
  const runId = getFlag(argv, "--run-id");
2156
2162
  if (!runId)
2157
2163
  throw new Error("prepare-dispatch requires --run-id <run_id>");
2164
+ const artifactsDir = getArtifactsDir(argv);
2165
+ const sessionConfig = await loadSessionConfig(artifactsDir).catch(() => ({}));
2166
+ const provider = createFreshSessionProvider(getExplicitProvider(argv), sessionConfig);
2167
+ const hostModel = getHostModel(argv) ?? sessionConfig.block_quota?.host_model ?? null;
2158
2168
  const result = await prepareDispatchArtifacts({
2159
2169
  packageRoot,
2160
2170
  runId,
2161
- artifactsDir: getArtifactsDir(argv),
2171
+ artifactsDir,
2162
2172
  root: getFlag(argv, "--root") ? getRootDir(argv) : undefined,
2163
- hostModel: getHostModel(argv),
2173
+ sessionConfig,
2174
+ hostModel,
2175
+ queryLimits: provider.queryLimits?.bind(provider),
2164
2176
  hostActiveSubagentLimit: getHostMaxActiveSubagents(argv),
2165
2177
  });
2166
2178
  console.log(JSON.stringify(result, null, 2));
@@ -2958,7 +2970,7 @@ async function cmdQuota(argv) {
2958
2970
  explicitLimit: getHostMaxActiveSubagents(argv),
2959
2971
  sessionConfig,
2960
2972
  });
2961
- const quotaSource = new CompositeQuotaSource([new LearnedQuotaSource(halfLifeHours)]);
2973
+ const quotaSource = buildQuotaSource({ halfLifeHours });
2962
2974
  const quotaSourceSnapshot = await quotaSource.queryCurrentUsage(providerModelKey).catch(() => null);
2963
2975
  const queryDiscoveredLimits = await lookupDiscoveredLimits(providerModelKey).catch(() => null);
2964
2976
  const waveSchedule = scheduleWave({
@@ -22,8 +22,11 @@ function commandExists(command) {
22
22
  return result.status === 0;
23
23
  }
24
24
  export function resolveFreshSessionProviderName(name, sessionConfig = {}, options = {}) {
25
- const requestedProvider = name ?? sessionConfig.provider ?? "local-subprocess";
26
- if (requestedProvider !== "auto") {
25
+ const requestedProvider = name ?? sessionConfig.provider;
26
+ const shouldAutoDetect = requestedProvider === undefined ||
27
+ requestedProvider === "auto" ||
28
+ (name === undefined && requestedProvider === "local-subprocess");
29
+ if (!shouldAutoDetect) {
27
30
  return requestedProvider;
28
31
  }
29
32
  const env = options.env ?? process.env;
@@ -65,8 +68,12 @@ export function resolveFreshSessionProviderName(name, sessionConfig = {}, option
65
68
  export function createFreshSessionProvider(name, sessionConfig = {}) {
66
69
  const providerName = resolveFreshSessionProviderName(name, sessionConfig);
67
70
  const opentoken = sessionConfig.opentoken ?? {};
71
+ const requestedProvider = name ?? sessionConfig.provider;
72
+ const autoDetectionRequested = requestedProvider === undefined ||
73
+ requestedProvider === "auto" ||
74
+ (name === undefined && requestedProvider === "local-subprocess");
68
75
  if (providerName === "local-subprocess" &&
69
- (name ?? sessionConfig.provider) === "auto") {
76
+ autoDetectionRequested) {
70
77
  process.stderr.write("audit-code: auto provider resolved to local-subprocess — no capable agent provider detected. " +
71
78
  "Agent tasks will require manual dispatch. Configure claude-code, opencode, or subprocess-template " +
72
79
  "in session-config.json to automate them.\n");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auditor-lambda",
3
- "version": "0.6.3",
3
+ "version": "0.6.5",
4
4
  "private": false,
5
5
  "description": "Portable hybrid code-auditing framework for arbitrary repositories.",
6
6
  "type": "module",
@@ -5,6 +5,7 @@ import { mkdirSync, existsSync, readFileSync, writeFileSync } from 'fs';
5
5
  import { fileURLToPath } from 'url';
6
6
 
7
7
  const pkgRoot = dirname(dirname(fileURLToPath(import.meta.url)));
8
+ const packageVersion = JSON.parse(readFileSync(join(pkgRoot, 'package.json'), 'utf8')).version ?? '0.0.0';
8
9
  const promptSourceFile = join(pkgRoot, 'skills', 'audit-code', 'audit-code.prompt.md');
9
10
  const skillSourceFile = join(pkgRoot, 'skills', 'audit-code', 'SKILL.md');
10
11
  const codexOpenAiAgentSourceFile = join(pkgRoot, 'skills', 'audit-code', 'agents', 'openai.yaml');
@@ -99,8 +100,8 @@ function renderGlobalMcpLauncher(installedPkgRoot) {
99
100
  "import { join } from 'node:path';",
100
101
  "import { homedir } from 'node:os';",
101
102
  '',
102
- 'const repoRoot = process.cwd();',
103
- "const artifactsDir = join(repoRoot, '.audit-artifacts');",
103
+ "const repoRoot = process.env.AUDIT_CODE_REPO_ROOT || process.cwd();",
104
+ "const artifactsDir = process.env.AUDIT_CODE_ARTIFACTS_DIR || join(repoRoot, '.audit-artifacts');",
104
105
  `const globalPackageRoot = ${JSON.stringify(installedPkgRoot)};`,
105
106
  "const logPath = join(homedir(), '.audit-code', 'mcp-server.log');",
106
107
  '',
@@ -330,6 +331,37 @@ function mergeOpenCodeGlobalConfig(existing) {
330
331
  };
331
332
  }
332
333
 
334
+ function claudePluginExternalDir() {
335
+ return join(homedir(), '.claude', 'plugins', 'marketplaces', 'claude-plugins-official', 'external_plugins', 'audit-code');
336
+ }
337
+
338
+ function claudeDesktopConfigPath() {
339
+ if (process.platform === 'win32') {
340
+ return join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'Claude', 'claude_desktop_config.json');
341
+ }
342
+ if (process.platform === 'darwin') {
343
+ return join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
344
+ }
345
+ return join(homedir(), '.config', 'Claude', 'claude_desktop_config.json');
346
+ }
347
+
348
+ function mergeClaudeDesktopConfig(existing, globalMcpLauncherPath) {
349
+ const parsed = existing ? JSON.parse(existing) : {};
350
+ const mcpServers = parsed.mcpServers && typeof parsed.mcpServers === 'object' && !Array.isArray(parsed.mcpServers)
351
+ ? parsed.mcpServers
352
+ : {};
353
+ return {
354
+ ...parsed,
355
+ mcpServers: {
356
+ ...mcpServers,
357
+ auditor: {
358
+ command: 'node',
359
+ args: [replaceBackslashes(globalMcpLauncherPath)],
360
+ },
361
+ },
362
+ };
363
+ }
364
+
333
365
  function installMergedJson(path, buildMerged) {
334
366
  const existing = existsSync(path) ? readFileSync(path, 'utf8') : null;
335
367
  const merged = buildMerged(existing);
@@ -431,3 +463,57 @@ try {
431
463
  } catch (err) {
432
464
  console.warn(`audit-code: could not install Antigravity plugin (${err.message})`);
433
465
  }
466
+
467
+ // Install Claude Desktop plugin so /audit-code appears in the slash-command menu
468
+ // Claude Desktop reads external plugins from ~/.claude/plugins/marketplaces/claude-plugins-official/external_plugins/
469
+ const claudePluginDir = claudePluginExternalDir();
470
+ const claudePluginManifestPath = join(claudePluginDir, '.claude-plugin', 'plugin.json');
471
+ const claudePluginCommandPath = join(claudePluginDir, 'commands', 'audit-code.md');
472
+ const claudePluginSkillPath = join(claudePluginDir, 'skills', 'audit-code', 'SKILL.md');
473
+ try {
474
+ const manifest = {
475
+ name: 'audit-code',
476
+ description: 'Autonomous local-loop code auditing workflow',
477
+ version: packageVersion,
478
+ author: {
479
+ name: 'auditor-lambda',
480
+ url: 'https://github.com/OhOkThisIsFine/auditor-lambda',
481
+ },
482
+ homepage: 'https://github.com/OhOkThisIsFine/auditor-lambda',
483
+ repository: 'https://github.com/OhOkThisIsFine/auditor-lambda',
484
+ license: 'MIT',
485
+ keywords: ['audit', 'code-audit', 'static-analysis', 'orchestration'],
486
+ };
487
+ const manifestAction = writeGeneratedFile(
488
+ claudePluginManifestPath,
489
+ Buffer.from(JSON.stringify(manifest, null, 2) + '\n'),
490
+ );
491
+ console.log(`audit-code: ${manifestAction} Claude Desktop plugin manifest at ${claudePluginManifestPath}`);
492
+
493
+ const commandAction = writeGeneratedFile(claudePluginCommandPath, promptSource);
494
+ console.log(`audit-code: ${commandAction} Claude Desktop plugin command at ${claudePluginCommandPath}`);
495
+
496
+ const skillAction = writeGeneratedFile(claudePluginSkillPath, skillSource);
497
+ console.log(`audit-code: ${skillAction} Claude Desktop plugin skill at ${claudePluginSkillPath}`);
498
+
499
+ console.log(`audit-code: restart Claude Desktop for /audit-code to appear in the slash-command menu`);
500
+ } catch (err) {
501
+ console.warn(`audit-code: could not install Claude Desktop plugin (${err.message})`);
502
+ console.warn(` Plugin directory: ${claudePluginDir}`);
503
+ }
504
+
505
+ // Register auditor MCP server with Claude Desktop so /audit-code appears in its slash-command menu
506
+ const claudeDesktopConfig = claudeDesktopConfigPath();
507
+ try {
508
+ const action = installMergedJson(claudeDesktopConfig, (existing) =>
509
+ mergeClaudeDesktopConfig(existing, globalMcpLauncherPath),
510
+ );
511
+ console.log(`audit-code: ${action} Claude Desktop MCP server entry in ${claudeDesktopConfig}`);
512
+ console.log(`audit-code: restart Claude Desktop for /audit-code to appear`);
513
+ console.log(`audit-code: to target a specific repo, set AUDIT_CODE_REPO_ROOT in Claude Desktop's MCP env settings`);
514
+ } catch (err) {
515
+ console.warn(`audit-code: could not update Claude Desktop config (${err.message})`);
516
+ console.warn(` To register manually, add "mcpServers.auditor" to:`);
517
+ console.warn(` ${claudeDesktopConfig}`);
518
+ console.warn(` with command "node" and args ["${replaceBackslashes(globalMcpLauncherPath)}"]`);
519
+ }