auditor-lambda 0.1.0 → 0.2.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 (58) hide show
  1. package/README.md +2 -1
  2. package/audit-code-wrapper-lib.mjs +458 -380
  3. package/dist/cli.js +258 -11
  4. package/dist/coverage.d.ts +0 -1
  5. package/dist/coverage.js +3 -34
  6. package/dist/extractors/fileInventory.js +2 -0
  7. package/dist/io/artifacts.js +2 -1
  8. package/dist/orchestrator/advance.js +70 -52
  9. package/dist/orchestrator/flowCoverage.js +2 -1
  10. package/dist/orchestrator/flowPlanning.d.ts +1 -1
  11. package/dist/orchestrator/flowPlanning.js +21 -28
  12. package/dist/orchestrator/internalExecutors.js +0 -1
  13. package/dist/orchestrator/taskBuilder.d.ts +7 -2
  14. package/dist/orchestrator/taskBuilder.js +55 -47
  15. package/dist/prompts/renderWorkerPrompt.js +32 -0
  16. package/dist/providers/claudeCodeProvider.js +6 -0
  17. package/dist/providers/index.js +5 -2
  18. package/dist/providers/opencodeProvider.js +6 -1
  19. package/dist/providers/types.d.ts +1 -0
  20. package/dist/reporting/mergeFindings.js +0 -7
  21. package/dist/reporting/rootCause.d.ts +0 -1
  22. package/dist/reporting/rootCause.js +0 -6
  23. package/dist/reporting/synthesis.js +18 -0
  24. package/dist/supervisor/runLedger.js +6 -2
  25. package/dist/types/sessionConfig.d.ts +8 -0
  26. package/dist/types/workerSession.d.ts +2 -0
  27. package/dist/types.d.ts +1 -2
  28. package/dist/validation/auditResults.d.ts +11 -0
  29. package/dist/validation/auditResults.js +118 -0
  30. package/dist/validation/sessionConfig.js +15 -1
  31. package/docs/agent-integrations.md +61 -56
  32. package/docs/agent-roles.md +69 -69
  33. package/docs/architecture.md +90 -90
  34. package/docs/artifacts.md +69 -69
  35. package/docs/bootstrap-install.md +1 -1
  36. package/docs/model-selection.md +86 -86
  37. package/docs/next-steps.md +11 -9
  38. package/docs/packaging.md +3 -3
  39. package/docs/pipeline.md +152 -152
  40. package/docs/production-readiness.md +6 -5
  41. package/docs/repo-layout.md +18 -18
  42. package/docs/run-flow.md +5 -5
  43. package/docs/session-config.md +216 -210
  44. package/docs/supervisor.md +70 -70
  45. package/docs/windows-setup.md +139 -139
  46. package/package.json +56 -56
  47. package/schemas/audit-code-v1alpha1.schema.json +76 -76
  48. package/schemas/audit_result.schema.json +48 -48
  49. package/schemas/audit_task.schema.json +49 -49
  50. package/schemas/coverage_matrix.schema.json +0 -15
  51. package/schemas/file_disposition.schema.json +33 -33
  52. package/schemas/finding.schema.json +58 -62
  53. package/schemas/flow_coverage.schema.json +44 -44
  54. package/schemas/root_cause_clusters.schema.json +0 -4
  55. package/schemas/runtime_validation_report.schema.json +34 -34
  56. package/schemas/synthesis_report.schema.json +61 -61
  57. package/skills/audit-code/SKILL.md +37 -37
  58. package/skills/audit-code/audit-code.prompt.md +56 -54
@@ -1,36 +1,53 @@
1
- import { access, mkdir, open, readFile, readdir, stat, unlink, writeFile } from 'node:fs/promises';
2
- import { constants } from 'node:fs';
3
- import { spawn } from 'node:child_process';
4
- import { dirname, join, relative, resolve } from 'node:path';
5
- import { fileURLToPath } from 'node:url';
6
-
1
+ import {
2
+ access,
3
+ mkdir,
4
+ open,
5
+ readFile,
6
+ readdir,
7
+ stat,
8
+ unlink,
9
+ writeFile,
10
+ } from "node:fs/promises";
11
+ import { constants } from "node:fs";
12
+ import { spawn } from "node:child_process";
13
+ import { dirname, join, relative, resolve } from "node:path";
14
+ import { fileURLToPath } from "node:url";
15
+
7
16
  const repoRoot = dirname(fileURLToPath(import.meta.url));
8
- const distEntry = join(repoRoot, 'dist', 'index.js');
9
- const packageJsonPath = join(repoRoot, 'package.json');
10
- const promptAssetPath = join(repoRoot, 'skills', 'audit-code', 'audit-code.prompt.md');
11
- const skillAssetPath = join(repoRoot, 'skills', 'audit-code', 'SKILL.md');
12
- const tsconfigPath = join(repoRoot, 'tsconfig.json');
13
- const sourceRoot = join(repoRoot, 'src');
14
- const buildLockPath = join(repoRoot, '.audit-code-build.lock');
17
+ const distEntry = join(repoRoot, "dist", "index.js");
18
+ const packageJsonPath = join(repoRoot, "package.json");
19
+ const promptAssetPath = join(
20
+ repoRoot,
21
+ "skills",
22
+ "audit-code",
23
+ "audit-code.prompt.md",
24
+ );
25
+ const skillAssetPath = join(repoRoot, "skills", "audit-code", "SKILL.md");
26
+ const tsconfigPath = join(repoRoot, "tsconfig.json");
27
+ const sourceRoot = join(repoRoot, "src");
28
+ const buildLockPath = join(repoRoot, ".audit-code-build.lock");
15
29
  const BUILD_LOCK_MAX_AGE_MS = 5 * 60 * 1000;
16
30
  const BUILD_LOCK_WAIT_TIMEOUT_MS = 2 * 60 * 1000;
17
31
  const BUILD_LOCK_WAIT_INTERVAL_MS = 200;
18
- const INSTALL_MARKER_START = '<!-- audit-code:begin -->';
19
- const INSTALL_MARKER_END = '<!-- audit-code:end -->';
20
- const INSTALL_GUIDE_FILENAME = 'GETTING-STARTED.md';
21
- const DEFAULT_INSTALL_HOST = 'all';
22
- const packageVersion = JSON.parse(await readFile(packageJsonPath, 'utf8')).version;
23
-
24
- function hasFlag(argv, name) {
25
- return argv.includes(name);
26
- }
27
-
28
- function getFlag(argv, name) {
29
- const index = argv.indexOf(name);
30
- if (index < 0) return undefined;
31
- return argv[index + 1];
32
- }
33
-
32
+ const INSTALL_MARKER_START = "<!-- audit-code:begin -->";
33
+ const INSTALL_MARKER_END = "<!-- audit-code:end -->";
34
+ const INSTALL_GUIDE_FILENAME = "GETTING-STARTED.md";
35
+ const DEFAULT_INSTALL_HOST = "all";
36
+ const INSTALLED_PROMPT_FILENAME = "audit-code.import.md";
37
+ const packageVersion = JSON.parse(
38
+ await readFile(packageJsonPath, "utf8"),
39
+ ).version;
40
+
41
+ function hasFlag(argv, name) {
42
+ return argv.includes(name);
43
+ }
44
+
45
+ function getFlag(argv, name) {
46
+ const index = argv.indexOf(name);
47
+ if (index < 0) return undefined;
48
+ return argv[index + 1];
49
+ }
50
+
34
51
  function setDefaultFlag(argv, name, value) {
35
52
  if (!hasFlag(argv, name)) {
36
53
  argv.push(name, value);
@@ -44,11 +61,11 @@ function requireFlagValue(argv, name) {
44
61
  }
45
62
  return value;
46
63
  }
47
-
48
- function npmExecutable() {
49
- return process.platform === 'win32' ? 'npm.cmd' : 'npm';
50
- }
51
-
64
+
65
+ function npmExecutable() {
66
+ return process.platform === "win32" ? "npm.cmd" : "npm";
67
+ }
68
+
52
69
  function nodeExecutable() {
53
70
  return process.execPath;
54
71
  }
@@ -60,13 +77,13 @@ function quoteForCmd(arg) {
60
77
  }
61
78
 
62
79
  function resolveSpawn(command, args) {
63
- if (!(process.platform === 'win32' && /\.(cmd|bat)$/i.test(command))) {
80
+ if (!(process.platform === "win32" && /\.(cmd|bat)$/i.test(command))) {
64
81
  return { command, args };
65
82
  }
66
83
 
67
84
  return {
68
- command: process.env.ComSpec ?? 'cmd.exe',
69
- args: ['/d', '/s', '/c', [command, ...args].map(quoteForCmd).join(' ')],
85
+ command: process.env.ComSpec ?? "cmd.exe",
86
+ args: ["/d", "/s", "/c", [command, ...args].map(quoteForCmd).join(" ")],
70
87
  };
71
88
  }
72
89
 
@@ -75,72 +92,78 @@ function run(command, args, options = {}) {
75
92
  const resolved = resolveSpawn(command, args);
76
93
  const child = spawn(resolved.command, resolved.args, {
77
94
  cwd: repoRoot,
78
- stdio: options.capture ? ['ignore', 'pipe', 'pipe'] : 'inherit',
79
- env: process.env
95
+ stdio: options.capture ? ["ignore", "pipe", "pipe"] : "inherit",
96
+ env: process.env,
80
97
  });
81
-
82
- let stdout = '';
83
- let stderr = '';
84
-
85
- if (options.capture) {
86
- child.stdout?.on('data', (chunk) => {
87
- stdout += String(chunk);
88
- });
89
- child.stderr?.on('data', (chunk) => {
90
- stderr += String(chunk);
91
- });
92
- }
93
-
94
- child.on('error', rejectPromise);
95
- child.on('exit', (code) => {
96
- if (code === 0) {
97
- resolvePromise({ stdout, stderr });
98
- return;
99
- }
100
- rejectPromise(new Error(options.capture ? stderr || `Command failed with exit code ${code}.` : `Command failed with exit code ${code}.`));
101
- });
102
- });
103
- }
104
-
105
- async function sleep(ms) {
106
- await new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
107
- }
108
-
98
+
99
+ let stdout = "";
100
+ let stderr = "";
101
+
102
+ if (options.capture) {
103
+ child.stdout?.on("data", (chunk) => {
104
+ stdout += String(chunk);
105
+ });
106
+ child.stderr?.on("data", (chunk) => {
107
+ stderr += String(chunk);
108
+ });
109
+ }
110
+
111
+ child.on("error", rejectPromise);
112
+ child.on("exit", (code) => {
113
+ if (code === 0) {
114
+ resolvePromise({ stdout, stderr });
115
+ return;
116
+ }
117
+ rejectPromise(
118
+ new Error(
119
+ options.capture
120
+ ? stderr || `Command failed with exit code ${code}.`
121
+ : `Command failed with exit code ${code}.`,
122
+ ),
123
+ );
124
+ });
125
+ });
126
+ }
127
+
128
+ async function sleep(ms) {
129
+ await new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
130
+ }
131
+
109
132
  async function fileExists(path) {
110
133
  try {
111
134
  await access(path, constants.F_OK);
112
135
  return true;
113
136
  } catch {
114
- return false;
115
- }
116
- }
117
-
118
- async function newestMtimeMs(path) {
119
- const stats = await stat(path);
120
- if (!stats.isDirectory()) {
121
- return stats.mtimeMs;
122
- }
123
-
124
- let newest = stats.mtimeMs;
125
- const entries = await readdir(path, { withFileTypes: true });
126
- for (const entry of entries) {
127
- const childPath = join(path, entry.name);
128
- if (entry.isDirectory()) {
129
- newest = Math.max(newest, await newestMtimeMs(childPath));
130
- continue;
131
- }
132
- if (entry.isFile()) {
133
- newest = Math.max(newest, (await stat(childPath)).mtimeMs);
134
- }
135
- }
136
- return newest;
137
- }
138
-
137
+ return false;
138
+ }
139
+ }
140
+
141
+ async function newestMtimeMs(path) {
142
+ const stats = await stat(path);
143
+ if (!stats.isDirectory()) {
144
+ return stats.mtimeMs;
145
+ }
146
+
147
+ let newest = stats.mtimeMs;
148
+ const entries = await readdir(path, { withFileTypes: true });
149
+ for (const entry of entries) {
150
+ const childPath = join(path, entry.name);
151
+ if (entry.isDirectory()) {
152
+ newest = Math.max(newest, await newestMtimeMs(childPath));
153
+ continue;
154
+ }
155
+ if (entry.isFile()) {
156
+ newest = Math.max(newest, (await stat(childPath)).mtimeMs);
157
+ }
158
+ }
159
+ return newest;
160
+ }
161
+
139
162
  async function shouldBuildDist() {
140
163
  if (!(await fileExists(distEntry))) {
141
164
  if (!(await fileExists(sourceRoot)) || !(await fileExists(tsconfigPath))) {
142
165
  throw new Error(
143
- 'Bundled dist is missing and source files are unavailable for rebuild.',
166
+ "Bundled dist is missing and source files are unavailable for rebuild.",
144
167
  );
145
168
  }
146
169
  return true;
@@ -152,115 +175,120 @@ async function shouldBuildDist() {
152
175
 
153
176
  const distMtime = (await stat(distEntry)).mtimeMs;
154
177
  const sourceMtime = await newestMtimeMs(sourceRoot);
155
- const tsconfigMtime = (await stat(tsconfigPath)).mtimeMs;
156
- const packageJsonMtime = (await stat(packageJsonPath)).mtimeMs;
157
- const newestInput = Math.max(sourceMtime, tsconfigMtime, packageJsonMtime);
158
- return distMtime < newestInput;
159
- }
160
-
161
- async function releaseBuildLock(handle) {
162
- try {
163
- await handle?.close();
164
- } finally {
165
- await unlink(buildLockPath).catch(() => {});
166
- }
167
- }
168
-
169
- async function waitForPeerBuild() {
170
- const start = Date.now();
171
-
172
- while (true) {
173
- if (!(await fileExists(buildLockPath))) {
174
- return;
175
- }
176
-
177
- if (Date.now() - start > BUILD_LOCK_WAIT_TIMEOUT_MS) {
178
- throw new Error(`Timed out waiting for build lock ${buildLockPath}.`);
179
- }
180
-
181
- await sleep(BUILD_LOCK_WAIT_INTERVAL_MS);
182
- }
183
- }
184
-
185
- async function acquireBuildLock() {
186
- while (true) {
187
- try {
188
- const handle = await open(buildLockPath, 'wx');
189
- await handle.writeFile(JSON.stringify({ pid: process.pid, acquired_at: new Date().toISOString() }));
190
- return handle;
191
- } catch (error) {
192
- if (error && error.code === 'EEXIST') {
193
- try {
194
- const lockStats = await stat(buildLockPath);
195
- if (Date.now() - lockStats.mtimeMs > BUILD_LOCK_MAX_AGE_MS) {
196
- await unlink(buildLockPath).catch(() => {});
197
- continue;
198
- }
199
- } catch {
200
- continue;
201
- }
202
-
203
- await waitForPeerBuild();
204
- if (!(await shouldBuildDist())) {
205
- return null;
206
- }
207
- continue;
208
- }
209
- throw error;
210
- }
211
- }
212
- }
213
-
214
- async function ensureBuilt() {
215
- if (!(await shouldBuildDist())) {
216
- return;
217
- }
218
-
219
- const lockHandle = await acquireBuildLock();
220
- if (!lockHandle) {
221
- return;
222
- }
223
-
224
- try {
225
- if (!(await shouldBuildDist())) {
226
- return;
227
- }
228
- await run(npmExecutable(), ['run', 'build']);
229
- } finally {
230
- await releaseBuildLock(lockHandle);
231
- }
232
- }
233
-
178
+ const tsconfigMtime = (await stat(tsconfigPath)).mtimeMs;
179
+ const packageJsonMtime = (await stat(packageJsonPath)).mtimeMs;
180
+ const newestInput = Math.max(sourceMtime, tsconfigMtime, packageJsonMtime);
181
+ return distMtime < newestInput;
182
+ }
183
+
184
+ async function releaseBuildLock(handle) {
185
+ try {
186
+ await handle?.close();
187
+ } finally {
188
+ await unlink(buildLockPath).catch(() => {});
189
+ }
190
+ }
191
+
192
+ async function waitForPeerBuild() {
193
+ const start = Date.now();
194
+
195
+ while (true) {
196
+ if (!(await fileExists(buildLockPath))) {
197
+ return;
198
+ }
199
+
200
+ if (Date.now() - start > BUILD_LOCK_WAIT_TIMEOUT_MS) {
201
+ throw new Error(`Timed out waiting for build lock ${buildLockPath}.`);
202
+ }
203
+
204
+ await sleep(BUILD_LOCK_WAIT_INTERVAL_MS);
205
+ }
206
+ }
207
+
208
+ async function acquireBuildLock() {
209
+ while (true) {
210
+ try {
211
+ const handle = await open(buildLockPath, "wx");
212
+ await handle.writeFile(
213
+ JSON.stringify({
214
+ pid: process.pid,
215
+ acquired_at: new Date().toISOString(),
216
+ }),
217
+ );
218
+ return handle;
219
+ } catch (error) {
220
+ if (error && error.code === "EEXIST") {
221
+ try {
222
+ const lockStats = await stat(buildLockPath);
223
+ if (Date.now() - lockStats.mtimeMs > BUILD_LOCK_MAX_AGE_MS) {
224
+ await unlink(buildLockPath).catch(() => {});
225
+ continue;
226
+ }
227
+ } catch {
228
+ continue;
229
+ }
230
+
231
+ await waitForPeerBuild();
232
+ if (!(await shouldBuildDist())) {
233
+ return null;
234
+ }
235
+ continue;
236
+ }
237
+ throw error;
238
+ }
239
+ }
240
+ }
241
+
242
+ async function ensureBuilt() {
243
+ if (!(await shouldBuildDist())) {
244
+ return;
245
+ }
246
+
247
+ const lockHandle = await acquireBuildLock();
248
+ if (!lockHandle) {
249
+ return;
250
+ }
251
+
252
+ try {
253
+ if (!(await shouldBuildDist())) {
254
+ return;
255
+ }
256
+ await run(npmExecutable(), ["run", "build"]);
257
+ } finally {
258
+ await releaseBuildLock(lockHandle);
259
+ }
260
+ }
261
+
234
262
  function printHelp({ usageName, preferredEntrypoint }) {
235
263
  const lines = [
236
264
  `Usage: node ${usageName} [--single-step] [--root PATH] [--artifacts-dir PATH] [--results FILE] [--updates FILE] [--external-analyzer-results FILE]`,
237
- '',
238
- 'Helper commands:',
239
- '- prompt-path prints the absolute path to the canonical /audit-code prompt asset',
240
- '- install bootstraps /audit-code into supported repo-local host surfaces',
241
- '- install-host --host copilot keeps the narrower Copilot-focused install path available',
242
- '- validate checks the current artifact bundle plus session-config/provider readiness and exits non-zero when issues exist',
243
- '',
244
- 'Primary usage:',
245
- '- from the repository root, run the wrapper with no arguments',
246
- '- default behavior advances the audit automatically until it completes or no further automatic progress is possible',
247
- '- each wrapper response refreshes operator-handoff.json and operator-handoff.md under the artifacts directory',
248
- '- use --single-step only for debugging or bounded-step testing',
249
- '',
250
- 'Defaults:',
251
- '- --root .',
252
- '- --artifacts-dir <root>/.audit-artifacts',
253
- '',
254
- 'Completion signals:',
255
- '- done: audit_state.status is complete',
256
- '- blocked/no further automatic progress: progress_made is false and next_likely_step is null'
257
- ];
258
-
259
- if (preferredEntrypoint && preferredEntrypoint !== usageName) {
260
- lines.push('', `Preferred entrypoint: node ${preferredEntrypoint}`);
261
- }
262
-
263
- console.log(lines.join('\n'));
265
+ "",
266
+ "Helper commands:",
267
+ "- prompt-path prints the absolute path to the canonical /audit-code prompt asset",
268
+ "- install bootstraps /audit-code into supported repo-local host surfaces",
269
+ "- install-host --host copilot keeps the narrower Copilot-focused install path available",
270
+ "- validate checks the current artifact bundle plus session-config/provider readiness and exits non-zero when issues exist",
271
+ "",
272
+ "Primary usage:",
273
+ "- from the repository root, run the wrapper with no arguments",
274
+ "- default behavior advances the audit automatically until it completes or no further automatic progress is possible",
275
+ "- each wrapper response refreshes operator-handoff.json and operator-handoff.md under the artifacts directory",
276
+ "- use --single-step only for debugging or bounded-step testing",
277
+ "",
278
+ "Defaults:",
279
+ "- --root .",
280
+ "- --artifacts-dir <root>/.audit-artifacts",
281
+ "",
282
+ "Completion signals:",
283
+ "- done: audit_state.status is complete",
284
+ "- blocked/no further automatic progress: progress_made is false and next_likely_step is null",
285
+ ];
286
+
287
+ if (preferredEntrypoint && preferredEntrypoint !== usageName) {
288
+ lines.push("", `Preferred entrypoint: node ${preferredEntrypoint}`);
289
+ }
290
+
291
+ console.log(lines.join("\n"));
264
292
  }
265
293
 
266
294
  async function printPromptPath() {
@@ -272,7 +300,7 @@ async function printPromptPath() {
272
300
  }
273
301
 
274
302
  function normalizeNewlines(value) {
275
- return value.replace(/\r\n/g, '\n');
303
+ return value.replace(/\r\n/g, "\n");
276
304
  }
277
305
 
278
306
  function splitFrontmatter(markdown) {
@@ -294,22 +322,22 @@ function renderFrontmatter(fields) {
294
322
  return false;
295
323
  }
296
324
 
297
- if (typeof value === 'string') {
325
+ if (typeof value === "string") {
298
326
  return value.length > 0;
299
327
  }
300
328
 
301
329
  return true;
302
330
  });
303
331
  if (entries.length === 0) {
304
- return '';
332
+ return "";
305
333
  }
306
334
 
307
335
  return [
308
- '---',
336
+ "---",
309
337
  ...entries.map(([key, value]) => `${key}: ${value}`),
310
- '---',
311
- '',
312
- ].join('\n');
338
+ "---",
339
+ "",
340
+ ].join("\n");
313
341
  }
314
342
 
315
343
  function renderPromptFile(fields, body) {
@@ -317,19 +345,19 @@ function renderPromptFile(fields, body) {
317
345
  }
318
346
 
319
347
  function toRepoRelativePath(root, targetPath) {
320
- const value = relative(root, targetPath).replace(/\\/g, '/');
321
- return value.length > 0 ? value : '.';
348
+ const value = relative(root, targetPath).replace(/\\/g, "/");
349
+ return value.length > 0 ? value : ".";
322
350
  }
323
351
 
324
352
  function buildInstallDirective(relativePromptPath) {
325
353
  return [
326
354
  INSTALL_MARKER_START,
327
- '## /audit-code',
328
- 'When the user enters `/audit-code`, treat it as this repository\'s autonomous audit workflow.',
329
- `If your host does not automatically register the installed slash command file, load and follow [the repo-local audit directive](${relativePromptPath.replace(/\\/g, '/')}).`,
330
- 'Normal usage should stay conversation-first and avoid manual `--root`, provider flags, or model-selection arguments.',
355
+ "## /audit-code",
356
+ "When the user enters `/audit-code`, treat it as this repository's autonomous audit workflow.",
357
+ `If your host does not automatically register the installed slash command file, load and follow [the repo-local audit directive](${relativePromptPath.replace(/\\/g, "/")}).`,
358
+ "Normal usage should stay conversation-first and avoid manual `--root`, provider flags, or model-selection arguments.",
331
359
  INSTALL_MARKER_END,
332
- ].join('\n');
360
+ ].join("\n");
333
361
  }
334
362
 
335
363
  function buildInstallHostGuidance({
@@ -341,90 +369,90 @@ function buildInstallHostGuidance({
341
369
 
342
370
  if (slashCommandSurfaces.vscode_prompt) {
343
371
  guidance.push({
344
- host: 'vscode',
345
- label: 'VS Code',
346
- support_level: 'supported',
347
- setup_kind: 'repo-local-slash-command',
372
+ host: "vscode",
373
+ label: "VS Code",
374
+ support_level: "supported",
375
+ setup_kind: "repo-local-slash-command",
348
376
  summary:
349
- 'Use the generated VS Code / Copilot prompt surface, then invoke `/audit-code` in chat.',
377
+ "Use the generated VS Code / Copilot prompt surface, then invoke `/audit-code` in chat.",
350
378
  primary_path: slashCommandSurfaces.vscode_prompt,
351
379
  supporting_paths: [
352
380
  instructionSurfaces.copilot_instructions,
353
381
  instructionSurfaces.agents,
354
382
  ].filter(Boolean),
355
383
  steps: [
356
- 'Open this repository in VS Code or GitHub Copilot Chat.',
357
- 'Invoke `/audit-code` in chat.',
358
- 'Use the integrated terminal and run `audit-code` only when you intentionally need the repo-local backend fallback.',
384
+ "Open this repository in VS Code or GitHub Copilot Chat.",
385
+ "Invoke `/audit-code` in chat.",
386
+ "Use the integrated terminal and run `audit-code` only when you intentionally need the repo-local backend fallback.",
359
387
  ],
360
388
  });
361
389
  }
362
390
 
363
391
  if (slashCommandSurfaces.opencode_command) {
364
392
  guidance.push({
365
- host: 'opencode',
366
- label: 'OpenCode',
367
- support_level: 'supported',
368
- setup_kind: 'repo-local-slash-command',
393
+ host: "opencode",
394
+ label: "OpenCode",
395
+ support_level: "supported",
396
+ setup_kind: "repo-local-slash-command",
369
397
  summary:
370
- 'Use the generated OpenCode command surface so `/audit-code` is available without extra provider flags.',
398
+ "Use the generated OpenCode command surface so `/audit-code` is available without extra provider flags.",
371
399
  primary_path: slashCommandSurfaces.opencode_command,
372
400
  supporting_paths: [instructionSurfaces.agents].filter(Boolean),
373
401
  steps: [
374
- 'Open this repository in OpenCode.',
375
- 'Invoke `/audit-code` from the OpenCode command surface.',
376
- 'Use the repo-local backend wrapper only when you intentionally need the fallback automation path.',
402
+ "Open this repository in OpenCode.",
403
+ "Invoke `/audit-code` from the OpenCode command surface.",
404
+ "Use the repo-local backend wrapper only when you intentionally need the fallback automation path.",
377
405
  ],
378
406
  });
379
407
  }
380
408
 
381
409
  if (slashCommandSurfaces.claude_code_command) {
382
410
  guidance.push({
383
- host: 'claude-code',
384
- label: 'Claude Code',
385
- support_level: 'supported',
386
- setup_kind: 'repo-local-slash-command',
411
+ host: "claude-code",
412
+ label: "Claude Code",
413
+ support_level: "supported",
414
+ setup_kind: "repo-local-slash-command",
387
415
  summary:
388
- 'Use the generated Claude Code command surface so `/audit-code` is available inside the repository without extra provider wiring.',
416
+ "Use the generated Claude Code command surface so `/audit-code` is available inside the repository without extra provider wiring.",
389
417
  primary_path: slashCommandSurfaces.claude_code_command,
390
418
  supporting_paths: [instructionSurfaces.claude].filter(Boolean),
391
419
  steps: [
392
- 'Open this repository in Claude Code.',
393
- 'Invoke `/audit-code` from the Claude Code project command surface.',
394
- 'Use the terminal fallback and run `audit-code` only when you intentionally need the repo-local backend wrapper.',
420
+ "Open this repository in Claude Code.",
421
+ "Invoke `/audit-code` from the Claude Code project command surface.",
422
+ "Use the terminal fallback and run `audit-code` only when you intentionally need the repo-local backend wrapper.",
395
423
  ],
396
424
  });
397
425
  }
398
426
 
399
427
  guidance.push({
400
- host: 'claude-desktop',
401
- label: 'Claude Desktop',
402
- support_level: 'manual-import',
403
- setup_kind: 'prompt-import',
428
+ host: "claude-desktop",
429
+ label: "Claude Desktop",
430
+ support_level: "manual-import",
431
+ setup_kind: "prompt-import",
404
432
  summary:
405
- 'No verified project-local slash-command surface is shipped for Claude Desktop, so use the installed prompt asset as the primary path.',
433
+ "No verified project-local slash-command surface is shipped for Claude Desktop, so use the installed prompt asset as the primary path.",
406
434
  primary_path: installedPromptPath,
407
435
  supporting_paths: [instructionSurfaces.claude].filter(Boolean),
408
436
  steps: [
409
- 'Import the installed prompt asset into Claude Desktop\'s prompt or instruction surface.',
410
- 'Invoke `/audit-code` conversationally inside Claude Desktop after the prompt is available.',
411
- 'If you intentionally need the repo-local backend fallback instead, run `audit-code` from the repository root.',
437
+ "Import the installed prompt asset into Claude Desktop's prompt or instruction surface.",
438
+ "Invoke `/audit-code` conversationally inside Claude Desktop after the prompt is available.",
439
+ "If you intentionally need the repo-local backend fallback instead, run `audit-code` from the repository root.",
412
440
  ],
413
441
  });
414
442
 
415
443
  guidance.push({
416
- host: 'antigravity',
417
- label: 'Antigravity',
418
- support_level: 'manual-import',
419
- setup_kind: 'prompt-import-or-terminal',
444
+ host: "antigravity",
445
+ label: "Antigravity",
446
+ support_level: "manual-import",
447
+ setup_kind: "prompt-import-or-terminal",
420
448
  summary:
421
- 'No verified repo-local slash-command surface is shipped for Antigravity, so start from the installed prompt asset or an Antigravity-managed terminal.',
449
+ "No verified repo-local slash-command surface is shipped for Antigravity, so start from the installed prompt asset or an Antigravity-managed terminal.",
422
450
  primary_path: installedPromptPath,
423
451
  supporting_paths: [instructionSurfaces.agents].filter(Boolean),
424
452
  steps: [
425
- 'Import the installed prompt asset into Antigravity\'s prompt or instruction surface when that surface is available.',
426
- 'Invoke `/audit-code` conversationally inside Antigravity.',
427
- 'If you prefer the backend fallback, run `audit-code` from an Antigravity-managed terminal with `local-subprocess` first.',
453
+ "Import the installed prompt asset into Antigravity's prompt or instruction surface when that surface is available.",
454
+ "Invoke `/audit-code` conversationally inside Antigravity.",
455
+ "If you prefer the backend fallback, run `audit-code` from an Antigravity-managed terminal with `local-subprocess` first.",
428
456
  ],
429
457
  });
430
458
 
@@ -434,30 +462,30 @@ function buildInstallHostGuidance({
434
462
  function renderInstallHostSection(root, guide) {
435
463
  const lines = [
436
464
  `## ${guide.label}`,
437
- '',
465
+ "",
438
466
  `Support level: ${guide.support_level}`,
439
467
  `Setup kind: ${guide.setup_kind}`,
440
- '',
468
+ "",
441
469
  guide.summary,
442
- '',
443
- 'Primary repo-local path:',
470
+ "",
471
+ "Primary repo-local path:",
444
472
  `- \`${toRepoRelativePath(root, guide.primary_path)}\``,
445
473
  ];
446
474
 
447
475
  if (guide.supporting_paths.length > 0) {
448
- lines.push('', 'Supporting repo-local paths:');
476
+ lines.push("", "Supporting repo-local paths:");
449
477
  for (const targetPath of guide.supporting_paths) {
450
478
  lines.push(`- \`${toRepoRelativePath(root, targetPath)}\``);
451
479
  }
452
480
  }
453
481
 
454
- lines.push('', 'Recommended steps:');
482
+ lines.push("", "Recommended steps:");
455
483
  for (const step of guide.steps) {
456
484
  lines.push(`- ${step}`);
457
485
  }
458
- lines.push('');
486
+ lines.push("");
459
487
 
460
- return lines.join('\n');
488
+ return lines.join("\n");
461
489
  }
462
490
 
463
491
  function renderInstallGuide({
@@ -473,67 +501,67 @@ function renderInstallGuide({
473
501
  const slashCommandPaths = Object.values(slashCommandSurfaces).filter(Boolean);
474
502
  const instructionPaths = Object.values(instructionSurfaces).filter(Boolean);
475
503
  const lines = [
476
- '# audit-code bootstrap guide',
477
- '',
478
- 'The canonical product route is `/audit-code` in conversation.',
479
- '',
480
- 'Canonical installed assets:',
504
+ "# audit-code bootstrap guide",
505
+ "",
506
+ "The canonical product route is `/audit-code` in conversation.",
507
+ "",
508
+ "Canonical installed assets:",
481
509
  `- prompt asset: \`${toRepoRelativePath(root, installedPromptPath)}\``,
482
510
  `- skill asset: \`${toRepoRelativePath(root, installedSkillPath)}\``,
483
511
  ];
484
512
 
485
513
  if (slashCommandPaths.length > 0) {
486
- lines.push('', 'Repo-local slash-command surfaces:');
514
+ lines.push("", "Repo-local slash-command surfaces:");
487
515
  for (const targetPath of slashCommandPaths) {
488
516
  lines.push(`- \`${toRepoRelativePath(root, targetPath)}\``);
489
517
  }
490
518
  }
491
519
 
492
520
  if (instructionPaths.length > 0) {
493
- lines.push('', 'Compatibility instruction surfaces:');
521
+ lines.push("", "Compatibility instruction surfaces:");
494
522
  for (const targetPath of instructionPaths) {
495
523
  lines.push(`- \`${toRepoRelativePath(root, targetPath)}\``);
496
524
  }
497
525
  }
498
526
 
499
- lines.push('', 'Host-specific quick starts:');
527
+ lines.push("", "Host-specific quick starts:");
500
528
  for (const guide of hostGuidance) {
501
529
  lines.push(`- ${guide.label}: ${guide.summary}`);
502
530
  }
503
531
 
504
532
  for (const guide of hostGuidance) {
505
- lines.push('', renderInstallHostSection(root, guide).trimEnd());
533
+ lines.push("", renderInstallHostSection(root, guide).trimEnd());
506
534
  }
507
535
 
508
536
  lines.push(
509
- '',
510
- 'Backend fallback:',
511
- '- from the repository root, run `audit-code` only when you intentionally need the repo-local backend wrapper',
537
+ "",
538
+ "Backend fallback:",
539
+ "- from the repository root, run `audit-code` only when you intentionally need the repo-local backend wrapper",
512
540
  );
513
541
 
514
542
  if (unsupportedHosts.length > 0) {
515
- lines.push('', 'Hosts still requiring extra handling today:');
543
+ lines.push("", "Hosts still requiring extra handling today:");
516
544
  for (const item of unsupportedHosts) {
517
545
  lines.push(`- ${item.host}: ${item.reason}`);
518
546
  }
519
547
  }
520
548
 
521
- if (host !== 'all') {
549
+ if (host !== "all") {
522
550
  lines.push(
523
- '',
551
+ "",
524
552
  `This install was scoped to \`${host}\`, so some repo-local surfaces may be intentionally omitted.`,
525
553
  );
526
554
  }
527
555
 
528
- lines.push('');
529
- return lines.join('\n');
556
+ lines.push("");
557
+ return lines.join("\n");
530
558
  }
531
559
 
532
560
  function upsertManagedBlock(existingContent, blockContent) {
533
561
  const normalized = normalizeNewlines(existingContent);
534
562
  const blockPattern = new RegExp(
535
563
  `${INSTALL_MARKER_START}[\\s\\S]*?${INSTALL_MARKER_END}`,
536
- 'u',
564
+ "u",
537
565
  );
538
566
 
539
567
  if (blockPattern.test(normalized)) {
@@ -544,34 +572,34 @@ function upsertManagedBlock(existingContent, blockContent) {
544
572
  return `${blockContent}\n`;
545
573
  }
546
574
 
547
- return `${normalized.replace(/\s+$/u, '')}\n\n${blockContent}\n`;
575
+ return `${normalized.replace(/\s+$/u, "")}\n\n${blockContent}\n`;
548
576
  }
549
577
 
550
578
  async function writeManagedMarkdown(targetPath, blockContent) {
551
579
  const existed = await fileExists(targetPath);
552
- const existingContent = existed ? await readFile(targetPath, 'utf8') : '';
580
+ const existingContent = existed ? await readFile(targetPath, "utf8") : "";
553
581
  const nextContent = upsertManagedBlock(existingContent, blockContent);
554
582
  await mkdir(dirname(targetPath), { recursive: true });
555
- await writeFile(targetPath, nextContent, 'utf8');
583
+ await writeFile(targetPath, nextContent, "utf8");
556
584
  return {
557
585
  path: targetPath,
558
- mode: existed ? 'updated' : 'created',
586
+ mode: existed ? "updated" : "created",
559
587
  };
560
588
  }
561
589
 
562
590
  async function writeGeneratedMarkdown(targetPath, content) {
563
591
  const existed = await fileExists(targetPath);
564
592
  await mkdir(dirname(targetPath), { recursive: true });
565
- await writeFile(targetPath, content, 'utf8');
593
+ await writeFile(targetPath, content, "utf8");
566
594
  return {
567
595
  path: targetPath,
568
- mode: existed ? 'updated' : 'created',
596
+ mode: existed ? "updated" : "created",
569
597
  };
570
598
  }
571
599
 
572
600
  function getInstallProfile(host) {
573
601
  switch (host) {
574
- case 'all':
602
+ case "all":
575
603
  return {
576
604
  writeVSCodePrompt: true,
577
605
  writeCopilotInstructions: true,
@@ -581,7 +609,7 @@ function getInstallProfile(host) {
581
609
  writeClaudeCommand: true,
582
610
  writeCompatibilitySkills: true,
583
611
  };
584
- case 'copilot':
612
+ case "copilot":
585
613
  return {
586
614
  writeVSCodePrompt: true,
587
615
  writeCopilotInstructions: true,
@@ -591,7 +619,7 @@ function getInstallProfile(host) {
591
619
  writeClaudeCommand: false,
592
620
  writeCompatibilitySkills: false,
593
621
  };
594
- case 'vscode':
622
+ case "vscode":
595
623
  return {
596
624
  writeVSCodePrompt: true,
597
625
  writeCopilotInstructions: true,
@@ -601,7 +629,7 @@ function getInstallProfile(host) {
601
629
  writeClaudeCommand: false,
602
630
  writeCompatibilitySkills: false,
603
631
  };
604
- case 'opencode':
632
+ case "opencode":
605
633
  return {
606
634
  writeVSCodePrompt: false,
607
635
  writeCopilotInstructions: false,
@@ -611,7 +639,7 @@ function getInstallProfile(host) {
611
639
  writeClaudeCommand: false,
612
640
  writeCompatibilitySkills: true,
613
641
  };
614
- case 'claude-code':
642
+ case "claude-code":
615
643
  return {
616
644
  writeVSCodePrompt: false,
617
645
  writeCopilotInstructions: false,
@@ -642,48 +670,65 @@ async function assertDirectoryExists(path, description) {
642
670
  }
643
671
 
644
672
  async function installBootstrap(argv) {
645
- const host = (getFlag(argv, '--host') ?? DEFAULT_INSTALL_HOST).toLowerCase();
646
- const root = resolve(getFlag(argv, '--root') ?? '.');
647
- await assertDirectoryExists(root, 'Target repository root');
673
+ const host = (getFlag(argv, "--host") ?? DEFAULT_INSTALL_HOST).toLowerCase();
674
+ const root = resolve(getFlag(argv, "--root") ?? ".");
675
+ await assertDirectoryExists(root, "Target repository root");
648
676
  const profile = getInstallProfile(host);
649
- const promptSource = await readFile(promptAssetPath, 'utf8');
650
- const skillSource = await readFile(skillAssetPath, 'utf8');
677
+ const promptSource = await readFile(promptAssetPath, "utf8");
678
+ const skillSource = await readFile(skillAssetPath, "utf8");
651
679
  const { body: promptBody } = splitFrontmatter(promptSource);
652
- const installedPromptPath = join(root, '.audit-code', 'install', 'audit-code.prompt.md');
653
- const installedSkillPath = join(root, '.audit-code', 'install', 'SKILL.md');
654
- const installGuidePath = join(root, '.audit-code', 'install', INSTALL_GUIDE_FILENAME);
680
+ const installedPromptPath = join(
681
+ root,
682
+ ".audit-code",
683
+ "install",
684
+ INSTALLED_PROMPT_FILENAME,
685
+ );
686
+ const legacyInstalledPromptPath = join(
687
+ root,
688
+ ".audit-code",
689
+ "install",
690
+ "audit-code.prompt.md",
691
+ );
692
+ const installedSkillPath = join(root, ".audit-code", "install", "SKILL.md");
693
+ const installGuidePath = join(
694
+ root,
695
+ ".audit-code",
696
+ "install",
697
+ INSTALL_GUIDE_FILENAME,
698
+ );
655
699
  const slashCommandSurfaces = {
656
700
  vscode_prompt: profile.writeVSCodePrompt
657
- ? join(root, '.github', 'prompts', 'audit-code.prompt.md')
701
+ ? join(root, ".github", "prompts", "audit-code.prompt.md")
658
702
  : null,
659
703
  opencode_command: profile.writeOpenCodeCommand
660
- ? join(root, '.opencode', 'commands', 'audit-code.md')
704
+ ? join(root, ".opencode", "commands", "audit-code.md")
661
705
  : null,
662
706
  claude_code_command: profile.writeClaudeCommand
663
- ? join(root, '.claude', 'commands', 'audit-code.md')
707
+ ? join(root, ".claude", "commands", "audit-code.md")
664
708
  : null,
665
709
  };
666
710
  const instructionSurfaces = {
667
711
  copilot_instructions: profile.writeCopilotInstructions
668
- ? join(root, '.github', 'copilot-instructions.md')
712
+ ? join(root, ".github", "copilot-instructions.md")
669
713
  : null,
670
- agents: profile.writeAgents ? join(root, 'AGENTS.md') : null,
671
- claude: profile.writeClaudeMemory ? join(root, 'CLAUDE.md') : null,
714
+ agents: profile.writeAgents ? join(root, "AGENTS.md") : null,
715
+ claude: profile.writeClaudeMemory ? join(root, "CLAUDE.md") : null,
672
716
  };
673
- const unsupportedHosts = host === 'all'
674
- ? [
675
- {
676
- host: 'claude-desktop',
677
- reason:
678
- 'No verified project-local slash-command installation surface is currently shipped for Claude Desktop.',
679
- },
680
- {
681
- host: 'antigravity',
682
- reason:
683
- 'No verified repo-local slash-command installation surface is currently shipped for Antigravity.',
684
- },
685
- ]
686
- : [];
717
+ const unsupportedHosts =
718
+ host === "all"
719
+ ? [
720
+ {
721
+ host: "claude-desktop",
722
+ reason:
723
+ "No verified project-local slash-command installation surface is currently shipped for Claude Desktop.",
724
+ },
725
+ {
726
+ host: "antigravity",
727
+ reason:
728
+ "No verified repo-local slash-command installation surface is currently shipped for Antigravity.",
729
+ },
730
+ ]
731
+ : [];
687
732
  const hostGuidance = buildInstallHostGuidance({
688
733
  installedPromptPath,
689
734
  slashCommandSurfaces,
@@ -691,23 +736,21 @@ async function installBootstrap(argv) {
691
736
  });
692
737
 
693
738
  const results = [];
694
- results.push(
695
- await writeGeneratedMarkdown(
696
- installedPromptPath,
697
- promptSource,
698
- ),
699
- );
739
+ if (await fileExists(legacyInstalledPromptPath)) {
740
+ await unlink(legacyInstalledPromptPath).catch(() => {});
741
+ }
742
+ results.push(await writeGeneratedMarkdown(installedPromptPath, promptSource));
700
743
  results.push(await writeGeneratedMarkdown(installedSkillPath, skillSource));
701
744
 
702
745
  if (profile.writeVSCodePrompt) {
703
746
  results.push(
704
747
  await writeGeneratedMarkdown(
705
- join(root, '.github', 'prompts', 'audit-code.prompt.md'),
748
+ join(root, ".github", "prompts", "audit-code.prompt.md"),
706
749
  renderPromptFile(
707
750
  {
708
- name: 'audit-code',
709
- description: 'Autonomous local loop code auditing',
710
- agent: 'agent',
751
+ name: "audit-code",
752
+ description: "Autonomous local loop code auditing",
753
+ agent: "agent",
711
754
  },
712
755
  promptBody,
713
756
  ),
@@ -718,11 +761,11 @@ async function installBootstrap(argv) {
718
761
  if (profile.writeOpenCodeCommand) {
719
762
  results.push(
720
763
  await writeGeneratedMarkdown(
721
- join(root, '.opencode', 'commands', 'audit-code.md'),
764
+ join(root, ".opencode", "commands", "audit-code.md"),
722
765
  renderPromptFile(
723
766
  {
724
- description: 'Autonomous local loop code auditing',
725
- agent: 'build',
767
+ description: "Autonomous local loop code auditing",
768
+ agent: "build",
726
769
  subtask: false,
727
770
  },
728
771
  promptBody,
@@ -734,10 +777,10 @@ async function installBootstrap(argv) {
734
777
  if (profile.writeClaudeCommand) {
735
778
  results.push(
736
779
  await writeGeneratedMarkdown(
737
- join(root, '.claude', 'commands', 'audit-code.md'),
780
+ join(root, ".claude", "commands", "audit-code.md"),
738
781
  renderPromptFile(
739
782
  {
740
- description: 'Autonomous local loop code auditing',
783
+ description: "Autonomous local loop code auditing",
741
784
  },
742
785
  promptBody,
743
786
  ),
@@ -745,14 +788,16 @@ async function installBootstrap(argv) {
745
788
  );
746
789
  }
747
790
 
748
- const compatibilityBlockTargets = Object.values(instructionSurfaces).filter(Boolean);
791
+ const compatibilityBlockTargets =
792
+ Object.values(instructionSurfaces).filter(Boolean);
749
793
 
750
794
  for (const targetPath of compatibilityBlockTargets) {
751
795
  results.push(
752
796
  await writeManagedMarkdown(
753
797
  targetPath,
754
798
  buildInstallDirective(
755
- relative(dirname(targetPath), installedPromptPath) || './.audit-code/install/audit-code.prompt.md',
799
+ relative(dirname(targetPath), installedPromptPath) ||
800
+ `./.audit-code/install/${INSTALLED_PROMPT_FILENAME}`,
756
801
  ),
757
802
  ),
758
803
  );
@@ -760,18 +805,18 @@ async function installBootstrap(argv) {
760
805
 
761
806
  if (profile.writeCompatibilitySkills) {
762
807
  const skillTargets = [
763
- join(root, '.opencode', 'skills', 'audit-code'),
764
- join(root, '.claude', 'skills', 'audit-code'),
765
- join(root, '.agents', 'skills', 'audit-code'),
808
+ join(root, ".opencode", "skills", "audit-code"),
809
+ join(root, ".claude", "skills", "audit-code"),
810
+ join(root, ".agents", "skills", "audit-code"),
766
811
  ];
767
812
 
768
813
  for (const targetDir of skillTargets) {
769
814
  results.push(
770
- await writeGeneratedMarkdown(join(targetDir, 'SKILL.md'), skillSource),
815
+ await writeGeneratedMarkdown(join(targetDir, "SKILL.md"), skillSource),
771
816
  );
772
817
  results.push(
773
818
  await writeGeneratedMarkdown(
774
- join(targetDir, 'audit-code.prompt.md'),
819
+ join(targetDir, "audit-code.prompt.md"),
775
820
  promptSource,
776
821
  ),
777
822
  );
@@ -794,6 +839,27 @@ async function installBootstrap(argv) {
794
839
  ),
795
840
  );
796
841
 
842
+ const sessionConfigPath = join(
843
+ root,
844
+ ".audit-artifacts",
845
+ "session-config.json",
846
+ );
847
+ let sessionConfigWritten = false;
848
+ if (!(await fileExists(sessionConfigPath))) {
849
+ const insideClaudeCode = Boolean(process.env.CLAUDECODE);
850
+ const defaultConfig = insideClaudeCode
851
+ ? { provider: "local-subprocess" }
852
+ : { provider: "auto" };
853
+ await mkdir(dirname(sessionConfigPath), { recursive: true });
854
+ await writeFile(
855
+ sessionConfigPath,
856
+ JSON.stringify(defaultConfig, null, 2) + "\n",
857
+ "utf8",
858
+ );
859
+ results.push({ path: sessionConfigPath, mode: "created" });
860
+ sessionConfigWritten = true;
861
+ }
862
+
797
863
  console.log(
798
864
  JSON.stringify(
799
865
  {
@@ -809,9 +875,9 @@ async function installBootstrap(argv) {
809
875
  host_guidance: hostGuidance,
810
876
  unsupported_hosts: unsupportedHosts,
811
877
  next_steps: [
812
- 'Open the repository in your preferred host and follow the matching host_guidance entry.',
878
+ "Open the repository in your preferred host and follow the matching host_guidance entry.",
813
879
  `Open ${installGuidePath} for repo-local quick-start steps for VS Code, OpenCode, Claude Code, Claude Desktop, and Antigravity.`,
814
- 'If a host does not auto-discover slash commands, use the installed prompt asset or the listed compatibility instruction surfaces.',
880
+ "If a host does not auto-discover slash commands, use the installed prompt asset or the listed compatibility instruction surfaces.",
815
881
  ],
816
882
  },
817
883
  null,
@@ -821,9 +887,9 @@ async function installBootstrap(argv) {
821
887
  }
822
888
 
823
889
  async function installHostPrompt(argv) {
824
- const host = requireFlagValue(argv, '--host').toLowerCase();
890
+ const host = requireFlagValue(argv, "--host").toLowerCase();
825
891
 
826
- if (host !== 'copilot') {
892
+ if (host !== "copilot") {
827
893
  throw new Error(
828
894
  `install-host currently supports only "copilot". Use "install --host ${host}" for the broader bootstrap flow.`,
829
895
  );
@@ -832,13 +898,20 @@ async function installHostPrompt(argv) {
832
898
  await installBootstrap(argv);
833
899
  }
834
900
 
835
- async function runDistCommand(commandName, argv, { ensureArtifactsDir = false } = {}) {
901
+ async function runDistCommand(
902
+ commandName,
903
+ argv,
904
+ { ensureArtifactsDir = false } = {},
905
+ ) {
836
906
  const commandArgs = [...argv];
837
- const rootValue = resolve(getFlag(commandArgs, '--root') ?? '.');
838
- const artifactsDir = resolve(getFlag(commandArgs, '--artifacts-dir') ?? join(rootValue, '.audit-artifacts'));
907
+ const rootValue = resolve(getFlag(commandArgs, "--root") ?? ".");
908
+ const artifactsDir = resolve(
909
+ getFlag(commandArgs, "--artifacts-dir") ??
910
+ join(rootValue, ".audit-artifacts"),
911
+ );
839
912
 
840
- setDefaultFlag(commandArgs, '--root', rootValue);
841
- setDefaultFlag(commandArgs, '--artifacts-dir', artifactsDir);
913
+ setDefaultFlag(commandArgs, "--root", rootValue);
914
+ setDefaultFlag(commandArgs, "--artifacts-dir", artifactsDir);
842
915
 
843
916
  if (ensureArtifactsDir) {
844
917
  await mkdir(artifactsDir, { recursive: true });
@@ -852,54 +925,59 @@ export async function runAuditCodeWrapper({
852
925
  usageName,
853
926
  argv = process.argv.slice(2),
854
927
  ensureArtifactsDir = true,
855
- preferredEntrypoint,
856
- defaultSingleStep = false
857
- }) {
858
- if (hasFlag(argv, '--help') || hasFlag(argv, '-h')) {
859
- printHelp({ usageName, preferredEntrypoint });
860
- return;
861
- }
862
-
863
- if (hasFlag(argv, '--version') || hasFlag(argv, '-v')) {
928
+ preferredEntrypoint,
929
+ defaultSingleStep = false,
930
+ }) {
931
+ if (hasFlag(argv, "--help") || hasFlag(argv, "-h")) {
932
+ printHelp({ usageName, preferredEntrypoint });
933
+ return;
934
+ }
935
+
936
+ if (hasFlag(argv, "--version") || hasFlag(argv, "-v")) {
864
937
  console.log(packageVersion);
865
938
  return;
866
939
  }
867
940
 
868
- if (argv[0] === 'prompt-path') {
941
+ if (argv[0] === "prompt-path") {
869
942
  await printPromptPath();
870
943
  return;
871
944
  }
872
945
 
873
- if (argv[0] === 'install') {
946
+ if (argv[0] === "install") {
874
947
  await installBootstrap(argv.slice(1));
875
948
  return;
876
949
  }
877
950
 
878
- if (argv[0] === 'install-host') {
951
+ if (argv[0] === "install-host") {
879
952
  await installHostPrompt(argv.slice(1));
880
953
  return;
881
954
  }
882
955
 
883
- if (argv[0] === 'validate') {
884
- await runDistCommand('validate', argv.slice(1));
956
+ if (argv[0] === "validate") {
957
+ await runDistCommand("validate", argv.slice(1));
885
958
  return;
886
959
  }
887
960
 
888
961
  const wrapperArgs = [...argv];
889
- if (defaultSingleStep && !hasFlag(wrapperArgs, '--single-step')) {
890
- wrapperArgs.push('--single-step');
891
- }
892
- const rootValue = resolve(getFlag(wrapperArgs, '--root') ?? '.');
893
- const artifactsDir = resolve(getFlag(wrapperArgs, '--artifacts-dir') ?? join(rootValue, '.audit-artifacts'));
894
-
895
- setDefaultFlag(wrapperArgs, '--root', rootValue);
896
- setDefaultFlag(wrapperArgs, '--artifacts-dir', artifactsDir);
897
-
898
- if (ensureArtifactsDir) {
899
- await mkdir(artifactsDir, { recursive: true });
900
- }
901
-
902
- await ensureBuilt();
903
- const command = hasFlag(wrapperArgs, '--single-step') ? 'advance-audit' : 'run-to-completion';
904
- await run(nodeExecutable(), [distEntry, command, ...wrapperArgs]);
905
- }
962
+ if (defaultSingleStep && !hasFlag(wrapperArgs, "--single-step")) {
963
+ wrapperArgs.push("--single-step");
964
+ }
965
+ const rootValue = resolve(getFlag(wrapperArgs, "--root") ?? ".");
966
+ const artifactsDir = resolve(
967
+ getFlag(wrapperArgs, "--artifacts-dir") ??
968
+ join(rootValue, ".audit-artifacts"),
969
+ );
970
+
971
+ setDefaultFlag(wrapperArgs, "--root", rootValue);
972
+ setDefaultFlag(wrapperArgs, "--artifacts-dir", artifactsDir);
973
+
974
+ if (ensureArtifactsDir) {
975
+ await mkdir(artifactsDir, { recursive: true });
976
+ }
977
+
978
+ await ensureBuilt();
979
+ const command = hasFlag(wrapperArgs, "--single-step")
980
+ ? "advance-audit"
981
+ : "run-to-completion";
982
+ await run(nodeExecutable(), [distEntry, command, ...wrapperArgs]);
983
+ }