auditor-lambda 0.9.2 → 0.10.1

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 (44) hide show
  1. package/README.md +2 -9
  2. package/audit-code-wrapper-lib.mjs +19 -920
  3. package/dist/cli/args.d.ts +11 -0
  4. package/dist/cli/args.js +14 -1
  5. package/dist/cli/auditStep.d.ts +1 -33
  6. package/dist/cli/dispatch.d.ts +47 -0
  7. package/dist/cli/dispatch.js +146 -11
  8. package/dist/cli/mergeAndIngestCommand.js +36 -9
  9. package/dist/cli/nextStepCommand.js +3 -1
  10. package/dist/cli/prompts.d.ts +2 -0
  11. package/dist/cli/prompts.js +11 -0
  12. package/dist/cli/semanticReviewStep.js +12 -1
  13. package/dist/cli/steps.d.ts +15 -0
  14. package/dist/cli.js +1 -8
  15. package/dist/io/artifacts.d.ts +9 -1
  16. package/dist/io/artifacts.js +7 -0
  17. package/dist/io/runArtifacts.d.ts +14 -0
  18. package/dist/io/runArtifacts.js +23 -0
  19. package/dist/orchestrator/designReviewPrompt.d.ts +4 -1
  20. package/dist/orchestrator/designReviewPrompt.js +43 -2
  21. package/dist/orchestrator/executorResult.d.ts +25 -0
  22. package/dist/orchestrator/intakeExecutors.d.ts +19 -1
  23. package/dist/orchestrator/intakeExecutors.js +89 -3
  24. package/dist/orchestrator/nextStep.d.ts +1 -0
  25. package/dist/orchestrator/nextStep.js +1 -1
  26. package/dist/orchestrator/state.js +8 -1
  27. package/dist/providers/constants.d.ts +1 -1
  28. package/dist/providers/constants.js +1 -1
  29. package/dist/quota/index.d.ts +2 -0
  30. package/dist/quota/index.js +4 -0
  31. package/dist/reporting/synthesis.d.ts +8 -0
  32. package/dist/reporting/synthesis.js +16 -1
  33. package/dist/supervisor/operatorHandoff.js +2 -0
  34. package/dist/types/auditScope.d.ts +16 -2
  35. package/dist/validation/sessionConfig.js +35 -0
  36. package/docs/contracts.md +0 -16
  37. package/docs/operator-guide.md +6 -8
  38. package/package.json +1 -1
  39. package/schemas/audit_findings.schema.json +1 -0
  40. package/scripts/postinstall.mjs +0 -174
  41. package/skills/audit-code/SKILL.md +17 -1
  42. package/skills/audit-code/audit-code.prompt.md +25 -0
  43. package/dist/mcp/server.d.ts +0 -72
  44. package/dist/mcp/server.js +0 -765
@@ -26,6 +26,7 @@
26
26
  },
27
27
  "audited_file_count": { "type": "integer", "minimum": 0 },
28
28
  "excluded_file_count": { "type": "integer", "minimum": 0 },
29
+ "budget_deferred_task_count": { "type": "integer", "minimum": 0 },
29
30
  "runtime_validation_status_breakdown": {
30
31
  "type": "object",
31
32
  "additionalProperties": { "type": "integer", "minimum": 0 }
@@ -92,114 +92,6 @@ function renderOpenCodeExternalDirectoryPermission() {
92
92
  return { '*': 'allow' };
93
93
  }
94
94
 
95
- function renderGlobalMcpLauncher(installedPkgRoot) {
96
- return [
97
- "import { access, readFile, appendFile } from 'node:fs/promises';",
98
- "import { constants } from 'node:fs';",
99
- "import { spawn } from 'node:child_process';",
100
- "import { join } from 'node:path';",
101
- "import { homedir } from 'node:os';",
102
- '',
103
- "const repoRoot = process.env.AUDIT_CODE_REPO_ROOT || process.cwd();",
104
- "const artifactsDir = process.env.AUDIT_CODE_ARTIFACTS_DIR || join(repoRoot, '.audit-artifacts');",
105
- `const globalPackageRoot = ${JSON.stringify(installedPkgRoot)};`,
106
- "const logPath = join(homedir(), '.audit-code', 'mcp-server.log');",
107
- '',
108
- 'async function log(msg) {',
109
- ' try {',
110
- ' const ts = new Date().toISOString();',
111
- " await appendFile(logPath, `${ts} ${msg}\\n`, 'utf8');",
112
- ' } catch {',
113
- ' // ignore log failures',
114
- ' }',
115
- '}',
116
- '',
117
- 'async function exists(path) {',
118
- ' try {',
119
- ' await access(path, constants.F_OK);',
120
- ' return true;',
121
- ' } catch {',
122
- ' return false;',
123
- ' }',
124
- '}',
125
- '',
126
- 'function spawnForward(command, args) {',
127
- ' return new Promise((resolvePromise, rejectPromise) => {',
128
- ' const child = spawn(command, args, {',
129
- ' cwd: repoRoot,',
130
- ' env: process.env,',
131
- " stdio: ['inherit', 'inherit', 'inherit'],",
132
- ' });',
133
- " child.on('error', rejectPromise);",
134
- " child.on('exit', (code) => resolvePromise(code ?? 1));",
135
- ' });',
136
- '}',
137
- '',
138
- 'async function tryCandidates() {',
139
- " const localPackageEntrypoint = join(repoRoot, 'node_modules', 'auditor-lambda', 'audit-code.mjs');",
140
- " const localBin = process.platform === 'win32'",
141
- " ? join(repoRoot, 'node_modules', '.bin', 'audit-code.cmd')",
142
- " : join(repoRoot, 'node_modules', '.bin', 'audit-code');",
143
- " const repoPackageJsonPath = join(repoRoot, 'package.json');",
144
- " const globalPackageEntrypoint = globalPackageRoot ? join(globalPackageRoot, 'audit-code.mjs') : null;",
145
- " const sharedArgs = ['mcp', '--root', repoRoot, '--artifacts-dir', artifactsDir];",
146
- '',
147
- ' if (await exists(localPackageEntrypoint)) {',
148
- " await log(`launching local node_modules candidate: ${localPackageEntrypoint}`);",
149
- ' return await spawnForward(process.execPath, [localPackageEntrypoint, ...sharedArgs]);',
150
- ' }',
151
- '',
152
- " if (await exists(repoPackageJsonPath) && await exists(join(repoRoot, 'audit-code.mjs'))) {",
153
- ' try {',
154
- " const packageJson = JSON.parse(await readFile(repoPackageJsonPath, 'utf8'));",
155
- " if (packageJson?.name === 'auditor-lambda') {",
156
- " await log(`launching repo-root candidate: ${join(repoRoot, 'audit-code.mjs')}`);",
157
- " return await spawnForward(process.execPath, [join(repoRoot, 'audit-code.mjs'), ...sharedArgs]);",
158
- ' }',
159
- ' } catch {',
160
- ' // fall through to the next candidate',
161
- ' }',
162
- ' }',
163
- '',
164
- ' if (globalPackageEntrypoint && await exists(globalPackageEntrypoint)) {',
165
- " await log(`launching global candidate: ${globalPackageEntrypoint}`);",
166
- ' return await spawnForward(process.execPath, [globalPackageEntrypoint, ...sharedArgs]);',
167
- ' }',
168
- '',
169
- ' if (await exists(localBin)) {',
170
- " await log(`launching local bin candidate: ${localBin}`);",
171
- ' return await spawnForward(localBin, sharedArgs);',
172
- ' }',
173
- '',
174
- " const pathCandidate = process.platform === 'win32' ? 'audit-code.cmd' : 'audit-code';",
175
- " await log(`trying PATH candidate: ${pathCandidate}`);",
176
- ' let exitCode = await spawnForward(pathCandidate, sharedArgs).catch(() => null);',
177
- " if (typeof exitCode === 'number') {",
178
- ' return exitCode;',
179
- ' }',
180
- '',
181
- " exitCode = await spawnForward('npx', ['--no-install', 'audit-code', ...sharedArgs]).catch(() => null);",
182
- " if (typeof exitCode === 'number') {",
183
- ' return exitCode;',
184
- ' }',
185
- '',
186
- " await log('ERROR: no candidate found');",
187
- ' throw new Error(',
188
- " 'Unable to locate an audit-code executable. Install auditor-lambda globally or as a local dependency.',",
189
- ' );',
190
- '}',
191
- '',
192
- "log(`run-mcp-server.mjs started: node=${process.execPath} cwd=${repoRoot} globalPkg=${globalPackageRoot}`).catch(() => {});",
193
- 'const code = await tryCandidates().catch(async (err) => {',
194
- " await log(`FATAL: ${err.message}`);",
195
- ' process.stderr.write(err.message + "\\n");',
196
- ' return 1;',
197
- '});',
198
- 'process.exitCode = code;',
199
- '',
200
- ].join('\n');
201
- }
202
-
203
95
  function objectValue(value) {
204
96
  return value && typeof value === 'object' && !Array.isArray(value)
205
97
  ? value
@@ -284,7 +176,6 @@ function mergeOpenCodeGlobalConfig(existing) {
284
176
  const parsed = existing ? JSON.parse(existing) : {};
285
177
  const auditPermission = renderOpenCodePermissionConfig();
286
178
  const existingAuditor = objectValue(objectValue(parsed.agent).auditor);
287
- const pkgEntrypoint = replaceBackslashes(join(pkgRoot, 'audit-code.mjs'));
288
179
  return {
289
180
  ...parsed,
290
181
  command: {
@@ -298,15 +189,6 @@ function mergeOpenCodeGlobalConfig(existing) {
298
189
  subtask: false,
299
190
  },
300
191
  },
301
- mcp: {
302
- ...objectValue(parsed.mcp),
303
- auditor: {
304
- type: 'local',
305
- command: ['node', pkgEntrypoint, 'mcp'],
306
- enabled: true,
307
- timeout: 10000,
308
- },
309
- },
310
192
  permission: {
311
193
  ...mergeOpenCodePermissionConfig(parsed.permission, auditPermission),
312
194
  external_directory: { '*': 'allow' },
@@ -334,33 +216,6 @@ function claudePluginExternalDir() {
334
216
  return join(homedir(), '.claude', 'plugins', 'marketplaces', 'claude-plugins-official', 'external_plugins', 'audit-code');
335
217
  }
336
218
 
337
- function claudeDesktopConfigPath() {
338
- if (process.platform === 'win32') {
339
- return join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'Claude', 'claude_desktop_config.json');
340
- }
341
- if (process.platform === 'darwin') {
342
- return join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
343
- }
344
- return join(homedir(), '.config', 'Claude', 'claude_desktop_config.json');
345
- }
346
-
347
- function mergeClaudeDesktopConfig(existing, globalMcpLauncherPath) {
348
- const parsed = existing ? JSON.parse(existing) : {};
349
- const mcpServers = parsed.mcpServers && typeof parsed.mcpServers === 'object' && !Array.isArray(parsed.mcpServers)
350
- ? parsed.mcpServers
351
- : {};
352
- return {
353
- ...parsed,
354
- mcpServers: {
355
- ...mcpServers,
356
- auditor: {
357
- command: 'node',
358
- args: [replaceBackslashes(globalMcpLauncherPath)],
359
- },
360
- },
361
- };
362
- }
363
-
364
219
  function installMergedJson(path, buildMerged) {
365
220
  const existing = existsSync(path) ? readFileSync(path, 'utf8') : null;
366
221
  const merged = buildMerged(existing);
@@ -429,17 +284,6 @@ for (const install of installs) {
429
284
  }
430
285
  }
431
286
 
432
- // Install global MCP launcher for OpenCode (and other hosts that support global config)
433
- const globalMcpLauncherPath = join(homedir(), '.audit-code', 'run-mcp-server.mjs');
434
- try {
435
- const action = writeGeneratedFile(globalMcpLauncherPath, Buffer.from(renderGlobalMcpLauncher(pkgRoot)));
436
- console.log(`audit-code: ${action} global MCP launcher at ${globalMcpLauncherPath}`);
437
- succeeded++;
438
- } catch (err) {
439
- console.warn(`audit-code: could not install global MCP launcher (${err.message})`);
440
- failed++;
441
- }
442
-
443
287
  // Install OpenCode global command and MCP via merged config
444
288
  const opencodeGlobalConfig = join(homedir(), '.config', 'opencode', 'opencode.json');
445
289
  try {
@@ -515,22 +359,4 @@ try {
515
359
  failed++;
516
360
  }
517
361
 
518
- // Register auditor MCP server with Claude Desktop so /audit-code appears in its slash-command menu
519
- const claudeDesktopConfig = claudeDesktopConfigPath();
520
- try {
521
- const action = installMergedJson(claudeDesktopConfig, (existing) =>
522
- mergeClaudeDesktopConfig(existing, globalMcpLauncherPath),
523
- );
524
- console.log(`audit-code: ${action} Claude Desktop MCP server entry in ${claudeDesktopConfig}`);
525
- console.log(`audit-code: restart Claude Desktop for /audit-code to appear`);
526
- console.log(`audit-code: to target a specific repo, set AUDIT_CODE_REPO_ROOT in Claude Desktop's MCP env settings`);
527
- succeeded++;
528
- } catch (err) {
529
- console.warn(`audit-code: could not update Claude Desktop config (${err.message})`);
530
- console.warn(` To register manually, add "mcpServers.auditor" to:`);
531
- console.warn(` ${claudeDesktopConfig}`);
532
- console.warn(` with command "node" and args ["${replaceBackslashes(globalMcpLauncherPath)}"]`);
533
- failed++;
534
- }
535
-
536
362
  console.log(`audit-code: postinstall complete — ${succeeded} succeeded, ${failed} failed (${Date.now() - postinstallStart}ms)`);
@@ -22,7 +22,9 @@ dispatch them. The conversation orchestrator owns dispatch and ingestion control
22
22
  it should not perform broad review itself when subagents are available.
23
23
  Entering `/audit-code` is explicit user authorization to fan out those review
24
24
  subagents; do not require a separate delegation request before parallel
25
- dispatch.
25
+ dispatch — unless `confirmation_recommended` is true (agent_count exceeds
26
+ `sessionConfig.dispatch.confirm_threshold`, default 10), in which case pause for
27
+ user confirmation.
26
28
 
27
29
  If the host cannot delegate to subagents, the conversation orchestrator may
28
30
  complete exactly one assigned review task, ingest it through the provided backend
@@ -36,6 +38,20 @@ the orchestrator does not need to infer the first task from a broad batch prompt
36
38
  Subagent fan-out belongs to the host agent runtime rather than to repo-local
37
39
  backend provider settings.
38
40
 
41
+ ### Scope confirmation
42
+
43
+ The loader emits a scope summary after the intake step (the first `next-step`):
44
+ the resolved repo root, the auditable file count, whether git is available, and
45
+ any mis-scope smells. It echoes `Auditing <root>, <N> files, git: yes/no` so the
46
+ operator can see exactly what is about to be audited. When a mis-scope smell is
47
+ set — the resolved root has no `.git` but an ancestor does, or the root is a
48
+ workspace member of a parent monorepo — the loader pauses and requires explicit
49
+ confirmation before continuing. Expect the workflow to pause on the first step
50
+ when targeting a workspace subdirectory or a non-git root whose ancestor is a
51
+ repo; in the normal case the echo is informational and the run proceeds without
52
+ interruption. Resolution behaviour is unchanged — only the visibility and the
53
+ confirm gate are added.
54
+
39
55
  When dispatch-plan entries include provider-neutral complexity and
40
56
  `model_hint.tier` metadata, a capable host may map those tiers to its own
41
57
  subagent models. The backend should not prescribe concrete model names.
@@ -48,6 +48,31 @@ follow only that prompt. Do not read packet prompts, schemas, command catalogs,
48
48
  or handoff files unless the current step prompt explicitly instructs you to do
49
49
  so.
50
50
 
51
+ If the returned step is a dispatch step, before launching subagents check
52
+ `progress.confirmation_recommended` in `steps/current-step.json`:
53
+
54
+ - If `progress.confirmation_recommended` is `true`, pause and ask the user:
55
+ "Ready to launch **{progress.dispatch_summary}** — continue?"
56
+ Wait for an affirmative reply before proceeding with subagent dispatch.
57
+ - If `progress.confirmation_recommended` is `false` (or absent), proceed
58
+ immediately.
59
+
60
+ After the **first** `next-step` (the intake step) completes, confirm the audit
61
+ scope before proceeding. Read `scope_summary.json` from the `.audit-artifacts/`
62
+ directory (if absent, extract the JSON that follows the `SCOPE_SUMMARY:` marker
63
+ at the start of the step's `progress_summary`). It contains `repo_root`,
64
+ `auditable_file_count`, `git_available`, and `mis_scope_smells`. Then:
65
+
66
+ - Echo one informational line to the user:
67
+ `Auditing <repo_root>, <auditable_file_count> files, git: <yes|no>`.
68
+ - If `mis_scope_smells` is **non-empty**, display each smell as a warning and ask
69
+ `Auditing <repo_root>, <auditable_file_count> files, git: <yes|no> — proceed? (yes/no)`.
70
+ Wait for an affirmative reply before the next `next-step`. If the user declines,
71
+ stop and suggest the correct root (e.g. the ancestor git repo or monorepo root
72
+ named in the smell).
73
+ - If `mis_scope_smells` is empty, the echo is informational only — continue
74
+ automatically without interrupting the workflow.
75
+
51
76
  Use MCP tools only as a compatibility adapter when direct shell access to
52
77
  `audit-code next-step` is unavailable. The MCP `start_audit` and
53
78
  `continue_audit` tools return the same one-step contract; they are not a
@@ -1,72 +0,0 @@
1
- interface ServerOptions {
2
- root: string;
3
- artifactsDir: string;
4
- }
5
- interface JsonRpcRequest {
6
- jsonrpc?: string;
7
- id?: string | number | null;
8
- method?: string;
9
- params?: Record<string, unknown>;
10
- }
11
- interface JsonRpcResponse {
12
- jsonrpc: "2.0";
13
- id: string | number | null;
14
- result?: unknown;
15
- error?: {
16
- code: number;
17
- message: string;
18
- data?: unknown;
19
- };
20
- }
21
- interface ToolCallContext {
22
- root: string;
23
- artifactsDir: string;
24
- }
25
- export declare function parseContentLength(headerBlock: string): number;
26
- interface ResourceRegistryEntry {
27
- uri: string;
28
- name: string;
29
- description: string;
30
- mimeType: string;
31
- read: (context: ToolCallContext) => Promise<{
32
- mimeType: string;
33
- text: string;
34
- }>;
35
- }
36
- export declare const resourceRegistry: ResourceRegistryEntry[];
37
- interface PromptRegistryEntry {
38
- name: string;
39
- description: string;
40
- arguments: Array<{
41
- name: string;
42
- required?: boolean;
43
- description: string;
44
- }>;
45
- render: (args: Record<string, unknown> | undefined) => string;
46
- }
47
- export declare const promptRegistry: PromptRegistryEntry[];
48
- /**
49
- * Extract zero or more complete Content-Length framed messages from a buffer.
50
- * Returns an array of parsed body strings and the remaining unconsumed buffer.
51
- * On framing errors, emits a framing error response via `emit` and resets the buffer.
52
- */
53
- export declare function extractFrames(buffer: Buffer, emit: (response: JsonRpcResponse) => void): {
54
- bodies: string[];
55
- remaining: Buffer<ArrayBufferLike>;
56
- };
57
- interface DispatchContext {
58
- version: string;
59
- defaults: ServerOptions;
60
- shutdownRequested: boolean;
61
- }
62
- /**
63
- * Dispatch a single JSON-RPC request and return the response(s) to send,
64
- * plus updated shutdown state.
65
- */
66
- export declare function dispatchRequest(request: JsonRpcRequest, ctx: DispatchContext): Promise<{
67
- responses: JsonRpcResponse[];
68
- shutdownRequested: boolean;
69
- exit?: number;
70
- }>;
71
- export declare function runAuditCodeMcpServer(argv: string[]): Promise<void>;
72
- export {};