auditor-lambda 0.1.0

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 (199) hide show
  1. package/README.md +173 -0
  2. package/audit-code-wrapper-lib.mjs +905 -0
  3. package/audit-code.mjs +13 -0
  4. package/dist/adapters/coverageSummary.d.ts +8 -0
  5. package/dist/adapters/coverageSummary.js +13 -0
  6. package/dist/adapters/eslint.d.ts +13 -0
  7. package/dist/adapters/eslint.js +21 -0
  8. package/dist/adapters/normalizeExternal.d.ts +12 -0
  9. package/dist/adapters/normalizeExternal.js +19 -0
  10. package/dist/adapters/npmAudit.d.ts +15 -0
  11. package/dist/adapters/npmAudit.js +12 -0
  12. package/dist/adapters/semgrep.d.ts +22 -0
  13. package/dist/adapters/semgrep.js +14 -0
  14. package/dist/cli.d.ts +1 -0
  15. package/dist/cli.js +724 -0
  16. package/dist/coverage.d.ts +11 -0
  17. package/dist/coverage.js +102 -0
  18. package/dist/extractors/bucketing.d.ts +7 -0
  19. package/dist/extractors/bucketing.js +72 -0
  20. package/dist/extractors/disposition.d.ts +4 -0
  21. package/dist/extractors/disposition.js +41 -0
  22. package/dist/extractors/fileInventory.d.ts +7 -0
  23. package/dist/extractors/fileInventory.js +44 -0
  24. package/dist/extractors/flows.d.ts +5 -0
  25. package/dist/extractors/flows.js +125 -0
  26. package/dist/extractors/fsIntake.d.ts +8 -0
  27. package/dist/extractors/fsIntake.js +66 -0
  28. package/dist/extractors/graph.d.ts +4 -0
  29. package/dist/extractors/graph.js +46 -0
  30. package/dist/extractors/ignore.d.ts +1 -0
  31. package/dist/extractors/ignore.js +17 -0
  32. package/dist/extractors/risk.d.ts +5 -0
  33. package/dist/extractors/risk.js +45 -0
  34. package/dist/extractors/surfaces.d.ts +4 -0
  35. package/dist/extractors/surfaces.js +40 -0
  36. package/dist/index.d.ts +1 -0
  37. package/dist/index.js +1 -0
  38. package/dist/io/artifacts.d.ts +38 -0
  39. package/dist/io/artifacts.js +100 -0
  40. package/dist/io/json.d.ts +8 -0
  41. package/dist/io/json.js +96 -0
  42. package/dist/io/runArtifacts.d.ts +14 -0
  43. package/dist/io/runArtifacts.js +37 -0
  44. package/dist/orchestrator/advance.d.ts +24 -0
  45. package/dist/orchestrator/advance.js +104 -0
  46. package/dist/orchestrator/artifactMetadata.d.ts +4 -0
  47. package/dist/orchestrator/artifactMetadata.js +111 -0
  48. package/dist/orchestrator/autoFixExecutor.d.ts +3 -0
  49. package/dist/orchestrator/autoFixExecutor.js +63 -0
  50. package/dist/orchestrator/chunking.d.ts +5 -0
  51. package/dist/orchestrator/chunking.js +13 -0
  52. package/dist/orchestrator/dependencyMap.d.ts +1 -0
  53. package/dist/orchestrator/dependencyMap.js +82 -0
  54. package/dist/orchestrator/executors.d.ts +6 -0
  55. package/dist/orchestrator/executors.js +52 -0
  56. package/dist/orchestrator/flowCoverage.d.ts +4 -0
  57. package/dist/orchestrator/flowCoverage.js +44 -0
  58. package/dist/orchestrator/flowPlanning.d.ts +3 -0
  59. package/dist/orchestrator/flowPlanning.js +42 -0
  60. package/dist/orchestrator/flowRequeue.d.ts +5 -0
  61. package/dist/orchestrator/flowRequeue.js +58 -0
  62. package/dist/orchestrator/internalExecutors.d.ts +16 -0
  63. package/dist/orchestrator/internalExecutors.js +212 -0
  64. package/dist/orchestrator/nextStep.d.ts +9 -0
  65. package/dist/orchestrator/nextStep.js +44 -0
  66. package/dist/orchestrator/planning.d.ts +4 -0
  67. package/dist/orchestrator/planning.js +62 -0
  68. package/dist/orchestrator/requeue.d.ts +3 -0
  69. package/dist/orchestrator/requeue.js +25 -0
  70. package/dist/orchestrator/requeueCommand.d.ts +10 -0
  71. package/dist/orchestrator/requeueCommand.js +27 -0
  72. package/dist/orchestrator/resultIngestion.d.ts +2 -0
  73. package/dist/orchestrator/resultIngestion.js +13 -0
  74. package/dist/orchestrator/runtimeValidation.d.ts +7 -0
  75. package/dist/orchestrator/runtimeValidation.js +103 -0
  76. package/dist/orchestrator/runtimeValidationUpdate.d.ts +2 -0
  77. package/dist/orchestrator/runtimeValidationUpdate.js +52 -0
  78. package/dist/orchestrator/staleness.d.ts +2 -0
  79. package/dist/orchestrator/staleness.js +83 -0
  80. package/dist/orchestrator/state.d.ts +3 -0
  81. package/dist/orchestrator/state.js +85 -0
  82. package/dist/orchestrator/syntaxResolutionExecutor.d.ts +3 -0
  83. package/dist/orchestrator/syntaxResolutionExecutor.js +99 -0
  84. package/dist/orchestrator/taskBuilder.d.ts +12 -0
  85. package/dist/orchestrator/taskBuilder.js +154 -0
  86. package/dist/orchestrator/unitBuilder.d.ts +3 -0
  87. package/dist/orchestrator/unitBuilder.js +145 -0
  88. package/dist/orchestrator.d.ts +6 -0
  89. package/dist/orchestrator.js +33 -0
  90. package/dist/prompts/renderWorkerPrompt.d.ts +2 -0
  91. package/dist/prompts/renderWorkerPrompt.js +19 -0
  92. package/dist/providers/claudeCodeProvider.d.ts +8 -0
  93. package/dist/providers/claudeCodeProvider.js +20 -0
  94. package/dist/providers/index.d.ts +7 -0
  95. package/dist/providers/index.js +77 -0
  96. package/dist/providers/localSubprocessProvider.d.ts +5 -0
  97. package/dist/providers/localSubprocessProvider.js +13 -0
  98. package/dist/providers/opencodeProvider.d.ts +8 -0
  99. package/dist/providers/opencodeProvider.js +15 -0
  100. package/dist/providers/spawnLoggedCommand.d.ts +2 -0
  101. package/dist/providers/spawnLoggedCommand.js +48 -0
  102. package/dist/providers/subprocessTemplateProvider.d.ts +8 -0
  103. package/dist/providers/subprocessTemplateProvider.js +41 -0
  104. package/dist/providers/types.d.ts +22 -0
  105. package/dist/providers/types.js +1 -0
  106. package/dist/providers/vscodeTaskProvider.d.ts +8 -0
  107. package/dist/providers/vscodeTaskProvider.js +14 -0
  108. package/dist/reporting/mergeFindings.d.ts +4 -0
  109. package/dist/reporting/mergeFindings.js +136 -0
  110. package/dist/reporting/rootCause.d.ts +11 -0
  111. package/dist/reporting/rootCause.js +69 -0
  112. package/dist/reporting/synthesis.d.ts +21 -0
  113. package/dist/reporting/synthesis.js +55 -0
  114. package/dist/supervisor/operatorHandoff.d.ts +37 -0
  115. package/dist/supervisor/operatorHandoff.js +144 -0
  116. package/dist/supervisor/runLedger.d.ts +3 -0
  117. package/dist/supervisor/runLedger.js +17 -0
  118. package/dist/supervisor/sessionConfig.d.ts +4 -0
  119. package/dist/supervisor/sessionConfig.js +26 -0
  120. package/dist/types/artifactMetadata.d.ts +8 -0
  121. package/dist/types/artifactMetadata.js +1 -0
  122. package/dist/types/auditState.d.ts +14 -0
  123. package/dist/types/auditState.js +1 -0
  124. package/dist/types/disposition.d.ts +9 -0
  125. package/dist/types/disposition.js +1 -0
  126. package/dist/types/externalAnalyzer.d.ts +16 -0
  127. package/dist/types/externalAnalyzer.js +1 -0
  128. package/dist/types/flowCoverage.d.ts +11 -0
  129. package/dist/types/flowCoverage.js +1 -0
  130. package/dist/types/flows.d.ts +11 -0
  131. package/dist/types/flows.js +1 -0
  132. package/dist/types/graph.d.ts +18 -0
  133. package/dist/types/graph.js +1 -0
  134. package/dist/types/risk.d.ts +9 -0
  135. package/dist/types/risk.js +1 -0
  136. package/dist/types/runLedger.d.ts +13 -0
  137. package/dist/types/runLedger.js +1 -0
  138. package/dist/types/runtimeValidation.d.ts +22 -0
  139. package/dist/types/runtimeValidation.js +1 -0
  140. package/dist/types/sessionConfig.d.ts +27 -0
  141. package/dist/types/sessionConfig.js +1 -0
  142. package/dist/types/surfaces.d.ts +11 -0
  143. package/dist/types/surfaces.js +1 -0
  144. package/dist/types/workerResult.d.ts +13 -0
  145. package/dist/types/workerResult.js +1 -0
  146. package/dist/types/workerSession.d.ts +13 -0
  147. package/dist/types/workerSession.js +1 -0
  148. package/dist/types.d.ts +104 -0
  149. package/dist/types.js +1 -0
  150. package/dist/validation/artifacts.d.ts +3 -0
  151. package/dist/validation/artifacts.js +191 -0
  152. package/dist/validation/basic.d.ts +5 -0
  153. package/dist/validation/basic.js +9 -0
  154. package/dist/validation/sessionConfig.d.ts +6 -0
  155. package/dist/validation/sessionConfig.js +139 -0
  156. package/docs/agent-integrations.md +237 -0
  157. package/docs/agent-roles.md +69 -0
  158. package/docs/architecture.md +90 -0
  159. package/docs/artifacts.md +69 -0
  160. package/docs/bootstrap-install.md +79 -0
  161. package/docs/contract.md +140 -0
  162. package/docs/github-copilot.md +50 -0
  163. package/docs/model-selection.md +86 -0
  164. package/docs/next-steps.md +161 -0
  165. package/docs/packaging.md +88 -0
  166. package/docs/pipeline.md +152 -0
  167. package/docs/product-direction.md +111 -0
  168. package/docs/production-launch-bar.md +83 -0
  169. package/docs/production-readiness.md +52 -0
  170. package/docs/repo-layout.md +30 -0
  171. package/docs/run-flow.md +49 -0
  172. package/docs/session-config.md +232 -0
  173. package/docs/supervisor.md +83 -0
  174. package/docs/usage.md +172 -0
  175. package/docs/windows-setup.md +146 -0
  176. package/package.json +56 -0
  177. package/schemas/audit-code-v1alpha1.schema.json +191 -0
  178. package/schemas/audit_result.schema.json +48 -0
  179. package/schemas/audit_state.schema.json +36 -0
  180. package/schemas/audit_task.schema.json +49 -0
  181. package/schemas/blind_spot_register.schema.json +40 -0
  182. package/schemas/coverage_matrix.schema.json +50 -0
  183. package/schemas/critical_flows.schema.json +38 -0
  184. package/schemas/external_analyzer_results.schema.json +31 -0
  185. package/schemas/file_disposition.schema.json +33 -0
  186. package/schemas/finding.schema.json +62 -0
  187. package/schemas/flow_coverage.schema.json +44 -0
  188. package/schemas/graph_bundle.schema.json +55 -0
  189. package/schemas/merged_findings.schema.json +14 -0
  190. package/schemas/repo_manifest.schema.json +37 -0
  191. package/schemas/risk_register.schema.json +30 -0
  192. package/schemas/root_cause_clusters.schema.json +31 -0
  193. package/schemas/runtime_validation_report.schema.json +34 -0
  194. package/schemas/runtime_validation_tasks.schema.json +36 -0
  195. package/schemas/surface_manifest.schema.json +32 -0
  196. package/schemas/synthesis_report.schema.json +61 -0
  197. package/schemas/unit_manifest.schema.json +36 -0
  198. package/skills/audit-code/SKILL.md +54 -0
  199. package/skills/audit-code/audit-code.prompt.md +66 -0
@@ -0,0 +1,905 @@
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
+
7
+ 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');
15
+ const BUILD_LOCK_MAX_AGE_MS = 5 * 60 * 1000;
16
+ const BUILD_LOCK_WAIT_TIMEOUT_MS = 2 * 60 * 1000;
17
+ 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
+
34
+ function setDefaultFlag(argv, name, value) {
35
+ if (!hasFlag(argv, name)) {
36
+ argv.push(name, value);
37
+ }
38
+ }
39
+
40
+ function requireFlagValue(argv, name) {
41
+ const value = getFlag(argv, name);
42
+ if (!value) {
43
+ throw new Error(`${name} requires a value.`);
44
+ }
45
+ return value;
46
+ }
47
+
48
+ function npmExecutable() {
49
+ return process.platform === 'win32' ? 'npm.cmd' : 'npm';
50
+ }
51
+
52
+ function nodeExecutable() {
53
+ return process.execPath;
54
+ }
55
+
56
+ function quoteForCmd(arg) {
57
+ if (arg.length === 0) return '""';
58
+ if (!/[\s"]/u.test(arg)) return arg;
59
+ return `"${arg.replace(/"/g, '""')}"`;
60
+ }
61
+
62
+ function resolveSpawn(command, args) {
63
+ if (!(process.platform === 'win32' && /\.(cmd|bat)$/i.test(command))) {
64
+ return { command, args };
65
+ }
66
+
67
+ return {
68
+ command: process.env.ComSpec ?? 'cmd.exe',
69
+ args: ['/d', '/s', '/c', [command, ...args].map(quoteForCmd).join(' ')],
70
+ };
71
+ }
72
+
73
+ function run(command, args, options = {}) {
74
+ return new Promise((resolvePromise, rejectPromise) => {
75
+ const resolved = resolveSpawn(command, args);
76
+ const child = spawn(resolved.command, resolved.args, {
77
+ cwd: repoRoot,
78
+ stdio: options.capture ? ['ignore', 'pipe', 'pipe'] : 'inherit',
79
+ env: process.env
80
+ });
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
+
109
+ async function fileExists(path) {
110
+ try {
111
+ await access(path, constants.F_OK);
112
+ return true;
113
+ } 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
+
139
+ async function shouldBuildDist() {
140
+ if (!(await fileExists(distEntry))) {
141
+ if (!(await fileExists(sourceRoot)) || !(await fileExists(tsconfigPath))) {
142
+ throw new Error(
143
+ 'Bundled dist is missing and source files are unavailable for rebuild.',
144
+ );
145
+ }
146
+ return true;
147
+ }
148
+
149
+ if (!(await fileExists(sourceRoot)) || !(await fileExists(tsconfigPath))) {
150
+ return false;
151
+ }
152
+
153
+ const distMtime = (await stat(distEntry)).mtimeMs;
154
+ 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
+
234
+ function printHelp({ usageName, preferredEntrypoint }) {
235
+ const lines = [
236
+ `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'));
264
+ }
265
+
266
+ async function printPromptPath() {
267
+ if (!(await fileExists(promptAssetPath))) {
268
+ throw new Error(`Canonical prompt asset is missing: ${promptAssetPath}`);
269
+ }
270
+
271
+ console.log(resolve(promptAssetPath));
272
+ }
273
+
274
+ function normalizeNewlines(value) {
275
+ return value.replace(/\r\n/g, '\n');
276
+ }
277
+
278
+ function splitFrontmatter(markdown) {
279
+ const normalized = normalizeNewlines(markdown);
280
+ const match = normalized.match(/^---\n([\s\S]*?)\n---\n?/u);
281
+ if (!match) {
282
+ return { frontmatter: null, body: normalized };
283
+ }
284
+
285
+ return {
286
+ frontmatter: match[1],
287
+ body: normalized.slice(match[0].length),
288
+ };
289
+ }
290
+
291
+ function renderFrontmatter(fields) {
292
+ const entries = Object.entries(fields).filter(([, value]) => {
293
+ if (value === undefined || value === null) {
294
+ return false;
295
+ }
296
+
297
+ if (typeof value === 'string') {
298
+ return value.length > 0;
299
+ }
300
+
301
+ return true;
302
+ });
303
+ if (entries.length === 0) {
304
+ return '';
305
+ }
306
+
307
+ return [
308
+ '---',
309
+ ...entries.map(([key, value]) => `${key}: ${value}`),
310
+ '---',
311
+ '',
312
+ ].join('\n');
313
+ }
314
+
315
+ function renderPromptFile(fields, body) {
316
+ return `${renderFrontmatter(fields)}${body.trimStart()}`;
317
+ }
318
+
319
+ function toRepoRelativePath(root, targetPath) {
320
+ const value = relative(root, targetPath).replace(/\\/g, '/');
321
+ return value.length > 0 ? value : '.';
322
+ }
323
+
324
+ function buildInstallDirective(relativePromptPath) {
325
+ return [
326
+ 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.',
331
+ INSTALL_MARKER_END,
332
+ ].join('\n');
333
+ }
334
+
335
+ function buildInstallHostGuidance({
336
+ installedPromptPath,
337
+ slashCommandSurfaces,
338
+ instructionSurfaces,
339
+ }) {
340
+ const guidance = [];
341
+
342
+ if (slashCommandSurfaces.vscode_prompt) {
343
+ guidance.push({
344
+ host: 'vscode',
345
+ label: 'VS Code',
346
+ support_level: 'supported',
347
+ setup_kind: 'repo-local-slash-command',
348
+ summary:
349
+ 'Use the generated VS Code / Copilot prompt surface, then invoke `/audit-code` in chat.',
350
+ primary_path: slashCommandSurfaces.vscode_prompt,
351
+ supporting_paths: [
352
+ instructionSurfaces.copilot_instructions,
353
+ instructionSurfaces.agents,
354
+ ].filter(Boolean),
355
+ 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.',
359
+ ],
360
+ });
361
+ }
362
+
363
+ if (slashCommandSurfaces.opencode_command) {
364
+ guidance.push({
365
+ host: 'opencode',
366
+ label: 'OpenCode',
367
+ support_level: 'supported',
368
+ setup_kind: 'repo-local-slash-command',
369
+ summary:
370
+ 'Use the generated OpenCode command surface so `/audit-code` is available without extra provider flags.',
371
+ primary_path: slashCommandSurfaces.opencode_command,
372
+ supporting_paths: [instructionSurfaces.agents].filter(Boolean),
373
+ 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.',
377
+ ],
378
+ });
379
+ }
380
+
381
+ if (slashCommandSurfaces.claude_code_command) {
382
+ guidance.push({
383
+ host: 'claude-code',
384
+ label: 'Claude Code',
385
+ support_level: 'supported',
386
+ setup_kind: 'repo-local-slash-command',
387
+ summary:
388
+ 'Use the generated Claude Code command surface so `/audit-code` is available inside the repository without extra provider wiring.',
389
+ primary_path: slashCommandSurfaces.claude_code_command,
390
+ supporting_paths: [instructionSurfaces.claude].filter(Boolean),
391
+ 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.',
395
+ ],
396
+ });
397
+ }
398
+
399
+ guidance.push({
400
+ host: 'claude-desktop',
401
+ label: 'Claude Desktop',
402
+ support_level: 'manual-import',
403
+ setup_kind: 'prompt-import',
404
+ summary:
405
+ 'No verified project-local slash-command surface is shipped for Claude Desktop, so use the installed prompt asset as the primary path.',
406
+ primary_path: installedPromptPath,
407
+ supporting_paths: [instructionSurfaces.claude].filter(Boolean),
408
+ 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.',
412
+ ],
413
+ });
414
+
415
+ guidance.push({
416
+ host: 'antigravity',
417
+ label: 'Antigravity',
418
+ support_level: 'manual-import',
419
+ setup_kind: 'prompt-import-or-terminal',
420
+ 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.',
422
+ primary_path: installedPromptPath,
423
+ supporting_paths: [instructionSurfaces.agents].filter(Boolean),
424
+ 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.',
428
+ ],
429
+ });
430
+
431
+ return guidance;
432
+ }
433
+
434
+ function renderInstallHostSection(root, guide) {
435
+ const lines = [
436
+ `## ${guide.label}`,
437
+ '',
438
+ `Support level: ${guide.support_level}`,
439
+ `Setup kind: ${guide.setup_kind}`,
440
+ '',
441
+ guide.summary,
442
+ '',
443
+ 'Primary repo-local path:',
444
+ `- \`${toRepoRelativePath(root, guide.primary_path)}\``,
445
+ ];
446
+
447
+ if (guide.supporting_paths.length > 0) {
448
+ lines.push('', 'Supporting repo-local paths:');
449
+ for (const targetPath of guide.supporting_paths) {
450
+ lines.push(`- \`${toRepoRelativePath(root, targetPath)}\``);
451
+ }
452
+ }
453
+
454
+ lines.push('', 'Recommended steps:');
455
+ for (const step of guide.steps) {
456
+ lines.push(`- ${step}`);
457
+ }
458
+ lines.push('');
459
+
460
+ return lines.join('\n');
461
+ }
462
+
463
+ function renderInstallGuide({
464
+ root,
465
+ host,
466
+ installedPromptPath,
467
+ installedSkillPath,
468
+ slashCommandSurfaces,
469
+ instructionSurfaces,
470
+ unsupportedHosts,
471
+ hostGuidance,
472
+ }) {
473
+ const slashCommandPaths = Object.values(slashCommandSurfaces).filter(Boolean);
474
+ const instructionPaths = Object.values(instructionSurfaces).filter(Boolean);
475
+ const lines = [
476
+ '# audit-code bootstrap guide',
477
+ '',
478
+ 'The canonical product route is `/audit-code` in conversation.',
479
+ '',
480
+ 'Canonical installed assets:',
481
+ `- prompt asset: \`${toRepoRelativePath(root, installedPromptPath)}\``,
482
+ `- skill asset: \`${toRepoRelativePath(root, installedSkillPath)}\``,
483
+ ];
484
+
485
+ if (slashCommandPaths.length > 0) {
486
+ lines.push('', 'Repo-local slash-command surfaces:');
487
+ for (const targetPath of slashCommandPaths) {
488
+ lines.push(`- \`${toRepoRelativePath(root, targetPath)}\``);
489
+ }
490
+ }
491
+
492
+ if (instructionPaths.length > 0) {
493
+ lines.push('', 'Compatibility instruction surfaces:');
494
+ for (const targetPath of instructionPaths) {
495
+ lines.push(`- \`${toRepoRelativePath(root, targetPath)}\``);
496
+ }
497
+ }
498
+
499
+ lines.push('', 'Host-specific quick starts:');
500
+ for (const guide of hostGuidance) {
501
+ lines.push(`- ${guide.label}: ${guide.summary}`);
502
+ }
503
+
504
+ for (const guide of hostGuidance) {
505
+ lines.push('', renderInstallHostSection(root, guide).trimEnd());
506
+ }
507
+
508
+ lines.push(
509
+ '',
510
+ 'Backend fallback:',
511
+ '- from the repository root, run `audit-code` only when you intentionally need the repo-local backend wrapper',
512
+ );
513
+
514
+ if (unsupportedHosts.length > 0) {
515
+ lines.push('', 'Hosts still requiring extra handling today:');
516
+ for (const item of unsupportedHosts) {
517
+ lines.push(`- ${item.host}: ${item.reason}`);
518
+ }
519
+ }
520
+
521
+ if (host !== 'all') {
522
+ lines.push(
523
+ '',
524
+ `This install was scoped to \`${host}\`, so some repo-local surfaces may be intentionally omitted.`,
525
+ );
526
+ }
527
+
528
+ lines.push('');
529
+ return lines.join('\n');
530
+ }
531
+
532
+ function upsertManagedBlock(existingContent, blockContent) {
533
+ const normalized = normalizeNewlines(existingContent);
534
+ const blockPattern = new RegExp(
535
+ `${INSTALL_MARKER_START}[\\s\\S]*?${INSTALL_MARKER_END}`,
536
+ 'u',
537
+ );
538
+
539
+ if (blockPattern.test(normalized)) {
540
+ return normalized.replace(blockPattern, blockContent);
541
+ }
542
+
543
+ if (normalized.trim().length === 0) {
544
+ return `${blockContent}\n`;
545
+ }
546
+
547
+ return `${normalized.replace(/\s+$/u, '')}\n\n${blockContent}\n`;
548
+ }
549
+
550
+ async function writeManagedMarkdown(targetPath, blockContent) {
551
+ const existed = await fileExists(targetPath);
552
+ const existingContent = existed ? await readFile(targetPath, 'utf8') : '';
553
+ const nextContent = upsertManagedBlock(existingContent, blockContent);
554
+ await mkdir(dirname(targetPath), { recursive: true });
555
+ await writeFile(targetPath, nextContent, 'utf8');
556
+ return {
557
+ path: targetPath,
558
+ mode: existed ? 'updated' : 'created',
559
+ };
560
+ }
561
+
562
+ async function writeGeneratedMarkdown(targetPath, content) {
563
+ const existed = await fileExists(targetPath);
564
+ await mkdir(dirname(targetPath), { recursive: true });
565
+ await writeFile(targetPath, content, 'utf8');
566
+ return {
567
+ path: targetPath,
568
+ mode: existed ? 'updated' : 'created',
569
+ };
570
+ }
571
+
572
+ function getInstallProfile(host) {
573
+ switch (host) {
574
+ case 'all':
575
+ return {
576
+ writeVSCodePrompt: true,
577
+ writeCopilotInstructions: true,
578
+ writeAgents: true,
579
+ writeClaudeMemory: true,
580
+ writeOpenCodeCommand: true,
581
+ writeClaudeCommand: true,
582
+ writeCompatibilitySkills: true,
583
+ };
584
+ case 'copilot':
585
+ return {
586
+ writeVSCodePrompt: true,
587
+ writeCopilotInstructions: true,
588
+ writeAgents: false,
589
+ writeClaudeMemory: false,
590
+ writeOpenCodeCommand: false,
591
+ writeClaudeCommand: false,
592
+ writeCompatibilitySkills: false,
593
+ };
594
+ case 'vscode':
595
+ return {
596
+ writeVSCodePrompt: true,
597
+ writeCopilotInstructions: true,
598
+ writeAgents: true,
599
+ writeClaudeMemory: true,
600
+ writeOpenCodeCommand: false,
601
+ writeClaudeCommand: false,
602
+ writeCompatibilitySkills: false,
603
+ };
604
+ case 'opencode':
605
+ return {
606
+ writeVSCodePrompt: false,
607
+ writeCopilotInstructions: false,
608
+ writeAgents: true,
609
+ writeClaudeMemory: false,
610
+ writeOpenCodeCommand: true,
611
+ writeClaudeCommand: false,
612
+ writeCompatibilitySkills: true,
613
+ };
614
+ case 'claude-code':
615
+ return {
616
+ writeVSCodePrompt: false,
617
+ writeCopilotInstructions: false,
618
+ writeAgents: false,
619
+ writeClaudeMemory: true,
620
+ writeOpenCodeCommand: false,
621
+ writeClaudeCommand: true,
622
+ writeCompatibilitySkills: true,
623
+ };
624
+ default:
625
+ throw new Error(
626
+ `Unsupported host "${host}". Supported hosts: all, copilot, vscode, opencode, claude-code.`,
627
+ );
628
+ }
629
+ }
630
+
631
+ async function assertDirectoryExists(path, description) {
632
+ let stats;
633
+ try {
634
+ stats = await stat(path);
635
+ } catch {
636
+ throw new Error(`${description} does not exist: ${path}`);
637
+ }
638
+
639
+ if (!stats.isDirectory()) {
640
+ throw new Error(`${description} is not a directory: ${path}`);
641
+ }
642
+ }
643
+
644
+ 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');
648
+ const profile = getInstallProfile(host);
649
+ const promptSource = await readFile(promptAssetPath, 'utf8');
650
+ const skillSource = await readFile(skillAssetPath, 'utf8');
651
+ 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);
655
+ const slashCommandSurfaces = {
656
+ vscode_prompt: profile.writeVSCodePrompt
657
+ ? join(root, '.github', 'prompts', 'audit-code.prompt.md')
658
+ : null,
659
+ opencode_command: profile.writeOpenCodeCommand
660
+ ? join(root, '.opencode', 'commands', 'audit-code.md')
661
+ : null,
662
+ claude_code_command: profile.writeClaudeCommand
663
+ ? join(root, '.claude', 'commands', 'audit-code.md')
664
+ : null,
665
+ };
666
+ const instructionSurfaces = {
667
+ copilot_instructions: profile.writeCopilotInstructions
668
+ ? join(root, '.github', 'copilot-instructions.md')
669
+ : null,
670
+ agents: profile.writeAgents ? join(root, 'AGENTS.md') : null,
671
+ claude: profile.writeClaudeMemory ? join(root, 'CLAUDE.md') : null,
672
+ };
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
+ : [];
687
+ const hostGuidance = buildInstallHostGuidance({
688
+ installedPromptPath,
689
+ slashCommandSurfaces,
690
+ instructionSurfaces,
691
+ });
692
+
693
+ const results = [];
694
+ results.push(
695
+ await writeGeneratedMarkdown(
696
+ installedPromptPath,
697
+ promptSource,
698
+ ),
699
+ );
700
+ results.push(await writeGeneratedMarkdown(installedSkillPath, skillSource));
701
+
702
+ if (profile.writeVSCodePrompt) {
703
+ results.push(
704
+ await writeGeneratedMarkdown(
705
+ join(root, '.github', 'prompts', 'audit-code.prompt.md'),
706
+ renderPromptFile(
707
+ {
708
+ name: 'audit-code',
709
+ description: 'Autonomous local loop code auditing',
710
+ agent: 'agent',
711
+ },
712
+ promptBody,
713
+ ),
714
+ ),
715
+ );
716
+ }
717
+
718
+ if (profile.writeOpenCodeCommand) {
719
+ results.push(
720
+ await writeGeneratedMarkdown(
721
+ join(root, '.opencode', 'commands', 'audit-code.md'),
722
+ renderPromptFile(
723
+ {
724
+ description: 'Autonomous local loop code auditing',
725
+ agent: 'build',
726
+ subtask: false,
727
+ },
728
+ promptBody,
729
+ ),
730
+ ),
731
+ );
732
+ }
733
+
734
+ if (profile.writeClaudeCommand) {
735
+ results.push(
736
+ await writeGeneratedMarkdown(
737
+ join(root, '.claude', 'commands', 'audit-code.md'),
738
+ renderPromptFile(
739
+ {
740
+ description: 'Autonomous local loop code auditing',
741
+ },
742
+ promptBody,
743
+ ),
744
+ ),
745
+ );
746
+ }
747
+
748
+ const compatibilityBlockTargets = Object.values(instructionSurfaces).filter(Boolean);
749
+
750
+ for (const targetPath of compatibilityBlockTargets) {
751
+ results.push(
752
+ await writeManagedMarkdown(
753
+ targetPath,
754
+ buildInstallDirective(
755
+ relative(dirname(targetPath), installedPromptPath) || './.audit-code/install/audit-code.prompt.md',
756
+ ),
757
+ ),
758
+ );
759
+ }
760
+
761
+ if (profile.writeCompatibilitySkills) {
762
+ const skillTargets = [
763
+ join(root, '.opencode', 'skills', 'audit-code'),
764
+ join(root, '.claude', 'skills', 'audit-code'),
765
+ join(root, '.agents', 'skills', 'audit-code'),
766
+ ];
767
+
768
+ for (const targetDir of skillTargets) {
769
+ results.push(
770
+ await writeGeneratedMarkdown(join(targetDir, 'SKILL.md'), skillSource),
771
+ );
772
+ results.push(
773
+ await writeGeneratedMarkdown(
774
+ join(targetDir, 'audit-code.prompt.md'),
775
+ promptSource,
776
+ ),
777
+ );
778
+ }
779
+ }
780
+
781
+ results.push(
782
+ await writeGeneratedMarkdown(
783
+ installGuidePath,
784
+ renderInstallGuide({
785
+ root,
786
+ host,
787
+ installedPromptPath,
788
+ installedSkillPath,
789
+ slashCommandSurfaces,
790
+ instructionSurfaces,
791
+ unsupportedHosts,
792
+ hostGuidance,
793
+ }),
794
+ ),
795
+ );
796
+
797
+ console.log(
798
+ JSON.stringify(
799
+ {
800
+ host,
801
+ repo_root: root,
802
+ installed_prompt_path: installedPromptPath,
803
+ installed_skill_path: installedSkillPath,
804
+ install_guide_path: installGuidePath,
805
+ source_prompt_path: resolve(promptAssetPath),
806
+ files: results,
807
+ slash_command_surfaces: slashCommandSurfaces,
808
+ instruction_surfaces: instructionSurfaces,
809
+ host_guidance: hostGuidance,
810
+ unsupported_hosts: unsupportedHosts,
811
+ next_steps: [
812
+ 'Open the repository in your preferred host and follow the matching host_guidance entry.',
813
+ `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.',
815
+ ],
816
+ },
817
+ null,
818
+ 2,
819
+ ),
820
+ );
821
+ }
822
+
823
+ async function installHostPrompt(argv) {
824
+ const host = requireFlagValue(argv, '--host').toLowerCase();
825
+
826
+ if (host !== 'copilot') {
827
+ throw new Error(
828
+ `install-host currently supports only "copilot". Use "install --host ${host}" for the broader bootstrap flow.`,
829
+ );
830
+ }
831
+
832
+ await installBootstrap(argv);
833
+ }
834
+
835
+ async function runDistCommand(commandName, argv, { ensureArtifactsDir = false } = {}) {
836
+ const commandArgs = [...argv];
837
+ const rootValue = resolve(getFlag(commandArgs, '--root') ?? '.');
838
+ const artifactsDir = resolve(getFlag(commandArgs, '--artifacts-dir') ?? join(rootValue, '.audit-artifacts'));
839
+
840
+ setDefaultFlag(commandArgs, '--root', rootValue);
841
+ setDefaultFlag(commandArgs, '--artifacts-dir', artifactsDir);
842
+
843
+ if (ensureArtifactsDir) {
844
+ await mkdir(artifactsDir, { recursive: true });
845
+ }
846
+
847
+ await ensureBuilt();
848
+ await run(nodeExecutable(), [distEntry, commandName, ...commandArgs]);
849
+ }
850
+
851
+ export async function runAuditCodeWrapper({
852
+ usageName,
853
+ argv = process.argv.slice(2),
854
+ 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')) {
864
+ console.log(packageVersion);
865
+ return;
866
+ }
867
+
868
+ if (argv[0] === 'prompt-path') {
869
+ await printPromptPath();
870
+ return;
871
+ }
872
+
873
+ if (argv[0] === 'install') {
874
+ await installBootstrap(argv.slice(1));
875
+ return;
876
+ }
877
+
878
+ if (argv[0] === 'install-host') {
879
+ await installHostPrompt(argv.slice(1));
880
+ return;
881
+ }
882
+
883
+ if (argv[0] === 'validate') {
884
+ await runDistCommand('validate', argv.slice(1));
885
+ return;
886
+ }
887
+
888
+ 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
+ }