auditor-lambda 0.3.6 → 0.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,7 +22,28 @@ The canonical asset for editor and conversation integrations is:
22
22
 
23
23
  Packaged installs and repository checkouts both ship that prompt asset.
24
24
 
25
- The recommended zero-guess setup path is now:
25
+ The intended user install is one global tool install:
26
+
27
+ ```bash
28
+ npm install -g auditor-lambda
29
+ ```
30
+
31
+ That makes `audit-code` available on `PATH`. During package install, the package
32
+ also writes user-level command/skill assets for hosts we can seed safely, including
33
+ the Claude command file and Codex skill bundle.
34
+
35
+ After that, invoke `/audit-code` in a supported host. The prompt self-bootstraps
36
+ the current repository by running:
37
+
38
+ ```bash
39
+ audit-code ensure --quiet
40
+ ```
41
+
42
+ That command writes or refreshes the repo-local assets only when they are missing
43
+ or stale, then normal audit execution continues without manual paths, provider
44
+ flags, or model-selection arguments.
45
+
46
+ The explicit repair and compatibility setup path remains:
26
47
 
27
48
  ```bash
28
49
  audit-code install
@@ -36,9 +57,9 @@ That bootstraps repo-local `/audit-code` surfaces for the hosts we can automate
36
57
  - VS Code prompt, custom agent, Copilot instructions, and `.vscode/mcp.json`
37
58
  - Antigravity planning-mode guidance plus the shared repo-local MCP launcher
38
59
 
39
- Re-run the same `audit-code install` command whenever the packaged prompt or
40
- skill changes. It is the single supported refresh path for the shared
41
- `.audit-code/install/*` assets and every generated host surface.
60
+ `audit-code ensure` refreshes those files automatically when the packaged prompt
61
+ or skill changes. Use `audit-code install` or `audit-code ensure --force` when
62
+ you intentionally want to rewrite every generated host surface on demand.
42
63
 
43
64
  After bootstrap, you can smoke-test the generated host assets and launcher from the repository root:
44
65
 
@@ -161,7 +182,8 @@ Optional backend config:
161
182
  ## Practical Guidance
162
183
 
163
184
  - use `/audit-code` in conversation as the canonical product surface
164
- - use `audit-code install` first when you want the lowest-friction repo bootstrap
185
+ - install once with `npm install -g auditor-lambda`, then let `/audit-code` run `audit-code ensure --quiet` in each repository
186
+ - use `audit-code install` when you want to repair or force-refresh repo-local host assets
165
187
  - use `audit-code prompt-path` to locate the packaged prompt asset
166
188
  - use `audit-code` from the repository root only when you need the repo-local backend fallback
167
189
  - use omitted provider or `local-subprocess` for the safest deterministic fallback behavior
@@ -253,6 +253,7 @@ function printHelp({ usageName, preferredEntrypoint }) {
253
253
  '',
254
254
  'Helper commands:',
255
255
  '- prompt-path prints the absolute path to the canonical /audit-code prompt asset',
256
+ '- ensure lazily bootstraps repo-local /audit-code assets when they are missing or stale',
256
257
  '- install bootstraps /audit-code into supported repo-local host surfaces',
257
258
  '- verify-install smoke-tests the generated host assets and repo-local MCP launchers after install',
258
259
  '- mcp starts the local stdio MCP server for repo-local IDE integrations',
@@ -1952,7 +1953,188 @@ async function verifyInstalledBootstrap(argv) {
1952
1953
  process.exitCode = issueCount > 0 ? 1 : 0;
1953
1954
  }
1954
1955
 
1955
- async function installBootstrap(argv) {
1956
+ async function readTextIfExists(path) {
1957
+ try {
1958
+ return await readFile(path, 'utf8');
1959
+ } catch {
1960
+ return null;
1961
+ }
1962
+ }
1963
+
1964
+ async function detectBootstrapRefreshReason(root, host) {
1965
+ const installManifestPath = join(
1966
+ root,
1967
+ '.audit-code',
1968
+ 'install',
1969
+ INSTALL_MANIFEST_FILENAME,
1970
+ );
1971
+
1972
+ if (!(await fileExists(installManifestPath))) {
1973
+ return 'missing_install_manifest';
1974
+ }
1975
+
1976
+ let installManifest;
1977
+ try {
1978
+ installManifest = JSON.parse(await readFile(installManifestPath, 'utf8'));
1979
+ } catch {
1980
+ return 'invalid_install_manifest';
1981
+ }
1982
+
1983
+ if (installManifest?.contract_version !== 'audit-code-install/v1alpha1') {
1984
+ return 'stale_install_manifest_contract';
1985
+ }
1986
+
1987
+ const assetPaths = installManifest.asset_paths ?? {};
1988
+ const hostCatalog = new Set(
1989
+ (installManifest.hosts ?? []).map((entry) => entry.host),
1990
+ );
1991
+
1992
+ for (const hostKey of getInstallHostKeys(host)) {
1993
+ if (!hostCatalog.has(hostKey)) {
1994
+ return `missing_host_surface:${hostKey}`;
1995
+ }
1996
+
1997
+ const definition = INSTALL_HOST_DEFINITIONS[hostKey];
1998
+ const requiredPathKeys = [
1999
+ definition.primary_path_key,
2000
+ ...definition.supporting_path_keys,
2001
+ ];
2002
+ for (const pathKey of requiredPathKeys) {
2003
+ const targetPath = assetPaths[pathKey];
2004
+ if (targetPath && !(await fileExists(targetPath))) {
2005
+ return `missing_host_asset:${hostKey}:${pathKey}`;
2006
+ }
2007
+ }
2008
+ }
2009
+
2010
+ const installedPrompt = await readTextIfExists(assetPaths.installedPromptPath);
2011
+ if (installedPrompt === null) {
2012
+ return 'missing_installed_prompt';
2013
+ }
2014
+ const sourcePrompt = await readFile(promptAssetPath, 'utf8');
2015
+ if (installedPrompt !== sourcePrompt) {
2016
+ return 'stale_installed_prompt';
2017
+ }
2018
+ const { body: sourcePromptBody } = splitFrontmatter(sourcePrompt);
2019
+
2020
+ const installedSkill = await readTextIfExists(assetPaths.installedSkillPath);
2021
+ if (installedSkill === null) {
2022
+ return 'missing_installed_skill';
2023
+ }
2024
+ const sourceSkill = (await readFile(skillAssetPath, 'utf8')).replace(/\r\n/g, '\n');
2025
+ if (installedSkill.replace(/\r\n/g, '\n') !== sourceSkill) {
2026
+ return 'stale_installed_skill';
2027
+ }
2028
+
2029
+ for (const hostKey of getInstallHostKeys(host)) {
2030
+ switch (hostKey) {
2031
+ case 'codex': {
2032
+ const codexSkill = await readTextIfExists(assetPaths.codexSkillPath);
2033
+ if (codexSkill?.replace(/\r\n/g, '\n') !== sourceSkill) {
2034
+ return 'stale_host_asset:codex:skill';
2035
+ }
2036
+ const codexPrompt = await readTextIfExists(assetPaths.codexPromptPath);
2037
+ if (codexPrompt !== sourcePrompt) {
2038
+ return 'stale_host_asset:codex:prompt';
2039
+ }
2040
+ break;
2041
+ }
2042
+ case 'opencode': {
2043
+ const opencodeSkill = await readTextIfExists(assetPaths.opencodeSkillPath);
2044
+ if (opencodeSkill?.replace(/\r\n/g, '\n') !== sourceSkill) {
2045
+ return 'stale_host_asset:opencode:skill';
2046
+ }
2047
+ const opencodePrompt = await readTextIfExists(assetPaths.opencodePromptPath);
2048
+ if (opencodePrompt !== sourcePrompt) {
2049
+ return 'stale_host_asset:opencode:prompt';
2050
+ }
2051
+ const opencodeCommand = await readTextIfExists(assetPaths.opencodeCommandPath);
2052
+ if (opencodeCommand === null) {
2053
+ return 'missing_host_asset:opencode:command';
2054
+ }
2055
+ if (splitFrontmatter(opencodeCommand).body !== sourcePromptBody.trimStart()) {
2056
+ return 'stale_host_asset:opencode:command';
2057
+ }
2058
+ break;
2059
+ }
2060
+ case 'vscode': {
2061
+ const vscodePrompt = await readTextIfExists(assetPaths.vscodePromptPath);
2062
+ if (vscodePrompt === null) {
2063
+ return 'missing_host_asset:vscode:prompt';
2064
+ }
2065
+ if (splitFrontmatter(vscodePrompt).body !== sourcePromptBody.trimStart()) {
2066
+ return 'stale_host_asset:vscode:prompt';
2067
+ }
2068
+ break;
2069
+ }
2070
+ default:
2071
+ break;
2072
+ }
2073
+ }
2074
+
2075
+ const launcherPath =
2076
+ assetPaths.mcpLauncherPath ?? installManifest.mcp_server_launcher_path;
2077
+ const launcher = launcherPath ? await readTextIfExists(launcherPath) : null;
2078
+ if (launcher === null) {
2079
+ return 'missing_mcp_launcher';
2080
+ }
2081
+ if (!launcher.includes('Unable to locate an audit-code executable')) {
2082
+ return 'stale_mcp_launcher';
2083
+ }
2084
+
2085
+ return null;
2086
+ }
2087
+
2088
+ async function ensureBootstrap(argv) {
2089
+ const host = (getFlag(argv, '--host') ?? DEFAULT_INSTALL_HOST).toLowerCase();
2090
+ const root = resolve(getFlag(argv, '--root') ?? '.');
2091
+ const quiet = hasFlag(argv, '--quiet');
2092
+ const force = hasFlag(argv, '--force');
2093
+ await assertDirectoryExists(root, 'Target repository root');
2094
+
2095
+ const reason = force
2096
+ ? 'forced'
2097
+ : await detectBootstrapRefreshReason(root, host);
2098
+
2099
+ if (reason) {
2100
+ const installed = await installBootstrap(argv, { quiet: true });
2101
+ const payload = {
2102
+ status: 'ok',
2103
+ action: 'installed',
2104
+ reason,
2105
+ host: installed.host,
2106
+ repo_root: installed.repo_root,
2107
+ install_manifest_path: installed.install_manifest_path,
2108
+ mcp_server_launcher_path: installed.mcp_server_launcher_path,
2109
+ host_count: installed.host_guidance.length,
2110
+ file_count: installed.files.length,
2111
+ };
2112
+ if (!quiet) {
2113
+ console.log(JSON.stringify(payload, null, 2));
2114
+ }
2115
+ return payload;
2116
+ }
2117
+
2118
+ const payload = {
2119
+ status: 'ok',
2120
+ action: 'skipped',
2121
+ reason: null,
2122
+ host,
2123
+ repo_root: root,
2124
+ install_manifest_path: join(
2125
+ root,
2126
+ '.audit-code',
2127
+ 'install',
2128
+ INSTALL_MANIFEST_FILENAME,
2129
+ ),
2130
+ };
2131
+ if (!quiet) {
2132
+ console.log(JSON.stringify(payload, null, 2));
2133
+ }
2134
+ return payload;
2135
+ }
2136
+
2137
+ async function installBootstrap(argv, options = {}) {
1956
2138
  const host = (getFlag(argv, '--host') ?? DEFAULT_INSTALL_HOST).toLowerCase();
1957
2139
  const root = resolve(getFlag(argv, '--root') ?? '.');
1958
2140
  await assertDirectoryExists(root, 'Target repository root');
@@ -2221,49 +2403,49 @@ async function installBootstrap(argv) {
2221
2403
  results.push({ path: sessionConfigPath, mode: 'created' });
2222
2404
  }
2223
2405
 
2224
- console.log(
2225
- JSON.stringify(
2226
- {
2227
- host,
2228
- repo_root: root,
2229
- installed_prompt_path: installedPromptPath,
2230
- installed_skill_path: installedSkillPath,
2231
- install_guide_path: installGuidePath,
2232
- install_manifest_path: installManifestPath,
2233
- mcp_server_launcher_path: mcpLauncherPath,
2234
- source_prompt_path: resolve(promptAssetPath),
2235
- source_skill_path: resolve(skillAssetPath),
2236
- files: results,
2237
- slash_command_surfaces: {
2238
- vscode_prompt: assetPaths.vscodePromptPath,
2239
- opencode_command: assetPaths.opencodeCommandPath,
2240
- },
2241
- instruction_surfaces: {
2242
- agents: assetPaths.agentsInstructionsPath,
2243
- copilot_instructions: assetPaths.copilotInstructionsPath,
2244
- },
2245
- mcp_surfaces: {
2246
- vscode_workspace: assetPaths.vscodeMcpConfigPath,
2247
- opencode_project: assetPaths.opencodeConfigPath,
2248
- codex_setup: assetPaths.codexMcpSetupPath,
2249
- shared_launcher: mcpLauncherPath,
2250
- claude_desktop_dxt: assetPaths.claudeDesktopDxtPath,
2251
- claude_desktop_mcpb: assetPaths.claudeDesktopMcpbPath,
2252
- antigravity_planning_guide: assetPaths.antigravityPlanningGuidePath,
2253
- },
2254
- host_guidance: hostGuidance,
2255
- unsupported_hosts: [],
2256
- next_steps: [
2257
- 'Open the repository in your preferred host and follow the matching host_guidance entry.',
2258
- `Open ${installGuidePath} for repo-local quick-start steps for Codex, Claude Desktop, OpenCode, VS Code, and Antigravity.`,
2259
- 'Run `audit-code verify-install` from the repository root to smoke-test the generated launchers and host configs.',
2260
- 'Use the shared MCP launcher as the source of truth for local stdio MCP registration across hosts.',
2261
- ],
2262
- },
2263
- null,
2264
- 2,
2265
- ),
2266
- );
2406
+ const payload = {
2407
+ host,
2408
+ repo_root: root,
2409
+ installed_prompt_path: installedPromptPath,
2410
+ installed_skill_path: installedSkillPath,
2411
+ install_guide_path: installGuidePath,
2412
+ install_manifest_path: installManifestPath,
2413
+ mcp_server_launcher_path: mcpLauncherPath,
2414
+ source_prompt_path: resolve(promptAssetPath),
2415
+ source_skill_path: resolve(skillAssetPath),
2416
+ files: results,
2417
+ slash_command_surfaces: {
2418
+ vscode_prompt: assetPaths.vscodePromptPath,
2419
+ opencode_command: assetPaths.opencodeCommandPath,
2420
+ },
2421
+ instruction_surfaces: {
2422
+ agents: assetPaths.agentsInstructionsPath,
2423
+ copilot_instructions: assetPaths.copilotInstructionsPath,
2424
+ },
2425
+ mcp_surfaces: {
2426
+ vscode_workspace: assetPaths.vscodeMcpConfigPath,
2427
+ opencode_project: assetPaths.opencodeConfigPath,
2428
+ codex_setup: assetPaths.codexMcpSetupPath,
2429
+ shared_launcher: mcpLauncherPath,
2430
+ claude_desktop_dxt: assetPaths.claudeDesktopDxtPath,
2431
+ claude_desktop_mcpb: assetPaths.claudeDesktopMcpbPath,
2432
+ antigravity_planning_guide: assetPaths.antigravityPlanningGuidePath,
2433
+ },
2434
+ host_guidance: hostGuidance,
2435
+ unsupported_hosts: [],
2436
+ next_steps: [
2437
+ 'Open the repository in your preferred host and follow the matching host_guidance entry.',
2438
+ `Open ${installGuidePath} for repo-local quick-start steps for Codex, Claude Desktop, OpenCode, VS Code, and Antigravity.`,
2439
+ 'Run `audit-code verify-install` from the repository root to smoke-test the generated launchers and host configs.',
2440
+ 'Use the shared MCP launcher as the source of truth for local stdio MCP registration across hosts.',
2441
+ ],
2442
+ };
2443
+
2444
+ if (!options.quiet) {
2445
+ console.log(JSON.stringify(payload, null, 2));
2446
+ }
2447
+
2448
+ return payload;
2267
2449
  }
2268
2450
 
2269
2451
  async function installHostPrompt(argv) {
@@ -2316,6 +2498,11 @@ export async function runAuditCodeWrapper({
2316
2498
  return;
2317
2499
  }
2318
2500
 
2501
+ if (argv[0] === 'ensure') {
2502
+ await ensureBootstrap(argv.slice(1));
2503
+ return;
2504
+ }
2505
+
2319
2506
  if (argv[0] === 'install') {
2320
2507
  await installBootstrap(argv.slice(1));
2321
2508
  return;
package/dist/cli.js CHANGED
@@ -1504,6 +1504,9 @@ function buildDispatchModelHint(complexity) {
1504
1504
  if (complexity.tags.some((tag) => tag === "external_analyzer_signal" || tag.startsWith("external_tool:"))) {
1505
1505
  deepReasons.push("external_analyzer_signal");
1506
1506
  }
1507
+ if (complexity.tags.includes("lens_verification")) {
1508
+ deepReasons.push("lens_verification");
1509
+ }
1507
1510
  if (deepReasons.length > 0) {
1508
1511
  return { tier: "deep", reasons: deepReasons };
1509
1512
  }
@@ -1680,15 +1683,31 @@ async function cmdPrepareDispatch(argv) {
1680
1683
  : [];
1681
1684
  const taskSections = packetTasks.flatMap((task) => {
1682
1685
  const lensDef = lensDefs[task.lens];
1686
+ const inputLines = task.inputs
1687
+ ? Object.entries(task.inputs)
1688
+ .sort(([a], [b]) => a.localeCompare(b))
1689
+ .map(([key, value]) => `input.${key}: ${value}`)
1690
+ : [];
1691
+ const isLensVerification = task.tags?.includes("lens_verification") ?? false;
1683
1692
  return [
1684
1693
  `### ${task.task_id}`,
1685
1694
  `unit_id: ${task.unit_id}`,
1686
1695
  `pass_id: ${task.pass_id}`,
1687
1696
  `lens: ${task.lens}`,
1697
+ ...(task.tags?.length ? [`tags: ${task.tags.join(", ")}`] : []),
1698
+ ...inputLines,
1688
1699
  `rationale: ${task.rationale}`,
1689
1700
  "",
1690
1701
  `Lens guidance: ${lensDef?.description ?? task.lens}`,
1691
1702
  `Do NOT report: ${lensDef?.do_not_report ?? "N/A"}`,
1703
+ ...(isLensVerification
1704
+ ? [
1705
+ "",
1706
+ "Lens verification mode: review the prior result summary in the rationale and use only targeted source checks.",
1707
+ "Do not redo every packet and do not write direct findings for this task.",
1708
+ "Return findings: [] plus verification metadata. Include followup_tasks only for bounded, specific re-review packets.",
1709
+ ]
1710
+ : []),
1692
1711
  "",
1693
1712
  ];
1694
1713
  });
@@ -1735,6 +1754,13 @@ async function cmdPrepareDispatch(argv) {
1735
1754
  " file_coverage [{path, total_lines}] - one entry per assigned file; use the line counts listed above",
1736
1755
  " findings [] or array of finding objects",
1737
1756
  "",
1757
+ "Lens verification tasks:",
1758
+ " tasks tagged lens_verification must use findings: [] and include verification:",
1759
+ " {verified: boolean, needs_followup: boolean, concerns?: string[],",
1760
+ " coverage_concerns?: string[], confidence_concerns?: string[],",
1761
+ " followup_tasks?: AuditTask[]}.",
1762
+ " Follow-up AuditTask suggestions must stay bounded to files in this packet and use the same lens.",
1763
+ "",
1738
1764
  "Each finding object:",
1739
1765
  " id unique ID, e.g. \"COR-001\"",
1740
1766
  " title short title",
@@ -93,13 +93,13 @@ export async function writeDispatchBatchFiles(artifactsDir, runs, currentTasks)
93
93
  "",
94
94
  `This batch launched ${runs.length} deferred review run(s).`,
95
95
  "Each run keeps its own task.json, prompt.md, result.json, and status.json under .audit-artifacts/runs/<run_id>/.",
96
- "Use current-tasks.json for the combined task list, then inspect the per-run files below when you need exact prompts or result paths.",
96
+ "Use current-tasks.json for the combined task list. The per-run files below are operational references for launched workers; do not read per-run prompt or schema files unless debugging a failed dispatch.",
97
97
  "",
98
98
  "Runs:",
99
99
  ...runs.flatMap((run) => [
100
100
  `- ${run.run_id}`,
101
101
  ` task: ${run.task_path}`,
102
- ` prompt: ${run.prompt_path}`,
102
+ ` prompt (worker-owned; do not read during normal orchestration): ${run.prompt_path}`,
103
103
  ` result: ${run.result_path}`,
104
104
  ` status: ${run.status_path}`,
105
105
  ...(run.audit_results_path
@@ -32,6 +32,7 @@ function appendSelectiveDeepeningTasks(params) {
32
32
  lineIndex,
33
33
  runtimeValidationTasks: params.bundle.runtime_validation_tasks,
34
34
  runtimeValidationReport: params.runtimeValidationReport ?? params.bundle.runtime_validation_report,
35
+ externalAnalyzerResults: params.bundle.external_analyzer_results,
35
36
  });
36
37
  if (selectiveDeepeningTasks.length === 0) {
37
38
  return { bundle: params.bundle, taskCount: 0, artifacts: [] };
@@ -1,4 +1,5 @@
1
1
  import type { AuditResult, AuditTask } from "../types.js";
2
+ import type { ExternalAnalyzerResults } from "../types/externalAnalyzer.js";
2
3
  import type { RuntimeValidationReport, RuntimeValidationTaskManifest } from "../types/runtimeValidation.js";
3
4
  export interface BuildSelectiveDeepeningTaskOptions {
4
5
  existingTasks?: AuditTask[];
@@ -6,9 +7,12 @@ export interface BuildSelectiveDeepeningTaskOptions {
6
7
  lineIndex?: Record<string, number>;
7
8
  runtimeValidationTasks?: RuntimeValidationTaskManifest;
8
9
  runtimeValidationReport?: RuntimeValidationReport;
10
+ externalAnalyzerResults?: ExternalAnalyzerResults;
9
11
  maxTasks?: number;
10
12
  }
11
13
  export declare function buildSelectiveDeepeningTasks(options: BuildSelectiveDeepeningTaskOptions): AuditTask[];
12
14
  export declare const selectiveDeepeningTestUtils: {
13
15
  DEEPENING_TAG: string;
16
+ LENS_VERIFICATION_TAG: string;
17
+ LENS_VERIFICATION_FOLLOWUP_TAG: string;
14
18
  };