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
@@ -0,0 +1,1140 @@
1
+ import { mkdir, readFile, stat, unlink, writeFile } from 'node:fs/promises';
2
+ import { dirname, join, relative, resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import {
5
+ fileExists,
6
+ readJson,
7
+ readTextIfExists,
8
+ writeGeneratedJson,
9
+ writeGeneratedMarkdown,
10
+ writeManagedMarkdown,
11
+ writeMergedGeneratedJson,
12
+ } from './audit-code-wrapper-io.mjs';
13
+ import {
14
+ assertOpenCodeAuditPermissionConfig,
15
+ buildMergedOpenCodeProjectConfig,
16
+ } from './audit-code-wrapper-opencode.mjs';
17
+ import {
18
+ findLegacyAuditCodeSurfaceFiles,
19
+ removeLegacyAuditCodeSurfaceFiles,
20
+ } from './audit-code-wrapper-legacy.mjs';
21
+
22
+ const repoRoot = dirname(fileURLToPath(import.meta.url));
23
+ const promptAssetPath = join(repoRoot, 'skills', 'audit-code', 'audit-code.prompt.md');
24
+ const skillAssetPath = join(repoRoot, 'skills', 'audit-code', 'SKILL.md');
25
+ const INSTALL_MARKER_START = '<!-- audit-code:begin -->';
26
+ const INSTALL_MARKER_END = '<!-- audit-code:end -->';
27
+ const INSTALL_GUIDE_FILENAME = 'GETTING-STARTED.md';
28
+ const INSTALL_MANIFEST_FILENAME = 'manifest.json';
29
+ const DEFAULT_INSTALL_HOST = 'all';
30
+ const INSTALLED_PROMPT_FILENAME = 'audit-code.import.md';
31
+
32
+ function hasFlag(argv, name) {
33
+ return argv.includes(name);
34
+ }
35
+
36
+ function getFlag(argv, name) {
37
+ const index = argv.indexOf(name);
38
+ if (index < 0) return undefined;
39
+ return argv[index + 1];
40
+ }
41
+
42
+ function requireFlagValue(argv, name) {
43
+ const value = getFlag(argv, name);
44
+ if (!value) {
45
+ throw new Error(`${name} requires a value.`);
46
+ }
47
+ return value;
48
+ }
49
+
50
+ function normalizeNewlines(value) {
51
+ return value.replace(/\r\n/g, '\n');
52
+ }
53
+
54
+ function splitFrontmatter(markdown) {
55
+ const normalized = normalizeNewlines(markdown);
56
+ const match = normalized.match(/^---\n([\s\S]*?)\n---\n?/u);
57
+ if (!match) {
58
+ return { frontmatter: null, body: normalized };
59
+ }
60
+
61
+ return {
62
+ frontmatter: match[1],
63
+ body: normalized.slice(match[0].length),
64
+ };
65
+ }
66
+
67
+ function renderFrontmatter(fields) {
68
+ const entries = Object.entries(fields).filter(([, value]) => {
69
+ if (value === undefined || value === null) {
70
+ return false;
71
+ }
72
+
73
+ if (typeof value === 'string') {
74
+ return value.length > 0;
75
+ }
76
+
77
+ return true;
78
+ });
79
+ if (entries.length === 0) {
80
+ return '';
81
+ }
82
+
83
+ return [
84
+ '---',
85
+ ...entries.map(([key, value]) => `${key}: ${value}`),
86
+ '---',
87
+ '',
88
+ ].join('\n');
89
+ }
90
+
91
+ function renderPromptFile(fields, body) {
92
+ return `${renderFrontmatter(fields)}${body.trimStart()}`;
93
+ }
94
+
95
+ function toRepoRelativePath(root, targetPath) {
96
+ const value = relative(root, targetPath).replace(/\\/g, '/');
97
+ return value.length > 0 ? value : '.';
98
+ }
99
+
100
+ function buildInstallDirective(relativePromptPath) {
101
+ return [
102
+ INSTALL_MARKER_START,
103
+ '## /audit-code',
104
+ 'When the user enters `/audit-code`, treat it as this repository\'s autonomous audit workflow.',
105
+ `If your host does not automatically register the installed slash command file, load and follow [the repo-local audit directive](${relativePromptPath.replace(/\\/g, '/')}).`,
106
+ 'Normal usage should stay conversation-first and avoid manual `--root`, provider flags, or model-selection arguments.',
107
+ INSTALL_MARKER_END,
108
+ ].join('\n');
109
+ }
110
+
111
+ function replaceBackslashes(value) {
112
+ return value.replace(/\\/g, '/');
113
+ }
114
+
115
+ function renderVSCodeAgentFile() {
116
+ return [
117
+ '---',
118
+ 'description: Plan and orchestrate /audit-code through the next-step machine before making code changes.',
119
+ '---',
120
+ '',
121
+ '# Auditor Agent',
122
+ '',
123
+ '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.',
124
+ '',
125
+ 'When the user asks to run or continue `/audit-code`:',
126
+ '',
127
+ '- run `audit-code next-step` directly when shell access is available',
128
+ '- if MCP is the only available integration, call `start_audit`, `get_status`, and `continue_audit`; those tools return the same one-step contract',
129
+ '- read `audit-code://handoff/current` and `audit-code://artifacts/current` when the audit blocks or you need current context',
130
+ '- prefer imported audit results and runtime updates over ad hoc manual state edits',
131
+ '- treat the deterministic audit report as the final source of truth once the audit completes',
132
+ '',
133
+ ].join('\n');
134
+ }
135
+
136
+ function renderCodexAutomationRecipe() {
137
+ return [
138
+ '# Codex re-audit automation recipe',
139
+ '',
140
+ 'Suggested recurring task:',
141
+ '',
142
+ '- 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.',
143
+ '- Cadence: daily on active branches or before release cut-offs',
144
+ '- Inputs: repository root',
145
+ '',
146
+ 'Use this recipe as a starting point for a Codex automation once the local workflow is stable in your environment.',
147
+ '',
148
+ ].join('\n');
149
+ }
150
+
151
+ function renderAntigravityPlanningGuide(root) {
152
+ return [
153
+ '# Antigravity planning-mode guide',
154
+ '',
155
+ 'Recommended workflow:',
156
+ '',
157
+ '1. Open Antigravity in Planning mode.',
158
+ '2. Load the repo-local prompt asset or the AGENTS instructions before starting the audit conversation.',
159
+ '3. Ask Antigravity to use `audit-code next-step` directly.',
160
+ '4. Review Antigravity artifacts before accepting major code changes or imported evidence.',
161
+ '',
162
+ 'Recommended repo-local paths:',
163
+ `- prompt asset: \`${toRepoRelativePath(root, join(root, '.audit-code', 'install', INSTALLED_PROMPT_FILENAME))}\``,
164
+ '',
165
+ 'Artifact round-tripping policy:',
166
+ '',
167
+ '- Browser walkthroughs and validation artifacts should be converted into runtime validation updates before import.',
168
+ '- Task-specific review artifacts should be normalized into `AuditResult` payloads before using `import_results`.',
169
+ '',
170
+ ].join('\n');
171
+ }
172
+
173
+ function renderGeminiCommandToml(promptBody) {
174
+ const escapedBody = promptBody.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
175
+ return [
176
+ '# /audit-code — Autonomous local-loop code auditing',
177
+ '# Registered as a Gemini/Antigravity slash command.',
178
+ '',
179
+ 'description = "Autonomous local-loop code auditing — loads one backend-rendered audit step at a time"',
180
+ '',
181
+ 'prompt = """',
182
+ promptBody.trimEnd(),
183
+ '"""',
184
+ '',
185
+ ].join('\n');
186
+ }
187
+
188
+ export const INSTALL_PROFILE_FLAGS = [
189
+ 'writeVSCode',
190
+ 'writeCopilotInstructions',
191
+ 'writeOpenCode',
192
+ 'writeCodex',
193
+ 'writeAntigravity',
194
+ 'writeAgents',
195
+ ];
196
+
197
+ export const INSTALL_HOST_ORDER = [
198
+ 'codex',
199
+ 'opencode',
200
+ 'vscode',
201
+ 'antigravity',
202
+ ];
203
+
204
+ export const INSTALL_HOST_DEFINITIONS = {
205
+ codex: {
206
+ host: 'codex',
207
+ label: 'Codex',
208
+ support_level: 'supported',
209
+ setup_kind: 'global-skill+instructions',
210
+ summary:
211
+ 'Use the global Codex skill installed by npm plus AGENTS fallback instructions for this repository. Repo-local Codex skill bundles are intentionally not generated.',
212
+ primary_path_key: 'agentsInstructionsPath',
213
+ supporting_path_keys: [
214
+ 'installedPromptPath',
215
+ ],
216
+ steps: [
217
+ 'Open this repository in Codex.',
218
+ 'Use the global `/audit-code` skill installed by `npm install -g auditor-lambda`.',
219
+ 'If the global skill is unavailable, follow the AGENTS fallback instructions that point at the repo-local prompt asset.',
220
+ ],
221
+ profile: {
222
+ writeAgents: true,
223
+ },
224
+ async verify({ checks, assetPaths, collectVerifyCheck: collect }) {
225
+ await collect(checks, 'codex_global_surface', async () => {
226
+ const content = await readFile(assetPaths.agentsInstructionsPath, 'utf8');
227
+ if (!content.includes('/audit-code')) {
228
+ throw new Error(`AGENTS instructions do not reference /audit-code: ${assetPaths.agentsInstructionsPath}`);
229
+ }
230
+ return {
231
+ summary: 'Codex uses the global skill surface with AGENTS fallback instructions.',
232
+ path: assetPaths.agentsInstructionsPath,
233
+ };
234
+ });
235
+ },
236
+ },
237
+ opencode: {
238
+ host: 'opencode',
239
+ label: 'OpenCode',
240
+ support_level: 'supported',
241
+ setup_kind: 'global-command+project-permissions',
242
+ summary:
243
+ 'Use the global OpenCode `/audit-code` command installed by npm plus generated project permissions.',
244
+ primary_path_key: 'opencodeConfigPath',
245
+ supporting_path_keys: [
246
+ 'agentsInstructionsPath',
247
+ ],
248
+ steps: [
249
+ 'Open this repository in OpenCode.',
250
+ 'Use the global `/audit-code` command installed by `npm install -g auditor-lambda`.',
251
+ 'Let OpenCode load the generated `opencode.json` for project permissions; the global command drives `audit-code next-step` directly.',
252
+ ],
253
+ profile: {
254
+ writeOpenCode: true,
255
+ writeAgents: true,
256
+ },
257
+ async verify({ checks, assetPaths, collectVerifyCheck: collect }) {
258
+ await collect(checks, 'opencode_config', async () => {
259
+ const config = await readJson(assetPaths.opencodeConfigPath, 'OpenCode project config');
260
+ if (config?.command?.['audit-code']) {
261
+ 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.');
262
+ }
263
+ if (config?.mcp?.auditor) {
264
+ 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.');
265
+ }
266
+ assertOpenCodeAuditPermissionConfig(config?.permission, 'permission');
267
+ assertOpenCodeAuditPermissionConfig(config?.agent?.auditor?.permission, 'agent.auditor.permission');
268
+ return {
269
+ summary: 'OpenCode project config has audit permissions; /audit-code is supplied by the global npm-installed config.',
270
+ path: assetPaths.opencodeConfigPath,
271
+ };
272
+ });
273
+ },
274
+ },
275
+ vscode: {
276
+ host: 'vscode',
277
+ label: 'VS Code',
278
+ support_level: 'supported',
279
+ setup_kind: 'prompt+agent',
280
+ summary:
281
+ 'Use the generated prompt file and custom agent for next-step-first VS Code integration.',
282
+ primary_path_key: 'vscodePromptPath',
283
+ supporting_path_keys: [
284
+ 'vscodeAgentPath',
285
+ 'copilotInstructionsPath',
286
+ ],
287
+ steps: [
288
+ 'Open this repository in VS Code with Copilot.',
289
+ 'Invoke `/audit-code` from the generated prompt or chat so the workflow calls `audit-code next-step` directly.',
290
+ ],
291
+ profile: {
292
+ writeVSCode: true,
293
+ writeCopilotInstructions: true,
294
+ },
295
+ async verify({ checks, assetPaths, collectVerifyCheck: collect }) {
296
+ await collect(checks, 'vscode_prompt', async () => {
297
+ const content = await readFile(assetPaths.vscodePromptPath, 'utf8');
298
+ if (!content.includes('name: audit-code')) {
299
+ throw new Error(`VS Code prompt file is missing the expected frontmatter name: ${assetPaths.vscodePromptPath}`);
300
+ }
301
+ const { body: promptBody } = splitFrontmatter(content);
302
+ const { body: sourceBody } = splitFrontmatter(await readFile(promptAssetPath, 'utf8'));
303
+ if (promptBody !== sourceBody.trimStart()) {
304
+ throw new Error(
305
+ `VS Code prompt body is out of sync with the source prompt. Run "audit-code install --host vscode" or "audit-code install".`,
306
+ );
307
+ }
308
+ return {
309
+ summary: 'VS Code prompt file is present and uses the source prompt body.',
310
+ path: assetPaths.vscodePromptPath,
311
+ };
312
+ });
313
+ },
314
+ },
315
+ antigravity: {
316
+ host: 'antigravity',
317
+ label: 'Antigravity',
318
+ support_level: 'supported',
319
+ setup_kind: 'agent-skill+gemini-command+planning-guide',
320
+ summary:
321
+ '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.',
322
+ primary_path_key: 'antigravitySkillPath',
323
+ supporting_path_keys: [
324
+ 'geminiCommandPath',
325
+ 'antigravityPlanningGuidePath',
326
+ 'agentsInstructionsPath',
327
+ 'installedPromptPath',
328
+ ],
329
+ steps: [
330
+ 'Open this repository in Antigravity.',
331
+ 'The audit-code skill is automatically discovered from .agent/skills/audit-code/SKILL.md.',
332
+ 'The /audit-code slash command is also available from .gemini/commands/audit-code.toml.',
333
+ 'Use `audit-code next-step` directly.',
334
+ ],
335
+ profile: {
336
+ writeAntigravity: true,
337
+ writeAgents: true,
338
+ },
339
+ async verify({ checks, assetPaths, collectVerifyCheck: collect }) {
340
+ await collect(checks, 'antigravity_skill', async () => {
341
+ const content = await readFile(assetPaths.antigravitySkillPath, 'utf8');
342
+ if (!content.includes('name: audit-code')) {
343
+ throw new Error('Antigravity skill SKILL.md must contain "name: audit-code" in frontmatter.');
344
+ }
345
+ return {
346
+ summary: 'Antigravity .agent/skills/audit-code/SKILL.md is present and valid.',
347
+ path: assetPaths.antigravitySkillPath,
348
+ };
349
+ });
350
+ await collect(checks, 'antigravity_guide', async () => {
351
+ const content = await readFile(assetPaths.antigravityPlanningGuidePath, 'utf8');
352
+ if (!content.includes(INSTALLED_PROMPT_FILENAME)) {
353
+ throw new Error(`Antigravity guide must reference ${INSTALLED_PROMPT_FILENAME}.`);
354
+ }
355
+ return {
356
+ summary: 'Antigravity planning guide references the repo-local prompt asset.',
357
+ path: assetPaths.antigravityPlanningGuidePath,
358
+ };
359
+ });
360
+ },
361
+ },
362
+ };
363
+
364
+ function supportedInstallHostsMessage() {
365
+ return ['all', 'copilot', ...INSTALL_HOST_ORDER].join(', ');
366
+ }
367
+
368
+ export function getInstallHostKeys(host) {
369
+ if (host === 'all') {
370
+ return INSTALL_HOST_ORDER;
371
+ }
372
+
373
+ if (host === 'copilot') {
374
+ return ['vscode'];
375
+ }
376
+
377
+ if (INSTALL_HOST_DEFINITIONS[host]) {
378
+ return [host];
379
+ }
380
+
381
+ throw new Error(
382
+ `Unsupported host "${host}". Supported hosts: ${supportedInstallHostsMessage()}.`,
383
+ );
384
+ }
385
+
386
+ export function getInstallProfile(host) {
387
+ const profile = Object.fromEntries(
388
+ INSTALL_PROFILE_FLAGS.map((flag) => [flag, false]),
389
+ );
390
+
391
+ for (const hostKey of getInstallHostKeys(host)) {
392
+ const hostProfile = INSTALL_HOST_DEFINITIONS[hostKey].profile;
393
+ for (const flag of INSTALL_PROFILE_FLAGS) {
394
+ profile[flag] = profile[flag] || Boolean(hostProfile[flag]);
395
+ }
396
+ }
397
+
398
+ return profile;
399
+ }
400
+
401
+ export function buildInstallAssetPaths(root, profile) {
402
+ const installedPromptPath = join(root, '.audit-code', 'install', INSTALLED_PROMPT_FILENAME);
403
+ const installedSkillPath = join(root, '.audit-code', 'install', 'SKILL.md');
404
+ const installGuidePath = join(root, '.audit-code', 'install', INSTALL_GUIDE_FILENAME);
405
+ const installManifestPath = join(root, '.audit-code', 'install', INSTALL_MANIFEST_FILENAME);
406
+ return {
407
+ installedPromptPath,
408
+ installedSkillPath,
409
+ installGuidePath,
410
+ installManifestPath,
411
+ agentsInstructionsPath: profile.writeAgents ? join(root, 'AGENTS.md') : null,
412
+ copilotInstructionsPath: profile.writeCopilotInstructions
413
+ ? join(root, '.github', 'copilot-instructions.md')
414
+ : null,
415
+ codexSkillPath: profile.writeCodex
416
+ ? join(root, '.codex', 'skills', 'audit-code', 'SKILL.md')
417
+ : null,
418
+ codexPromptPath: profile.writeCodex
419
+ ? join(root, '.codex', 'skills', 'audit-code', 'audit-code.prompt.md')
420
+ : null,
421
+ codexAutomationRecipePath: profile.writeCodex
422
+ ? join(root, '.audit-code', 'install', 'codex', 'RE-AUDIT-AUTOMATION.md')
423
+ : null,
424
+ opencodeConfigPath: profile.writeOpenCode
425
+ ? join(root, 'opencode.json')
426
+ : null,
427
+ vscodePromptPath: profile.writeVSCode
428
+ ? join(root, '.github', 'prompts', 'audit-code.prompt.md')
429
+ : null,
430
+ vscodeAgentPath: profile.writeVSCode
431
+ ? join(root, '.github', 'agents', 'auditor.agent.md')
432
+ : null,
433
+ antigravityPlanningGuidePath: profile.writeAntigravity
434
+ ? join(root, '.audit-code', 'install', 'antigravity', 'PLANNING-MODE.md')
435
+ : null,
436
+ geminiCommandPath: profile.writeAntigravity
437
+ ? join(root, '.gemini', 'commands', 'audit-code.toml')
438
+ : null,
439
+ antigravitySkillPath: profile.writeAntigravity
440
+ ? join(root, '.agent', 'skills', 'audit-code', 'SKILL.md')
441
+ : null,
442
+ };
443
+ }
444
+
445
+ export function buildHostCatalog({ root, host, assets }) {
446
+ return getInstallHostKeys(host)
447
+ .map((hostKey) => {
448
+ const definition = INSTALL_HOST_DEFINITIONS[hostKey];
449
+ const primaryPath = assets[definition.primary_path_key];
450
+ if (!primaryPath) {
451
+ return null;
452
+ }
453
+
454
+ return {
455
+ host: definition.host,
456
+ label: definition.label,
457
+ support_level: definition.support_level,
458
+ setup_kind: definition.setup_kind,
459
+ summary: definition.summary,
460
+ primary_path: primaryPath,
461
+ supporting_paths: definition.supporting_path_keys
462
+ .map((key) => assets[key])
463
+ .filter(Boolean),
464
+ steps: definition.steps,
465
+ };
466
+ })
467
+ .filter(Boolean)
468
+ .map((entry) => ({
469
+ ...entry,
470
+ primary_path: entry.primary_path,
471
+ supporting_paths: entry.supporting_paths,
472
+ repo_relative_primary_path: toRepoRelativePath(root, entry.primary_path),
473
+ repo_relative_supporting_paths: entry.supporting_paths.map((path) => toRepoRelativePath(root, path)),
474
+ }));
475
+ }
476
+
477
+ export function renderInstallGuide({
478
+ root,
479
+ host,
480
+ installedPromptPath,
481
+ installedSkillPath,
482
+ installManifestPath,
483
+ hostGuidance,
484
+ }) {
485
+ const lines = [
486
+ '# audit-code bootstrap guide',
487
+ '',
488
+ 'The canonical product route is `/audit-code` in conversation.',
489
+ '',
490
+ 'Shared repo-local assets:',
491
+ `- prompt asset: \`${toRepoRelativePath(root, installedPromptPath)}\``,
492
+ `- skill asset: \`${toRepoRelativePath(root, installedSkillPath)}\``,
493
+ `- host manifest: \`${toRepoRelativePath(root, installManifestPath)}\``,
494
+ '',
495
+ 'Host-specific quick starts:',
496
+ ];
497
+
498
+ for (const guide of hostGuidance) {
499
+ lines.push(`- ${guide.label}: ${guide.summary}`);
500
+ }
501
+
502
+ for (const guide of hostGuidance) {
503
+ lines.push('', `## ${guide.label}`, '');
504
+ lines.push(`Support level: ${guide.support_level}`);
505
+ lines.push(`Setup kind: ${guide.setup_kind}`);
506
+ lines.push('');
507
+ lines.push(guide.summary);
508
+ lines.push('');
509
+ lines.push('Primary repo-local path:');
510
+ lines.push(`- \`${toRepoRelativePath(root, guide.primary_path)}\``);
511
+ if (guide.supporting_paths.length > 0) {
512
+ lines.push('', 'Supporting repo-local paths:');
513
+ for (const path of guide.supporting_paths) {
514
+ lines.push(`- \`${toRepoRelativePath(root, path)}\``);
515
+ }
516
+ }
517
+ lines.push('', 'Recommended steps:');
518
+ for (const step of guide.steps) {
519
+ lines.push(`- ${step}`);
520
+ }
521
+ }
522
+
523
+ lines.push('', 'Backend fallback:');
524
+ lines.push('- from the repository root, run `audit-code` only when you intentionally need the repo-local backend wrapper');
525
+ lines.push('- run `audit-code verify-install` after bootstrap when you want to smoke-test the generated launchers and host configs');
526
+ lines.push('- rerun `audit-code install` to refresh every generated host surface from the shared prompt and skill assets together');
527
+
528
+ if (host !== 'all') {
529
+ lines.push('');
530
+ lines.push(`This install was scoped to \`${host}\`, so assets for other hosts may be intentionally omitted.`);
531
+ }
532
+
533
+ lines.push('');
534
+ return lines.join('\n');
535
+ }
536
+
537
+ async function assertDirectoryExists(path, description) {
538
+ let stats;
539
+ try {
540
+ stats = await stat(path);
541
+ } catch {
542
+ throw new Error(`${description} does not exist: ${path}`);
543
+ }
544
+
545
+ if (!stats.isDirectory()) {
546
+ throw new Error(`${description} is not a directory: ${path}`);
547
+ }
548
+ }
549
+
550
+ async function collectVerifyCheck(target, id, fn) {
551
+ try {
552
+ const details = await fn();
553
+ target.push({
554
+ id,
555
+ status: 'ok',
556
+ ...(details ?? {}),
557
+ });
558
+ } catch (error) {
559
+ target.push({
560
+ id,
561
+ status: 'error',
562
+ summary: error instanceof Error ? error.message : String(error),
563
+ });
564
+ }
565
+ }
566
+
567
+ async function ensureFile(path, description) {
568
+ let stats;
569
+ try {
570
+ stats = await stat(path);
571
+ } catch {
572
+ throw new Error(`${description} does not exist: ${path}`);
573
+ }
574
+
575
+ if (!stats.isFile()) {
576
+ throw new Error(`${description} is not a file: ${path}`);
577
+ }
578
+
579
+ return stats;
580
+ }
581
+
582
+ export async function writeCoreInstallAssets(root, assetPaths, promptSource, skillSource) {
583
+ const results = [];
584
+ const legacyInstalledPromptPath = join(root, '.audit-code', 'install', 'audit-code.prompt.md');
585
+ if (await fileExists(legacyInstalledPromptPath)) {
586
+ await unlink(legacyInstalledPromptPath).catch(() => {});
587
+ }
588
+ results.push(await writeGeneratedMarkdown(assetPaths.installedPromptPath, promptSource));
589
+ results.push(await writeGeneratedMarkdown(assetPaths.installedSkillPath, skillSource));
590
+
591
+ const compatibilityBlockTargets = [
592
+ assetPaths.agentsInstructionsPath,
593
+ assetPaths.copilotInstructionsPath,
594
+ ].filter(Boolean);
595
+ for (const targetPath of compatibilityBlockTargets) {
596
+ results.push(
597
+ await writeManagedMarkdown(
598
+ targetPath,
599
+ buildInstallDirective(
600
+ relative(dirname(targetPath), assetPaths.installedPromptPath) || `./.audit-code/install/${INSTALLED_PROMPT_FILENAME}`,
601
+ ),
602
+ ),
603
+ );
604
+ }
605
+
606
+ results.push(...await removeLegacyAuditCodeSurfaceFiles(root));
607
+ return results;
608
+ }
609
+
610
+ export async function writeCodexAssets(assetPaths, promptSource, skillSource) {
611
+ return [
612
+ await writeGeneratedMarkdown(assetPaths.codexSkillPath, skillSource),
613
+ await writeGeneratedMarkdown(assetPaths.codexPromptPath, promptSource),
614
+ await writeGeneratedMarkdown(assetPaths.codexAutomationRecipePath, renderCodexAutomationRecipe()),
615
+ ];
616
+ }
617
+
618
+ export async function writeOpenCodeAssets(assetPaths, root) {
619
+ return [
620
+ await writeMergedGeneratedJson(
621
+ assetPaths.opencodeConfigPath,
622
+ 'OpenCode project config',
623
+ (existing) => buildMergedOpenCodeProjectConfig(existing, root),
624
+ ),
625
+ ];
626
+ }
627
+
628
+ export async function writeVSCodeAssets(assetPaths, promptBody) {
629
+ return [
630
+ await writeGeneratedMarkdown(
631
+ assetPaths.vscodePromptPath,
632
+ renderPromptFile(
633
+ {
634
+ name: 'audit-code',
635
+ description: 'Autonomous local loop code auditing',
636
+ agent: 'auditor',
637
+ },
638
+ promptBody,
639
+ ),
640
+ ),
641
+ await writeGeneratedMarkdown(assetPaths.vscodeAgentPath, renderVSCodeAgentFile()),
642
+ ];
643
+ }
644
+
645
+ export async function writeAntigravityAssets(assetPaths, promptBody, skillSource, root) {
646
+ return [
647
+ await writeGeneratedMarkdown(
648
+ assetPaths.antigravityPlanningGuidePath,
649
+ renderAntigravityPlanningGuide(root),
650
+ ),
651
+ await writeGeneratedMarkdown(assetPaths.geminiCommandPath, renderGeminiCommandToml(promptBody)),
652
+ await writeGeneratedMarkdown(assetPaths.antigravitySkillPath, skillSource),
653
+ ];
654
+ }
655
+
656
+ export async function installBootstrap(argv, options = {}) {
657
+ const host = (getFlag(argv, '--host') ?? DEFAULT_INSTALL_HOST).toLowerCase();
658
+ const root = resolve(getFlag(argv, '--root') ?? '.');
659
+ await assertDirectoryExists(root, 'Target repository root');
660
+ const profile = getInstallProfile(host);
661
+ const promptSource = await readFile(promptAssetPath, 'utf8');
662
+ const skillSource = (await readFile(skillAssetPath, 'utf8')).replace(/\r\n/g, '\n');
663
+ const { body: promptBody } = splitFrontmatter(promptSource);
664
+ const assetPaths = buildInstallAssetPaths(root, profile);
665
+ const {
666
+ installedPromptPath,
667
+ installedSkillPath,
668
+ installGuidePath,
669
+ installManifestPath,
670
+ } = assetPaths;
671
+
672
+ const results = [];
673
+ results.push(...await writeCoreInstallAssets(root, assetPaths, promptSource, skillSource));
674
+
675
+ if (profile.writeCodex) {
676
+ results.push(...await writeCodexAssets(assetPaths, promptSource, skillSource));
677
+ }
678
+ if (profile.writeOpenCode) {
679
+ results.push(...await writeOpenCodeAssets(assetPaths, root));
680
+ }
681
+ if (profile.writeVSCode) {
682
+ results.push(...await writeVSCodeAssets(assetPaths, promptBody));
683
+ }
684
+ if (profile.writeAntigravity) {
685
+ results.push(...await writeAntigravityAssets(assetPaths, promptBody, skillSource, root));
686
+ }
687
+
688
+ const hostGuidance = buildHostCatalog({
689
+ root,
690
+ host,
691
+ assets: assetPaths,
692
+ });
693
+
694
+ const installManifest = {
695
+ contract_version: 'audit-code-install/v1alpha1',
696
+ host,
697
+ repo_root: root,
698
+ installed_prompt_path: installedPromptPath,
699
+ installed_skill_path: installedSkillPath,
700
+ install_guide_path: installGuidePath,
701
+ install_manifest_path: installManifestPath,
702
+ source_prompt_path: resolve(promptAssetPath),
703
+ source_skill_path: resolve(skillAssetPath),
704
+ asset_paths: assetPaths,
705
+ hosts: hostGuidance,
706
+ };
707
+
708
+ results.push(
709
+ await writeGeneratedMarkdown(
710
+ installGuidePath,
711
+ renderInstallGuide({
712
+ root,
713
+ host,
714
+ installedPromptPath,
715
+ installedSkillPath,
716
+ installManifestPath,
717
+ hostGuidance,
718
+ }),
719
+ ),
720
+ );
721
+ results.push(await writeGeneratedJson(installManifestPath, installManifest));
722
+
723
+ const sessionConfigPath = join(root, '.audit-artifacts', 'session-config.json');
724
+ if (!(await fileExists(sessionConfigPath))) {
725
+ const defaultConfig = { provider: 'local-subprocess' };
726
+ await mkdir(dirname(sessionConfigPath), { recursive: true });
727
+ await writeFile(sessionConfigPath, JSON.stringify(defaultConfig, null, 2) + '\n', 'utf8');
728
+ results.push({ path: sessionConfigPath, mode: 'created' });
729
+ }
730
+
731
+ const payload = {
732
+ host,
733
+ repo_root: root,
734
+ installed_prompt_path: installedPromptPath,
735
+ installed_skill_path: installedSkillPath,
736
+ install_guide_path: installGuidePath,
737
+ install_manifest_path: installManifestPath,
738
+ source_prompt_path: resolve(promptAssetPath),
739
+ source_skill_path: resolve(skillAssetPath),
740
+ files: results,
741
+ slash_command_surfaces: {
742
+ vscode_prompt: assetPaths.vscodePromptPath,
743
+ opencode_config: assetPaths.opencodeConfigPath,
744
+ gemini_command: assetPaths.geminiCommandPath,
745
+ antigravity_skill: assetPaths.antigravitySkillPath,
746
+ },
747
+ instruction_surfaces: {
748
+ agents: assetPaths.agentsInstructionsPath,
749
+ copilot_instructions: assetPaths.copilotInstructionsPath,
750
+ },
751
+ host_guidance: hostGuidance,
752
+ unsupported_hosts: [],
753
+ next_steps: [
754
+ 'Open the repository in your preferred host and follow the matching host_guidance entry.',
755
+ `Open ${installGuidePath} for repo-local quick-start steps for Codex, OpenCode, VS Code, and Antigravity.`,
756
+ 'Run `audit-code verify-install` from the repository root to smoke-test the generated host configs.',
757
+ ],
758
+ };
759
+
760
+ if (!options.quiet) {
761
+ console.log(JSON.stringify(payload, null, 2));
762
+ }
763
+
764
+ return payload;
765
+ }
766
+
767
+ export async function verifyInstalledBootstrap(argv) {
768
+ const root = resolve(getFlag(argv, '--root') ?? '.');
769
+ const requestedHost = getFlag(argv, '--host')?.toLowerCase() ?? null;
770
+ const installManifestPath = join(
771
+ root,
772
+ '.audit-code',
773
+ 'install',
774
+ INSTALL_MANIFEST_FILENAME,
775
+ );
776
+ const installGuidePath = join(
777
+ root,
778
+ '.audit-code',
779
+ 'install',
780
+ INSTALL_GUIDE_FILENAME,
781
+ );
782
+
783
+ await assertDirectoryExists(root, 'Target repository root');
784
+
785
+ const generalChecks = [];
786
+ const hostResults = [];
787
+ let installManifest;
788
+
789
+ await collectVerifyCheck(generalChecks, 'install_manifest', async () => {
790
+ await ensureFile(installManifestPath, 'Install manifest');
791
+ installManifest = await readJson(installManifestPath, 'Install manifest');
792
+ if (installManifest?.contract_version !== 'audit-code-install/v1alpha1') {
793
+ throw new Error(
794
+ `Unexpected install manifest contract version: ${installManifest?.contract_version ?? 'missing'}.`,
795
+ );
796
+ }
797
+ return {
798
+ summary: 'Install manifest parsed successfully.',
799
+ path: installManifestPath,
800
+ };
801
+ });
802
+
803
+ if (!installManifest) {
804
+ console.log(
805
+ JSON.stringify(
806
+ {
807
+ root,
808
+ requested_host: requestedHost ?? 'all',
809
+ status: 'error',
810
+ issue_count: generalChecks.filter((check) => check.status === 'error').length,
811
+ checks: generalChecks,
812
+ hosts: [],
813
+ },
814
+ null,
815
+ 2,
816
+ ),
817
+ );
818
+ process.exitCode = 1;
819
+ return;
820
+ }
821
+
822
+ const assetPaths = installManifest.asset_paths ?? {};
823
+ const hostCatalog = new Map(
824
+ (installManifest.hosts ?? []).map((entry) => [entry.host, entry]),
825
+ );
826
+ const selectedHosts = requestedHost && requestedHost !== 'all'
827
+ ? getInstallHostKeys(requestedHost)
828
+ : [...hostCatalog.keys()];
829
+
830
+ await collectVerifyCheck(generalChecks, 'install_guide', async () => {
831
+ const guide = await readFile(installGuidePath, 'utf8');
832
+ if (!guide.includes('# audit-code bootstrap guide')) {
833
+ throw new Error(`Install guide does not look like an audit-code bootstrap guide: ${installGuidePath}`);
834
+ }
835
+ return {
836
+ summary: 'Install guide is present and readable.',
837
+ path: installGuidePath,
838
+ };
839
+ });
840
+
841
+ await collectVerifyCheck(generalChecks, 'installed_prompt', async () => {
842
+ await ensureFile(assetPaths.installedPromptPath, 'Installed prompt asset');
843
+ const installedPrompt = await readFile(assetPaths.installedPromptPath, 'utf8');
844
+ const sourcePrompt = await readFile(promptAssetPath, 'utf8');
845
+ if (installedPrompt !== sourcePrompt) {
846
+ throw new Error(
847
+ `Installed prompt is out of sync with the source prompt. Run "audit-code install" from ${root}.`,
848
+ );
849
+ }
850
+ return {
851
+ summary: 'Installed prompt asset is present and matches the source prompt.',
852
+ path: assetPaths.installedPromptPath,
853
+ };
854
+ });
855
+
856
+ await collectVerifyCheck(generalChecks, 'installed_skill', async () => {
857
+ await ensureFile(assetPaths.installedSkillPath, 'Installed skill asset');
858
+ const installedSkill = (await readFile(assetPaths.installedSkillPath, 'utf8')).replace(/\r\n/g, '\n');
859
+ const sourceSkill = (await readFile(skillAssetPath, 'utf8')).replace(/\r\n/g, '\n');
860
+ if (installedSkill !== sourceSkill) {
861
+ throw new Error(
862
+ `Installed skill is out of sync with the source skill. Run "audit-code install" from ${root}.`,
863
+ );
864
+ }
865
+ return {
866
+ summary: 'Installed skill asset is present and matches the source skill.',
867
+ path: assetPaths.installedSkillPath,
868
+ };
869
+ });
870
+
871
+ await collectVerifyCheck(generalChecks, 'legacy_local_surfaces', async () => {
872
+ const legacySurfaces = await findLegacyAuditCodeSurfaceFiles(root);
873
+ if (legacySurfaces.length > 0) {
874
+ throw new Error(
875
+ `Legacy local /audit-code surfaces are still present: ${legacySurfaces.join(', ')}. Run "audit-code install" from ${root}.`,
876
+ );
877
+ }
878
+ return {
879
+ summary: 'No legacy local /audit-code command or skill surfaces were found.',
880
+ };
881
+ });
882
+
883
+ for (const hostKey of selectedHosts) {
884
+ const checks = [];
885
+ const hostEntry = hostCatalog.get(hostKey);
886
+
887
+ if (!hostEntry) {
888
+ checks.push({
889
+ id: 'host_manifest_entry',
890
+ status: 'error',
891
+ summary: `Install manifest does not contain host guidance for "${hostKey}".`,
892
+ });
893
+ hostResults.push({ host: hostKey, status: 'error', checks });
894
+ continue;
895
+ }
896
+
897
+ await collectVerifyCheck(checks, 'host_manifest_entry', async () => ({
898
+ summary: `Host guidance exists for ${hostEntry.label}.`,
899
+ primary_path: hostEntry.primary_path,
900
+ }));
901
+
902
+ const hostDefinition = INSTALL_HOST_DEFINITIONS[hostKey];
903
+ if (hostDefinition?.verify) {
904
+ await hostDefinition.verify({ checks, root, assetPaths, collectVerifyCheck });
905
+ } else {
906
+ checks.push({
907
+ id: 'host_handler',
908
+ status: 'error',
909
+ summary: `No verification handler is implemented for host "${hostKey}".`,
910
+ });
911
+ }
912
+
913
+ hostResults.push({
914
+ host: hostKey,
915
+ status: checks.some((check) => check.status === 'error') ? 'error' : 'ok',
916
+ checks,
917
+ });
918
+ }
919
+
920
+ const issueCount =
921
+ generalChecks.filter((check) => check.status === 'error').length +
922
+ hostResults.reduce(
923
+ (sum, host) => sum + host.checks.filter((check) => check.status === 'error').length,
924
+ 0,
925
+ );
926
+
927
+ console.log(
928
+ JSON.stringify(
929
+ {
930
+ root,
931
+ requested_host: requestedHost ?? 'all',
932
+ manifest_path: installManifestPath,
933
+ status: issueCount > 0 ? 'error' : 'ok',
934
+ issue_count: issueCount,
935
+ checks: generalChecks,
936
+ hosts: hostResults,
937
+ },
938
+ null,
939
+ 2,
940
+ ),
941
+ );
942
+
943
+ process.exitCode = issueCount > 0 ? 1 : 0;
944
+ }
945
+
946
+ export async function detectBootstrapRefreshReason(root, host) {
947
+ const installManifestPath = join(
948
+ root,
949
+ '.audit-code',
950
+ 'install',
951
+ INSTALL_MANIFEST_FILENAME,
952
+ );
953
+
954
+ if (!(await fileExists(installManifestPath))) {
955
+ return 'missing_install_manifest';
956
+ }
957
+
958
+ let installManifest;
959
+ try {
960
+ installManifest = JSON.parse(await readFile(installManifestPath, 'utf8'));
961
+ } catch {
962
+ return 'invalid_install_manifest';
963
+ }
964
+
965
+ if (installManifest?.contract_version !== 'audit-code-install/v1alpha1') {
966
+ return 'stale_install_manifest_contract';
967
+ }
968
+
969
+ const assetPaths = installManifest.asset_paths ?? {};
970
+ const hostCatalog = new Set(
971
+ (installManifest.hosts ?? []).map((entry) => entry.host),
972
+ );
973
+
974
+ if (hostCatalog.has('codex') && (assetPaths.codexSkillPath || assetPaths.codexPromptPath)) {
975
+ return 'legacy_local_audit_code_surface';
976
+ }
977
+
978
+ if ((await findLegacyAuditCodeSurfaceFiles(root)).length > 0) {
979
+ return 'legacy_local_audit_code_surface';
980
+ }
981
+
982
+ for (const hostKey of getInstallHostKeys(host)) {
983
+ if (!hostCatalog.has(hostKey)) {
984
+ return `missing_host_surface:${hostKey}`;
985
+ }
986
+
987
+ const definition = INSTALL_HOST_DEFINITIONS[hostKey];
988
+ const requiredPathKeys = [
989
+ definition.primary_path_key,
990
+ ...definition.supporting_path_keys,
991
+ ];
992
+ for (const pathKey of requiredPathKeys) {
993
+ const targetPath = assetPaths[pathKey];
994
+ if (targetPath && !(await fileExists(targetPath))) {
995
+ return `missing_host_asset:${hostKey}:${pathKey}`;
996
+ }
997
+ }
998
+ }
999
+
1000
+ const installedPrompt = await readTextIfExists(assetPaths.installedPromptPath);
1001
+ if (installedPrompt === null) {
1002
+ return 'missing_installed_prompt';
1003
+ }
1004
+ const sourcePrompt = await readFile(promptAssetPath, 'utf8');
1005
+ if (installedPrompt !== sourcePrompt) {
1006
+ return 'stale_installed_prompt';
1007
+ }
1008
+ const { body: sourcePromptBody } = splitFrontmatter(sourcePrompt);
1009
+
1010
+ const installedSkill = await readTextIfExists(assetPaths.installedSkillPath);
1011
+ if (installedSkill === null) {
1012
+ return 'missing_installed_skill';
1013
+ }
1014
+ const sourceSkill = (await readFile(skillAssetPath, 'utf8')).replace(/\r\n/g, '\n');
1015
+ if (installedSkill.replace(/\r\n/g, '\n') !== sourceSkill) {
1016
+ return 'stale_installed_skill';
1017
+ }
1018
+
1019
+ for (const hostKey of getInstallHostKeys(host)) {
1020
+ switch (hostKey) {
1021
+ case 'codex': {
1022
+ break;
1023
+ }
1024
+ case 'opencode': {
1025
+ const opencodeConfig = await readJson(assetPaths.opencodeConfigPath, 'OpenCode config').catch(() => null);
1026
+ if (opencodeConfig?.command?.['audit-code']) {
1027
+ return 'stale_host_asset:opencode:local_command';
1028
+ }
1029
+ if (opencodeConfig?.mcp?.auditor) {
1030
+ return 'stale_host_asset:opencode:project_mcp';
1031
+ }
1032
+ try {
1033
+ assertOpenCodeAuditPermissionConfig(opencodeConfig?.permission, 'permission');
1034
+ assertOpenCodeAuditPermissionConfig(opencodeConfig?.agent?.auditor?.permission, 'agent.auditor.permission');
1035
+ } catch {
1036
+ return 'stale_host_asset:opencode:permissions';
1037
+ }
1038
+ if (await fileExists(join(root, '.opencode', 'commands', 'audit-code.md'))) {
1039
+ return 'stale_host_asset:opencode:legacy_command_file';
1040
+ }
1041
+ break;
1042
+ }
1043
+ case 'vscode': {
1044
+ const vscodePrompt = await readTextIfExists(assetPaths.vscodePromptPath);
1045
+ if (vscodePrompt === null) {
1046
+ return 'missing_host_asset:vscode:prompt';
1047
+ }
1048
+ if (splitFrontmatter(vscodePrompt).body !== sourcePromptBody.trimStart()) {
1049
+ return 'stale_host_asset:vscode:prompt';
1050
+ }
1051
+ break;
1052
+ }
1053
+ case 'antigravity': {
1054
+ const expectedSkillPath = join(root, '.agent', 'skills', 'audit-code', 'SKILL.md');
1055
+ if (!(await fileExists(expectedSkillPath))) {
1056
+ return 'missing_host_asset:antigravity:skill';
1057
+ }
1058
+ const antigravitySkill = await readTextIfExists(expectedSkillPath);
1059
+ if (antigravitySkill !== null && antigravitySkill.replace(/\r\n/g, '\n') !== sourceSkill) {
1060
+ return 'stale_host_asset:antigravity:skill';
1061
+ }
1062
+ break;
1063
+ }
1064
+ default:
1065
+ break;
1066
+ }
1067
+ }
1068
+
1069
+ return null;
1070
+ }
1071
+
1072
+ export async function ensureBootstrap(argv) {
1073
+ const host = (getFlag(argv, '--host') ?? DEFAULT_INSTALL_HOST).toLowerCase();
1074
+ const root = resolve(getFlag(argv, '--root') ?? '.');
1075
+ const quiet = hasFlag(argv, '--quiet');
1076
+ const force = hasFlag(argv, '--force');
1077
+ await assertDirectoryExists(root, 'Target repository root');
1078
+
1079
+ const reason = force
1080
+ ? 'forced'
1081
+ : await detectBootstrapRefreshReason(root, host);
1082
+
1083
+ if (reason) {
1084
+ const installed = await installBootstrap(argv, { quiet: true });
1085
+ const payload = {
1086
+ status: 'ok',
1087
+ action: 'installed',
1088
+ reason,
1089
+ host: installed.host,
1090
+ repo_root: installed.repo_root,
1091
+ install_manifest_path: installed.install_manifest_path,
1092
+ host_count: installed.host_guidance.length,
1093
+ file_count: installed.files.length,
1094
+ };
1095
+ if (!quiet) {
1096
+ console.log(JSON.stringify(payload, null, 2));
1097
+ }
1098
+ return payload;
1099
+ }
1100
+
1101
+ const payload = {
1102
+ status: 'ok',
1103
+ action: 'skipped',
1104
+ reason: null,
1105
+ host,
1106
+ repo_root: root,
1107
+ install_manifest_path: join(
1108
+ root,
1109
+ '.audit-code',
1110
+ 'install',
1111
+ INSTALL_MANIFEST_FILENAME,
1112
+ ),
1113
+ };
1114
+ if (!quiet) {
1115
+ console.log(JSON.stringify(payload, null, 2));
1116
+ }
1117
+ return payload;
1118
+ }
1119
+
1120
+ export async function installHostPrompt(argv) {
1121
+ const host = requireFlagValue(argv, '--host').toLowerCase();
1122
+
1123
+ if (host !== 'copilot') {
1124
+ throw new Error(
1125
+ `install-host currently supports only "copilot". Use "install --host ${host}" for the broader bootstrap flow.`,
1126
+ );
1127
+ }
1128
+
1129
+ await installBootstrap(argv);
1130
+ }
1131
+
1132
+ // Underscore-aliased re-exports consumed by host-bootstrap-descriptors.test.mjs
1133
+ // via audit-code-wrapper-lib.mjs. Keep these so the test import chain resolves
1134
+ // without modification to the test file.
1135
+ export {
1136
+ INSTALL_HOST_ORDER as _INSTALL_HOST_ORDER,
1137
+ INSTALL_HOST_DEFINITIONS as _INSTALL_HOST_DEFINITIONS,
1138
+ getInstallHostKeys as _getInstallHostKeys,
1139
+ getInstallProfile as _getInstallProfile,
1140
+ };