auditor-lambda 0.10.3 → 0.10.7

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 (183) hide show
  1. package/audit-code-wrapper-build.mjs +198 -0
  2. package/audit-code-wrapper-install-hosts.mjs +1140 -0
  3. package/audit-code-wrapper-io.mjs +155 -0
  4. package/audit-code-wrapper-legacy.mjs +125 -0
  5. package/audit-code-wrapper-lib.mjs +17 -1801
  6. package/audit-code-wrapper-opencode.mjs +256 -0
  7. package/dispatch/merge-results.mjs +5 -3
  8. package/dispatch/validate-result.mjs +2 -2
  9. package/dist/adapters/coverageSummary.js +6 -2
  10. package/dist/adapters/normalizeExternal.js +16 -1
  11. package/dist/adapters/npmAudit.js +20 -9
  12. package/dist/adapters/semgrep.js +26 -1
  13. package/dist/cli/advanceAuditCommand.d.ts +1 -0
  14. package/dist/cli/advanceAuditCommand.js +95 -0
  15. package/dist/cli/args.js +1 -2
  16. package/dist/cli/auditStep.js +2 -2
  17. package/dist/cli/cleanup.d.ts +11 -1
  18. package/dist/cli/cleanup.js +25 -5
  19. package/dist/cli/cleanupCommand.d.ts +1 -0
  20. package/dist/cli/cleanupCommand.js +24 -0
  21. package/dist/cli/dispatch.d.ts +55 -31
  22. package/dist/cli/dispatch.js +298 -241
  23. package/dist/cli/dispatchStatusCommand.d.ts +1 -0
  24. package/dist/cli/dispatchStatusCommand.js +68 -0
  25. package/dist/cli/explainTaskCommand.d.ts +1 -0
  26. package/dist/cli/explainTaskCommand.js +33 -0
  27. package/dist/cli/importExternalAnalyzerCommand.d.ts +1 -0
  28. package/dist/cli/importExternalAnalyzerCommand.js +20 -0
  29. package/dist/cli/ingestResultsCommand.d.ts +1 -0
  30. package/dist/cli/ingestResultsCommand.js +34 -0
  31. package/dist/cli/intakeCommand.d.ts +1 -0
  32. package/dist/cli/intakeCommand.js +17 -0
  33. package/dist/cli/lineIndex.js +19 -12
  34. package/dist/cli/nextStepCommand.d.ts +139 -0
  35. package/dist/cli/nextStepCommand.js +281 -232
  36. package/dist/cli/planCommand.d.ts +1 -0
  37. package/dist/cli/planCommand.js +16 -0
  38. package/dist/cli/prepareDispatchCommand.d.ts +1 -0
  39. package/dist/cli/prepareDispatchCommand.js +25 -0
  40. package/dist/cli/quotaCommand.d.ts +1 -0
  41. package/dist/cli/quotaCommand.js +56 -0
  42. package/dist/cli/requeueCommand.d.ts +1 -0
  43. package/dist/cli/requeueCommand.js +10 -0
  44. package/dist/cli/runToCompletion.js +451 -412
  45. package/dist/cli/sampleRunCommand.d.ts +1 -0
  46. package/dist/cli/sampleRunCommand.js +93 -0
  47. package/dist/cli/statusCommand.js +1 -1
  48. package/dist/cli/steps.js +4 -1
  49. package/dist/cli/submitPacketCommand.js +16 -15
  50. package/dist/cli/synthesizeCommand.d.ts +1 -0
  51. package/dist/cli/synthesizeCommand.js +15 -0
  52. package/dist/cli/updateRuntimeValidationCommand.d.ts +1 -0
  53. package/dist/cli/updateRuntimeValidationCommand.js +16 -0
  54. package/dist/cli/validateCommand.d.ts +1 -0
  55. package/dist/cli/validateCommand.js +41 -0
  56. package/dist/cli/validateResultCommand.d.ts +1 -0
  57. package/dist/cli/validateResultCommand.js +63 -0
  58. package/dist/cli/validateResultsCommand.d.ts +1 -0
  59. package/dist/cli/validateResultsCommand.js +31 -0
  60. package/dist/cli/workerRunCommand.d.ts +15 -1
  61. package/dist/cli/workerRunCommand.js +40 -4
  62. package/dist/cli.d.ts +3 -2
  63. package/dist/cli.js +21 -628
  64. package/dist/coverage.js +7 -3
  65. package/dist/extractors/analyzers/css.js +2 -2
  66. package/dist/extractors/analyzers/html.js +2 -2
  67. package/dist/extractors/analyzers/python.js +2 -2
  68. package/dist/extractors/analyzers/registry.js +17 -36
  69. package/dist/extractors/analyzers/treeSitter.d.ts +10 -1
  70. package/dist/extractors/analyzers/treeSitter.js +28 -6
  71. package/dist/extractors/analyzers/typescript.js +104 -85
  72. package/dist/extractors/browserExtension.js +4 -1
  73. package/dist/extractors/designAssessment.js +21 -21
  74. package/dist/extractors/fsIntake.js +34 -10
  75. package/dist/extractors/graph.js +17 -7
  76. package/dist/extractors/graphManifestEdges/cargo.d.ts +4 -0
  77. package/dist/extractors/graphManifestEdges/cargo.js +107 -0
  78. package/dist/extractors/graphManifestEdges/go.d.ts +5 -0
  79. package/dist/extractors/graphManifestEdges/go.js +151 -0
  80. package/dist/extractors/graphManifestEdges/index.d.ts +8 -0
  81. package/dist/extractors/graphManifestEdges/index.js +11 -0
  82. package/dist/extractors/graphManifestEdges/jsonc.d.ts +3 -0
  83. package/dist/extractors/graphManifestEdges/jsonc.js +97 -0
  84. package/dist/extractors/graphManifestEdges/maven.d.ts +3 -0
  85. package/dist/extractors/graphManifestEdges/maven.js +73 -0
  86. package/dist/extractors/graphManifestEdges/packageJson.d.ts +19 -0
  87. package/dist/extractors/graphManifestEdges/packageJson.js +204 -0
  88. package/dist/extractors/graphManifestEdges/pnpm.d.ts +2 -0
  89. package/dist/extractors/graphManifestEdges/pnpm.js +42 -0
  90. package/dist/extractors/graphManifestEdges/pyproject.d.ts +3 -0
  91. package/dist/extractors/graphManifestEdges/pyproject.js +83 -0
  92. package/dist/extractors/graphManifestEdges/toml.d.ts +4 -0
  93. package/dist/extractors/graphManifestEdges/toml.js +68 -0
  94. package/dist/extractors/graphManifestEdges/typescript.d.ts +3 -0
  95. package/dist/extractors/graphManifestEdges/typescript.js +56 -0
  96. package/dist/extractors/graphManifestEdges/workspace.d.ts +10 -0
  97. package/dist/extractors/graphManifestEdges/workspace.js +72 -0
  98. package/dist/extractors/graphManifestEdges/yaml.d.ts +3 -0
  99. package/dist/extractors/graphManifestEdges/yaml.js +59 -0
  100. package/dist/extractors/graphManifestEdges/yamlPaths.d.ts +4 -0
  101. package/dist/extractors/graphManifestEdges/yamlPaths.js +89 -0
  102. package/dist/extractors/graphPythonImports.js +4 -20
  103. package/dist/extractors/pathPatterns.js +3 -13
  104. package/dist/io/artifacts.d.ts +1 -1
  105. package/dist/io/artifacts.js +4 -1
  106. package/dist/io/runArtifacts.d.ts +8 -2
  107. package/dist/io/runArtifacts.js +103 -69
  108. package/dist/io/toolingManifest.js +2 -1
  109. package/dist/orchestrator/advance.js +36 -0
  110. package/dist/orchestrator/artifactFreshness.d.ts +1 -1
  111. package/dist/orchestrator/artifactFreshness.js +1 -1
  112. package/dist/orchestrator/artifactMetadata.js +5 -5
  113. package/dist/orchestrator/auditTaskUtils.d.ts +4 -0
  114. package/dist/orchestrator/auditTaskUtils.js +8 -12
  115. package/dist/orchestrator/autoFixExecutor.js +40 -26
  116. package/dist/orchestrator/dependencyMap.js +1 -1
  117. package/dist/orchestrator/executorResult.d.ts +33 -0
  118. package/dist/orchestrator/executors.d.ts +7 -0
  119. package/dist/orchestrator/executors.js +24 -0
  120. package/dist/orchestrator/fileAnchors.js +42 -29
  121. package/dist/orchestrator/fileIntegrity.js +6 -1
  122. package/dist/orchestrator/flowCoverage.js +1 -2
  123. package/dist/orchestrator/flowPlanning.js +8 -4
  124. package/dist/orchestrator/graphEnrichmentExecutor.js +67 -45
  125. package/dist/orchestrator/ingestionExecutors.js +9 -1
  126. package/dist/orchestrator/intakeExecutors.d.ts +0 -4
  127. package/dist/orchestrator/intakeExecutors.js +24 -14
  128. package/dist/orchestrator/localCommands.d.ts +1 -0
  129. package/dist/orchestrator/localCommands.js +10 -17
  130. package/dist/orchestrator/nextStep.js +3 -1
  131. package/dist/orchestrator/requeueCommand.js +4 -0
  132. package/dist/orchestrator/reviewPacketGraph.js +50 -18
  133. package/dist/orchestrator/reviewPackets.js +10 -8
  134. package/dist/orchestrator/runtimeCommand.js +35 -7
  135. package/dist/orchestrator/runtimeValidationUpdate.js +6 -0
  136. package/dist/orchestrator/selectiveDeepening/highRiskClean.js +3 -2
  137. package/dist/orchestrator/selectiveDeepening/lensVerification.js +44 -18
  138. package/dist/orchestrator/staleness.js +3 -3
  139. package/dist/orchestrator/state.js +1 -1
  140. package/dist/orchestrator/syntaxResolutionExecutor.js +17 -24
  141. package/dist/orchestrator/synthesisExecutors.js +1 -0
  142. package/dist/orchestrator/taskBuilder.js +5 -4
  143. package/dist/providers/claudeCodeProvider.js +4 -1
  144. package/dist/providers/opencodeProvider.js +4 -1
  145. package/dist/quota/discoveredLimits.js +3 -3
  146. package/dist/quota/headerExtraction.js +5 -2
  147. package/dist/quota/headerExtractors/claudeCodeHeaderExtractor.js +3 -0
  148. package/dist/quota/headerExtractors/index.js +3 -3
  149. package/dist/quota/index.d.ts +3 -1
  150. package/dist/quota/index.js +3 -0
  151. package/dist/reporting/findingRanks.d.ts +3 -0
  152. package/dist/reporting/findingRanks.js +24 -0
  153. package/dist/reporting/mergeFindings.js +1 -24
  154. package/dist/reporting/synthesis.d.ts +3 -1
  155. package/dist/reporting/synthesis.js +30 -6
  156. package/dist/reporting/synthesisNarrativePrompt.js +3 -0
  157. package/dist/reporting/workBlocks.js +1 -14
  158. package/dist/supervisor/operatorHandoff.js +2 -6
  159. package/dist/supervisor/runLedger.js +30 -41
  160. package/dist/types/activeDispatch.d.ts +31 -0
  161. package/dist/types/activeDispatch.js +2 -0
  162. package/dist/types.d.ts +21 -4
  163. package/dist/types.js +24 -16
  164. package/dist/validation/artifacts.js +3 -0
  165. package/dist/validation/auditResults.js +8 -2
  166. package/package.json +2 -2
  167. package/schemas/audit_findings.schema.json +5 -1
  168. package/schemas/audit_plan_metrics.schema.json +1 -1
  169. package/schemas/audit_result.schema.json +5 -6
  170. package/schemas/audit_task.schema.json +1 -4
  171. package/schemas/blind_spot_register.schema.json +1 -1
  172. package/schemas/coverage_matrix.schema.json +2 -8
  173. package/schemas/finding.schema.json +1 -16
  174. package/schemas/flow_coverage.schema.json +2 -8
  175. package/schemas/graph_bundle.schema.json +31 -0
  176. package/schemas/lens.schema.json +7 -0
  177. package/schemas/review_packets.schema.json +6 -17
  178. package/schemas/step_contract.schema.json +8 -2
  179. package/schemas/unit_manifest.schema.json +1 -4
  180. package/scripts/postinstall.mjs +3 -1
  181. package/skills/audit-code/audit-code.prompt.md +2 -3
  182. package/dist/extractors/graphManifestEdges.d.ts +0 -12
  183. package/dist/extractors/graphManifestEdges.js +0 -1135
@@ -1,27 +1,27 @@
1
- import { access, cp, mkdir, open, readFile, readdir, stat, unlink, writeFile } from 'node:fs/promises';
2
- import { constants } from 'node:fs';
1
+ import { mkdir, readFile } from 'node:fs/promises';
3
2
  import { spawn } from 'node:child_process';
4
- import { createRequire } from 'node:module';
5
- import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
3
+ import { dirname, join, resolve } from 'node:path';
6
4
  import { fileURLToPath } from 'node:url';
5
+ import { ensureBuilt, shouldBuildDistForPaths, assertWorkspaceInstalled } from './audit-code-wrapper-build.mjs';
6
+ import { fileExists } from './audit-code-wrapper-io.mjs';
7
+ import {
8
+ installBootstrap,
9
+ verifyInstalledBootstrap,
10
+ ensureBootstrap,
11
+ installHostPrompt,
12
+ _INSTALL_HOST_ORDER,
13
+ _INSTALL_HOST_DEFINITIONS,
14
+ _getInstallHostKeys,
15
+ _getInstallProfile,
16
+ } from './audit-code-wrapper-install-hosts.mjs';
17
+
18
+ export { shouldBuildDistForPaths, assertWorkspaceInstalled };
19
+ export { _INSTALL_HOST_ORDER, _INSTALL_HOST_DEFINITIONS, _getInstallHostKeys, _getInstallProfile };
7
20
 
8
21
  const repoRoot = dirname(fileURLToPath(import.meta.url));
9
22
  const distEntry = join(repoRoot, 'dist', 'index.js');
10
23
  const packageJsonPath = join(repoRoot, 'package.json');
11
24
  const promptAssetPath = join(repoRoot, 'skills', 'audit-code', 'audit-code.prompt.md');
12
- const skillAssetPath = join(repoRoot, 'skills', 'audit-code', 'SKILL.md');
13
- const tsconfigPath = join(repoRoot, 'tsconfig.json');
14
- const sourceRoot = join(repoRoot, 'src');
15
- const buildLockPath = join(repoRoot, '.audit-code-build.lock');
16
- const BUILD_LOCK_MAX_AGE_MS = 5 * 60 * 1000;
17
- const BUILD_LOCK_WAIT_TIMEOUT_MS = 2 * 60 * 1000;
18
- const BUILD_LOCK_WAIT_INTERVAL_MS = 200;
19
- const INSTALL_MARKER_START = '<!-- audit-code:begin -->';
20
- const INSTALL_MARKER_END = '<!-- audit-code:end -->';
21
- const INSTALL_GUIDE_FILENAME = 'GETTING-STARTED.md';
22
- const INSTALL_MANIFEST_FILENAME = 'manifest.json';
23
- const DEFAULT_INSTALL_HOST = 'all';
24
- const INSTALLED_PROMPT_FILENAME = 'audit-code.import.md';
25
25
  const packageVersion = JSON.parse(await readFile(packageJsonPath, 'utf8')).version;
26
26
 
27
27
  function hasFlag(argv, name) {
@@ -40,18 +40,6 @@ function setDefaultFlag(argv, name, value) {
40
40
  }
41
41
  }
42
42
 
43
- function requireFlagValue(argv, name) {
44
- const value = getFlag(argv, name);
45
- if (!value) {
46
- throw new Error(`${name} requires a value.`);
47
- }
48
- return value;
49
- }
50
-
51
- function npmExecutable() {
52
- return process.platform === 'win32' ? 'npm.cmd' : 'npm';
53
- }
54
-
55
43
  function nodeExecutable() {
56
44
  return process.execPath;
57
45
  }
@@ -124,189 +112,6 @@ function run(command, args, options = {}) {
124
112
  });
125
113
  }
126
114
 
127
- async function sleep(ms) {
128
- await new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
129
- }
130
-
131
- async function fileExists(path) {
132
- try {
133
- await access(path, constants.F_OK);
134
- return true;
135
- } catch {
136
- return false;
137
- }
138
- }
139
-
140
- async function newestMtimeMs(path) {
141
- const stats = await stat(path);
142
- if (!stats.isDirectory()) {
143
- return stats.mtimeMs;
144
- }
145
-
146
- let newest = stats.mtimeMs;
147
- const entries = await readdir(path, { withFileTypes: true });
148
- for (const entry of entries) {
149
- const childPath = join(path, entry.name);
150
- if (entry.isDirectory()) {
151
- newest = Math.max(newest, await newestMtimeMs(childPath));
152
- continue;
153
- }
154
- if (entry.isFile()) {
155
- newest = Math.max(newest, (await stat(childPath)).mtimeMs);
156
- }
157
- }
158
- return newest;
159
- }
160
-
161
- export async function shouldBuildDistForPaths({
162
- distEntryPath,
163
- sourceRootPath,
164
- tsconfigPath: tsconfigPathValue,
165
- }) {
166
- if (!(await fileExists(distEntryPath))) {
167
- if (!(await fileExists(sourceRootPath)) || !(await fileExists(tsconfigPathValue))) {
168
- throw new Error(
169
- 'Bundled dist is missing and source files are unavailable for rebuild.',
170
- );
171
- }
172
- return true;
173
- }
174
-
175
- if (!(await fileExists(sourceRootPath)) || !(await fileExists(tsconfigPathValue))) {
176
- return false;
177
- }
178
-
179
- const distMtime = (await stat(distEntryPath)).mtimeMs;
180
- const sourceMtime = await newestMtimeMs(sourceRootPath);
181
- const tsconfigMtime = (await stat(tsconfigPathValue)).mtimeMs;
182
- const newestInput = Math.max(sourceMtime, tsconfigMtime);
183
- return distMtime < newestInput;
184
- }
185
-
186
- async function shouldBuildDist() {
187
- return await shouldBuildDistForPaths({
188
- distEntryPath: distEntry,
189
- sourceRootPath: sourceRoot,
190
- tsconfigPath,
191
- });
192
- }
193
-
194
- async function releaseBuildLock(handle) {
195
- try {
196
- await handle?.close();
197
- } finally {
198
- await unlink(buildLockPath).catch(() => {});
199
- }
200
- }
201
-
202
- async function waitForPeerBuild() {
203
- const start = Date.now();
204
-
205
- while (true) {
206
- if (!(await fileExists(buildLockPath))) {
207
- return;
208
- }
209
-
210
- if (Date.now() - start > BUILD_LOCK_WAIT_TIMEOUT_MS) {
211
- throw new Error(`Timed out waiting for build lock ${buildLockPath}.`);
212
- }
213
-
214
- await sleep(BUILD_LOCK_WAIT_INTERVAL_MS);
215
- }
216
- }
217
-
218
- async function acquireBuildLock() {
219
- while (true) {
220
- try {
221
- const handle = await open(buildLockPath, 'wx');
222
- await handle.writeFile(JSON.stringify({ pid: process.pid, acquired_at: new Date().toISOString() }));
223
- return handle;
224
- } catch (error) {
225
- if (error && error.code === 'EEXIST') {
226
- try {
227
- const lockStats = await stat(buildLockPath);
228
- if (Date.now() - lockStats.mtimeMs > BUILD_LOCK_MAX_AGE_MS) {
229
- await unlink(buildLockPath).catch(() => {});
230
- continue;
231
- }
232
- } catch {
233
- continue;
234
- }
235
-
236
- await waitForPeerBuild();
237
- if (!(await shouldBuildDist())) {
238
- return null;
239
- }
240
- continue;
241
- }
242
- throw error;
243
- }
244
- }
245
- }
246
-
247
- // Pure, testable core of the build preflight. `sharedManifestPath` is the
248
- // resolved path of @audit-tools/shared's package.json (or null if it could not
249
- // be resolved at all); `checkoutRoot` is the root this wrapper belongs to.
250
- export function assertWorkspaceInstalled({ checkoutRoot, sharedManifestPath }) {
251
- if (!sharedManifestPath) {
252
- throw new Error(
253
- 'Dependencies are not installed for this checkout. Run `npm install` from ' +
254
- 'the repository root, then retry — building from source needs node_modules ' +
255
- '(including the @audit-tools/shared workspace link).',
256
- );
257
- }
258
-
259
- const relToCheckout = relative(checkoutRoot, sharedManifestPath);
260
- if (relToCheckout.startsWith('..') || isAbsolute(relToCheckout)) {
261
- throw new Error(
262
- `@audit-tools/shared resolved to ${sharedManifestPath}, outside this ` +
263
- `checkout (${checkoutRoot}). node_modules was never installed here — ` +
264
- 'common in a fresh git worktree — so building would typecheck against ' +
265
- "another checkout's stale dist and report phantom \"missing export\" " +
266
- "errors. Run `npm install` from this checkout's root.",
267
- );
268
- }
269
- }
270
-
271
- // Catches the common fresh-checkout trap before `npm run build` runs: with no
272
- // local node_modules, Node/tsc resolve @audit-tools/shared against a different
273
- // checkout (e.g. the main repo when running inside a git worktree).
274
- async function preflightWorkspace() {
275
- const requireFromHere = createRequire(import.meta.url);
276
- let sharedManifestPath = null;
277
- try {
278
- sharedManifestPath = requireFromHere.resolve('@audit-tools/shared/package.json');
279
- } catch {
280
- sharedManifestPath = null;
281
- }
282
- assertWorkspaceInstalled({
283
- checkoutRoot: resolve(repoRoot, '..', '..'),
284
- sharedManifestPath,
285
- });
286
- }
287
-
288
- async function ensureBuilt() {
289
- if (!(await shouldBuildDist())) {
290
- return;
291
- }
292
-
293
- await preflightWorkspace();
294
-
295
- const lockHandle = await acquireBuildLock();
296
- if (!lockHandle) {
297
- return;
298
- }
299
-
300
- try {
301
- if (!(await shouldBuildDist())) {
302
- return;
303
- }
304
- await run(npmExecutable(), ['run', 'build']);
305
- } finally {
306
- await releaseBuildLock(lockHandle);
307
- }
308
- }
309
-
310
115
  function printHelp({ usageName, preferredEntrypoint }) {
311
116
  const lines = [
312
117
  `Usage: node ${usageName} [--single-step] [--root PATH] [--artifacts-dir PATH] [--results FILE] [--batch-results DIR] [--updates FILE] [--external-analyzer-results FILE] [--timeout MS]`,
@@ -358,1595 +163,6 @@ async function printPromptPath() {
358
163
  console.log(resolve(promptAssetPath));
359
164
  }
360
165
 
361
- function normalizeNewlines(value) {
362
- return value.replace(/\r\n/g, '\n');
363
- }
364
-
365
- function splitFrontmatter(markdown) {
366
- const normalized = normalizeNewlines(markdown);
367
- const match = normalized.match(/^---\n([\s\S]*?)\n---\n?/u);
368
- if (!match) {
369
- return { frontmatter: null, body: normalized };
370
- }
371
-
372
- return {
373
- frontmatter: match[1],
374
- body: normalized.slice(match[0].length),
375
- };
376
- }
377
-
378
- function renderFrontmatter(fields) {
379
- const entries = Object.entries(fields).filter(([, value]) => {
380
- if (value === undefined || value === null) {
381
- return false;
382
- }
383
-
384
- if (typeof value === 'string') {
385
- return value.length > 0;
386
- }
387
-
388
- return true;
389
- });
390
- if (entries.length === 0) {
391
- return '';
392
- }
393
-
394
- return [
395
- '---',
396
- ...entries.map(([key, value]) => `${key}: ${value}`),
397
- '---',
398
- '',
399
- ].join('\n');
400
- }
401
-
402
- function renderPromptFile(fields, body) {
403
- return `${renderFrontmatter(fields)}${body.trimStart()}`;
404
- }
405
-
406
- function toRepoRelativePath(root, targetPath) {
407
- const value = relative(root, targetPath).replace(/\\/g, '/');
408
- return value.length > 0 ? value : '.';
409
- }
410
-
411
- function buildInstallDirective(relativePromptPath) {
412
- return [
413
- INSTALL_MARKER_START,
414
- '## /audit-code',
415
- 'When the user enters `/audit-code`, treat it as this repository\'s autonomous audit workflow.',
416
- `If your host does not automatically register the installed slash command file, load and follow [the repo-local audit directive](${relativePromptPath.replace(/\\/g, '/')}).`,
417
- 'Normal usage should stay conversation-first and avoid manual `--root`, provider flags, or model-selection arguments.',
418
- INSTALL_MARKER_END,
419
- ].join('\n');
420
- }
421
-
422
- function upsertManagedBlock(existingContent, blockContent) {
423
- const normalized = normalizeNewlines(existingContent);
424
- const blockPattern = new RegExp(
425
- `${INSTALL_MARKER_START}[\\s\\S]*?${INSTALL_MARKER_END}`,
426
- 'u',
427
- );
428
-
429
- if (blockPattern.test(normalized)) {
430
- return normalized.replace(blockPattern, blockContent);
431
- }
432
-
433
- if (normalized.trim().length === 0) {
434
- return `${blockContent}\n`;
435
- }
436
-
437
- return `${normalized.replace(/\s+$/u, '')}\n\n${blockContent}\n`;
438
- }
439
-
440
- async function writeManagedMarkdown(targetPath, blockContent) {
441
- const existed = await fileExists(targetPath);
442
- const existingContent = existed ? await readFile(targetPath, 'utf8') : '';
443
- const nextContent = upsertManagedBlock(existingContent, blockContent);
444
- await mkdir(dirname(targetPath), { recursive: true });
445
- await writeFile(targetPath, nextContent, 'utf8');
446
- return {
447
- path: targetPath,
448
- mode: existed ? 'updated' : 'created',
449
- };
450
- }
451
-
452
- async function writeGeneratedMarkdown(targetPath, content) {
453
- const existed = await fileExists(targetPath);
454
- await mkdir(dirname(targetPath), { recursive: true });
455
- await writeFile(targetPath, content, 'utf8');
456
- return {
457
- path: targetPath,
458
- mode: existed ? 'updated' : 'created',
459
- };
460
- }
461
-
462
- function looksLikeAuditCodeSkill(content) {
463
- const normalized = normalizeNewlines(content);
464
- return (
465
- /^name:\s*audit-code\b/mu.test(normalized)
466
- || normalized.includes('Conversation-first autonomous code auditing workflow for the /audit-code command.')
467
- || normalized.includes('The canonical entrypoint is `/audit-code` in conversation.')
468
- );
469
- }
470
-
471
- function looksLikeAuditCodePrompt(content) {
472
- const normalized = normalizeNewlines(content);
473
- return (
474
- normalized.includes('# `/audit-code`')
475
- && (
476
- normalized.includes('audit-code orchestrator')
477
- || normalized.includes('Autonomous local loop code auditing')
478
- || normalized.includes('Conversation-first autonomous code auditing workflow')
479
- )
480
- );
481
- }
482
-
483
- function looksLikeAuditCodeInterfaceMetadata(content) {
484
- const normalized = normalizeNewlines(content);
485
- return (
486
- normalized.includes('audit-code')
487
- && (
488
- normalized.includes('display_name:')
489
- || normalized.includes('short_description:')
490
- || normalized.includes('default_prompt:')
491
- )
492
- && (
493
- normalized.includes('/audit-code')
494
- || normalized.includes('Start /audit-code')
495
- )
496
- );
497
- }
498
-
499
- async function buildLegacyAuditCodeSurfaceTargets(root) {
500
- const targets = [
501
- {
502
- host: 'codex',
503
- surface: 'skill',
504
- path: join(root, '.codex', 'skills', 'audit-code', 'SKILL.md'),
505
- matches: looksLikeAuditCodeSkill,
506
- },
507
- {
508
- host: 'codex',
509
- surface: 'prompt',
510
- path: join(root, '.codex', 'skills', 'audit-code', 'audit-code.prompt.md'),
511
- matches: looksLikeAuditCodePrompt,
512
- },
513
- {
514
- host: 'opencode',
515
- surface: 'command',
516
- path: join(root, '.opencode', 'commands', 'audit-code.md'),
517
- matches: looksLikeAuditCodePrompt,
518
- },
519
- {
520
- host: 'opencode',
521
- surface: 'skill',
522
- path: join(root, '.opencode', 'skills', 'audit-code', 'SKILL.md'),
523
- matches: looksLikeAuditCodeSkill,
524
- },
525
- {
526
- host: 'opencode',
527
- surface: 'prompt',
528
- path: join(root, '.opencode', 'skills', 'audit-code', 'audit-code.prompt.md'),
529
- matches: looksLikeAuditCodePrompt,
530
- },
531
- {
532
- host: 'claude',
533
- surface: 'command',
534
- path: join(root, '.claude', 'commands', 'audit-code.md'),
535
- matches: looksLikeAuditCodePrompt,
536
- },
537
- ];
538
-
539
- const codexAgentDir = join(root, '.codex', 'skills', 'audit-code', 'agents');
540
- const codexAgentEntries = await readdir(codexAgentDir).catch(() => []);
541
- for (const entry of codexAgentEntries) {
542
- targets.push({
543
- host: 'codex',
544
- surface: 'interface-metadata',
545
- path: join(codexAgentDir, entry),
546
- matches: looksLikeAuditCodeInterfaceMetadata,
547
- });
548
- }
549
-
550
- return targets;
551
- }
552
-
553
- async function findLegacyAuditCodeSurfaceFiles(root) {
554
- const matches = [];
555
- for (const target of await buildLegacyAuditCodeSurfaceTargets(root)) {
556
- const existing = await readTextIfExists(target.path);
557
- if (existing !== null && target.matches(existing)) {
558
- matches.push(target.path);
559
- }
560
- }
561
- return matches;
562
- }
563
-
564
- async function removeLegacyAuditCodeSurfaceFiles(root) {
565
- const removed = [];
566
- for (const target of await buildLegacyAuditCodeSurfaceTargets(root)) {
567
- const existing = await readTextIfExists(target.path);
568
- if (existing === null || !target.matches(existing)) {
569
- continue;
570
- }
571
- await unlink(target.path);
572
- removed.push({
573
- path: target.path,
574
- mode: 'removed',
575
- });
576
- }
577
- return removed;
578
- }
579
-
580
- async function writeGeneratedJson(targetPath, value) {
581
- const existed = await fileExists(targetPath);
582
- await mkdir(dirname(targetPath), { recursive: true });
583
- await writeFile(targetPath, JSON.stringify(value, null, 2) + '\n', 'utf8');
584
- return {
585
- path: targetPath,
586
- mode: existed ? 'updated' : 'created',
587
- };
588
- }
589
-
590
- async function readJsonObjectIfExists(targetPath, description) {
591
- if (!(await fileExists(targetPath))) {
592
- return {};
593
- }
594
-
595
- let parsed;
596
- try {
597
- parsed = JSON.parse(await readFile(targetPath, 'utf8'));
598
- } catch (error) {
599
- throw new Error(
600
- `${description} exists but is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
601
- );
602
- }
603
-
604
- if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
605
- throw new Error(`${description} must be a JSON object when it already exists.`);
606
- }
607
-
608
- return parsed;
609
- }
610
-
611
- async function writeMergedGeneratedJson(targetPath, description, buildValue) {
612
- const existed = await fileExists(targetPath);
613
- const existing = await readJsonObjectIfExists(targetPath, description);
614
- await mkdir(dirname(targetPath), { recursive: true });
615
- await writeFile(
616
- targetPath,
617
- JSON.stringify(buildValue(existing), null, 2) + '\n',
618
- 'utf8',
619
- );
620
- return {
621
- path: targetPath,
622
- mode: existed ? 'updated' : 'created',
623
- };
624
- }
625
-
626
- async function writeGeneratedBinary(targetPath, content) {
627
- const existed = await fileExists(targetPath);
628
- await mkdir(dirname(targetPath), { recursive: true });
629
- await writeFile(targetPath, content);
630
- return {
631
- path: targetPath,
632
- mode: existed ? 'updated' : 'created',
633
- };
634
- }
635
-
636
- function replaceBackslashes(value) {
637
- return value.replace(/\\/g, '/');
638
- }
639
-
640
- function renderVSCodeAgentFile() {
641
- return [
642
- '---',
643
- 'description: Plan and orchestrate /audit-code through the next-step machine before making code changes.',
644
- '---',
645
- '',
646
- '# Auditor Agent',
647
- '',
648
- 'Use `audit-code next-step` as the primary integration surface for the audit workflow. The installed auditor MCP server is a compatibility adapter over the same step contract.',
649
- '',
650
- 'When the user asks to run or continue `/audit-code`:',
651
- '',
652
- '- run `audit-code next-step` directly when shell access is available',
653
- '- if MCP is the only available integration, call `start_audit`, `get_status`, and `continue_audit`; those tools return the same one-step contract',
654
- '- read `audit-code://handoff/current` and `audit-code://artifacts/current` when the audit blocks or you need current context',
655
- '- prefer imported audit results and runtime updates over ad hoc manual state edits',
656
- '- treat the deterministic audit report as the final source of truth once the audit completes',
657
- '',
658
- ].join('\n');
659
- }
660
-
661
- function renderCodexAutomationRecipe() {
662
- return [
663
- '# Codex re-audit automation recipe',
664
- '',
665
- 'Suggested recurring task:',
666
- '',
667
- '- Prompt: Re-run the autonomous audit workflow for this repository with `audit-code next-step`, summarize only new or regressed findings, and stop once the deterministic report is current.',
668
- '- Cadence: daily on active branches or before release cut-offs',
669
- '- Inputs: repository root',
670
- '',
671
- 'Use this recipe as a starting point for a Codex automation once the local workflow is stable in your environment.',
672
- '',
673
- ].join('\n');
674
- }
675
-
676
- const OPENCODE_AUDIT_EDIT_PERMISSION = {
677
- '*': 'ask',
678
- '.audit-code/**': 'allow',
679
- '.audit-artifacts/**': 'allow',
680
- 'audit-report.md': 'allow',
681
- };
682
-
683
- const OPENCODE_AUDIT_BASH_PERMISSION = {
684
- '*': 'allow',
685
- 'audit-code run-to-completion*': 'deny',
686
- 'audit-code synthesize*': 'deny',
687
- 'audit-code cleanup*': 'deny',
688
- 'audit-code requeue*': 'deny',
689
- 'audit-code ingest-results*': 'deny',
690
- '*dist*index.js* run-to-completion*': 'deny',
691
- '*dist*index.js* synthesize*': 'deny',
692
- '*dist*index.js* cleanup*': 'deny',
693
- '*dist*index.js* requeue*': 'deny',
694
- '*dist*index.js* ingest-results*': 'deny',
695
- '*audit-code.mjs* run-to-completion*': 'deny',
696
- '*audit-code.mjs* synthesize*': 'deny',
697
- '*audit-code.mjs* cleanup*': 'deny',
698
- '*audit-code.mjs* requeue*': 'deny',
699
- '*audit-code.mjs* ingest-results*': 'deny',
700
- 'audit-code': 'allow',
701
- 'audit-code ensure*': 'allow',
702
- 'audit-code next-step*': 'allow',
703
- 'audit-code prepare-dispatch*': 'allow',
704
- 'audit-code submit-packet*': 'allow',
705
- 'audit-code merge-and-ingest*': 'allow',
706
- 'audit-code validate*': 'allow',
707
- '*audit-code.mjs': 'allow',
708
- '*audit-code.mjs* ensure*': 'allow',
709
- '*audit-code.mjs* next-step*': 'allow',
710
- '*audit-code.mjs* prepare-dispatch*': 'allow',
711
- '*audit-code.mjs* submit-packet*': 'allow',
712
- '*audit-code.mjs* merge-and-ingest*': 'allow',
713
- '*audit-code.mjs* worker-run*': 'allow',
714
- '*audit-code.mjs* validate*': 'allow',
715
- '*node* *auditor-lambda*dist*index.js* worker-run*': 'allow',
716
- 'git status*': 'allow',
717
- 'git diff*': 'allow',
718
- 'grep *': 'allow',
719
- 'rm *': 'deny',
720
- };
721
-
722
- function externalDirectoryPattern(path) {
723
- return `${replaceBackslashes(path).replace(/\/+$/u, '')}/**`;
724
- }
725
-
726
- function renderOpenCodeExternalDirectoryPermission() {
727
- return { '*': 'allow' };
728
- }
729
-
730
- function renderOpenCodePermissionConfig() {
731
- return {
732
- read: 'allow',
733
- glob: 'allow',
734
- grep: 'allow',
735
- external_directory: renderOpenCodeExternalDirectoryPermission(),
736
- edit: { ...OPENCODE_AUDIT_EDIT_PERMISSION },
737
- bash: { ...OPENCODE_AUDIT_BASH_PERMISSION },
738
- };
739
- }
740
-
741
-
742
- function renderOpenCodeProjectConfig(_root) {
743
- const auditPermission = renderOpenCodePermissionConfig();
744
- return {
745
- $schema: 'https://opencode.ai/config.json',
746
- permission: auditPermission,
747
- agent: {
748
- auditor: {
749
- description:
750
- 'Read-heavy audit orchestration agent for the /audit-code workflow.',
751
- permission: {
752
- ...auditPermission,
753
- 'auditor_*': 'allow',
754
- question: 'allow',
755
- task: 'allow',
756
- },
757
- },
758
- },
759
- };
760
- }
761
-
762
- function objectValue(value) {
763
- return value && typeof value === 'object' && !Array.isArray(value)
764
- ? value
765
- : {};
766
- }
767
-
768
- function mergeOpenCodePermissionRule(existingRule, generatedRule, managedRules = {}) {
769
- if (generatedRule && typeof generatedRule === 'object' && !Array.isArray(generatedRule)) {
770
- const generatedObject = generatedRule;
771
- const merged = {};
772
- const existingObject =
773
- existingRule && typeof existingRule === 'object' && !Array.isArray(existingRule)
774
- ? existingRule
775
- : {};
776
-
777
- if (typeof existingRule === 'string') {
778
- merged['*'] = existingRule;
779
- } else {
780
- merged['*'] = existingObject['*'] ?? generatedObject['*'] ?? 'ask';
781
- }
782
-
783
- for (const [key, value] of Object.entries(generatedObject)) {
784
- if (key !== '*') merged[key] = value;
785
- }
786
- for (const [key, value] of Object.entries(existingObject)) {
787
- if (key !== '*') merged[key] = value;
788
- }
789
- for (const [key, value] of Object.entries(managedRules)) {
790
- merged[key] = value;
791
- }
792
-
793
- return merged;
794
- }
795
-
796
- return existingRule ?? generatedRule;
797
- }
798
-
799
- function mergeOpenCodePermissionConfig(existingPermission, generatedPermission) {
800
- if (!existingPermission || typeof existingPermission !== 'object' || Array.isArray(existingPermission)) {
801
- return generatedPermission;
802
- }
803
-
804
- return {
805
- ...generatedPermission,
806
- ...existingPermission,
807
- read: generatedPermission.read,
808
- glob: generatedPermission.glob,
809
- grep: generatedPermission.grep,
810
- external_directory: mergeOpenCodePermissionRule(
811
- existingPermission.external_directory,
812
- generatedPermission.external_directory,
813
- generatedPermission.external_directory,
814
- ),
815
- edit: mergeOpenCodePermissionRule(
816
- existingPermission.edit,
817
- generatedPermission.edit,
818
- OPENCODE_AUDIT_EDIT_PERMISSION,
819
- ),
820
- bash: mergeOpenCodePermissionRule(
821
- existingPermission.bash,
822
- generatedPermission.bash,
823
- OPENCODE_AUDIT_BASH_PERMISSION,
824
- ),
825
- };
826
- }
827
-
828
- function removeManagedOpenCodeCommand(commandConfig) {
829
- const command = objectValue(commandConfig);
830
- const { 'audit-code': _managedAuditCodeCommand, ...remaining } = command;
831
- return remaining;
832
- }
833
-
834
- function assertOpenCodeAuditPermissionConfig(permissionConfig, label) {
835
- for (const tool of ['read', 'glob', 'grep']) {
836
- if (permissionConfig?.[tool] !== 'allow') {
837
- throw new Error(`OpenCode ${label}.${tool} must be allow. Run "audit-code install --host opencode".`);
838
- }
839
- }
840
- const externalDirectory = permissionConfig?.external_directory;
841
- if (!externalDirectory || typeof externalDirectory !== 'object' || Array.isArray(externalDirectory)) {
842
- throw new Error(`OpenCode ${label}.external_directory must set "*" to "allow". Run "audit-code install --host opencode".`);
843
- }
844
- if (externalDirectory['*'] !== 'allow') {
845
- throw new Error(`OpenCode ${label}.external_directory must set "*" to "allow". Run "audit-code install --host opencode".`);
846
- }
847
- const edit = permissionConfig?.edit;
848
- const bash = permissionConfig?.bash;
849
- if (!edit || typeof edit !== 'object' || Array.isArray(edit)) {
850
- throw new Error(`OpenCode ${label}.edit must allow audit-owned file paths. Run "audit-code install --host opencode".`);
851
- }
852
- for (const pattern of ['.audit-code/**', '.audit-artifacts/**', 'audit-report.md']) {
853
- if (edit[pattern] !== 'allow') {
854
- throw new Error(`OpenCode ${label}.edit must allow ${pattern}. Run "audit-code install --host opencode".`);
855
- }
856
- }
857
- if (!bash || typeof bash !== 'object' || Array.isArray(bash)) {
858
- throw new Error(`OpenCode ${label}.bash must allow audit-code commands. Run "audit-code install --host opencode".`);
859
- }
860
- for (const pattern of [
861
- 'audit-code',
862
- 'audit-code ensure*',
863
- 'audit-code next-step*',
864
- 'audit-code prepare-dispatch*',
865
- 'audit-code submit-packet*',
866
- 'audit-code merge-and-ingest*',
867
- '*audit-code.mjs',
868
- '*audit-code.mjs* next-step*',
869
- '*audit-code.mjs* submit-packet*',
870
- '*audit-code.mjs* merge-and-ingest*',
871
- '*audit-code.mjs* worker-run*',
872
- '*node* *auditor-lambda*dist*index.js* worker-run*',
873
- ]) {
874
- if (bash[pattern] !== 'allow') {
875
- throw new Error(`OpenCode ${label}.bash must allow ${pattern}. Run "audit-code install --host opencode".`);
876
- }
877
- }
878
- for (const pattern of [
879
- 'audit-code run-to-completion*',
880
- 'audit-code synthesize*',
881
- 'audit-code cleanup*',
882
- 'audit-code requeue*',
883
- 'audit-code ingest-results*',
884
- '*dist*index.js* run-to-completion*',
885
- '*dist*index.js* synthesize*',
886
- '*dist*index.js* cleanup*',
887
- '*dist*index.js* requeue*',
888
- '*dist*index.js* ingest-results*',
889
- '*audit-code.mjs* run-to-completion*',
890
- '*audit-code.mjs* synthesize*',
891
- '*audit-code.mjs* cleanup*',
892
- '*audit-code.mjs* requeue*',
893
- '*audit-code.mjs* ingest-results*',
894
- ]) {
895
- if (bash[pattern] !== 'deny') {
896
- throw new Error(`OpenCode ${label}.bash must deny ${pattern}. Run "audit-code install --host opencode".`);
897
- }
898
- }
899
- }
900
-
901
- function buildMergedOpenCodeProjectConfig(existing, root) {
902
- const generated = renderOpenCodeProjectConfig(root);
903
- const mergedMcp = objectValue(existing.mcp);
904
- delete mergedMcp.auditor;
905
- return {
906
- ...existing,
907
- $schema: existing.$schema ?? generated.$schema,
908
- command: removeManagedOpenCodeCommand(existing.command),
909
- mcp: mergedMcp,
910
- permission: {
911
- ...mergeOpenCodePermissionConfig(existing.permission, generated.permission),
912
- external_directory: { '*': 'allow' },
913
- },
914
- agent: {
915
- ...objectValue(existing.agent),
916
- auditor: {
917
- ...objectValue(objectValue(existing.agent).auditor),
918
- ...generated.agent.auditor,
919
- permission: {
920
- ...mergeOpenCodePermissionConfig(
921
- objectValue(objectValue(existing.agent).auditor).permission,
922
- generated.agent.auditor.permission,
923
- ),
924
- external_directory: { '*': 'allow' },
925
- },
926
- },
927
- },
928
- };
929
- }
930
-
931
- function renderAntigravityPlanningGuide(root) {
932
- return [
933
- '# Antigravity planning-mode guide',
934
- '',
935
- 'Recommended workflow:',
936
- '',
937
- '1. Open Antigravity in Planning mode.',
938
- '2. Load the repo-local prompt asset or the AGENTS instructions before starting the audit conversation.',
939
- '3. Ask Antigravity to use `audit-code next-step` directly.',
940
- '4. Review Antigravity artifacts before accepting major code changes or imported evidence.',
941
- '',
942
- 'Recommended repo-local paths:',
943
- `- prompt asset: \`${toRepoRelativePath(root, join(root, '.audit-code', 'install', INSTALLED_PROMPT_FILENAME))}\``,
944
- '',
945
- 'Artifact round-tripping policy:',
946
- '',
947
- '- Browser walkthroughs and validation artifacts should be converted into runtime validation updates before import.',
948
- '- Task-specific review artifacts should be normalized into `AuditResult` payloads before using `import_results`.',
949
- '',
950
- ].join('\n');
951
- }
952
-
953
- function renderGeminiCommandToml(promptBody) {
954
- const escapedBody = promptBody.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
955
- return [
956
- '# /audit-code \u2014 Autonomous local-loop code auditing',
957
- '# Registered as a Gemini/Antigravity slash command.',
958
- '',
959
- 'description = "Autonomous local-loop code auditing \u2014 loads one backend-rendered audit step at a time"',
960
- '',
961
- 'prompt = """',
962
- promptBody.trimEnd(),
963
- '"""',
964
- '',
965
- ].join('\n');
966
- }
967
-
968
- const INSTALL_PROFILE_FLAGS = [
969
- 'writeVSCode',
970
- 'writeCopilotInstructions',
971
- 'writeOpenCode',
972
- 'writeCodex',
973
- 'writeAntigravity',
974
- 'writeAgents',
975
- ];
976
-
977
- const INSTALL_HOST_ORDER = [
978
- 'codex',
979
- 'opencode',
980
- 'vscode',
981
- 'antigravity',
982
- ];
983
-
984
- const INSTALL_HOST_DEFINITIONS = {
985
- codex: {
986
- host: 'codex',
987
- label: 'Codex',
988
- support_level: 'supported',
989
- setup_kind: 'global-skill+instructions',
990
- summary:
991
- 'Use the global Codex skill installed by npm plus AGENTS fallback instructions for this repository. Repo-local Codex skill bundles are intentionally not generated.',
992
- primary_path_key: 'agentsInstructionsPath',
993
- supporting_path_keys: [
994
- 'installedPromptPath',
995
- ],
996
- steps: [
997
- 'Open this repository in Codex.',
998
- 'Use the global `/audit-code` skill installed by `npm install -g auditor-lambda`.',
999
- 'If the global skill is unavailable, follow the AGENTS fallback instructions that point at the repo-local prompt asset.',
1000
- ],
1001
- profile: {
1002
- writeAgents: true,
1003
- },
1004
- async verify({ checks, assetPaths, collectVerifyCheck: collect }) {
1005
- await collect(checks, 'codex_global_surface', async () => {
1006
- const content = await readFile(assetPaths.agentsInstructionsPath, 'utf8');
1007
- if (!content.includes('/audit-code')) {
1008
- throw new Error(`AGENTS instructions do not reference /audit-code: ${assetPaths.agentsInstructionsPath}`);
1009
- }
1010
- return {
1011
- summary: 'Codex uses the global skill surface with AGENTS fallback instructions.',
1012
- path: assetPaths.agentsInstructionsPath,
1013
- };
1014
- });
1015
- },
1016
- },
1017
- opencode: {
1018
- host: 'opencode',
1019
- label: 'OpenCode',
1020
- support_level: 'supported',
1021
- setup_kind: 'global-command+project-permissions',
1022
- summary:
1023
- 'Use the global OpenCode `/audit-code` command installed by npm plus generated project permissions.',
1024
- primary_path_key: 'opencodeConfigPath',
1025
- supporting_path_keys: [
1026
- 'agentsInstructionsPath',
1027
- ],
1028
- steps: [
1029
- 'Open this repository in OpenCode.',
1030
- 'Use the global `/audit-code` command installed by `npm install -g auditor-lambda`.',
1031
- 'Let OpenCode load the generated `opencode.json` for project permissions; the global command drives `audit-code next-step` directly.',
1032
- ],
1033
- profile: {
1034
- writeOpenCode: true,
1035
- writeAgents: true,
1036
- },
1037
- async verify({ checks, assetPaths, collectVerifyCheck: collect }) {
1038
- await collect(checks, 'opencode_config', async () => {
1039
- const config = await readJson(assetPaths.opencodeConfigPath, 'OpenCode project config');
1040
- if (config?.command?.['audit-code']) {
1041
- throw new Error('OpenCode project config must not define command["audit-code"]; the slash command is global npm-installed state. Run "audit-code install --host opencode" to remove the stale local command.');
1042
- }
1043
- if (config?.mcp?.auditor) {
1044
- throw new Error('OpenCode project config must not define mcp.auditor; the MCP server is supplied by the global npm-installed config. Run "audit-code install --host opencode" to remove the stale project-level MCP entry.');
1045
- }
1046
- assertOpenCodeAuditPermissionConfig(config?.permission, 'permission');
1047
- assertOpenCodeAuditPermissionConfig(config?.agent?.auditor?.permission, 'agent.auditor.permission');
1048
- return {
1049
- summary: 'OpenCode project config has audit permissions; /audit-code is supplied by the global npm-installed config.',
1050
- path: assetPaths.opencodeConfigPath,
1051
- };
1052
- });
1053
- },
1054
- },
1055
- vscode: {
1056
- host: 'vscode',
1057
- label: 'VS Code',
1058
- support_level: 'supported',
1059
- setup_kind: 'prompt+agent',
1060
- summary:
1061
- 'Use the generated prompt file and custom agent for next-step-first VS Code integration.',
1062
- primary_path_key: 'vscodePromptPath',
1063
- supporting_path_keys: [
1064
- 'vscodeAgentPath',
1065
- 'copilotInstructionsPath',
1066
- ],
1067
- steps: [
1068
- 'Open this repository in VS Code with Copilot.',
1069
- 'Invoke `/audit-code` from the generated prompt or chat so the workflow calls `audit-code next-step` directly.',
1070
- ],
1071
- profile: {
1072
- writeVSCode: true,
1073
- writeCopilotInstructions: true,
1074
- },
1075
- async verify({ checks, assetPaths, collectVerifyCheck: collect }) {
1076
- await collect(checks, 'vscode_prompt', async () => {
1077
- const content = await readFile(assetPaths.vscodePromptPath, 'utf8');
1078
- if (!content.includes('name: audit-code')) {
1079
- throw new Error(`VS Code prompt file is missing the expected frontmatter name: ${assetPaths.vscodePromptPath}`);
1080
- }
1081
- const { body: promptBody } = splitFrontmatter(content);
1082
- const { body: sourceBody } = splitFrontmatter(await readFile(promptAssetPath, 'utf8'));
1083
- if (promptBody !== sourceBody.trimStart()) {
1084
- throw new Error(
1085
- `VS Code prompt body is out of sync with the source prompt. Run "audit-code install --host vscode" or "audit-code install".`,
1086
- );
1087
- }
1088
- return {
1089
- summary: 'VS Code prompt file is present and uses the source prompt body.',
1090
- path: assetPaths.vscodePromptPath,
1091
- };
1092
- });
1093
- },
1094
- },
1095
- antigravity: {
1096
- host: 'antigravity',
1097
- label: 'Antigravity',
1098
- support_level: 'supported',
1099
- setup_kind: 'agent-skill+gemini-command+planning-guide',
1100
- summary:
1101
- 'Uses the project-scoped .agent/skills/audit-code/SKILL.md skill, the .gemini/commands/audit-code.toml slash command, the planning guide, and AGENTS instructions.',
1102
- primary_path_key: 'antigravitySkillPath',
1103
- supporting_path_keys: [
1104
- 'geminiCommandPath',
1105
- 'antigravityPlanningGuidePath',
1106
- 'agentsInstructionsPath',
1107
- 'installedPromptPath',
1108
- ],
1109
- steps: [
1110
- 'Open this repository in Antigravity.',
1111
- 'The audit-code skill is automatically discovered from .agent/skills/audit-code/SKILL.md.',
1112
- 'The /audit-code slash command is also available from .gemini/commands/audit-code.toml.',
1113
- 'Use `audit-code next-step` directly.',
1114
- ],
1115
- profile: {
1116
- writeAntigravity: true,
1117
- writeAgents: true,
1118
- },
1119
- async verify({ checks, assetPaths, collectVerifyCheck: collect }) {
1120
- await collect(checks, 'antigravity_skill', async () => {
1121
- const content = await readFile(assetPaths.antigravitySkillPath, 'utf8');
1122
- if (!content.includes('name: audit-code')) {
1123
- throw new Error('Antigravity skill SKILL.md must contain "name: audit-code" in frontmatter.');
1124
- }
1125
- return {
1126
- summary: 'Antigravity .agent/skills/audit-code/SKILL.md is present and valid.',
1127
- path: assetPaths.antigravitySkillPath,
1128
- };
1129
- });
1130
- await collect(checks, 'antigravity_guide', async () => {
1131
- const content = await readFile(assetPaths.antigravityPlanningGuidePath, 'utf8');
1132
- if (!content.includes(INSTALLED_PROMPT_FILENAME)) {
1133
- throw new Error(`Antigravity guide must reference ${INSTALLED_PROMPT_FILENAME}.`);
1134
- }
1135
- return {
1136
- summary: 'Antigravity planning guide references the repo-local prompt asset.',
1137
- path: assetPaths.antigravityPlanningGuidePath,
1138
- };
1139
- });
1140
- },
1141
- },
1142
- };
1143
-
1144
- function supportedInstallHostsMessage() {
1145
- return ['all', 'copilot', ...INSTALL_HOST_ORDER].join(', ');
1146
- }
1147
-
1148
- function getInstallHostKeys(host) {
1149
- if (host === 'all') {
1150
- return INSTALL_HOST_ORDER;
1151
- }
1152
-
1153
- if (host === 'copilot') {
1154
- return ['vscode'];
1155
- }
1156
-
1157
- if (INSTALL_HOST_DEFINITIONS[host]) {
1158
- return [host];
1159
- }
1160
-
1161
- throw new Error(
1162
- `Unsupported host "${host}". Supported hosts: ${supportedInstallHostsMessage()}.`,
1163
- );
1164
- }
1165
-
1166
- function getInstallProfile(host) {
1167
- const profile = Object.fromEntries(
1168
- INSTALL_PROFILE_FLAGS.map((flag) => [flag, false]),
1169
- );
1170
-
1171
- for (const hostKey of getInstallHostKeys(host)) {
1172
- const hostProfile = INSTALL_HOST_DEFINITIONS[hostKey].profile;
1173
- for (const flag of INSTALL_PROFILE_FLAGS) {
1174
- profile[flag] = profile[flag] || Boolean(hostProfile[flag]);
1175
- }
1176
- }
1177
-
1178
- return profile;
1179
- }
1180
-
1181
- export {
1182
- INSTALL_HOST_ORDER as _INSTALL_HOST_ORDER,
1183
- INSTALL_HOST_DEFINITIONS as _INSTALL_HOST_DEFINITIONS,
1184
- getInstallHostKeys as _getInstallHostKeys,
1185
- getInstallProfile as _getInstallProfile,
1186
- };
1187
-
1188
- function buildHostCatalog({ root, host, assets }) {
1189
- return getInstallHostKeys(host)
1190
- .map((hostKey) => {
1191
- const definition = INSTALL_HOST_DEFINITIONS[hostKey];
1192
- const primaryPath = assets[definition.primary_path_key];
1193
- if (!primaryPath) {
1194
- return null;
1195
- }
1196
-
1197
- return {
1198
- host: definition.host,
1199
- label: definition.label,
1200
- support_level: definition.support_level,
1201
- setup_kind: definition.setup_kind,
1202
- summary: definition.summary,
1203
- primary_path: primaryPath,
1204
- supporting_paths: definition.supporting_path_keys
1205
- .map((key) => assets[key])
1206
- .filter(Boolean),
1207
- steps: definition.steps,
1208
- };
1209
- })
1210
- .filter(Boolean)
1211
- .map((entry) => ({
1212
- ...entry,
1213
- primary_path: entry.primary_path,
1214
- supporting_paths: entry.supporting_paths,
1215
- repo_relative_primary_path: toRepoRelativePath(root, entry.primary_path),
1216
- repo_relative_supporting_paths: entry.supporting_paths.map((path) => toRepoRelativePath(root, path)),
1217
- }));
1218
- }
1219
-
1220
- function renderInstallGuide({
1221
- root,
1222
- host,
1223
- installedPromptPath,
1224
- installedSkillPath,
1225
- installManifestPath,
1226
- hostGuidance,
1227
- }) {
1228
- const lines = [
1229
- '# audit-code bootstrap guide',
1230
- '',
1231
- 'The canonical product route is `/audit-code` in conversation.',
1232
- '',
1233
- 'Shared repo-local assets:',
1234
- `- prompt asset: \`${toRepoRelativePath(root, installedPromptPath)}\``,
1235
- `- skill asset: \`${toRepoRelativePath(root, installedSkillPath)}\``,
1236
- `- host manifest: \`${toRepoRelativePath(root, installManifestPath)}\``,
1237
- '',
1238
- 'Host-specific quick starts:',
1239
- ];
1240
-
1241
- for (const guide of hostGuidance) {
1242
- lines.push(`- ${guide.label}: ${guide.summary}`);
1243
- }
1244
-
1245
- for (const guide of hostGuidance) {
1246
- lines.push('', `## ${guide.label}`, '');
1247
- lines.push(`Support level: ${guide.support_level}`);
1248
- lines.push(`Setup kind: ${guide.setup_kind}`);
1249
- lines.push('');
1250
- lines.push(guide.summary);
1251
- lines.push('');
1252
- lines.push('Primary repo-local path:');
1253
- lines.push(`- \`${toRepoRelativePath(root, guide.primary_path)}\``);
1254
- if (guide.supporting_paths.length > 0) {
1255
- lines.push('', 'Supporting repo-local paths:');
1256
- for (const path of guide.supporting_paths) {
1257
- lines.push(`- \`${toRepoRelativePath(root, path)}\``);
1258
- }
1259
- }
1260
- lines.push('', 'Recommended steps:');
1261
- for (const step of guide.steps) {
1262
- lines.push(`- ${step}`);
1263
- }
1264
- }
1265
-
1266
- lines.push('', 'Backend fallback:');
1267
- lines.push('- from the repository root, run `audit-code` only when you intentionally need the repo-local backend wrapper');
1268
- lines.push('- run `audit-code verify-install` after bootstrap when you want to smoke-test the generated launchers and host configs');
1269
- lines.push('- rerun `audit-code install` to refresh every generated host surface from the shared prompt and skill assets together');
1270
-
1271
- if (host !== 'all') {
1272
- lines.push('');
1273
- lines.push(`This install was scoped to \`${host}\`, so assets for other hosts may be intentionally omitted.`);
1274
- }
1275
-
1276
- lines.push('');
1277
- return lines.join('\n');
1278
- }
1279
-
1280
- async function assertDirectoryExists(path, description) {
1281
- let stats;
1282
- try {
1283
- stats = await stat(path);
1284
- } catch {
1285
- throw new Error(`${description} does not exist: ${path}`);
1286
- }
1287
-
1288
- if (!stats.isDirectory()) {
1289
- throw new Error(`${description} is not a directory: ${path}`);
1290
- }
1291
- }
1292
-
1293
- async function collectVerifyCheck(target, id, fn) {
1294
- try {
1295
- const details = await fn();
1296
- target.push({
1297
- id,
1298
- status: 'ok',
1299
- ...(details ?? {}),
1300
- });
1301
- } catch (error) {
1302
- target.push({
1303
- id,
1304
- status: 'error',
1305
- summary: error instanceof Error ? error.message : String(error),
1306
- });
1307
- }
1308
- }
1309
-
1310
- async function ensureFile(path, description) {
1311
- let stats;
1312
- try {
1313
- stats = await stat(path);
1314
- } catch {
1315
- throw new Error(`${description} does not exist: ${path}`);
1316
- }
1317
-
1318
- if (!stats.isFile()) {
1319
- throw new Error(`${description} is not a file: ${path}`);
1320
- }
1321
-
1322
- return stats;
1323
- }
1324
-
1325
- async function readJson(path, description) {
1326
- const content = await readFile(path, 'utf8');
1327
- try {
1328
- return JSON.parse(content);
1329
- } catch (error) {
1330
- throw new Error(
1331
- `${description} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
1332
- );
1333
- }
1334
- }
1335
-
1336
- async function verifyZipFile(path, description) {
1337
- const content = await readFile(path);
1338
- if (content.length < 4 || content[0] !== 0x50 || content[1] !== 0x4b) {
1339
- throw new Error(`${description} is not a valid ZIP-like archive: ${path}`);
1340
- }
1341
- return content.length;
1342
- }
1343
-
1344
- async function verifyInstalledBootstrap(argv) {
1345
- const root = resolve(getFlag(argv, '--root') ?? '.');
1346
- const requestedHost = getFlag(argv, '--host')?.toLowerCase() ?? null;
1347
- const installManifestPath = join(
1348
- root,
1349
- '.audit-code',
1350
- 'install',
1351
- INSTALL_MANIFEST_FILENAME,
1352
- );
1353
- const installGuidePath = join(
1354
- root,
1355
- '.audit-code',
1356
- 'install',
1357
- INSTALL_GUIDE_FILENAME,
1358
- );
1359
-
1360
- await assertDirectoryExists(root, 'Target repository root');
1361
-
1362
- const generalChecks = [];
1363
- const hostResults = [];
1364
- let installManifest;
1365
-
1366
- await collectVerifyCheck(generalChecks, 'install_manifest', async () => {
1367
- await ensureFile(installManifestPath, 'Install manifest');
1368
- installManifest = await readJson(installManifestPath, 'Install manifest');
1369
- if (installManifest?.contract_version !== 'audit-code-install/v1alpha1') {
1370
- throw new Error(
1371
- `Unexpected install manifest contract version: ${installManifest?.contract_version ?? 'missing'}.`,
1372
- );
1373
- }
1374
- return {
1375
- summary: 'Install manifest parsed successfully.',
1376
- path: installManifestPath,
1377
- };
1378
- });
1379
-
1380
- if (!installManifest) {
1381
- console.log(
1382
- JSON.stringify(
1383
- {
1384
- root,
1385
- requested_host: requestedHost ?? 'all',
1386
- status: 'error',
1387
- issue_count: generalChecks.filter((check) => check.status === 'error').length,
1388
- checks: generalChecks,
1389
- hosts: [],
1390
- },
1391
- null,
1392
- 2,
1393
- ),
1394
- );
1395
- process.exitCode = 1;
1396
- return;
1397
- }
1398
-
1399
- const assetPaths = installManifest.asset_paths ?? {};
1400
- const hostCatalog = new Map(
1401
- (installManifest.hosts ?? []).map((entry) => [entry.host, entry]),
1402
- );
1403
- const selectedHosts = requestedHost && requestedHost !== 'all'
1404
- ? getInstallHostKeys(requestedHost)
1405
- : [...hostCatalog.keys()];
1406
-
1407
- await collectVerifyCheck(generalChecks, 'install_guide', async () => {
1408
- const guide = await readFile(installGuidePath, 'utf8');
1409
- if (!guide.includes('# audit-code bootstrap guide')) {
1410
- throw new Error(`Install guide does not look like an audit-code bootstrap guide: ${installGuidePath}`);
1411
- }
1412
- return {
1413
- summary: 'Install guide is present and readable.',
1414
- path: installGuidePath,
1415
- };
1416
- });
1417
-
1418
- await collectVerifyCheck(generalChecks, 'installed_prompt', async () => {
1419
- await ensureFile(assetPaths.installedPromptPath, 'Installed prompt asset');
1420
- const installedPrompt = await readFile(assetPaths.installedPromptPath, 'utf8');
1421
- const sourcePrompt = await readFile(promptAssetPath, 'utf8');
1422
- if (installedPrompt !== sourcePrompt) {
1423
- throw new Error(
1424
- `Installed prompt is out of sync with the source prompt. Run "audit-code install" from ${root}.`,
1425
- );
1426
- }
1427
- return {
1428
- summary: 'Installed prompt asset is present and matches the source prompt.',
1429
- path: assetPaths.installedPromptPath,
1430
- };
1431
- });
1432
-
1433
- await collectVerifyCheck(generalChecks, 'installed_skill', async () => {
1434
- await ensureFile(assetPaths.installedSkillPath, 'Installed skill asset');
1435
- const installedSkill = (await readFile(assetPaths.installedSkillPath, 'utf8')).replace(/\r\n/g, '\n');
1436
- const sourceSkill = (await readFile(skillAssetPath, 'utf8')).replace(/\r\n/g, '\n');
1437
- if (installedSkill !== sourceSkill) {
1438
- throw new Error(
1439
- `Installed skill is out of sync with the source skill. Run "audit-code install" from ${root}.`,
1440
- );
1441
- }
1442
- return {
1443
- summary: 'Installed skill asset is present and matches the source skill.',
1444
- path: assetPaths.installedSkillPath,
1445
- };
1446
- });
1447
-
1448
- await collectVerifyCheck(generalChecks, 'legacy_local_surfaces', async () => {
1449
- const legacySurfaces = await findLegacyAuditCodeSurfaceFiles(root);
1450
- if (legacySurfaces.length > 0) {
1451
- throw new Error(
1452
- `Legacy local /audit-code surfaces are still present: ${legacySurfaces.join(', ')}. Run "audit-code install" from ${root}.`,
1453
- );
1454
- }
1455
- return {
1456
- summary: 'No legacy local /audit-code command or skill surfaces were found.',
1457
- };
1458
- });
1459
-
1460
- for (const hostKey of selectedHosts) {
1461
- const checks = [];
1462
- const hostEntry = hostCatalog.get(hostKey);
1463
-
1464
- if (!hostEntry) {
1465
- checks.push({
1466
- id: 'host_manifest_entry',
1467
- status: 'error',
1468
- summary: `Install manifest does not contain host guidance for "${hostKey}".`,
1469
- });
1470
- hostResults.push({ host: hostKey, status: 'error', checks });
1471
- continue;
1472
- }
1473
-
1474
- await collectVerifyCheck(checks, 'host_manifest_entry', async () => ({
1475
- summary: `Host guidance exists for ${hostEntry.label}.`,
1476
- primary_path: hostEntry.primary_path,
1477
- }));
1478
-
1479
- const hostDefinition = INSTALL_HOST_DEFINITIONS[hostKey];
1480
- if (hostDefinition?.verify) {
1481
- await hostDefinition.verify({ checks, root, assetPaths, collectVerifyCheck });
1482
- } else {
1483
- checks.push({
1484
- id: 'host_handler',
1485
- status: 'error',
1486
- summary: `No verification handler is implemented for host "${hostKey}".`,
1487
- });
1488
- }
1489
-
1490
- hostResults.push({
1491
- host: hostKey,
1492
- status: checks.some((check) => check.status === 'error') ? 'error' : 'ok',
1493
- checks,
1494
- });
1495
- }
1496
-
1497
- const issueCount =
1498
- generalChecks.filter((check) => check.status === 'error').length +
1499
- hostResults.reduce(
1500
- (sum, host) => sum + host.checks.filter((check) => check.status === 'error').length,
1501
- 0,
1502
- );
1503
-
1504
- console.log(
1505
- JSON.stringify(
1506
- {
1507
- root,
1508
- requested_host: requestedHost ?? 'all',
1509
- manifest_path: installManifestPath,
1510
- status: issueCount > 0 ? 'error' : 'ok',
1511
- issue_count: issueCount,
1512
- checks: generalChecks,
1513
- hosts: hostResults,
1514
- },
1515
- null,
1516
- 2,
1517
- ),
1518
- );
1519
-
1520
- process.exitCode = issueCount > 0 ? 1 : 0;
1521
- }
1522
-
1523
- async function readTextIfExists(path) {
1524
- try {
1525
- return await readFile(path, 'utf8');
1526
- } catch {
1527
- return null;
1528
- }
1529
- }
1530
-
1531
- async function detectBootstrapRefreshReason(root, host) {
1532
- const installManifestPath = join(
1533
- root,
1534
- '.audit-code',
1535
- 'install',
1536
- INSTALL_MANIFEST_FILENAME,
1537
- );
1538
-
1539
- if (!(await fileExists(installManifestPath))) {
1540
- return 'missing_install_manifest';
1541
- }
1542
-
1543
- let installManifest;
1544
- try {
1545
- installManifest = JSON.parse(await readFile(installManifestPath, 'utf8'));
1546
- } catch {
1547
- return 'invalid_install_manifest';
1548
- }
1549
-
1550
- if (installManifest?.contract_version !== 'audit-code-install/v1alpha1') {
1551
- return 'stale_install_manifest_contract';
1552
- }
1553
-
1554
- const assetPaths = installManifest.asset_paths ?? {};
1555
- const hostCatalog = new Set(
1556
- (installManifest.hosts ?? []).map((entry) => entry.host),
1557
- );
1558
-
1559
- if (hostCatalog.has('codex') && (assetPaths.codexSkillPath || assetPaths.codexPromptPath)) {
1560
- return 'legacy_local_audit_code_surface';
1561
- }
1562
-
1563
- if ((await findLegacyAuditCodeSurfaceFiles(root)).length > 0) {
1564
- return 'legacy_local_audit_code_surface';
1565
- }
1566
-
1567
- for (const hostKey of getInstallHostKeys(host)) {
1568
- if (!hostCatalog.has(hostKey)) {
1569
- return `missing_host_surface:${hostKey}`;
1570
- }
1571
-
1572
- const definition = INSTALL_HOST_DEFINITIONS[hostKey];
1573
- const requiredPathKeys = [
1574
- definition.primary_path_key,
1575
- ...definition.supporting_path_keys,
1576
- ];
1577
- for (const pathKey of requiredPathKeys) {
1578
- const targetPath = assetPaths[pathKey];
1579
- if (targetPath && !(await fileExists(targetPath))) {
1580
- return `missing_host_asset:${hostKey}:${pathKey}`;
1581
- }
1582
- }
1583
- }
1584
-
1585
- const installedPrompt = await readTextIfExists(assetPaths.installedPromptPath);
1586
- if (installedPrompt === null) {
1587
- return 'missing_installed_prompt';
1588
- }
1589
- const sourcePrompt = await readFile(promptAssetPath, 'utf8');
1590
- if (installedPrompt !== sourcePrompt) {
1591
- return 'stale_installed_prompt';
1592
- }
1593
- const { body: sourcePromptBody } = splitFrontmatter(sourcePrompt);
1594
-
1595
- const installedSkill = await readTextIfExists(assetPaths.installedSkillPath);
1596
- if (installedSkill === null) {
1597
- return 'missing_installed_skill';
1598
- }
1599
- const sourceSkill = (await readFile(skillAssetPath, 'utf8')).replace(/\r\n/g, '\n');
1600
- if (installedSkill.replace(/\r\n/g, '\n') !== sourceSkill) {
1601
- return 'stale_installed_skill';
1602
- }
1603
-
1604
- for (const hostKey of getInstallHostKeys(host)) {
1605
- switch (hostKey) {
1606
- case 'codex': {
1607
- break;
1608
- }
1609
- case 'opencode': {
1610
- const opencodeConfig = await readJson(assetPaths.opencodeConfigPath, 'OpenCode config').catch(() => null);
1611
- if (opencodeConfig?.command?.['audit-code']) {
1612
- return 'stale_host_asset:opencode:local_command';
1613
- }
1614
- if (opencodeConfig?.mcp?.auditor) {
1615
- return 'stale_host_asset:opencode:project_mcp';
1616
- }
1617
- try {
1618
- assertOpenCodeAuditPermissionConfig(opencodeConfig?.permission, 'permission');
1619
- assertOpenCodeAuditPermissionConfig(opencodeConfig?.agent?.auditor?.permission, 'agent.auditor.permission');
1620
- } catch {
1621
- return 'stale_host_asset:opencode:permissions';
1622
- }
1623
- if (await fileExists(join(root, '.opencode', 'commands', 'audit-code.md'))) {
1624
- return 'stale_host_asset:opencode:legacy_command_file';
1625
- }
1626
- break;
1627
- }
1628
- case 'vscode': {
1629
- const vscodePrompt = await readTextIfExists(assetPaths.vscodePromptPath);
1630
- if (vscodePrompt === null) {
1631
- return 'missing_host_asset:vscode:prompt';
1632
- }
1633
- if (splitFrontmatter(vscodePrompt).body !== sourcePromptBody.trimStart()) {
1634
- return 'stale_host_asset:vscode:prompt';
1635
- }
1636
- break;
1637
- }
1638
- case 'antigravity': {
1639
- const expectedSkillPath = join(root, '.agent', 'skills', 'audit-code', 'SKILL.md');
1640
- if (!(await fileExists(expectedSkillPath))) {
1641
- return 'missing_host_asset:antigravity:skill';
1642
- }
1643
- const antigravitySkill = await readTextIfExists(expectedSkillPath);
1644
- if (antigravitySkill !== null && antigravitySkill.replace(/\r\n/g, '\n') !== sourceSkill) {
1645
- return 'stale_host_asset:antigravity:skill';
1646
- }
1647
- break;
1648
- }
1649
- default:
1650
- break;
1651
- }
1652
- }
1653
-
1654
- return null;
1655
- }
1656
-
1657
- async function ensureBootstrap(argv) {
1658
- const host = (getFlag(argv, '--host') ?? DEFAULT_INSTALL_HOST).toLowerCase();
1659
- const root = resolve(getFlag(argv, '--root') ?? '.');
1660
- const quiet = hasFlag(argv, '--quiet');
1661
- const force = hasFlag(argv, '--force');
1662
- await assertDirectoryExists(root, 'Target repository root');
1663
-
1664
- const reason = force
1665
- ? 'forced'
1666
- : await detectBootstrapRefreshReason(root, host);
1667
-
1668
- if (reason) {
1669
- const installed = await installBootstrap(argv, { quiet: true });
1670
- const payload = {
1671
- status: 'ok',
1672
- action: 'installed',
1673
- reason,
1674
- host: installed.host,
1675
- repo_root: installed.repo_root,
1676
- install_manifest_path: installed.install_manifest_path,
1677
- host_count: installed.host_guidance.length,
1678
- file_count: installed.files.length,
1679
- };
1680
- if (!quiet) {
1681
- console.log(JSON.stringify(payload, null, 2));
1682
- }
1683
- return payload;
1684
- }
1685
-
1686
- const payload = {
1687
- status: 'ok',
1688
- action: 'skipped',
1689
- reason: null,
1690
- host,
1691
- repo_root: root,
1692
- install_manifest_path: join(
1693
- root,
1694
- '.audit-code',
1695
- 'install',
1696
- INSTALL_MANIFEST_FILENAME,
1697
- ),
1698
- };
1699
- if (!quiet) {
1700
- console.log(JSON.stringify(payload, null, 2));
1701
- }
1702
- return payload;
1703
- }
1704
-
1705
- // Compute the full asset-path map for an install profile. Each per-host path is
1706
- // gated on its profile flag (null when the host is not part of this profile).
1707
- function buildInstallAssetPaths(root, profile) {
1708
- const installedPromptPath = join(root, '.audit-code', 'install', INSTALLED_PROMPT_FILENAME);
1709
- const installedSkillPath = join(root, '.audit-code', 'install', 'SKILL.md');
1710
- const installGuidePath = join(root, '.audit-code', 'install', INSTALL_GUIDE_FILENAME);
1711
- const installManifestPath = join(root, '.audit-code', 'install', INSTALL_MANIFEST_FILENAME);
1712
- return {
1713
- installedPromptPath,
1714
- installedSkillPath,
1715
- installGuidePath,
1716
- installManifestPath,
1717
- agentsInstructionsPath: profile.writeAgents ? join(root, 'AGENTS.md') : null,
1718
- copilotInstructionsPath: profile.writeCopilotInstructions
1719
- ? join(root, '.github', 'copilot-instructions.md')
1720
- : null,
1721
- codexSkillPath: profile.writeCodex
1722
- ? join(root, '.codex', 'skills', 'audit-code', 'SKILL.md')
1723
- : null,
1724
- codexPromptPath: profile.writeCodex
1725
- ? join(root, '.codex', 'skills', 'audit-code', 'audit-code.prompt.md')
1726
- : null,
1727
- codexAutomationRecipePath: profile.writeCodex
1728
- ? join(root, '.audit-code', 'install', 'codex', 'RE-AUDIT-AUTOMATION.md')
1729
- : null,
1730
- opencodeConfigPath: profile.writeOpenCode
1731
- ? join(root, 'opencode.json')
1732
- : null,
1733
- vscodePromptPath: profile.writeVSCode
1734
- ? join(root, '.github', 'prompts', 'audit-code.prompt.md')
1735
- : null,
1736
- vscodeAgentPath: profile.writeVSCode
1737
- ? join(root, '.github', 'agents', 'auditor.agent.md')
1738
- : null,
1739
- antigravityPlanningGuidePath: profile.writeAntigravity
1740
- ? join(root, '.audit-code', 'install', 'antigravity', 'PLANNING-MODE.md')
1741
- : null,
1742
- geminiCommandPath: profile.writeAntigravity
1743
- ? join(root, '.gemini', 'commands', 'audit-code.toml')
1744
- : null,
1745
- antigravitySkillPath: profile.writeAntigravity
1746
- ? join(root, '.agent', 'skills', 'audit-code', 'SKILL.md')
1747
- : null,
1748
- };
1749
- }
1750
-
1751
- // Always-written core assets (installed prompt + skill,
1752
- // AGENTS/copilot compatibility directive blocks) plus legacy-surface cleanup.
1753
- async function writeCoreInstallAssets(root, assetPaths, promptSource, skillSource) {
1754
- const results = [];
1755
- const legacyInstalledPromptPath = join(root, '.audit-code', 'install', 'audit-code.prompt.md');
1756
- if (await fileExists(legacyInstalledPromptPath)) {
1757
- await unlink(legacyInstalledPromptPath).catch(() => {});
1758
- }
1759
- results.push(await writeGeneratedMarkdown(assetPaths.installedPromptPath, promptSource));
1760
- results.push(await writeGeneratedMarkdown(assetPaths.installedSkillPath, skillSource));
1761
-
1762
- const compatibilityBlockTargets = [
1763
- assetPaths.agentsInstructionsPath,
1764
- assetPaths.copilotInstructionsPath,
1765
- ].filter(Boolean);
1766
- for (const targetPath of compatibilityBlockTargets) {
1767
- results.push(
1768
- await writeManagedMarkdown(
1769
- targetPath,
1770
- buildInstallDirective(
1771
- relative(dirname(targetPath), assetPaths.installedPromptPath) || `./.audit-code/install/${INSTALLED_PROMPT_FILENAME}`,
1772
- ),
1773
- ),
1774
- );
1775
- }
1776
-
1777
- results.push(...await removeLegacyAuditCodeSurfaceFiles(root));
1778
- return results;
1779
- }
1780
-
1781
- async function writeCodexAssets(assetPaths, promptSource, skillSource) {
1782
- return [
1783
- await writeGeneratedMarkdown(assetPaths.codexSkillPath, skillSource),
1784
- await writeGeneratedMarkdown(assetPaths.codexPromptPath, promptSource),
1785
- await writeGeneratedMarkdown(assetPaths.codexAutomationRecipePath, renderCodexAutomationRecipe()),
1786
- ];
1787
- }
1788
-
1789
- async function writeOpenCodeAssets(assetPaths, root) {
1790
- return [
1791
- await writeMergedGeneratedJson(
1792
- assetPaths.opencodeConfigPath,
1793
- 'OpenCode project config',
1794
- (existing) => buildMergedOpenCodeProjectConfig(existing, root),
1795
- ),
1796
- ];
1797
- }
1798
-
1799
- async function writeVSCodeAssets(assetPaths, promptBody) {
1800
- return [
1801
- await writeGeneratedMarkdown(
1802
- assetPaths.vscodePromptPath,
1803
- renderPromptFile(
1804
- {
1805
- name: 'audit-code',
1806
- description: 'Autonomous local loop code auditing',
1807
- agent: 'auditor',
1808
- },
1809
- promptBody,
1810
- ),
1811
- ),
1812
- await writeGeneratedMarkdown(assetPaths.vscodeAgentPath, renderVSCodeAgentFile()),
1813
- ];
1814
- }
1815
-
1816
- async function writeAntigravityAssets(assetPaths, promptBody, skillSource, root) {
1817
- return [
1818
- await writeGeneratedMarkdown(
1819
- assetPaths.antigravityPlanningGuidePath,
1820
- renderAntigravityPlanningGuide(root),
1821
- ),
1822
- await writeGeneratedMarkdown(assetPaths.geminiCommandPath, renderGeminiCommandToml(promptBody)),
1823
- await writeGeneratedMarkdown(assetPaths.antigravitySkillPath, skillSource),
1824
- ];
1825
- }
1826
-
1827
- async function installBootstrap(argv, options = {}) {
1828
- const host = (getFlag(argv, '--host') ?? DEFAULT_INSTALL_HOST).toLowerCase();
1829
- const root = resolve(getFlag(argv, '--root') ?? '.');
1830
- await assertDirectoryExists(root, 'Target repository root');
1831
- const profile = getInstallProfile(host);
1832
- const promptSource = await readFile(promptAssetPath, 'utf8');
1833
- const skillSource = (await readFile(skillAssetPath, 'utf8')).replace(/\r\n/g, '\n');
1834
- const { body: promptBody } = splitFrontmatter(promptSource);
1835
- const assetPaths = buildInstallAssetPaths(root, profile);
1836
- const {
1837
- installedPromptPath,
1838
- installedSkillPath,
1839
- installGuidePath,
1840
- installManifestPath,
1841
- } = assetPaths;
1842
-
1843
- const results = [];
1844
- results.push(...await writeCoreInstallAssets(root, assetPaths, promptSource, skillSource));
1845
-
1846
- if (profile.writeCodex) {
1847
- results.push(...await writeCodexAssets(assetPaths, promptSource, skillSource));
1848
- }
1849
- if (profile.writeOpenCode) {
1850
- results.push(...await writeOpenCodeAssets(assetPaths, root));
1851
- }
1852
- if (profile.writeVSCode) {
1853
- results.push(...await writeVSCodeAssets(assetPaths, promptBody));
1854
- }
1855
- if (profile.writeAntigravity) {
1856
- results.push(...await writeAntigravityAssets(assetPaths, promptBody, skillSource, root));
1857
- }
1858
-
1859
- const hostGuidance = buildHostCatalog({
1860
- root,
1861
- host,
1862
- assets: assetPaths,
1863
- });
1864
-
1865
- const installManifest = {
1866
- contract_version: 'audit-code-install/v1alpha1',
1867
- host,
1868
- repo_root: root,
1869
- installed_prompt_path: installedPromptPath,
1870
- installed_skill_path: installedSkillPath,
1871
- install_guide_path: installGuidePath,
1872
- install_manifest_path: installManifestPath,
1873
- source_prompt_path: resolve(promptAssetPath),
1874
- source_skill_path: resolve(skillAssetPath),
1875
- asset_paths: assetPaths,
1876
- hosts: hostGuidance,
1877
- };
1878
-
1879
- results.push(
1880
- await writeGeneratedMarkdown(
1881
- installGuidePath,
1882
- renderInstallGuide({
1883
- root,
1884
- host,
1885
- installedPromptPath,
1886
- installedSkillPath,
1887
- installManifestPath,
1888
- hostGuidance,
1889
- }),
1890
- ),
1891
- );
1892
- results.push(await writeGeneratedJson(installManifestPath, installManifest));
1893
-
1894
- const sessionConfigPath = join(root, '.audit-artifacts', 'session-config.json');
1895
- if (!(await fileExists(sessionConfigPath))) {
1896
- const defaultConfig = { provider: 'local-subprocess' };
1897
- await mkdir(dirname(sessionConfigPath), { recursive: true });
1898
- await writeFile(sessionConfigPath, JSON.stringify(defaultConfig, null, 2) + '\n', 'utf8');
1899
- results.push({ path: sessionConfigPath, mode: 'created' });
1900
- }
1901
-
1902
- const payload = {
1903
- host,
1904
- repo_root: root,
1905
- installed_prompt_path: installedPromptPath,
1906
- installed_skill_path: installedSkillPath,
1907
- install_guide_path: installGuidePath,
1908
- install_manifest_path: installManifestPath,
1909
- source_prompt_path: resolve(promptAssetPath),
1910
- source_skill_path: resolve(skillAssetPath),
1911
- files: results,
1912
- slash_command_surfaces: {
1913
- vscode_prompt: assetPaths.vscodePromptPath,
1914
- opencode_config: assetPaths.opencodeConfigPath,
1915
- gemini_command: assetPaths.geminiCommandPath,
1916
- antigravity_skill: assetPaths.antigravitySkillPath,
1917
- },
1918
- instruction_surfaces: {
1919
- agents: assetPaths.agentsInstructionsPath,
1920
- copilot_instructions: assetPaths.copilotInstructionsPath,
1921
- },
1922
- host_guidance: hostGuidance,
1923
- unsupported_hosts: [],
1924
- next_steps: [
1925
- 'Open the repository in your preferred host and follow the matching host_guidance entry.',
1926
- `Open ${installGuidePath} for repo-local quick-start steps for Codex, OpenCode, VS Code, and Antigravity.`,
1927
- 'Run `audit-code verify-install` from the repository root to smoke-test the generated host configs.',
1928
- ],
1929
- };
1930
-
1931
- if (!options.quiet) {
1932
- console.log(JSON.stringify(payload, null, 2));
1933
- }
1934
-
1935
- return payload;
1936
- }
1937
-
1938
- async function installHostPrompt(argv) {
1939
- const host = requireFlagValue(argv, '--host').toLowerCase();
1940
-
1941
- if (host !== 'copilot') {
1942
- throw new Error(
1943
- `install-host currently supports only "copilot". Use "install --host ${host}" for the broader bootstrap flow.`,
1944
- );
1945
- }
1946
-
1947
- await installBootstrap(argv);
1948
- }
1949
-
1950
166
  async function runDistCommand(commandName, argv, { ensureArtifactsDir = false } = {}) {
1951
167
  const commandArgs = [...argv];
1952
168
  const rootValue = resolve(getFlag(commandArgs, '--root') ?? '.');