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