sentinelayer-cli 0.4.5 → 0.6.2
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 +996 -998
- package/bin/create-sentinelayer.js +5 -5
- package/bin/sentinelayer-cli.js +4 -4
- package/bin/sl.js +5 -5
- package/package.json +63 -63
- package/src/agents/jules/config/definition.js +160 -209
- package/src/agents/jules/config/system-prompt.js +182 -175
- package/src/agents/jules/error-intake.js +51 -51
- package/src/agents/jules/fix-cycle.js +17 -377
- package/src/agents/jules/loop.js +450 -367
- package/src/agents/jules/pulse.js +10 -327
- package/src/agents/jules/stream.js +186 -186
- package/src/agents/jules/swarm/file-scanner.js +74 -74
- package/src/agents/jules/swarm/index.js +11 -11
- package/src/agents/jules/swarm/orchestrator.js +362 -362
- package/src/agents/jules/swarm/pattern-hunter.js +123 -123
- package/src/agents/jules/swarm/sub-agent.js +309 -308
- package/src/agents/jules/tools/aidenid-email.js +189 -0
- package/src/agents/jules/tools/auth-audit.js +1691 -557
- package/src/agents/jules/tools/dispatch.js +335 -327
- package/src/agents/jules/tools/file-edit.js +2 -180
- package/src/agents/jules/tools/file-read.js +2 -100
- package/src/agents/jules/tools/frontend-analyze.js +570 -570
- package/src/agents/jules/tools/glob.js +2 -168
- package/src/agents/jules/tools/grep.js +2 -228
- package/src/agents/jules/tools/index.js +29 -29
- package/src/agents/jules/tools/path-guards.js +2 -161
- package/src/agents/jules/tools/runtime-audit.js +507 -503
- package/src/agents/jules/tools/shell.js +2 -383
- package/src/agents/jules/tools/url-policy.js +100 -100
- package/src/agents/persona-visuals.js +61 -0
- package/src/agents/shared-tools/dispatch-core.js +315 -0
- package/src/agents/shared-tools/file-edit.js +180 -0
- package/src/agents/shared-tools/file-read.js +100 -0
- package/src/agents/shared-tools/glob.js +168 -0
- package/src/agents/shared-tools/grep.js +228 -0
- package/src/agents/shared-tools/index.js +46 -0
- package/src/agents/shared-tools/path-guards.js +161 -0
- package/src/agents/shared-tools/shell.js +383 -0
- package/src/ai/aidenid.js +1009 -972
- package/src/ai/client.js +553 -508
- package/src/ai/domain-target-store.js +268 -268
- package/src/ai/identity-store.js +270 -270
- package/src/ai/proxy.js +137 -0
- package/src/ai/site-store.js +145 -145
- package/src/audit/agents/architecture.js +180 -180
- package/src/audit/agents/compliance.js +179 -179
- package/src/audit/agents/documentation.js +165 -165
- package/src/audit/agents/performance.js +145 -145
- package/src/audit/agents/security.js +215 -215
- package/src/audit/agents/testing.js +172 -172
- package/src/audit/orchestrator.js +557 -557
- package/src/audit/package.js +204 -204
- package/src/audit/registry.js +284 -284
- package/src/audit/replay.js +103 -103
- package/src/auth/gate.js +371 -126
- package/src/auth/http.js +611 -270
- package/src/auth/service.js +1106 -891
- package/src/auth/session-store.js +813 -359
- package/src/cli.js +252 -252
- package/src/commands/ai/identity-lifecycle.js +1338 -1338
- package/src/commands/ai/provision-governance.js +1272 -1272
- package/src/commands/ai/shared.js +147 -147
- package/src/commands/ai.js +11 -11
- package/src/commands/apply.js +12 -12
- package/src/commands/audit.js +1166 -1166
- package/src/commands/auth.js +419 -375
- package/src/commands/chat.js +191 -191
- package/src/commands/config.js +184 -184
- package/src/commands/cost.js +311 -311
- package/src/commands/daemon/core.js +850 -850
- package/src/commands/daemon/extended.js +1048 -1048
- package/src/commands/daemon/shared.js +213 -213
- package/src/commands/daemon.js +11 -11
- package/src/commands/guide.js +174 -174
- package/src/commands/ingest.js +58 -58
- package/src/commands/init.js +55 -55
- package/src/commands/legacy-args.js +10 -10
- package/src/commands/mcp.js +461 -461
- package/src/commands/omargate.js +29 -21
- package/src/commands/persona.js +20 -20
- package/src/commands/plugin.js +260 -260
- package/src/commands/policy.js +132 -132
- package/src/commands/prompt.js +238 -238
- package/src/commands/review.js +704 -704
- package/src/commands/scan.js +872 -866
- package/src/commands/spec.js +716 -716
- package/src/commands/swarm.js +651 -651
- package/src/commands/telemetry.js +202 -202
- package/src/commands/watch.js +511 -510
- package/src/config/agent-dictionary.js +182 -182
- package/src/config/io.js +56 -56
- package/src/config/paths.js +18 -18
- package/src/config/schema.js +55 -55
- package/src/config/service.js +184 -184
- package/src/cost/budget.js +235 -235
- package/src/cost/history.js +188 -188
- package/src/cost/tracker.js +171 -171
- package/src/daemon/artifact-lineage.js +534 -534
- package/src/daemon/assignment-ledger.js +770 -770
- package/src/daemon/ast-parser-layer.js +258 -258
- package/src/daemon/budget-governor.js +633 -633
- package/src/daemon/callgraph-overlay.js +646 -646
- package/src/daemon/error-worker.js +626 -626
- package/src/daemon/fix-cycle.js +377 -0
- package/src/daemon/hybrid-mapper.js +929 -929
- package/src/daemon/jira-lifecycle.js +632 -632
- package/src/daemon/operator-control.js +657 -657
- package/src/daemon/pulse.js +327 -0
- package/src/daemon/reliability-lane.js +471 -471
- package/src/daemon/watchdog.js +971 -971
- package/src/guide/generator.js +316 -316
- package/src/ingest/engine.js +918 -918
- package/src/interactive/index.js +97 -95
- package/src/legacy-cli.js +2994 -2592
- package/src/mcp/registry.js +695 -695
- package/src/memory/blackboard.js +301 -301
- package/src/memory/retrieval.js +581 -581
- package/src/plugin/manifest.js +553 -553
- package/src/policy/packs.js +144 -144
- package/src/prompt/generator.js +118 -118
- package/src/review/ai-review.js +679 -669
- package/src/review/local-review.js +1305 -1295
- package/src/review/omargate-interactive.js +68 -0
- package/src/review/omargate-orchestrator.js +300 -0
- package/src/review/persona-prompts.js +296 -0
- package/src/review/replay.js +235 -235
- package/src/review/report.js +664 -664
- package/src/review/scan-modes.js +42 -0
- package/src/review/spec-binding.js +487 -487
- package/src/scaffold/generator.js +67 -67
- package/src/scaffold/templates.js +150 -150
- package/src/scan/generator.js +418 -418
- package/src/scan/gh-secrets.js +107 -107
- package/src/spec/generator.js +519 -519
- package/src/spec/regenerate.js +237 -237
- package/src/spec/templates.js +91 -91
- package/src/swarm/dashboard.js +247 -247
- package/src/swarm/factory.js +363 -363
- package/src/swarm/pentest.js +934 -934
- package/src/swarm/registry.js +419 -419
- package/src/swarm/report.js +158 -158
- package/src/swarm/runtime.js +576 -576
- package/src/swarm/scenario-dsl.js +272 -272
- package/src/telemetry/ledger.js +302 -302
- package/src/telemetry/session-tracker.js +234 -118
- package/src/telemetry/sync.js +203 -199
- package/src/ui/command-hints.js +13 -0
- package/src/ui/markdown.js +220 -220
package/src/commands/audit.js
CHANGED
|
@@ -1,1166 +1,1166 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import process from "node:process";
|
|
3
|
-
|
|
4
|
-
import pc from "picocolors";
|
|
5
|
-
|
|
6
|
-
import { runAuditOrchestrator } from "../audit/orchestrator.js";
|
|
7
|
-
import { loadAuditRunReport, resolveAuditRunDirectory, writeDdPackage } from "../audit/package.js";
|
|
8
|
-
import { writeAuditComparisonArtifact } from "../audit/replay.js";
|
|
9
|
-
import { loadAuditRegistry, selectAuditAgents } from "../audit/registry.js";
|
|
10
|
-
import { resolveOutputRoot } from "../config/service.js";
|
|
11
|
-
import { buildLegacyArgs } from "./legacy-args.js";
|
|
12
|
-
|
|
13
|
-
function shouldEmitJson(options, command) {
|
|
14
|
-
const local = Boolean(options && options.json);
|
|
15
|
-
const globalFromCommand =
|
|
16
|
-
command && command.optsWithGlobals ? Boolean(command.optsWithGlobals().json) : false;
|
|
17
|
-
return local || globalFromCommand;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function parseMaxParallel(rawValue) {
|
|
21
|
-
const normalized = Number(rawValue || 0);
|
|
22
|
-
if (!Number.isFinite(normalized) || normalized < 1) {
|
|
23
|
-
throw new Error("max-parallel must be an integer >= 1.");
|
|
24
|
-
}
|
|
25
|
-
return Math.floor(normalized);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function printAuditSummary(result) {
|
|
29
|
-
console.log(pc.bold("Audit orchestrator complete"));
|
|
30
|
-
console.log(pc.gray(`Run: ${result.runId}`));
|
|
31
|
-
console.log(pc.gray(`Report: ${result.reportMarkdownPath}`));
|
|
32
|
-
console.log(pc.gray(`JSON: ${result.reportJsonPath}`));
|
|
33
|
-
if (result.sharedMemory?.artifactPath) {
|
|
34
|
-
console.log(pc.gray(`Shared memory: ${result.sharedMemory.artifactPath}`));
|
|
35
|
-
}
|
|
36
|
-
if (result.ddPackage?.executiveSummaryPath) {
|
|
37
|
-
console.log(pc.gray(`DD package: ${result.ddPackage.executiveSummaryPath}`));
|
|
38
|
-
}
|
|
39
|
-
if (result.ingest?.refresh?.stale || result.ingest?.refresh?.refreshed) {
|
|
40
|
-
const reasons = (result.ingest?.refresh?.reasons || []).join(", ") || "none";
|
|
41
|
-
const line = `Ingest refresh: refreshed=${result.ingest?.refresh?.refreshed ? "yes" : "no"} stale=${
|
|
42
|
-
result.ingest?.refresh?.stale ? "yes" : "no"
|
|
43
|
-
} reasons=${reasons}`;
|
|
44
|
-
const color = result.ingest?.refresh?.stale && !result.ingest?.refresh?.refreshed ? pc.yellow : pc.gray;
|
|
45
|
-
console.log(color(line));
|
|
46
|
-
}
|
|
47
|
-
console.log(
|
|
48
|
-
`Summary: P0=${result.summary.P0} P1=${result.summary.P1} P2=${result.summary.P2} P3=${result.summary.P3}`
|
|
49
|
-
);
|
|
50
|
-
console.log(`Agents: ${result.agentResults.length}, max_parallel=${result.maxParallel}`);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function registerAuditCommand(program, invokeLegacy) {
|
|
54
|
-
const audit = program
|
|
55
|
-
.command("audit")
|
|
56
|
-
.description("Run audit orchestrator swarm or local compatibility audit")
|
|
57
|
-
.argument("[targetPath]", "Target workspace path", ".")
|
|
58
|
-
.option("--path <path>", "Target workspace path override")
|
|
59
|
-
.option("--agents <ids>", "Comma-separated agent ids (default: all built-in agents)", "")
|
|
60
|
-
.option("--max-parallel <n>", "Maximum agents processed in parallel", "3")
|
|
61
|
-
.option("--registry-file <path>", "Optional custom audit registry file")
|
|
62
|
-
.option("--output-dir <path>", "Optional artifact output root override")
|
|
63
|
-
.option("--refresh", "Refresh CODEBASE_INGEST before running audit")
|
|
64
|
-
.option("--dry-run", "Skip deterministic baseline and run orchestration planning only")
|
|
65
|
-
.option("--json", "Emit machine-readable output")
|
|
66
|
-
.action(async (targetPathArg, options, command) => {
|
|
67
|
-
const emitJson = shouldEmitJson(options, command);
|
|
68
|
-
const targetPath = path.resolve(process.cwd(), String(options.path || targetPathArg || "."));
|
|
69
|
-
const registry = await loadAuditRegistry({
|
|
70
|
-
registryFile: options.registryFile,
|
|
71
|
-
});
|
|
72
|
-
const selected = selectAuditAgents(registry.agents, options.agents);
|
|
73
|
-
if (selected.missing.length > 0) {
|
|
74
|
-
throw new Error(`Unknown agent id(s): ${selected.missing.join(", ")}`);
|
|
75
|
-
}
|
|
76
|
-
if (selected.selected.length === 0) {
|
|
77
|
-
throw new Error("No agents selected for audit run.");
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const result = await runAuditOrchestrator({
|
|
81
|
-
targetPath,
|
|
82
|
-
agents: selected.selected,
|
|
83
|
-
maxParallel: parseMaxParallel(options.maxParallel),
|
|
84
|
-
outputDir: options.outputDir,
|
|
85
|
-
dryRun: Boolean(options.dryRun),
|
|
86
|
-
refreshIngest: Boolean(options.refresh),
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
const payload = {
|
|
90
|
-
command: "audit",
|
|
91
|
-
targetPath: result.targetPath,
|
|
92
|
-
runId: result.runId,
|
|
93
|
-
runDirectory: result.runDirectory,
|
|
94
|
-
reportPath: result.reportMarkdownPath,
|
|
95
|
-
reportJsonPath: result.reportJsonPath,
|
|
96
|
-
dryRun: result.dryRun,
|
|
97
|
-
registrySource: registry.registrySource,
|
|
98
|
-
registryFile: registry.registryFile,
|
|
99
|
-
selectedAgents: result.selectedAgents,
|
|
100
|
-
maxParallel: result.maxParallel,
|
|
101
|
-
summary: result.summary,
|
|
102
|
-
agentCount: result.agentResults.length,
|
|
103
|
-
sharedMemoryPath: result.sharedMemory?.artifactPath || "",
|
|
104
|
-
sharedMemoryEntryCount: Number(result.sharedMemory?.entryCount || 0),
|
|
105
|
-
sharedMemoryQueryCount: Number(result.sharedMemory?.queryCount || 0),
|
|
106
|
-
ddPackageManifestPath: result.ddPackage?.manifestPath || "",
|
|
107
|
-
ddPackageFindingsPath: result.ddPackage?.findingsIndexPath || "",
|
|
108
|
-
ddPackageSummaryPath: result.ddPackage?.executiveSummaryPath || "",
|
|
109
|
-
ingestRefresh: result.ingest?.refresh || null,
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
if (emitJson) {
|
|
113
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
114
|
-
} else {
|
|
115
|
-
printAuditSummary(result);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (result.summary.blocking) {
|
|
119
|
-
process.exitCode = 2;
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
audit
|
|
124
|
-
.command("package")
|
|
125
|
-
.description("Build or rebuild a unified DD package from an audit run")
|
|
126
|
-
.option("--path <path>", "Target workspace path", ".")
|
|
127
|
-
.option("--run-id <id>", "Audit run id (defaults to latest run under output root)")
|
|
128
|
-
.option("--output-dir <path>", "Optional artifact output root override")
|
|
129
|
-
.option("--json", "Emit machine-readable output")
|
|
130
|
-
.action(async (options, command) => {
|
|
131
|
-
const emitJson = shouldEmitJson(options, command);
|
|
132
|
-
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
133
|
-
const outputRoot = await resolveOutputRoot({
|
|
134
|
-
cwd: targetPath,
|
|
135
|
-
outputDirOverride: options.outputDir,
|
|
136
|
-
env: process.env,
|
|
137
|
-
});
|
|
138
|
-
const runDirectory = await resolveAuditRunDirectory({
|
|
139
|
-
outputRoot,
|
|
140
|
-
runId: options.runId,
|
|
141
|
-
});
|
|
142
|
-
const { report } = await loadAuditRunReport(runDirectory);
|
|
143
|
-
const ddPackage = await writeDdPackage({
|
|
144
|
-
report,
|
|
145
|
-
runDirectory,
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
const payload = {
|
|
149
|
-
command: "audit package",
|
|
150
|
-
targetPath,
|
|
151
|
-
outputRoot,
|
|
152
|
-
runId: report.runId,
|
|
153
|
-
runDirectory,
|
|
154
|
-
reportPath: path.join(runDirectory, "AUDIT_REPORT.md"),
|
|
155
|
-
reportJsonPath: path.join(runDirectory, "AUDIT_REPORT.json"),
|
|
156
|
-
ddPackageManifestPath: ddPackage.manifestPath,
|
|
157
|
-
ddPackageFindingsPath: ddPackage.findingsIndexPath,
|
|
158
|
-
ddPackageSummaryPath: ddPackage.executiveSummaryPath,
|
|
159
|
-
findingsIndexCount: ddPackage.findingsIndexCount,
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
if (emitJson) {
|
|
163
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
164
|
-
} else {
|
|
165
|
-
console.log(pc.bold("Audit DD package complete"));
|
|
166
|
-
console.log(pc.gray(`Run: ${payload.runId}`));
|
|
167
|
-
console.log(pc.gray(`Manifest: ${payload.ddPackageManifestPath}`));
|
|
168
|
-
console.log(pc.gray(`Summary: ${payload.ddPackageSummaryPath}`));
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
audit
|
|
173
|
-
.command("replay")
|
|
174
|
-
.description("Replay an existing audit run with the same selected agent set")
|
|
175
|
-
.argument("<runId>", "Base audit run id to replay")
|
|
176
|
-
.option("--path <path>", "Workspace path override (defaults to original run target)")
|
|
177
|
-
.option("--registry-file <path>", "Optional custom audit registry file")
|
|
178
|
-
.option("--output-dir <path>", "Optional artifact output root override")
|
|
179
|
-
.option("--refresh", "Refresh CODEBASE_INGEST before replay")
|
|
180
|
-
.option("--json", "Emit machine-readable output")
|
|
181
|
-
.action(async (runId, options, command) => {
|
|
182
|
-
const emitJson = shouldEmitJson(options, command);
|
|
183
|
-
const initialTargetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
184
|
-
const outputRoot = await resolveOutputRoot({
|
|
185
|
-
cwd: initialTargetPath,
|
|
186
|
-
outputDirOverride: options.outputDir,
|
|
187
|
-
env: process.env,
|
|
188
|
-
});
|
|
189
|
-
const baseRunDirectory = await resolveAuditRunDirectory({
|
|
190
|
-
outputRoot,
|
|
191
|
-
runId,
|
|
192
|
-
});
|
|
193
|
-
const { report: baseReport } = await loadAuditRunReport(baseRunDirectory);
|
|
194
|
-
const targetPath = options.path
|
|
195
|
-
? path.resolve(process.cwd(), String(options.path || "."))
|
|
196
|
-
: path.resolve(String(baseReport.targetPath || "."));
|
|
197
|
-
|
|
198
|
-
const registry = await loadAuditRegistry({
|
|
199
|
-
registryFile: options.registryFile,
|
|
200
|
-
});
|
|
201
|
-
const selected = selectAuditAgents(registry.agents, (baseReport.selectedAgents || []).join(","));
|
|
202
|
-
if (selected.selected.length === 0) {
|
|
203
|
-
throw new Error("No eligible agents available for replay in the active registry.");
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const replayResult = await runAuditOrchestrator({
|
|
207
|
-
targetPath,
|
|
208
|
-
agents: selected.selected,
|
|
209
|
-
maxParallel: Number(baseReport.maxParallel || 1),
|
|
210
|
-
outputDir: options.outputDir,
|
|
211
|
-
dryRun: Boolean(baseReport.dryRun),
|
|
212
|
-
refreshIngest: Boolean(options.refresh),
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
const comparison = await writeAuditComparisonArtifact({
|
|
216
|
-
baseReport,
|
|
217
|
-
candidateReport: replayResult,
|
|
218
|
-
outputDirectory: replayResult.runDirectory,
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
const payload = {
|
|
222
|
-
command: "audit replay",
|
|
223
|
-
baseRunId: baseReport.runId,
|
|
224
|
-
replayRunId: replayResult.runId,
|
|
225
|
-
targetPath: replayResult.targetPath,
|
|
226
|
-
runDirectory: replayResult.runDirectory,
|
|
227
|
-
reportPath: replayResult.reportMarkdownPath,
|
|
228
|
-
reportJsonPath: replayResult.reportJsonPath,
|
|
229
|
-
comparisonPath: comparison.outputPath,
|
|
230
|
-
deterministicEquivalent: comparison.comparison.deterministicEquivalent,
|
|
231
|
-
addedCount: comparison.comparison.addedCount,
|
|
232
|
-
removedCount: comparison.comparison.removedCount,
|
|
233
|
-
ingestRefresh: replayResult.ingest?.refresh || null,
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
if (emitJson) {
|
|
237
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
238
|
-
} else {
|
|
239
|
-
console.log(pc.bold("Audit replay complete"));
|
|
240
|
-
console.log(pc.gray(`Base run: ${payload.baseRunId}`));
|
|
241
|
-
console.log(pc.gray(`Replay run: ${payload.replayRunId}`));
|
|
242
|
-
console.log(pc.gray(`Comparison: ${payload.comparisonPath}`));
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
audit
|
|
247
|
-
.command("diff")
|
|
248
|
-
.description("Diff two audit runs and emit a reproducibility comparison artifact")
|
|
249
|
-
.argument("<baseRunId>", "Base run id")
|
|
250
|
-
.argument("<candidateRunId>", "Candidate run id")
|
|
251
|
-
.option("--path <path>", "Workspace path for resolving output root", ".")
|
|
252
|
-
.option("--output-dir <path>", "Optional artifact output root override")
|
|
253
|
-
.option("--json", "Emit machine-readable output")
|
|
254
|
-
.action(async (baseRunId, candidateRunId, options, command) => {
|
|
255
|
-
const emitJson = shouldEmitJson(options, command);
|
|
256
|
-
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
257
|
-
const outputRoot = await resolveOutputRoot({
|
|
258
|
-
cwd: targetPath,
|
|
259
|
-
outputDirOverride: options.outputDir,
|
|
260
|
-
env: process.env,
|
|
261
|
-
});
|
|
262
|
-
const baseRunDirectory = await resolveAuditRunDirectory({
|
|
263
|
-
outputRoot,
|
|
264
|
-
runId: baseRunId,
|
|
265
|
-
});
|
|
266
|
-
const candidateRunDirectory = await resolveAuditRunDirectory({
|
|
267
|
-
outputRoot,
|
|
268
|
-
runId: candidateRunId,
|
|
269
|
-
});
|
|
270
|
-
const { report: baseReport } = await loadAuditRunReport(baseRunDirectory);
|
|
271
|
-
const { report: candidateReport } = await loadAuditRunReport(candidateRunDirectory);
|
|
272
|
-
const comparison = await writeAuditComparisonArtifact({
|
|
273
|
-
baseReport,
|
|
274
|
-
candidateReport,
|
|
275
|
-
outputDirectory: candidateRunDirectory,
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
const payload = {
|
|
279
|
-
command: "audit diff",
|
|
280
|
-
baseRunId: baseReport.runId,
|
|
281
|
-
candidateRunId: candidateReport.runId,
|
|
282
|
-
outputPath: comparison.outputPath,
|
|
283
|
-
deterministicEquivalent: comparison.comparison.deterministicEquivalent,
|
|
284
|
-
addedCount: comparison.comparison.addedCount,
|
|
285
|
-
removedCount: comparison.comparison.removedCount,
|
|
286
|
-
};
|
|
287
|
-
|
|
288
|
-
if (emitJson) {
|
|
289
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
290
|
-
} else {
|
|
291
|
-
console.log(pc.bold("Audit diff complete"));
|
|
292
|
-
console.log(pc.gray(`Comparison: ${payload.outputPath}`));
|
|
293
|
-
}
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
audit
|
|
297
|
-
.command("registry")
|
|
298
|
-
.description("List audit agent registry records")
|
|
299
|
-
.option("--registry-file <path>", "Optional custom audit registry file")
|
|
300
|
-
.option("--json", "Emit machine-readable output")
|
|
301
|
-
.action(async (options, command) => {
|
|
302
|
-
const emitJson = shouldEmitJson(options, command);
|
|
303
|
-
const registry = await loadAuditRegistry({
|
|
304
|
-
registryFile: options.registryFile,
|
|
305
|
-
});
|
|
306
|
-
const payload = {
|
|
307
|
-
command: "audit registry",
|
|
308
|
-
registrySource: registry.registrySource,
|
|
309
|
-
registryFile: registry.registryFile,
|
|
310
|
-
agentCount: registry.agents.length,
|
|
311
|
-
agents: registry.agents,
|
|
312
|
-
};
|
|
313
|
-
|
|
314
|
-
if (emitJson) {
|
|
315
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
console.log(pc.bold("Audit registry"));
|
|
320
|
-
console.log(pc.gray(`Source: ${registry.registrySource}`));
|
|
321
|
-
for (const agent of registry.agents) {
|
|
322
|
-
console.log(`- ${agent.id} (${agent.persona}) :: ${agent.domain}`);
|
|
323
|
-
}
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
audit
|
|
327
|
-
.command("security")
|
|
328
|
-
.description("Run security specialist agent only")
|
|
329
|
-
.option("--path <path>", "Target workspace path", ".")
|
|
330
|
-
.option("--registry-file <path>", "Optional custom audit registry file")
|
|
331
|
-
.option("--output-dir <path>", "Optional artifact output root override")
|
|
332
|
-
.option("--refresh", "Refresh CODEBASE_INGEST before security specialist run")
|
|
333
|
-
.option("--dry-run", "Skip deterministic baseline and run security planning only")
|
|
334
|
-
.option("--json", "Emit machine-readable output")
|
|
335
|
-
.action(async (options, command) => {
|
|
336
|
-
const emitJson = shouldEmitJson(options, command);
|
|
337
|
-
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
338
|
-
const registry = await loadAuditRegistry({
|
|
339
|
-
registryFile: options.registryFile,
|
|
340
|
-
});
|
|
341
|
-
const selected = selectAuditAgents(registry.agents, "security");
|
|
342
|
-
if (selected.selected.length !== 1) {
|
|
343
|
-
throw new Error("Security specialist agent is unavailable in the current registry.");
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
const result = await runAuditOrchestrator({
|
|
347
|
-
targetPath,
|
|
348
|
-
agents: selected.selected,
|
|
349
|
-
maxParallel: 1,
|
|
350
|
-
outputDir: options.outputDir,
|
|
351
|
-
dryRun: Boolean(options.dryRun),
|
|
352
|
-
refreshIngest: Boolean(options.refresh),
|
|
353
|
-
});
|
|
354
|
-
const securityAgent = result.agentResults.find((agent) => agent.agentId === "security") || null;
|
|
355
|
-
|
|
356
|
-
const payload = {
|
|
357
|
-
command: "audit security",
|
|
358
|
-
targetPath: result.targetPath,
|
|
359
|
-
runId: result.runId,
|
|
360
|
-
runDirectory: result.runDirectory,
|
|
361
|
-
reportPath: result.reportMarkdownPath,
|
|
362
|
-
reportJsonPath: result.reportJsonPath,
|
|
363
|
-
securityAgentPath: securityAgent?.artifactPath || null,
|
|
364
|
-
securitySpecialistReportPath: securityAgent?.specialistReportPath || null,
|
|
365
|
-
summary: result.summary,
|
|
366
|
-
specialistSummary: securityAgent?.summary || null,
|
|
367
|
-
dryRun: result.dryRun,
|
|
368
|
-
ingestRefresh: result.ingest?.refresh || null,
|
|
369
|
-
};
|
|
370
|
-
|
|
371
|
-
if (emitJson) {
|
|
372
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
373
|
-
} else {
|
|
374
|
-
console.log(pc.bold("Security specialist audit complete"));
|
|
375
|
-
console.log(pc.gray(`Run: ${result.runId}`));
|
|
376
|
-
console.log(pc.gray(`Report: ${result.reportMarkdownPath}`));
|
|
377
|
-
if (payload.securitySpecialistReportPath) {
|
|
378
|
-
console.log(pc.gray(`Security report: ${payload.securitySpecialistReportPath}`));
|
|
379
|
-
}
|
|
380
|
-
console.log(
|
|
381
|
-
`Summary: P0=${result.summary.P0} P1=${result.summary.P1} P2=${result.summary.P2} P3=${result.summary.P3}`
|
|
382
|
-
);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
if (result.summary.blocking) {
|
|
386
|
-
process.exitCode = 2;
|
|
387
|
-
}
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
audit
|
|
391
|
-
.command("architecture")
|
|
392
|
-
.description("Run architecture specialist agent only")
|
|
393
|
-
.option("--path <path>", "Target workspace path", ".")
|
|
394
|
-
.option("--registry-file <path>", "Optional custom audit registry file")
|
|
395
|
-
.option("--output-dir <path>", "Optional artifact output root override")
|
|
396
|
-
.option("--refresh", "Refresh CODEBASE_INGEST before architecture specialist run")
|
|
397
|
-
.option("--dry-run", "Skip deterministic baseline and run architecture planning only")
|
|
398
|
-
.option("--json", "Emit machine-readable output")
|
|
399
|
-
.action(async (options, command) => {
|
|
400
|
-
const emitJson = shouldEmitJson(options, command);
|
|
401
|
-
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
402
|
-
const registry = await loadAuditRegistry({
|
|
403
|
-
registryFile: options.registryFile,
|
|
404
|
-
});
|
|
405
|
-
const selected = selectAuditAgents(registry.agents, "architecture");
|
|
406
|
-
if (selected.selected.length !== 1) {
|
|
407
|
-
throw new Error("Architecture specialist agent is unavailable in the current registry.");
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
const result = await runAuditOrchestrator({
|
|
411
|
-
targetPath,
|
|
412
|
-
agents: selected.selected,
|
|
413
|
-
maxParallel: 1,
|
|
414
|
-
outputDir: options.outputDir,
|
|
415
|
-
dryRun: Boolean(options.dryRun),
|
|
416
|
-
refreshIngest: Boolean(options.refresh),
|
|
417
|
-
});
|
|
418
|
-
const architectureAgent =
|
|
419
|
-
result.agentResults.find((agent) => agent.agentId === "architecture") || null;
|
|
420
|
-
|
|
421
|
-
const payload = {
|
|
422
|
-
command: "audit architecture",
|
|
423
|
-
targetPath: result.targetPath,
|
|
424
|
-
runId: result.runId,
|
|
425
|
-
runDirectory: result.runDirectory,
|
|
426
|
-
reportPath: result.reportMarkdownPath,
|
|
427
|
-
reportJsonPath: result.reportJsonPath,
|
|
428
|
-
architectureAgentPath: architectureAgent?.artifactPath || null,
|
|
429
|
-
architectureSpecialistReportPath: architectureAgent?.specialistReportPath || null,
|
|
430
|
-
summary: result.summary,
|
|
431
|
-
specialistSummary: architectureAgent?.summary || null,
|
|
432
|
-
dryRun: result.dryRun,
|
|
433
|
-
ingestRefresh: result.ingest?.refresh || null,
|
|
434
|
-
};
|
|
435
|
-
|
|
436
|
-
if (emitJson) {
|
|
437
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
438
|
-
} else {
|
|
439
|
-
console.log(pc.bold("Architecture specialist audit complete"));
|
|
440
|
-
console.log(pc.gray(`Run: ${result.runId}`));
|
|
441
|
-
console.log(pc.gray(`Report: ${result.reportMarkdownPath}`));
|
|
442
|
-
if (payload.architectureSpecialistReportPath) {
|
|
443
|
-
console.log(pc.gray(`Architecture report: ${payload.architectureSpecialistReportPath}`));
|
|
444
|
-
}
|
|
445
|
-
console.log(
|
|
446
|
-
`Summary: P0=${result.summary.P0} P1=${result.summary.P1} P2=${result.summary.P2} P3=${result.summary.P3}`
|
|
447
|
-
);
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
if (result.summary.blocking) {
|
|
451
|
-
process.exitCode = 2;
|
|
452
|
-
}
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
audit
|
|
456
|
-
.command("testing")
|
|
457
|
-
.description("Run testing specialist agent only")
|
|
458
|
-
.option("--path <path>", "Target workspace path", ".")
|
|
459
|
-
.option("--registry-file <path>", "Optional custom audit registry file")
|
|
460
|
-
.option("--output-dir <path>", "Optional artifact output root override")
|
|
461
|
-
.option("--refresh", "Refresh CODEBASE_INGEST before testing specialist run")
|
|
462
|
-
.option("--dry-run", "Skip deterministic baseline and run testing planning only")
|
|
463
|
-
.option("--json", "Emit machine-readable output")
|
|
464
|
-
.action(async (options, command) => {
|
|
465
|
-
const emitJson = shouldEmitJson(options, command);
|
|
466
|
-
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
467
|
-
const registry = await loadAuditRegistry({
|
|
468
|
-
registryFile: options.registryFile,
|
|
469
|
-
});
|
|
470
|
-
const selected = selectAuditAgents(registry.agents, "testing");
|
|
471
|
-
if (selected.selected.length !== 1) {
|
|
472
|
-
throw new Error("Testing specialist agent is unavailable in the current registry.");
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
const result = await runAuditOrchestrator({
|
|
476
|
-
targetPath,
|
|
477
|
-
agents: selected.selected,
|
|
478
|
-
maxParallel: 1,
|
|
479
|
-
outputDir: options.outputDir,
|
|
480
|
-
dryRun: Boolean(options.dryRun),
|
|
481
|
-
refreshIngest: Boolean(options.refresh),
|
|
482
|
-
});
|
|
483
|
-
const testingAgent = result.agentResults.find((agent) => agent.agentId === "testing") || null;
|
|
484
|
-
|
|
485
|
-
const payload = {
|
|
486
|
-
command: "audit testing",
|
|
487
|
-
targetPath: result.targetPath,
|
|
488
|
-
runId: result.runId,
|
|
489
|
-
runDirectory: result.runDirectory,
|
|
490
|
-
reportPath: result.reportMarkdownPath,
|
|
491
|
-
reportJsonPath: result.reportJsonPath,
|
|
492
|
-
testingAgentPath: testingAgent?.artifactPath || null,
|
|
493
|
-
testingSpecialistReportPath: testingAgent?.specialistReportPath || null,
|
|
494
|
-
summary: result.summary,
|
|
495
|
-
specialistSummary: testingAgent?.summary || null,
|
|
496
|
-
dryRun: result.dryRun,
|
|
497
|
-
ingestRefresh: result.ingest?.refresh || null,
|
|
498
|
-
};
|
|
499
|
-
|
|
500
|
-
if (emitJson) {
|
|
501
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
502
|
-
} else {
|
|
503
|
-
console.log(pc.bold("Testing specialist audit complete"));
|
|
504
|
-
console.log(pc.gray(`Run: ${result.runId}`));
|
|
505
|
-
console.log(pc.gray(`Report: ${result.reportMarkdownPath}`));
|
|
506
|
-
if (payload.testingSpecialistReportPath) {
|
|
507
|
-
console.log(pc.gray(`Testing report: ${payload.testingSpecialistReportPath}`));
|
|
508
|
-
}
|
|
509
|
-
console.log(
|
|
510
|
-
`Summary: P0=${result.summary.P0} P1=${result.summary.P1} P2=${result.summary.P2} P3=${result.summary.P3}`
|
|
511
|
-
);
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
if (result.summary.blocking) {
|
|
515
|
-
process.exitCode = 2;
|
|
516
|
-
}
|
|
517
|
-
});
|
|
518
|
-
|
|
519
|
-
audit
|
|
520
|
-
.command("performance")
|
|
521
|
-
.description("Run performance specialist agent only")
|
|
522
|
-
.option("--path <path>", "Target workspace path", ".")
|
|
523
|
-
.option("--registry-file <path>", "Optional custom audit registry file")
|
|
524
|
-
.option("--output-dir <path>", "Optional artifact output root override")
|
|
525
|
-
.option("--refresh", "Refresh CODEBASE_INGEST before performance specialist run")
|
|
526
|
-
.option("--dry-run", "Skip deterministic baseline and run performance planning only")
|
|
527
|
-
.option("--json", "Emit machine-readable output")
|
|
528
|
-
.action(async (options, command) => {
|
|
529
|
-
const emitJson = shouldEmitJson(options, command);
|
|
530
|
-
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
531
|
-
const registry = await loadAuditRegistry({
|
|
532
|
-
registryFile: options.registryFile,
|
|
533
|
-
});
|
|
534
|
-
const selected = selectAuditAgents(registry.agents, "performance");
|
|
535
|
-
if (selected.selected.length !== 1) {
|
|
536
|
-
throw new Error("Performance specialist agent is unavailable in the current registry.");
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
const result = await runAuditOrchestrator({
|
|
540
|
-
targetPath,
|
|
541
|
-
agents: selected.selected,
|
|
542
|
-
maxParallel: 1,
|
|
543
|
-
outputDir: options.outputDir,
|
|
544
|
-
dryRun: Boolean(options.dryRun),
|
|
545
|
-
refreshIngest: Boolean(options.refresh),
|
|
546
|
-
});
|
|
547
|
-
const performanceAgent =
|
|
548
|
-
result.agentResults.find((agent) => agent.agentId === "performance") || null;
|
|
549
|
-
|
|
550
|
-
const payload = {
|
|
551
|
-
command: "audit performance",
|
|
552
|
-
targetPath: result.targetPath,
|
|
553
|
-
runId: result.runId,
|
|
554
|
-
runDirectory: result.runDirectory,
|
|
555
|
-
reportPath: result.reportMarkdownPath,
|
|
556
|
-
reportJsonPath: result.reportJsonPath,
|
|
557
|
-
performanceAgentPath: performanceAgent?.artifactPath || null,
|
|
558
|
-
performanceSpecialistReportPath: performanceAgent?.specialistReportPath || null,
|
|
559
|
-
summary: result.summary,
|
|
560
|
-
specialistSummary: performanceAgent?.summary || null,
|
|
561
|
-
dryRun: result.dryRun,
|
|
562
|
-
ingestRefresh: result.ingest?.refresh || null,
|
|
563
|
-
};
|
|
564
|
-
|
|
565
|
-
if (emitJson) {
|
|
566
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
567
|
-
} else {
|
|
568
|
-
console.log(pc.bold("Performance specialist audit complete"));
|
|
569
|
-
console.log(pc.gray(`Run: ${result.runId}`));
|
|
570
|
-
console.log(pc.gray(`Report: ${result.reportMarkdownPath}`));
|
|
571
|
-
if (payload.performanceSpecialistReportPath) {
|
|
572
|
-
console.log(pc.gray(`Performance report: ${payload.performanceSpecialistReportPath}`));
|
|
573
|
-
}
|
|
574
|
-
console.log(
|
|
575
|
-
`Summary: P0=${result.summary.P0} P1=${result.summary.P1} P2=${result.summary.P2} P3=${result.summary.P3}`
|
|
576
|
-
);
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
if (result.summary.blocking) {
|
|
580
|
-
process.exitCode = 2;
|
|
581
|
-
}
|
|
582
|
-
});
|
|
583
|
-
|
|
584
|
-
audit
|
|
585
|
-
.command("compliance")
|
|
586
|
-
.description("Run compliance specialist agent only")
|
|
587
|
-
.option("--path <path>", "Target workspace path", ".")
|
|
588
|
-
.option("--registry-file <path>", "Optional custom audit registry file")
|
|
589
|
-
.option("--output-dir <path>", "Optional artifact output root override")
|
|
590
|
-
.option("--refresh", "Refresh CODEBASE_INGEST before compliance specialist run")
|
|
591
|
-
.option("--dry-run", "Skip deterministic baseline and run compliance planning only")
|
|
592
|
-
.option("--json", "Emit machine-readable output")
|
|
593
|
-
.action(async (options, command) => {
|
|
594
|
-
const emitJson = shouldEmitJson(options, command);
|
|
595
|
-
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
596
|
-
const registry = await loadAuditRegistry({
|
|
597
|
-
registryFile: options.registryFile,
|
|
598
|
-
});
|
|
599
|
-
const selected = selectAuditAgents(registry.agents, "compliance");
|
|
600
|
-
if (selected.selected.length !== 1) {
|
|
601
|
-
throw new Error("Compliance specialist agent is unavailable in the current registry.");
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
const result = await runAuditOrchestrator({
|
|
605
|
-
targetPath,
|
|
606
|
-
agents: selected.selected,
|
|
607
|
-
maxParallel: 1,
|
|
608
|
-
outputDir: options.outputDir,
|
|
609
|
-
dryRun: Boolean(options.dryRun),
|
|
610
|
-
refreshIngest: Boolean(options.refresh),
|
|
611
|
-
});
|
|
612
|
-
const complianceAgent =
|
|
613
|
-
result.agentResults.find((agent) => agent.agentId === "compliance") || null;
|
|
614
|
-
|
|
615
|
-
const payload = {
|
|
616
|
-
command: "audit compliance",
|
|
617
|
-
targetPath: result.targetPath,
|
|
618
|
-
runId: result.runId,
|
|
619
|
-
runDirectory: result.runDirectory,
|
|
620
|
-
reportPath: result.reportMarkdownPath,
|
|
621
|
-
reportJsonPath: result.reportJsonPath,
|
|
622
|
-
complianceAgentPath: complianceAgent?.artifactPath || null,
|
|
623
|
-
complianceSpecialistReportPath: complianceAgent?.specialistReportPath || null,
|
|
624
|
-
summary: result.summary,
|
|
625
|
-
specialistSummary: complianceAgent?.summary || null,
|
|
626
|
-
dryRun: result.dryRun,
|
|
627
|
-
ingestRefresh: result.ingest?.refresh || null,
|
|
628
|
-
};
|
|
629
|
-
|
|
630
|
-
if (emitJson) {
|
|
631
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
632
|
-
} else {
|
|
633
|
-
console.log(pc.bold("Compliance specialist audit complete"));
|
|
634
|
-
console.log(pc.gray(`Run: ${result.runId}`));
|
|
635
|
-
console.log(pc.gray(`Report: ${result.reportMarkdownPath}`));
|
|
636
|
-
if (payload.complianceSpecialistReportPath) {
|
|
637
|
-
console.log(pc.gray(`Compliance report: ${payload.complianceSpecialistReportPath}`));
|
|
638
|
-
}
|
|
639
|
-
console.log(
|
|
640
|
-
`Summary: P0=${result.summary.P0} P1=${result.summary.P1} P2=${result.summary.P2} P3=${result.summary.P3}`
|
|
641
|
-
);
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
if (result.summary.blocking) {
|
|
645
|
-
process.exitCode = 2;
|
|
646
|
-
}
|
|
647
|
-
});
|
|
648
|
-
|
|
649
|
-
audit
|
|
650
|
-
.command("documentation")
|
|
651
|
-
.description("Run documentation specialist agent only")
|
|
652
|
-
.option("--path <path>", "Target workspace path", ".")
|
|
653
|
-
.option("--registry-file <path>", "Optional custom audit registry file")
|
|
654
|
-
.option("--output-dir <path>", "Optional artifact output root override")
|
|
655
|
-
.option("--refresh", "Refresh CODEBASE_INGEST before documentation specialist run")
|
|
656
|
-
.option("--dry-run", "Skip deterministic baseline and run documentation planning only")
|
|
657
|
-
.option("--json", "Emit machine-readable output")
|
|
658
|
-
.action(async (options, command) => {
|
|
659
|
-
const emitJson = shouldEmitJson(options, command);
|
|
660
|
-
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
661
|
-
const registry = await loadAuditRegistry({
|
|
662
|
-
registryFile: options.registryFile,
|
|
663
|
-
});
|
|
664
|
-
const selected = selectAuditAgents(registry.agents, "documentation");
|
|
665
|
-
if (selected.selected.length !== 1) {
|
|
666
|
-
throw new Error("Documentation specialist agent is unavailable in the current registry.");
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
const result = await runAuditOrchestrator({
|
|
670
|
-
targetPath,
|
|
671
|
-
agents: selected.selected,
|
|
672
|
-
maxParallel: 1,
|
|
673
|
-
outputDir: options.outputDir,
|
|
674
|
-
dryRun: Boolean(options.dryRun),
|
|
675
|
-
refreshIngest: Boolean(options.refresh),
|
|
676
|
-
});
|
|
677
|
-
const documentationAgent =
|
|
678
|
-
result.agentResults.find((agent) => agent.agentId === "documentation") || null;
|
|
679
|
-
|
|
680
|
-
const payload = {
|
|
681
|
-
command: "audit documentation",
|
|
682
|
-
targetPath: result.targetPath,
|
|
683
|
-
runId: result.runId,
|
|
684
|
-
runDirectory: result.runDirectory,
|
|
685
|
-
reportPath: result.reportMarkdownPath,
|
|
686
|
-
reportJsonPath: result.reportJsonPath,
|
|
687
|
-
documentationAgentPath: documentationAgent?.artifactPath || null,
|
|
688
|
-
documentationSpecialistReportPath: documentationAgent?.specialistReportPath || null,
|
|
689
|
-
summary: result.summary,
|
|
690
|
-
specialistSummary: documentationAgent?.summary || null,
|
|
691
|
-
dryRun: result.dryRun,
|
|
692
|
-
ingestRefresh: result.ingest?.refresh || null,
|
|
693
|
-
};
|
|
694
|
-
|
|
695
|
-
if (emitJson) {
|
|
696
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
697
|
-
} else {
|
|
698
|
-
console.log(pc.bold("Documentation specialist audit complete"));
|
|
699
|
-
console.log(pc.gray(`Run: ${result.runId}`));
|
|
700
|
-
console.log(pc.gray(`Report: ${result.reportMarkdownPath}`));
|
|
701
|
-
if (payload.documentationSpecialistReportPath) {
|
|
702
|
-
console.log(pc.gray(`Documentation report: ${payload.documentationSpecialistReportPath}`));
|
|
703
|
-
}
|
|
704
|
-
console.log(
|
|
705
|
-
`Summary: P0=${result.summary.P0} P1=${result.summary.P1} P2=${result.summary.P2} P3=${result.summary.P3}`
|
|
706
|
-
);
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
if (result.summary.blocking) {
|
|
710
|
-
process.exitCode = 2;
|
|
711
|
-
}
|
|
712
|
-
});
|
|
713
|
-
|
|
714
|
-
audit
|
|
715
|
-
.command("local")
|
|
716
|
-
.description("Compatibility mode: run legacy local readiness + policy audit")
|
|
717
|
-
.option("--path <path>", "Target repository path")
|
|
718
|
-
.option("--output-dir <path>", "Artifact root for report output")
|
|
719
|
-
.option("--json", "Emit machine-readable output")
|
|
720
|
-
.action(async (options, command) => {
|
|
721
|
-
const legacyArgs = buildLegacyArgs(["/audit"], {
|
|
722
|
-
commandOptions: options,
|
|
723
|
-
command,
|
|
724
|
-
});
|
|
725
|
-
await invokeLegacy(legacyArgs);
|
|
726
|
-
});
|
|
727
|
-
|
|
728
|
-
// ── Persona subcommands: sl audit <persona-name-or-domain> ─────────
|
|
729
|
-
//
|
|
730
|
-
// Every persona follows the same architecture:
|
|
731
|
-
// 1. Ingest codebase (auto-refresh if stale)
|
|
732
|
-
// 2. Run Omar baseline (7-layer deterministic) — persona does NOT see results yet
|
|
733
|
-
// 3. Build scope map from ingest
|
|
734
|
-
// 4. Wire memory (blackboard for sub-agents + FAISS recall from previous runs)
|
|
735
|
-
// 5. Run persona agentic loop BLIND-FIRST (no baseline anchoring)
|
|
736
|
-
// 6. After persona completes: RECONCILIATION against Omar baseline
|
|
737
|
-
// 7. Write artifacts + report
|
|
738
|
-
//
|
|
739
|
-
// Jules Tanaka (frontend) was the first onboarded, but all 13 personas
|
|
740
|
-
// share the same tool-equipped, budget-governed, isolated-context runtime.
|
|
741
|
-
//
|
|
742
|
-
audit
|
|
743
|
-
.command("frontend")
|
|
744
|
-
.alias("jules")
|
|
745
|
-
.description("Jules Tanaka — Frontend Runtime deep audit with agentic tool access")
|
|
746
|
-
.option("--path <path>", "Target repository path")
|
|
747
|
-
.option("--mode <mode>", "Agent mode: primary | secondary | tertiary", "primary")
|
|
748
|
-
.option("--max-cost <usd>", "Max cost budget in USD", "5.0")
|
|
749
|
-
.option("--max-turns <n>", "Max agentic loop turns", "25")
|
|
750
|
-
.option("--provider <name>", "LLM provider: openai | anthropic | google")
|
|
751
|
-
.option("--model <id>", "LLM model override")
|
|
752
|
-
.option("--api-key <key>", "Explicit API key override")
|
|
753
|
-
.option("--stream", "Emit NDJSON events to stdout as Jules works")
|
|
754
|
-
.option("--refresh", "Refresh CODEBASE_INGEST before auditing")
|
|
755
|
-
.option("--skip-baseline", "Skip Omar deterministic baseline (not recommended)")
|
|
756
|
-
.option("--url <url>", "Deployed URL for runtime audit (Lighthouse, headers, DevTools)")
|
|
757
|
-
.option("--skip-runtime", "Skip runtime audit even if URL is detected")
|
|
758
|
-
.option("--output-dir <path>", "Artifact output root override")
|
|
759
|
-
.option("--json", "Emit machine-readable final output")
|
|
760
|
-
.action(async (options, command) => {
|
|
761
|
-
const emitJson = shouldEmitJson(options, command);
|
|
762
|
-
const emitStream = Boolean(options.stream);
|
|
763
|
-
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
764
|
-
|
|
765
|
-
// Lazy-load modules
|
|
766
|
-
const { julesAuditLoop } = await import("../agents/jules/loop.js");
|
|
767
|
-
const { JULES_DEFINITION } = await import("../agents/jules/config/definition.js");
|
|
768
|
-
const { collectCodebaseIngest, generateCodebaseIngest } = await import("../ingest/engine.js");
|
|
769
|
-
const { resolveOutputRoot: resolveOut } = await import("../config/service.js");
|
|
770
|
-
const { createBlackboard } = await import("../memory/blackboard.js");
|
|
771
|
-
const { runDeterministicReviewPipeline } = await import("../review/local-review.js");
|
|
772
|
-
const fsp = await import("node:fs/promises");
|
|
773
|
-
|
|
774
|
-
const outputRoot = await resolveOut({ targetPath, outputDir: options.outputDir });
|
|
775
|
-
const artifactDir = path.join(outputRoot, "reports", "jules-" + Date.now());
|
|
776
|
-
await fsp.mkdir(artifactDir, { recursive: true });
|
|
777
|
-
|
|
778
|
-
// Build event handler
|
|
779
|
-
const onEvent = buildEventHandler(emitStream, emitJson, JULES_DEFINITION);
|
|
780
|
-
|
|
781
|
-
emitProgress(onEvent, JULES_DEFINITION, "Starting Jules Tanaka frontend audit...");
|
|
782
|
-
|
|
783
|
-
// ── [1] INGEST ────────────────────────────────────────────────
|
|
784
|
-
emitProgress(onEvent, JULES_DEFINITION, "Ingesting codebase...");
|
|
785
|
-
let ingest;
|
|
786
|
-
try {
|
|
787
|
-
ingest = await generateCodebaseIngest({
|
|
788
|
-
rootPath: targetPath, outputDir: outputRoot,
|
|
789
|
-
refresh: Boolean(options.refresh),
|
|
790
|
-
});
|
|
791
|
-
} catch {
|
|
792
|
-
ingest = await collectCodebaseIngest({ rootPath: targetPath });
|
|
793
|
-
}
|
|
794
|
-
emitProgress(onEvent, JULES_DEFINITION,
|
|
795
|
-
"Ingest complete: " + (ingest?.summary?.filesScanned || 0) + " files, " +
|
|
796
|
-
(ingest?.summary?.totalLoc || 0) + " LOC");
|
|
797
|
-
|
|
798
|
-
// ── [2] OMAR BASELINE (blind — Jules won't see until reconciliation) ──
|
|
799
|
-
let omarBaseline = null;
|
|
800
|
-
if (!options.skipBaseline) {
|
|
801
|
-
emitProgress(onEvent, JULES_DEFINITION, "Running Omar 7-layer deterministic baseline...");
|
|
802
|
-
try {
|
|
803
|
-
omarBaseline = await runDeterministicReviewPipeline({
|
|
804
|
-
targetPath,
|
|
805
|
-
mode: "full",
|
|
806
|
-
outputDir: artifactDir,
|
|
807
|
-
});
|
|
808
|
-
emitProgress(onEvent, JULES_DEFINITION,
|
|
809
|
-
"Baseline complete: P0=" + (omarBaseline?.summary?.P0 || 0) +
|
|
810
|
-
" P1=" + (omarBaseline?.summary?.P1 || 0) +
|
|
811
|
-
" P2=" + (omarBaseline?.summary?.P2 || 0) +
|
|
812
|
-
" (held for reconciliation — Jules runs blind-first)");
|
|
813
|
-
} catch (err) {
|
|
814
|
-
emitProgress(onEvent, JULES_DEFINITION,
|
|
815
|
-
"Baseline failed (non-blocking): " + err.message);
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
// ── [3] SCOPE MAP ─────────────────────────────────────────────
|
|
820
|
-
const scopeMap = buildScopeMapFromIngest(ingest, JULES_DEFINITION.defaultScope);
|
|
821
|
-
emitProgress(onEvent, JULES_DEFINITION,
|
|
822
|
-
"Scope: " + (scopeMap.primary?.length || 0) + " primary, " +
|
|
823
|
-
(scopeMap.secondary?.length || 0) + " secondary files");
|
|
824
|
-
|
|
825
|
-
// ── [4] MEMORY ────────────────────────────────────────────────
|
|
826
|
-
const blackboard = createBlackboard();
|
|
827
|
-
let memoryIndex = null;
|
|
828
|
-
try {
|
|
829
|
-
const { queryRetrievalIndex } = await import("../memory/retrieval.js");
|
|
830
|
-
memoryIndex = {
|
|
831
|
-
query: (opts) => queryRetrievalIndex({ targetPath, ...opts }),
|
|
832
|
-
index: (docs) => {}, // indexing happens after completion
|
|
833
|
-
};
|
|
834
|
-
} catch { /* memory retrieval unavailable — non-blocking */ }
|
|
835
|
-
|
|
836
|
-
// ── [5] PROVIDER ──────────────────────────────────────────────
|
|
837
|
-
const providerConfig = {};
|
|
838
|
-
if (options.provider) providerConfig.provider = options.provider;
|
|
839
|
-
if (options.model) providerConfig.model = options.model;
|
|
840
|
-
if (options.apiKey) providerConfig.apiKey = options.apiKey;
|
|
841
|
-
|
|
842
|
-
// ── [6] SYSTEM PROMPT (full production prompt) ─────────────────
|
|
843
|
-
const { buildJulesProductionPrompt } = await import("../agents/jules/config/system-prompt.js");
|
|
844
|
-
const systemPrompt = buildJulesProductionPrompt({
|
|
845
|
-
mode: options.mode,
|
|
846
|
-
framework: ingest?.frameworks?.[0] || "unknown",
|
|
847
|
-
componentCount: ingest?.indexedFiles?.files?.filter(
|
|
848
|
-
f => /\.(tsx|jsx|vue|svelte)$/.test(f.path || ""),
|
|
849
|
-
).length || 0,
|
|
850
|
-
scopeMap,
|
|
851
|
-
ingestSummary: ingest?.summary || {},
|
|
852
|
-
});
|
|
853
|
-
|
|
854
|
-
// ── [7] JULES AGENTIC LOOP (BLIND-FIRST — no baseline) ───────
|
|
855
|
-
emitProgress(onEvent, JULES_DEFINITION, "Starting blind-first deep analysis...");
|
|
856
|
-
let report;
|
|
857
|
-
const gen = julesAuditLoop({
|
|
858
|
-
systemPrompt,
|
|
859
|
-
scopeMap,
|
|
860
|
-
rootPath: targetPath,
|
|
861
|
-
// omarBaseline intentionally NOT passed here — Jules runs blind
|
|
862
|
-
blackboard,
|
|
863
|
-
memory: memoryIndex,
|
|
864
|
-
budget: {
|
|
865
|
-
maxCostUsd: parseFloat(options.maxCost) || 5.0,
|
|
866
|
-
maxOutputTokens: JULES_DEFINITION.budget.maxOutputTokens,
|
|
867
|
-
maxRuntimeMs: JULES_DEFINITION.budget.maxRuntimeMs,
|
|
868
|
-
maxToolCalls: JULES_DEFINITION.budget.maxToolCalls,
|
|
869
|
-
},
|
|
870
|
-
provider: providerConfig,
|
|
871
|
-
mode: options.mode,
|
|
872
|
-
maxTurns: parseInt(options.maxTurns) || 25,
|
|
873
|
-
onEvent,
|
|
874
|
-
});
|
|
875
|
-
|
|
876
|
-
let julesFindings = [];
|
|
877
|
-
for await (const evt of gen) {
|
|
878
|
-
if (evt.event === "agent_complete") {
|
|
879
|
-
report = evt;
|
|
880
|
-
julesFindings = evt.payload?.findings || [];
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
// ── [7.5] RUNTIME AUDIT (if --url provided or URL detected) ────
|
|
885
|
-
let runtimeResult = null;
|
|
886
|
-
const runtimeUrl = options.url || null;
|
|
887
|
-
if (runtimeUrl || !options.skipRuntime) {
|
|
888
|
-
emitProgress(onEvent, JULES_DEFINITION, "Checking for deployed URL...");
|
|
889
|
-
try {
|
|
890
|
-
const { runtimeAudit: runRT } = await import("../agents/jules/tools/runtime-audit.js");
|
|
891
|
-
|
|
892
|
-
// Detect URL if not provided
|
|
893
|
-
let targetUrl = runtimeUrl;
|
|
894
|
-
if (!targetUrl) {
|
|
895
|
-
const detected = runRT({ operation: "detect_deployed_url", path: targetPath });
|
|
896
|
-
if (detected.found) {
|
|
897
|
-
targetUrl = detected.primary;
|
|
898
|
-
emitProgress(onEvent, JULES_DEFINITION, "Detected deployed URL: " + targetUrl);
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
if (targetUrl) {
|
|
903
|
-
emitProgress(onEvent, JULES_DEFINITION, "Running runtime audit on " + targetUrl + "...");
|
|
904
|
-
|
|
905
|
-
// Response headers (security check)
|
|
906
|
-
const headers = runRT({ operation: "check_response_headers", url: targetUrl });
|
|
907
|
-
if (headers.available && headers.securityFindings) {
|
|
908
|
-
for (const hf of headers.securityFindings) {
|
|
909
|
-
julesFindings.push({
|
|
910
|
-
severity: hf.severity, file: targetUrl, line: 0,
|
|
911
|
-
title: "Missing security header: " + hf.header,
|
|
912
|
-
evidence: "HTTP response from " + targetUrl + " lacks " + hf.header,
|
|
913
|
-
source: "runtime_audit",
|
|
914
|
-
});
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
// Network waterfall
|
|
919
|
-
const waterfall = runRT({ operation: "check_network_waterfall", url: targetUrl });
|
|
920
|
-
|
|
921
|
-
// Lighthouse (if available)
|
|
922
|
-
const lighthouse = runRT({ operation: "lighthouse_scan", url: targetUrl, path: targetPath });
|
|
923
|
-
|
|
924
|
-
runtimeResult = { url: targetUrl, headers, waterfall, lighthouse };
|
|
925
|
-
emitProgress(onEvent, JULES_DEFINITION,
|
|
926
|
-
"Runtime audit complete" +
|
|
927
|
-
(lighthouse.available ? " (Lighthouse: perf=" + ((lighthouse.scores?.performance || 0) * 100).toFixed(0) + "%)" : "") +
|
|
928
|
-
(headers.available ? " (" + (headers.securityFindings?.length || 0) + " header findings)" : ""));
|
|
929
|
-
} else {
|
|
930
|
-
emitProgress(onEvent, JULES_DEFINITION, "No deployed URL found — skipping runtime audit");
|
|
931
|
-
}
|
|
932
|
-
} catch (rtErr) {
|
|
933
|
-
emitProgress(onEvent, JULES_DEFINITION, "Runtime audit failed (non-blocking): " + rtErr.message);
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
// ── [8] RECONCILIATION (now Jules gets the baseline) ──────────
|
|
938
|
-
emitProgress(onEvent, JULES_DEFINITION, "Reconciling against Omar baseline...");
|
|
939
|
-
const reconciliation = reconcileWithBaseline(julesFindings, omarBaseline);
|
|
940
|
-
|
|
941
|
-
if (onEvent && reconciliation.summary) {
|
|
942
|
-
onEvent({
|
|
943
|
-
stream: "sl_event", event: "reconciliation_complete",
|
|
944
|
-
agent: { id: JULES_DEFINITION.id, persona: JULES_DEFINITION.persona,
|
|
945
|
-
color: JULES_DEFINITION.color, avatar: JULES_DEFINITION.avatar },
|
|
946
|
-
payload: reconciliation.summary,
|
|
947
|
-
});
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
// ── [9] FINAL REPORT ──────────────────────────────────────────
|
|
951
|
-
const allFindings = [
|
|
952
|
-
...reconciliation.preserved,
|
|
953
|
-
...reconciliation.newFromJules,
|
|
954
|
-
];
|
|
955
|
-
const severityCounts = { P0: 0, P1: 0, P2: 0, P3: 0 };
|
|
956
|
-
for (const f of allFindings) {
|
|
957
|
-
const sev = (f.severity || "P3").toUpperCase();
|
|
958
|
-
if (severityCounts[sev] !== undefined) severityCounts[sev]++;
|
|
959
|
-
else severityCounts.P3++;
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
const reportPayload = {
|
|
963
|
-
command: "audit frontend",
|
|
964
|
-
persona: JULES_DEFINITION.persona,
|
|
965
|
-
targetPath,
|
|
966
|
-
mode: options.mode,
|
|
967
|
-
framework: ingest?.frameworks?.[0] || "unknown",
|
|
968
|
-
findings: allFindings,
|
|
969
|
-
summary: { total: allFindings.length, ...severityCounts, blocking: severityCounts.P0 > 0 || severityCounts.P1 > 0 },
|
|
970
|
-
reconciliation: reconciliation.summary,
|
|
971
|
-
baseline: omarBaseline ? { ran: true, findingCount: omarBaseline.findings?.length || 0 } : { ran: false },
|
|
972
|
-
runtime: runtimeResult ? {
|
|
973
|
-
ran: true, url: runtimeResult.url,
|
|
974
|
-
lighthouse: runtimeResult.lighthouse?.available ? runtimeResult.lighthouse.scores : null,
|
|
975
|
-
headerFindings: runtimeResult.headers?.securityFindings?.length || 0,
|
|
976
|
-
} : { ran: false },
|
|
977
|
-
julesUsage: report?.usage || {},
|
|
978
|
-
signature: JULES_DEFINITION.signature,
|
|
979
|
-
};
|
|
980
|
-
|
|
981
|
-
await fsp.writeFile(
|
|
982
|
-
path.join(artifactDir, "JULES_AUDIT.json"),
|
|
983
|
-
JSON.stringify(reportPayload, null, 2),
|
|
984
|
-
);
|
|
985
|
-
|
|
986
|
-
// Write baseline artifact if it exists
|
|
987
|
-
if (omarBaseline) {
|
|
988
|
-
await fsp.writeFile(
|
|
989
|
-
path.join(artifactDir, "OMAR_BASELINE.json"),
|
|
990
|
-
JSON.stringify(omarBaseline, null, 2),
|
|
991
|
-
);
|
|
992
|
-
}
|
|
993
|
-
|
|
994
|
-
// Write reconciliation artifact
|
|
995
|
-
await fsp.writeFile(
|
|
996
|
-
path.join(artifactDir, "RECONCILIATION.json"),
|
|
997
|
-
JSON.stringify(reconciliation, null, 2),
|
|
998
|
-
);
|
|
999
|
-
|
|
1000
|
-
if (emitJson) {
|
|
1001
|
-
console.log(JSON.stringify(reportPayload, null, 2));
|
|
1002
|
-
} else if (!emitStream) {
|
|
1003
|
-
process.stderr.write("\n" + JULES_DEFINITION.signature + "\n");
|
|
1004
|
-
process.stderr.write("Report: " + artifactDir + "/JULES_AUDIT.json\n");
|
|
1005
|
-
if (omarBaseline) {
|
|
1006
|
-
process.stderr.write("Baseline: " + artifactDir + "/OMAR_BASELINE.json\n");
|
|
1007
|
-
}
|
|
1008
|
-
process.stderr.write("Reconciliation: " + artifactDir + "/RECONCILIATION.json\n");
|
|
1009
|
-
process.stderr.write("Summary: " + allFindings.length + " findings (P0=" + severityCounts.P0 + " P1=" + severityCounts.P1 + " P2=" + severityCounts.P2 + ")\n");
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
// Sync run to dashboard (fire-and-forget)
|
|
1013
|
-
try {
|
|
1014
|
-
const { syncRunToDashboard } = await import("../telemetry/sync.js");
|
|
1015
|
-
const syncResult = await syncRunToDashboard({
|
|
1016
|
-
command: "audit frontend",
|
|
1017
|
-
persona: JULES_DEFINITION.persona,
|
|
1018
|
-
mode: options.mode,
|
|
1019
|
-
framework: ingest?.frameworks?.[0] || "unknown",
|
|
1020
|
-
summary: { total: allFindings.length, ...severityCounts, blocking: severityCounts.P0 > 0 || severityCounts.P1 > 0 },
|
|
1021
|
-
usage: report?.usage || {},
|
|
1022
|
-
stopReason: report?.usage?.stopReason || "completed",
|
|
1023
|
-
reconciliation: reconciliation.summary,
|
|
1024
|
-
runtime: runtimeResult ? { ran: true, url: runtimeResult.url } : { ran: false },
|
|
1025
|
-
});
|
|
1026
|
-
if (!emitJson && !emitStream && syncResult.synced) {
|
|
1027
|
-
process.stderr.write(JULES_DEFINITION.avatar + " Run synced to dashboard" + (syncResult.runId ? " (run:" + syncResult.runId + ")" : "") + "\n");
|
|
1028
|
-
}
|
|
1029
|
-
} catch { /* sync failure never blocks CLI */ }
|
|
1030
|
-
|
|
1031
|
-
if (severityCounts.P0 > 0 || severityCounts.P1 > 0) {
|
|
1032
|
-
process.exitCode = 2;
|
|
1033
|
-
}
|
|
1034
|
-
});
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
// ── Helpers for Jules invocation ──────────────────────────────────────
|
|
1038
|
-
|
|
1039
|
-
function buildScopeMapFromIngest(ingest, defaultScope) {
|
|
1040
|
-
if (!ingest || !ingest.indexedFiles) {
|
|
1041
|
-
return { primary: [], secondary: [], tertiary: [] };
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
const files = (ingest.indexedFiles.files || []).map(f => ({
|
|
1045
|
-
path: f.path,
|
|
1046
|
-
loc: f.loc || 0,
|
|
1047
|
-
language: f.language || "",
|
|
1048
|
-
}));
|
|
1049
|
-
|
|
1050
|
-
const matchesAny = (filePath, patterns) =>
|
|
1051
|
-
patterns.some(p => {
|
|
1052
|
-
const regex = new RegExp(
|
|
1053
|
-
"^" + p.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*") + "$",
|
|
1054
|
-
);
|
|
1055
|
-
return regex.test(filePath);
|
|
1056
|
-
});
|
|
1057
|
-
|
|
1058
|
-
return {
|
|
1059
|
-
primary: files.filter(f => matchesAny(f.path, defaultScope.primaryPatterns)),
|
|
1060
|
-
secondary: files.filter(f => matchesAny(f.path, defaultScope.secondaryPatterns)),
|
|
1061
|
-
tertiary: files.filter(f => matchesAny(f.path, defaultScope.tertiaryPatterns)),
|
|
1062
|
-
};
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
// ── Reconciliation ──────────────────────────────────────────────────
|
|
1066
|
-
|
|
1067
|
-
function reconcileWithBaseline(julesFindings, omarBaseline) {
|
|
1068
|
-
if (!omarBaseline || !omarBaseline.findings || !Array.isArray(omarBaseline.findings)) {
|
|
1069
|
-
return {
|
|
1070
|
-
preserved: julesFindings || [],
|
|
1071
|
-
newFromJules: [],
|
|
1072
|
-
rejectedBaseline: [],
|
|
1073
|
-
corroboratedBaseline: [],
|
|
1074
|
-
summary: {
|
|
1075
|
-
julesCount: (julesFindings || []).length,
|
|
1076
|
-
baselineCount: 0,
|
|
1077
|
-
preserved: (julesFindings || []).length,
|
|
1078
|
-
corroborated: 0,
|
|
1079
|
-
rejected: 0,
|
|
1080
|
-
newFromJules: 0,
|
|
1081
|
-
},
|
|
1082
|
-
};
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
const baselineFindings = omarBaseline.findings;
|
|
1086
|
-
const corroborated = [];
|
|
1087
|
-
const rejected = [];
|
|
1088
|
-
const newFromJules = [];
|
|
1089
|
-
|
|
1090
|
-
// Build a fingerprint set from baseline for matching
|
|
1091
|
-
const baselineFingerprints = new Set();
|
|
1092
|
-
for (const bf of baselineFindings) {
|
|
1093
|
-
const fp = (bf.file || "") + ":" + (bf.line || "") + ":" + (bf.severity || "");
|
|
1094
|
-
baselineFingerprints.add(fp);
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
// Classify Jules findings
|
|
1098
|
-
const julesMatched = new Set();
|
|
1099
|
-
for (const jf of (julesFindings || [])) {
|
|
1100
|
-
const fp = (jf.file || "") + ":" + (jf.line || "") + ":" + (jf.severity || "");
|
|
1101
|
-
if (baselineFingerprints.has(fp)) {
|
|
1102
|
-
corroborated.push({ ...jf, source: "corroborated", baselineMatch: true });
|
|
1103
|
-
julesMatched.add(fp);
|
|
1104
|
-
} else {
|
|
1105
|
-
newFromJules.push({ ...jf, source: "jules_new" });
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
// Baseline findings not corroborated by Jules — preserved (not silently dropped)
|
|
1110
|
-
const preservedBaseline = [];
|
|
1111
|
-
for (const bf of baselineFindings) {
|
|
1112
|
-
const fp = (bf.file || "") + ":" + (bf.line || "") + ":" + (bf.severity || "");
|
|
1113
|
-
if (!julesMatched.has(fp)) {
|
|
1114
|
-
preservedBaseline.push({ ...bf, source: "baseline_preserved" });
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
return {
|
|
1119
|
-
preserved: [...corroborated, ...preservedBaseline],
|
|
1120
|
-
newFromJules,
|
|
1121
|
-
rejectedBaseline: rejected,
|
|
1122
|
-
corroboratedBaseline: corroborated,
|
|
1123
|
-
summary: {
|
|
1124
|
-
julesCount: (julesFindings || []).length,
|
|
1125
|
-
baselineCount: baselineFindings.length,
|
|
1126
|
-
preserved: corroborated.length + preservedBaseline.length,
|
|
1127
|
-
corroborated: corroborated.length,
|
|
1128
|
-
preservedFromBaseline: preservedBaseline.length,
|
|
1129
|
-
rejected: rejected.length,
|
|
1130
|
-
newFromJules: newFromJules.length,
|
|
1131
|
-
},
|
|
1132
|
-
};
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
// ── Event helpers ───────────────────────────────────────────────────
|
|
1136
|
-
|
|
1137
|
-
function buildEventHandler(emitStream, emitJson, def) {
|
|
1138
|
-
if (emitStream) return (evt) => console.log(JSON.stringify(evt));
|
|
1139
|
-
if (emitJson) return undefined;
|
|
1140
|
-
return (evt) => {
|
|
1141
|
-
if (evt.event === "progress") {
|
|
1142
|
-
process.stderr.write(def.avatar + " " + def.shortName + ": " + (evt.payload?.message || "") + "\n");
|
|
1143
|
-
} else if (evt.event === "finding") {
|
|
1144
|
-
const f = evt.payload;
|
|
1145
|
-
process.stderr.write(def.avatar + " [" + (f.severity || "P3") + "] " + (f.file || "") + ":" + (f.line || "") + " " + (f.title || "") + "\n");
|
|
1146
|
-
} else if (evt.event === "heartbeat") {
|
|
1147
|
-
const h = evt.payload;
|
|
1148
|
-
process.stderr.write(def.avatar + " " + def.shortName + " [" + (h.turnsCompleted || 0) + "/" + (h.turnsMax || "?") + " turns, $" + (h.budgetRemaining?.costUsd?.toFixed(2) || "?") + "]\n");
|
|
1149
|
-
} else if (evt.event === "agent_complete") {
|
|
1150
|
-
process.stderr.write(def.avatar + " " + def.persona + " complete: " + (evt.payload?.total || 0) + " findings (P0=" + (evt.payload?.P0 || 0) + " P1=" + (evt.payload?.P1 || 0) + " P2=" + (evt.payload?.P2 || 0) + ")\n");
|
|
1151
|
-
} else if (evt.event === "reconciliation_complete") {
|
|
1152
|
-
const r = evt.payload;
|
|
1153
|
-
process.stderr.write(def.avatar + " Reconciliation: " + (r.corroborated || 0) + " corroborated, " + (r.newFromJules || 0) + " new, " + (r.preservedFromBaseline || 0) + " baseline preserved\n");
|
|
1154
|
-
}
|
|
1155
|
-
};
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
function emitProgress(onEvent, def, message) {
|
|
1159
|
-
if (onEvent) {
|
|
1160
|
-
onEvent({
|
|
1161
|
-
stream: "sl_event", event: "progress",
|
|
1162
|
-
agent: { id: def.id, persona: def.persona, color: def.color, avatar: def.avatar },
|
|
1163
|
-
payload: { phase: "setup", message },
|
|
1164
|
-
});
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
|
|
4
|
+
import pc from "picocolors";
|
|
5
|
+
|
|
6
|
+
import { runAuditOrchestrator } from "../audit/orchestrator.js";
|
|
7
|
+
import { loadAuditRunReport, resolveAuditRunDirectory, writeDdPackage } from "../audit/package.js";
|
|
8
|
+
import { writeAuditComparisonArtifact } from "../audit/replay.js";
|
|
9
|
+
import { loadAuditRegistry, selectAuditAgents } from "../audit/registry.js";
|
|
10
|
+
import { resolveOutputRoot } from "../config/service.js";
|
|
11
|
+
import { buildLegacyArgs } from "./legacy-args.js";
|
|
12
|
+
|
|
13
|
+
function shouldEmitJson(options, command) {
|
|
14
|
+
const local = Boolean(options && options.json);
|
|
15
|
+
const globalFromCommand =
|
|
16
|
+
command && command.optsWithGlobals ? Boolean(command.optsWithGlobals().json) : false;
|
|
17
|
+
return local || globalFromCommand;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function parseMaxParallel(rawValue) {
|
|
21
|
+
const normalized = Number(rawValue || 0);
|
|
22
|
+
if (!Number.isFinite(normalized) || normalized < 1) {
|
|
23
|
+
throw new Error("max-parallel must be an integer >= 1.");
|
|
24
|
+
}
|
|
25
|
+
return Math.floor(normalized);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function printAuditSummary(result) {
|
|
29
|
+
console.log(pc.bold("Audit orchestrator complete"));
|
|
30
|
+
console.log(pc.gray(`Run: ${result.runId}`));
|
|
31
|
+
console.log(pc.gray(`Report: ${result.reportMarkdownPath}`));
|
|
32
|
+
console.log(pc.gray(`JSON: ${result.reportJsonPath}`));
|
|
33
|
+
if (result.sharedMemory?.artifactPath) {
|
|
34
|
+
console.log(pc.gray(`Shared memory: ${result.sharedMemory.artifactPath}`));
|
|
35
|
+
}
|
|
36
|
+
if (result.ddPackage?.executiveSummaryPath) {
|
|
37
|
+
console.log(pc.gray(`DD package: ${result.ddPackage.executiveSummaryPath}`));
|
|
38
|
+
}
|
|
39
|
+
if (result.ingest?.refresh?.stale || result.ingest?.refresh?.refreshed) {
|
|
40
|
+
const reasons = (result.ingest?.refresh?.reasons || []).join(", ") || "none";
|
|
41
|
+
const line = `Ingest refresh: refreshed=${result.ingest?.refresh?.refreshed ? "yes" : "no"} stale=${
|
|
42
|
+
result.ingest?.refresh?.stale ? "yes" : "no"
|
|
43
|
+
} reasons=${reasons}`;
|
|
44
|
+
const color = result.ingest?.refresh?.stale && !result.ingest?.refresh?.refreshed ? pc.yellow : pc.gray;
|
|
45
|
+
console.log(color(line));
|
|
46
|
+
}
|
|
47
|
+
console.log(
|
|
48
|
+
`Summary: P0=${result.summary.P0} P1=${result.summary.P1} P2=${result.summary.P2} P3=${result.summary.P3}`
|
|
49
|
+
);
|
|
50
|
+
console.log(`Agents: ${result.agentResults.length}, max_parallel=${result.maxParallel}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function registerAuditCommand(program, invokeLegacy) {
|
|
54
|
+
const audit = program
|
|
55
|
+
.command("audit")
|
|
56
|
+
.description("Run audit orchestrator swarm or local compatibility audit")
|
|
57
|
+
.argument("[targetPath]", "Target workspace path", ".")
|
|
58
|
+
.option("--path <path>", "Target workspace path override")
|
|
59
|
+
.option("--agents <ids>", "Comma-separated agent ids (default: all built-in agents)", "")
|
|
60
|
+
.option("--max-parallel <n>", "Maximum agents processed in parallel", "3")
|
|
61
|
+
.option("--registry-file <path>", "Optional custom audit registry file")
|
|
62
|
+
.option("--output-dir <path>", "Optional artifact output root override")
|
|
63
|
+
.option("--refresh", "Refresh CODEBASE_INGEST before running audit")
|
|
64
|
+
.option("--dry-run", "Skip deterministic baseline and run orchestration planning only")
|
|
65
|
+
.option("--json", "Emit machine-readable output")
|
|
66
|
+
.action(async (targetPathArg, options, command) => {
|
|
67
|
+
const emitJson = shouldEmitJson(options, command);
|
|
68
|
+
const targetPath = path.resolve(process.cwd(), String(options.path || targetPathArg || "."));
|
|
69
|
+
const registry = await loadAuditRegistry({
|
|
70
|
+
registryFile: options.registryFile,
|
|
71
|
+
});
|
|
72
|
+
const selected = selectAuditAgents(registry.agents, options.agents);
|
|
73
|
+
if (selected.missing.length > 0) {
|
|
74
|
+
throw new Error(`Unknown agent id(s): ${selected.missing.join(", ")}`);
|
|
75
|
+
}
|
|
76
|
+
if (selected.selected.length === 0) {
|
|
77
|
+
throw new Error("No agents selected for audit run.");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const result = await runAuditOrchestrator({
|
|
81
|
+
targetPath,
|
|
82
|
+
agents: selected.selected,
|
|
83
|
+
maxParallel: parseMaxParallel(options.maxParallel),
|
|
84
|
+
outputDir: options.outputDir,
|
|
85
|
+
dryRun: Boolean(options.dryRun),
|
|
86
|
+
refreshIngest: Boolean(options.refresh),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const payload = {
|
|
90
|
+
command: "audit",
|
|
91
|
+
targetPath: result.targetPath,
|
|
92
|
+
runId: result.runId,
|
|
93
|
+
runDirectory: result.runDirectory,
|
|
94
|
+
reportPath: result.reportMarkdownPath,
|
|
95
|
+
reportJsonPath: result.reportJsonPath,
|
|
96
|
+
dryRun: result.dryRun,
|
|
97
|
+
registrySource: registry.registrySource,
|
|
98
|
+
registryFile: registry.registryFile,
|
|
99
|
+
selectedAgents: result.selectedAgents,
|
|
100
|
+
maxParallel: result.maxParallel,
|
|
101
|
+
summary: result.summary,
|
|
102
|
+
agentCount: result.agentResults.length,
|
|
103
|
+
sharedMemoryPath: result.sharedMemory?.artifactPath || "",
|
|
104
|
+
sharedMemoryEntryCount: Number(result.sharedMemory?.entryCount || 0),
|
|
105
|
+
sharedMemoryQueryCount: Number(result.sharedMemory?.queryCount || 0),
|
|
106
|
+
ddPackageManifestPath: result.ddPackage?.manifestPath || "",
|
|
107
|
+
ddPackageFindingsPath: result.ddPackage?.findingsIndexPath || "",
|
|
108
|
+
ddPackageSummaryPath: result.ddPackage?.executiveSummaryPath || "",
|
|
109
|
+
ingestRefresh: result.ingest?.refresh || null,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
if (emitJson) {
|
|
113
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
114
|
+
} else {
|
|
115
|
+
printAuditSummary(result);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (result.summary.blocking) {
|
|
119
|
+
process.exitCode = 2;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
audit
|
|
124
|
+
.command("package")
|
|
125
|
+
.description("Build or rebuild a unified DD package from an audit run")
|
|
126
|
+
.option("--path <path>", "Target workspace path", ".")
|
|
127
|
+
.option("--run-id <id>", "Audit run id (defaults to latest run under output root)")
|
|
128
|
+
.option("--output-dir <path>", "Optional artifact output root override")
|
|
129
|
+
.option("--json", "Emit machine-readable output")
|
|
130
|
+
.action(async (options, command) => {
|
|
131
|
+
const emitJson = shouldEmitJson(options, command);
|
|
132
|
+
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
133
|
+
const outputRoot = await resolveOutputRoot({
|
|
134
|
+
cwd: targetPath,
|
|
135
|
+
outputDirOverride: options.outputDir,
|
|
136
|
+
env: process.env,
|
|
137
|
+
});
|
|
138
|
+
const runDirectory = await resolveAuditRunDirectory({
|
|
139
|
+
outputRoot,
|
|
140
|
+
runId: options.runId,
|
|
141
|
+
});
|
|
142
|
+
const { report } = await loadAuditRunReport(runDirectory);
|
|
143
|
+
const ddPackage = await writeDdPackage({
|
|
144
|
+
report,
|
|
145
|
+
runDirectory,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const payload = {
|
|
149
|
+
command: "audit package",
|
|
150
|
+
targetPath,
|
|
151
|
+
outputRoot,
|
|
152
|
+
runId: report.runId,
|
|
153
|
+
runDirectory,
|
|
154
|
+
reportPath: path.join(runDirectory, "AUDIT_REPORT.md"),
|
|
155
|
+
reportJsonPath: path.join(runDirectory, "AUDIT_REPORT.json"),
|
|
156
|
+
ddPackageManifestPath: ddPackage.manifestPath,
|
|
157
|
+
ddPackageFindingsPath: ddPackage.findingsIndexPath,
|
|
158
|
+
ddPackageSummaryPath: ddPackage.executiveSummaryPath,
|
|
159
|
+
findingsIndexCount: ddPackage.findingsIndexCount,
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
if (emitJson) {
|
|
163
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
164
|
+
} else {
|
|
165
|
+
console.log(pc.bold("Audit DD package complete"));
|
|
166
|
+
console.log(pc.gray(`Run: ${payload.runId}`));
|
|
167
|
+
console.log(pc.gray(`Manifest: ${payload.ddPackageManifestPath}`));
|
|
168
|
+
console.log(pc.gray(`Summary: ${payload.ddPackageSummaryPath}`));
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
audit
|
|
173
|
+
.command("replay")
|
|
174
|
+
.description("Replay an existing audit run with the same selected agent set")
|
|
175
|
+
.argument("<runId>", "Base audit run id to replay")
|
|
176
|
+
.option("--path <path>", "Workspace path override (defaults to original run target)")
|
|
177
|
+
.option("--registry-file <path>", "Optional custom audit registry file")
|
|
178
|
+
.option("--output-dir <path>", "Optional artifact output root override")
|
|
179
|
+
.option("--refresh", "Refresh CODEBASE_INGEST before replay")
|
|
180
|
+
.option("--json", "Emit machine-readable output")
|
|
181
|
+
.action(async (runId, options, command) => {
|
|
182
|
+
const emitJson = shouldEmitJson(options, command);
|
|
183
|
+
const initialTargetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
184
|
+
const outputRoot = await resolveOutputRoot({
|
|
185
|
+
cwd: initialTargetPath,
|
|
186
|
+
outputDirOverride: options.outputDir,
|
|
187
|
+
env: process.env,
|
|
188
|
+
});
|
|
189
|
+
const baseRunDirectory = await resolveAuditRunDirectory({
|
|
190
|
+
outputRoot,
|
|
191
|
+
runId,
|
|
192
|
+
});
|
|
193
|
+
const { report: baseReport } = await loadAuditRunReport(baseRunDirectory);
|
|
194
|
+
const targetPath = options.path
|
|
195
|
+
? path.resolve(process.cwd(), String(options.path || "."))
|
|
196
|
+
: path.resolve(String(baseReport.targetPath || "."));
|
|
197
|
+
|
|
198
|
+
const registry = await loadAuditRegistry({
|
|
199
|
+
registryFile: options.registryFile,
|
|
200
|
+
});
|
|
201
|
+
const selected = selectAuditAgents(registry.agents, (baseReport.selectedAgents || []).join(","));
|
|
202
|
+
if (selected.selected.length === 0) {
|
|
203
|
+
throw new Error("No eligible agents available for replay in the active registry.");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const replayResult = await runAuditOrchestrator({
|
|
207
|
+
targetPath,
|
|
208
|
+
agents: selected.selected,
|
|
209
|
+
maxParallel: Number(baseReport.maxParallel || 1),
|
|
210
|
+
outputDir: options.outputDir,
|
|
211
|
+
dryRun: Boolean(baseReport.dryRun),
|
|
212
|
+
refreshIngest: Boolean(options.refresh),
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const comparison = await writeAuditComparisonArtifact({
|
|
216
|
+
baseReport,
|
|
217
|
+
candidateReport: replayResult,
|
|
218
|
+
outputDirectory: replayResult.runDirectory,
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
const payload = {
|
|
222
|
+
command: "audit replay",
|
|
223
|
+
baseRunId: baseReport.runId,
|
|
224
|
+
replayRunId: replayResult.runId,
|
|
225
|
+
targetPath: replayResult.targetPath,
|
|
226
|
+
runDirectory: replayResult.runDirectory,
|
|
227
|
+
reportPath: replayResult.reportMarkdownPath,
|
|
228
|
+
reportJsonPath: replayResult.reportJsonPath,
|
|
229
|
+
comparisonPath: comparison.outputPath,
|
|
230
|
+
deterministicEquivalent: comparison.comparison.deterministicEquivalent,
|
|
231
|
+
addedCount: comparison.comparison.addedCount,
|
|
232
|
+
removedCount: comparison.comparison.removedCount,
|
|
233
|
+
ingestRefresh: replayResult.ingest?.refresh || null,
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
if (emitJson) {
|
|
237
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
238
|
+
} else {
|
|
239
|
+
console.log(pc.bold("Audit replay complete"));
|
|
240
|
+
console.log(pc.gray(`Base run: ${payload.baseRunId}`));
|
|
241
|
+
console.log(pc.gray(`Replay run: ${payload.replayRunId}`));
|
|
242
|
+
console.log(pc.gray(`Comparison: ${payload.comparisonPath}`));
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
audit
|
|
247
|
+
.command("diff")
|
|
248
|
+
.description("Diff two audit runs and emit a reproducibility comparison artifact")
|
|
249
|
+
.argument("<baseRunId>", "Base run id")
|
|
250
|
+
.argument("<candidateRunId>", "Candidate run id")
|
|
251
|
+
.option("--path <path>", "Workspace path for resolving output root", ".")
|
|
252
|
+
.option("--output-dir <path>", "Optional artifact output root override")
|
|
253
|
+
.option("--json", "Emit machine-readable output")
|
|
254
|
+
.action(async (baseRunId, candidateRunId, options, command) => {
|
|
255
|
+
const emitJson = shouldEmitJson(options, command);
|
|
256
|
+
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
257
|
+
const outputRoot = await resolveOutputRoot({
|
|
258
|
+
cwd: targetPath,
|
|
259
|
+
outputDirOverride: options.outputDir,
|
|
260
|
+
env: process.env,
|
|
261
|
+
});
|
|
262
|
+
const baseRunDirectory = await resolveAuditRunDirectory({
|
|
263
|
+
outputRoot,
|
|
264
|
+
runId: baseRunId,
|
|
265
|
+
});
|
|
266
|
+
const candidateRunDirectory = await resolveAuditRunDirectory({
|
|
267
|
+
outputRoot,
|
|
268
|
+
runId: candidateRunId,
|
|
269
|
+
});
|
|
270
|
+
const { report: baseReport } = await loadAuditRunReport(baseRunDirectory);
|
|
271
|
+
const { report: candidateReport } = await loadAuditRunReport(candidateRunDirectory);
|
|
272
|
+
const comparison = await writeAuditComparisonArtifact({
|
|
273
|
+
baseReport,
|
|
274
|
+
candidateReport,
|
|
275
|
+
outputDirectory: candidateRunDirectory,
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const payload = {
|
|
279
|
+
command: "audit diff",
|
|
280
|
+
baseRunId: baseReport.runId,
|
|
281
|
+
candidateRunId: candidateReport.runId,
|
|
282
|
+
outputPath: comparison.outputPath,
|
|
283
|
+
deterministicEquivalent: comparison.comparison.deterministicEquivalent,
|
|
284
|
+
addedCount: comparison.comparison.addedCount,
|
|
285
|
+
removedCount: comparison.comparison.removedCount,
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
if (emitJson) {
|
|
289
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
290
|
+
} else {
|
|
291
|
+
console.log(pc.bold("Audit diff complete"));
|
|
292
|
+
console.log(pc.gray(`Comparison: ${payload.outputPath}`));
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
audit
|
|
297
|
+
.command("registry")
|
|
298
|
+
.description("List audit agent registry records")
|
|
299
|
+
.option("--registry-file <path>", "Optional custom audit registry file")
|
|
300
|
+
.option("--json", "Emit machine-readable output")
|
|
301
|
+
.action(async (options, command) => {
|
|
302
|
+
const emitJson = shouldEmitJson(options, command);
|
|
303
|
+
const registry = await loadAuditRegistry({
|
|
304
|
+
registryFile: options.registryFile,
|
|
305
|
+
});
|
|
306
|
+
const payload = {
|
|
307
|
+
command: "audit registry",
|
|
308
|
+
registrySource: registry.registrySource,
|
|
309
|
+
registryFile: registry.registryFile,
|
|
310
|
+
agentCount: registry.agents.length,
|
|
311
|
+
agents: registry.agents,
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
if (emitJson) {
|
|
315
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
console.log(pc.bold("Audit registry"));
|
|
320
|
+
console.log(pc.gray(`Source: ${registry.registrySource}`));
|
|
321
|
+
for (const agent of registry.agents) {
|
|
322
|
+
console.log(`- ${agent.id} (${agent.persona}) :: ${agent.domain}`);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
audit
|
|
327
|
+
.command("security")
|
|
328
|
+
.description("Run security specialist agent only")
|
|
329
|
+
.option("--path <path>", "Target workspace path", ".")
|
|
330
|
+
.option("--registry-file <path>", "Optional custom audit registry file")
|
|
331
|
+
.option("--output-dir <path>", "Optional artifact output root override")
|
|
332
|
+
.option("--refresh", "Refresh CODEBASE_INGEST before security specialist run")
|
|
333
|
+
.option("--dry-run", "Skip deterministic baseline and run security planning only")
|
|
334
|
+
.option("--json", "Emit machine-readable output")
|
|
335
|
+
.action(async (options, command) => {
|
|
336
|
+
const emitJson = shouldEmitJson(options, command);
|
|
337
|
+
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
338
|
+
const registry = await loadAuditRegistry({
|
|
339
|
+
registryFile: options.registryFile,
|
|
340
|
+
});
|
|
341
|
+
const selected = selectAuditAgents(registry.agents, "security");
|
|
342
|
+
if (selected.selected.length !== 1) {
|
|
343
|
+
throw new Error("Security specialist agent is unavailable in the current registry.");
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const result = await runAuditOrchestrator({
|
|
347
|
+
targetPath,
|
|
348
|
+
agents: selected.selected,
|
|
349
|
+
maxParallel: 1,
|
|
350
|
+
outputDir: options.outputDir,
|
|
351
|
+
dryRun: Boolean(options.dryRun),
|
|
352
|
+
refreshIngest: Boolean(options.refresh),
|
|
353
|
+
});
|
|
354
|
+
const securityAgent = result.agentResults.find((agent) => agent.agentId === "security") || null;
|
|
355
|
+
|
|
356
|
+
const payload = {
|
|
357
|
+
command: "audit security",
|
|
358
|
+
targetPath: result.targetPath,
|
|
359
|
+
runId: result.runId,
|
|
360
|
+
runDirectory: result.runDirectory,
|
|
361
|
+
reportPath: result.reportMarkdownPath,
|
|
362
|
+
reportJsonPath: result.reportJsonPath,
|
|
363
|
+
securityAgentPath: securityAgent?.artifactPath || null,
|
|
364
|
+
securitySpecialistReportPath: securityAgent?.specialistReportPath || null,
|
|
365
|
+
summary: result.summary,
|
|
366
|
+
specialistSummary: securityAgent?.summary || null,
|
|
367
|
+
dryRun: result.dryRun,
|
|
368
|
+
ingestRefresh: result.ingest?.refresh || null,
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
if (emitJson) {
|
|
372
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
373
|
+
} else {
|
|
374
|
+
console.log(pc.bold("Security specialist audit complete"));
|
|
375
|
+
console.log(pc.gray(`Run: ${result.runId}`));
|
|
376
|
+
console.log(pc.gray(`Report: ${result.reportMarkdownPath}`));
|
|
377
|
+
if (payload.securitySpecialistReportPath) {
|
|
378
|
+
console.log(pc.gray(`Security report: ${payload.securitySpecialistReportPath}`));
|
|
379
|
+
}
|
|
380
|
+
console.log(
|
|
381
|
+
`Summary: P0=${result.summary.P0} P1=${result.summary.P1} P2=${result.summary.P2} P3=${result.summary.P3}`
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (result.summary.blocking) {
|
|
386
|
+
process.exitCode = 2;
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
audit
|
|
391
|
+
.command("architecture")
|
|
392
|
+
.description("Run architecture specialist agent only")
|
|
393
|
+
.option("--path <path>", "Target workspace path", ".")
|
|
394
|
+
.option("--registry-file <path>", "Optional custom audit registry file")
|
|
395
|
+
.option("--output-dir <path>", "Optional artifact output root override")
|
|
396
|
+
.option("--refresh", "Refresh CODEBASE_INGEST before architecture specialist run")
|
|
397
|
+
.option("--dry-run", "Skip deterministic baseline and run architecture planning only")
|
|
398
|
+
.option("--json", "Emit machine-readable output")
|
|
399
|
+
.action(async (options, command) => {
|
|
400
|
+
const emitJson = shouldEmitJson(options, command);
|
|
401
|
+
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
402
|
+
const registry = await loadAuditRegistry({
|
|
403
|
+
registryFile: options.registryFile,
|
|
404
|
+
});
|
|
405
|
+
const selected = selectAuditAgents(registry.agents, "architecture");
|
|
406
|
+
if (selected.selected.length !== 1) {
|
|
407
|
+
throw new Error("Architecture specialist agent is unavailable in the current registry.");
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const result = await runAuditOrchestrator({
|
|
411
|
+
targetPath,
|
|
412
|
+
agents: selected.selected,
|
|
413
|
+
maxParallel: 1,
|
|
414
|
+
outputDir: options.outputDir,
|
|
415
|
+
dryRun: Boolean(options.dryRun),
|
|
416
|
+
refreshIngest: Boolean(options.refresh),
|
|
417
|
+
});
|
|
418
|
+
const architectureAgent =
|
|
419
|
+
result.agentResults.find((agent) => agent.agentId === "architecture") || null;
|
|
420
|
+
|
|
421
|
+
const payload = {
|
|
422
|
+
command: "audit architecture",
|
|
423
|
+
targetPath: result.targetPath,
|
|
424
|
+
runId: result.runId,
|
|
425
|
+
runDirectory: result.runDirectory,
|
|
426
|
+
reportPath: result.reportMarkdownPath,
|
|
427
|
+
reportJsonPath: result.reportJsonPath,
|
|
428
|
+
architectureAgentPath: architectureAgent?.artifactPath || null,
|
|
429
|
+
architectureSpecialistReportPath: architectureAgent?.specialistReportPath || null,
|
|
430
|
+
summary: result.summary,
|
|
431
|
+
specialistSummary: architectureAgent?.summary || null,
|
|
432
|
+
dryRun: result.dryRun,
|
|
433
|
+
ingestRefresh: result.ingest?.refresh || null,
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
if (emitJson) {
|
|
437
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
438
|
+
} else {
|
|
439
|
+
console.log(pc.bold("Architecture specialist audit complete"));
|
|
440
|
+
console.log(pc.gray(`Run: ${result.runId}`));
|
|
441
|
+
console.log(pc.gray(`Report: ${result.reportMarkdownPath}`));
|
|
442
|
+
if (payload.architectureSpecialistReportPath) {
|
|
443
|
+
console.log(pc.gray(`Architecture report: ${payload.architectureSpecialistReportPath}`));
|
|
444
|
+
}
|
|
445
|
+
console.log(
|
|
446
|
+
`Summary: P0=${result.summary.P0} P1=${result.summary.P1} P2=${result.summary.P2} P3=${result.summary.P3}`
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (result.summary.blocking) {
|
|
451
|
+
process.exitCode = 2;
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
audit
|
|
456
|
+
.command("testing")
|
|
457
|
+
.description("Run testing specialist agent only")
|
|
458
|
+
.option("--path <path>", "Target workspace path", ".")
|
|
459
|
+
.option("--registry-file <path>", "Optional custom audit registry file")
|
|
460
|
+
.option("--output-dir <path>", "Optional artifact output root override")
|
|
461
|
+
.option("--refresh", "Refresh CODEBASE_INGEST before testing specialist run")
|
|
462
|
+
.option("--dry-run", "Skip deterministic baseline and run testing planning only")
|
|
463
|
+
.option("--json", "Emit machine-readable output")
|
|
464
|
+
.action(async (options, command) => {
|
|
465
|
+
const emitJson = shouldEmitJson(options, command);
|
|
466
|
+
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
467
|
+
const registry = await loadAuditRegistry({
|
|
468
|
+
registryFile: options.registryFile,
|
|
469
|
+
});
|
|
470
|
+
const selected = selectAuditAgents(registry.agents, "testing");
|
|
471
|
+
if (selected.selected.length !== 1) {
|
|
472
|
+
throw new Error("Testing specialist agent is unavailable in the current registry.");
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const result = await runAuditOrchestrator({
|
|
476
|
+
targetPath,
|
|
477
|
+
agents: selected.selected,
|
|
478
|
+
maxParallel: 1,
|
|
479
|
+
outputDir: options.outputDir,
|
|
480
|
+
dryRun: Boolean(options.dryRun),
|
|
481
|
+
refreshIngest: Boolean(options.refresh),
|
|
482
|
+
});
|
|
483
|
+
const testingAgent = result.agentResults.find((agent) => agent.agentId === "testing") || null;
|
|
484
|
+
|
|
485
|
+
const payload = {
|
|
486
|
+
command: "audit testing",
|
|
487
|
+
targetPath: result.targetPath,
|
|
488
|
+
runId: result.runId,
|
|
489
|
+
runDirectory: result.runDirectory,
|
|
490
|
+
reportPath: result.reportMarkdownPath,
|
|
491
|
+
reportJsonPath: result.reportJsonPath,
|
|
492
|
+
testingAgentPath: testingAgent?.artifactPath || null,
|
|
493
|
+
testingSpecialistReportPath: testingAgent?.specialistReportPath || null,
|
|
494
|
+
summary: result.summary,
|
|
495
|
+
specialistSummary: testingAgent?.summary || null,
|
|
496
|
+
dryRun: result.dryRun,
|
|
497
|
+
ingestRefresh: result.ingest?.refresh || null,
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
if (emitJson) {
|
|
501
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
502
|
+
} else {
|
|
503
|
+
console.log(pc.bold("Testing specialist audit complete"));
|
|
504
|
+
console.log(pc.gray(`Run: ${result.runId}`));
|
|
505
|
+
console.log(pc.gray(`Report: ${result.reportMarkdownPath}`));
|
|
506
|
+
if (payload.testingSpecialistReportPath) {
|
|
507
|
+
console.log(pc.gray(`Testing report: ${payload.testingSpecialistReportPath}`));
|
|
508
|
+
}
|
|
509
|
+
console.log(
|
|
510
|
+
`Summary: P0=${result.summary.P0} P1=${result.summary.P1} P2=${result.summary.P2} P3=${result.summary.P3}`
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (result.summary.blocking) {
|
|
515
|
+
process.exitCode = 2;
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
audit
|
|
520
|
+
.command("performance")
|
|
521
|
+
.description("Run performance specialist agent only")
|
|
522
|
+
.option("--path <path>", "Target workspace path", ".")
|
|
523
|
+
.option("--registry-file <path>", "Optional custom audit registry file")
|
|
524
|
+
.option("--output-dir <path>", "Optional artifact output root override")
|
|
525
|
+
.option("--refresh", "Refresh CODEBASE_INGEST before performance specialist run")
|
|
526
|
+
.option("--dry-run", "Skip deterministic baseline and run performance planning only")
|
|
527
|
+
.option("--json", "Emit machine-readable output")
|
|
528
|
+
.action(async (options, command) => {
|
|
529
|
+
const emitJson = shouldEmitJson(options, command);
|
|
530
|
+
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
531
|
+
const registry = await loadAuditRegistry({
|
|
532
|
+
registryFile: options.registryFile,
|
|
533
|
+
});
|
|
534
|
+
const selected = selectAuditAgents(registry.agents, "performance");
|
|
535
|
+
if (selected.selected.length !== 1) {
|
|
536
|
+
throw new Error("Performance specialist agent is unavailable in the current registry.");
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const result = await runAuditOrchestrator({
|
|
540
|
+
targetPath,
|
|
541
|
+
agents: selected.selected,
|
|
542
|
+
maxParallel: 1,
|
|
543
|
+
outputDir: options.outputDir,
|
|
544
|
+
dryRun: Boolean(options.dryRun),
|
|
545
|
+
refreshIngest: Boolean(options.refresh),
|
|
546
|
+
});
|
|
547
|
+
const performanceAgent =
|
|
548
|
+
result.agentResults.find((agent) => agent.agentId === "performance") || null;
|
|
549
|
+
|
|
550
|
+
const payload = {
|
|
551
|
+
command: "audit performance",
|
|
552
|
+
targetPath: result.targetPath,
|
|
553
|
+
runId: result.runId,
|
|
554
|
+
runDirectory: result.runDirectory,
|
|
555
|
+
reportPath: result.reportMarkdownPath,
|
|
556
|
+
reportJsonPath: result.reportJsonPath,
|
|
557
|
+
performanceAgentPath: performanceAgent?.artifactPath || null,
|
|
558
|
+
performanceSpecialistReportPath: performanceAgent?.specialistReportPath || null,
|
|
559
|
+
summary: result.summary,
|
|
560
|
+
specialistSummary: performanceAgent?.summary || null,
|
|
561
|
+
dryRun: result.dryRun,
|
|
562
|
+
ingestRefresh: result.ingest?.refresh || null,
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
if (emitJson) {
|
|
566
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
567
|
+
} else {
|
|
568
|
+
console.log(pc.bold("Performance specialist audit complete"));
|
|
569
|
+
console.log(pc.gray(`Run: ${result.runId}`));
|
|
570
|
+
console.log(pc.gray(`Report: ${result.reportMarkdownPath}`));
|
|
571
|
+
if (payload.performanceSpecialistReportPath) {
|
|
572
|
+
console.log(pc.gray(`Performance report: ${payload.performanceSpecialistReportPath}`));
|
|
573
|
+
}
|
|
574
|
+
console.log(
|
|
575
|
+
`Summary: P0=${result.summary.P0} P1=${result.summary.P1} P2=${result.summary.P2} P3=${result.summary.P3}`
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
if (result.summary.blocking) {
|
|
580
|
+
process.exitCode = 2;
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
audit
|
|
585
|
+
.command("compliance")
|
|
586
|
+
.description("Run compliance specialist agent only")
|
|
587
|
+
.option("--path <path>", "Target workspace path", ".")
|
|
588
|
+
.option("--registry-file <path>", "Optional custom audit registry file")
|
|
589
|
+
.option("--output-dir <path>", "Optional artifact output root override")
|
|
590
|
+
.option("--refresh", "Refresh CODEBASE_INGEST before compliance specialist run")
|
|
591
|
+
.option("--dry-run", "Skip deterministic baseline and run compliance planning only")
|
|
592
|
+
.option("--json", "Emit machine-readable output")
|
|
593
|
+
.action(async (options, command) => {
|
|
594
|
+
const emitJson = shouldEmitJson(options, command);
|
|
595
|
+
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
596
|
+
const registry = await loadAuditRegistry({
|
|
597
|
+
registryFile: options.registryFile,
|
|
598
|
+
});
|
|
599
|
+
const selected = selectAuditAgents(registry.agents, "compliance");
|
|
600
|
+
if (selected.selected.length !== 1) {
|
|
601
|
+
throw new Error("Compliance specialist agent is unavailable in the current registry.");
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const result = await runAuditOrchestrator({
|
|
605
|
+
targetPath,
|
|
606
|
+
agents: selected.selected,
|
|
607
|
+
maxParallel: 1,
|
|
608
|
+
outputDir: options.outputDir,
|
|
609
|
+
dryRun: Boolean(options.dryRun),
|
|
610
|
+
refreshIngest: Boolean(options.refresh),
|
|
611
|
+
});
|
|
612
|
+
const complianceAgent =
|
|
613
|
+
result.agentResults.find((agent) => agent.agentId === "compliance") || null;
|
|
614
|
+
|
|
615
|
+
const payload = {
|
|
616
|
+
command: "audit compliance",
|
|
617
|
+
targetPath: result.targetPath,
|
|
618
|
+
runId: result.runId,
|
|
619
|
+
runDirectory: result.runDirectory,
|
|
620
|
+
reportPath: result.reportMarkdownPath,
|
|
621
|
+
reportJsonPath: result.reportJsonPath,
|
|
622
|
+
complianceAgentPath: complianceAgent?.artifactPath || null,
|
|
623
|
+
complianceSpecialistReportPath: complianceAgent?.specialistReportPath || null,
|
|
624
|
+
summary: result.summary,
|
|
625
|
+
specialistSummary: complianceAgent?.summary || null,
|
|
626
|
+
dryRun: result.dryRun,
|
|
627
|
+
ingestRefresh: result.ingest?.refresh || null,
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
if (emitJson) {
|
|
631
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
632
|
+
} else {
|
|
633
|
+
console.log(pc.bold("Compliance specialist audit complete"));
|
|
634
|
+
console.log(pc.gray(`Run: ${result.runId}`));
|
|
635
|
+
console.log(pc.gray(`Report: ${result.reportMarkdownPath}`));
|
|
636
|
+
if (payload.complianceSpecialistReportPath) {
|
|
637
|
+
console.log(pc.gray(`Compliance report: ${payload.complianceSpecialistReportPath}`));
|
|
638
|
+
}
|
|
639
|
+
console.log(
|
|
640
|
+
`Summary: P0=${result.summary.P0} P1=${result.summary.P1} P2=${result.summary.P2} P3=${result.summary.P3}`
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
if (result.summary.blocking) {
|
|
645
|
+
process.exitCode = 2;
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
audit
|
|
650
|
+
.command("documentation")
|
|
651
|
+
.description("Run documentation specialist agent only")
|
|
652
|
+
.option("--path <path>", "Target workspace path", ".")
|
|
653
|
+
.option("--registry-file <path>", "Optional custom audit registry file")
|
|
654
|
+
.option("--output-dir <path>", "Optional artifact output root override")
|
|
655
|
+
.option("--refresh", "Refresh CODEBASE_INGEST before documentation specialist run")
|
|
656
|
+
.option("--dry-run", "Skip deterministic baseline and run documentation planning only")
|
|
657
|
+
.option("--json", "Emit machine-readable output")
|
|
658
|
+
.action(async (options, command) => {
|
|
659
|
+
const emitJson = shouldEmitJson(options, command);
|
|
660
|
+
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
661
|
+
const registry = await loadAuditRegistry({
|
|
662
|
+
registryFile: options.registryFile,
|
|
663
|
+
});
|
|
664
|
+
const selected = selectAuditAgents(registry.agents, "documentation");
|
|
665
|
+
if (selected.selected.length !== 1) {
|
|
666
|
+
throw new Error("Documentation specialist agent is unavailable in the current registry.");
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
const result = await runAuditOrchestrator({
|
|
670
|
+
targetPath,
|
|
671
|
+
agents: selected.selected,
|
|
672
|
+
maxParallel: 1,
|
|
673
|
+
outputDir: options.outputDir,
|
|
674
|
+
dryRun: Boolean(options.dryRun),
|
|
675
|
+
refreshIngest: Boolean(options.refresh),
|
|
676
|
+
});
|
|
677
|
+
const documentationAgent =
|
|
678
|
+
result.agentResults.find((agent) => agent.agentId === "documentation") || null;
|
|
679
|
+
|
|
680
|
+
const payload = {
|
|
681
|
+
command: "audit documentation",
|
|
682
|
+
targetPath: result.targetPath,
|
|
683
|
+
runId: result.runId,
|
|
684
|
+
runDirectory: result.runDirectory,
|
|
685
|
+
reportPath: result.reportMarkdownPath,
|
|
686
|
+
reportJsonPath: result.reportJsonPath,
|
|
687
|
+
documentationAgentPath: documentationAgent?.artifactPath || null,
|
|
688
|
+
documentationSpecialistReportPath: documentationAgent?.specialistReportPath || null,
|
|
689
|
+
summary: result.summary,
|
|
690
|
+
specialistSummary: documentationAgent?.summary || null,
|
|
691
|
+
dryRun: result.dryRun,
|
|
692
|
+
ingestRefresh: result.ingest?.refresh || null,
|
|
693
|
+
};
|
|
694
|
+
|
|
695
|
+
if (emitJson) {
|
|
696
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
697
|
+
} else {
|
|
698
|
+
console.log(pc.bold("Documentation specialist audit complete"));
|
|
699
|
+
console.log(pc.gray(`Run: ${result.runId}`));
|
|
700
|
+
console.log(pc.gray(`Report: ${result.reportMarkdownPath}`));
|
|
701
|
+
if (payload.documentationSpecialistReportPath) {
|
|
702
|
+
console.log(pc.gray(`Documentation report: ${payload.documentationSpecialistReportPath}`));
|
|
703
|
+
}
|
|
704
|
+
console.log(
|
|
705
|
+
`Summary: P0=${result.summary.P0} P1=${result.summary.P1} P2=${result.summary.P2} P3=${result.summary.P3}`
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
if (result.summary.blocking) {
|
|
710
|
+
process.exitCode = 2;
|
|
711
|
+
}
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
audit
|
|
715
|
+
.command("local")
|
|
716
|
+
.description("Compatibility mode: run legacy local readiness + policy audit")
|
|
717
|
+
.option("--path <path>", "Target repository path")
|
|
718
|
+
.option("--output-dir <path>", "Artifact root for report output")
|
|
719
|
+
.option("--json", "Emit machine-readable output")
|
|
720
|
+
.action(async (options, command) => {
|
|
721
|
+
const legacyArgs = buildLegacyArgs(["/audit"], {
|
|
722
|
+
commandOptions: options,
|
|
723
|
+
command,
|
|
724
|
+
});
|
|
725
|
+
await invokeLegacy(legacyArgs);
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
// ── Persona subcommands: sl audit <persona-name-or-domain> ─────────
|
|
729
|
+
//
|
|
730
|
+
// Every persona follows the same architecture:
|
|
731
|
+
// 1. Ingest codebase (auto-refresh if stale)
|
|
732
|
+
// 2. Run Omar baseline (7-layer deterministic) — persona does NOT see results yet
|
|
733
|
+
// 3. Build scope map from ingest
|
|
734
|
+
// 4. Wire memory (blackboard for sub-agents + FAISS recall from previous runs)
|
|
735
|
+
// 5. Run persona agentic loop BLIND-FIRST (no baseline anchoring)
|
|
736
|
+
// 6. After persona completes: RECONCILIATION against Omar baseline
|
|
737
|
+
// 7. Write artifacts + report
|
|
738
|
+
//
|
|
739
|
+
// Jules Tanaka (frontend) was the first onboarded, but all 13 personas
|
|
740
|
+
// share the same tool-equipped, budget-governed, isolated-context runtime.
|
|
741
|
+
//
|
|
742
|
+
audit
|
|
743
|
+
.command("frontend")
|
|
744
|
+
.alias("jules")
|
|
745
|
+
.description("Jules Tanaka — Frontend Runtime deep audit with agentic tool access")
|
|
746
|
+
.option("--path <path>", "Target repository path")
|
|
747
|
+
.option("--mode <mode>", "Agent mode: primary | secondary | tertiary", "primary")
|
|
748
|
+
.option("--max-cost <usd>", "Max cost budget in USD", "5.0")
|
|
749
|
+
.option("--max-turns <n>", "Max agentic loop turns", "25")
|
|
750
|
+
.option("--provider <name>", "LLM provider: openai | anthropic | google")
|
|
751
|
+
.option("--model <id>", "LLM model override")
|
|
752
|
+
.option("--api-key <key>", "Explicit API key override")
|
|
753
|
+
.option("--stream", "Emit NDJSON events to stdout as Jules works")
|
|
754
|
+
.option("--refresh", "Refresh CODEBASE_INGEST before auditing")
|
|
755
|
+
.option("--skip-baseline", "Skip Omar deterministic baseline (not recommended)")
|
|
756
|
+
.option("--url <url>", "Deployed URL for runtime audit (Lighthouse, headers, DevTools)")
|
|
757
|
+
.option("--skip-runtime", "Skip runtime audit even if URL is detected")
|
|
758
|
+
.option("--output-dir <path>", "Artifact output root override")
|
|
759
|
+
.option("--json", "Emit machine-readable final output")
|
|
760
|
+
.action(async (options, command) => {
|
|
761
|
+
const emitJson = shouldEmitJson(options, command);
|
|
762
|
+
const emitStream = Boolean(options.stream);
|
|
763
|
+
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
764
|
+
|
|
765
|
+
// Lazy-load modules
|
|
766
|
+
const { julesAuditLoop } = await import("../agents/jules/loop.js");
|
|
767
|
+
const { JULES_DEFINITION } = await import("../agents/jules/config/definition.js");
|
|
768
|
+
const { collectCodebaseIngest, generateCodebaseIngest } = await import("../ingest/engine.js");
|
|
769
|
+
const { resolveOutputRoot: resolveOut } = await import("../config/service.js");
|
|
770
|
+
const { createBlackboard } = await import("../memory/blackboard.js");
|
|
771
|
+
const { runDeterministicReviewPipeline } = await import("../review/local-review.js");
|
|
772
|
+
const fsp = await import("node:fs/promises");
|
|
773
|
+
|
|
774
|
+
const outputRoot = await resolveOut({ targetPath, outputDir: options.outputDir });
|
|
775
|
+
const artifactDir = path.join(outputRoot, "reports", "jules-" + Date.now());
|
|
776
|
+
await fsp.mkdir(artifactDir, { recursive: true });
|
|
777
|
+
|
|
778
|
+
// Build event handler
|
|
779
|
+
const onEvent = buildEventHandler(emitStream, emitJson, JULES_DEFINITION);
|
|
780
|
+
|
|
781
|
+
emitProgress(onEvent, JULES_DEFINITION, "Starting Jules Tanaka frontend audit...");
|
|
782
|
+
|
|
783
|
+
// ── [1] INGEST ────────────────────────────────────────────────
|
|
784
|
+
emitProgress(onEvent, JULES_DEFINITION, "Ingesting codebase...");
|
|
785
|
+
let ingest;
|
|
786
|
+
try {
|
|
787
|
+
ingest = await generateCodebaseIngest({
|
|
788
|
+
rootPath: targetPath, outputDir: outputRoot,
|
|
789
|
+
refresh: Boolean(options.refresh),
|
|
790
|
+
});
|
|
791
|
+
} catch {
|
|
792
|
+
ingest = await collectCodebaseIngest({ rootPath: targetPath });
|
|
793
|
+
}
|
|
794
|
+
emitProgress(onEvent, JULES_DEFINITION,
|
|
795
|
+
"Ingest complete: " + (ingest?.summary?.filesScanned || 0) + " files, " +
|
|
796
|
+
(ingest?.summary?.totalLoc || 0) + " LOC");
|
|
797
|
+
|
|
798
|
+
// ── [2] OMAR BASELINE (blind — Jules won't see until reconciliation) ──
|
|
799
|
+
let omarBaseline = null;
|
|
800
|
+
if (!options.skipBaseline) {
|
|
801
|
+
emitProgress(onEvent, JULES_DEFINITION, "Running Omar 7-layer deterministic baseline...");
|
|
802
|
+
try {
|
|
803
|
+
omarBaseline = await runDeterministicReviewPipeline({
|
|
804
|
+
targetPath,
|
|
805
|
+
mode: "full",
|
|
806
|
+
outputDir: artifactDir,
|
|
807
|
+
});
|
|
808
|
+
emitProgress(onEvent, JULES_DEFINITION,
|
|
809
|
+
"Baseline complete: P0=" + (omarBaseline?.summary?.P0 || 0) +
|
|
810
|
+
" P1=" + (omarBaseline?.summary?.P1 || 0) +
|
|
811
|
+
" P2=" + (omarBaseline?.summary?.P2 || 0) +
|
|
812
|
+
" (held for reconciliation — Jules runs blind-first)");
|
|
813
|
+
} catch (err) {
|
|
814
|
+
emitProgress(onEvent, JULES_DEFINITION,
|
|
815
|
+
"Baseline failed (non-blocking): " + err.message);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// ── [3] SCOPE MAP ─────────────────────────────────────────────
|
|
820
|
+
const scopeMap = buildScopeMapFromIngest(ingest, JULES_DEFINITION.defaultScope);
|
|
821
|
+
emitProgress(onEvent, JULES_DEFINITION,
|
|
822
|
+
"Scope: " + (scopeMap.primary?.length || 0) + " primary, " +
|
|
823
|
+
(scopeMap.secondary?.length || 0) + " secondary files");
|
|
824
|
+
|
|
825
|
+
// ── [4] MEMORY ────────────────────────────────────────────────
|
|
826
|
+
const blackboard = createBlackboard();
|
|
827
|
+
let memoryIndex = null;
|
|
828
|
+
try {
|
|
829
|
+
const { queryRetrievalIndex } = await import("../memory/retrieval.js");
|
|
830
|
+
memoryIndex = {
|
|
831
|
+
query: (opts) => queryRetrievalIndex({ targetPath, ...opts }),
|
|
832
|
+
index: (docs) => {}, // indexing happens after completion
|
|
833
|
+
};
|
|
834
|
+
} catch { /* memory retrieval unavailable — non-blocking */ }
|
|
835
|
+
|
|
836
|
+
// ── [5] PROVIDER ──────────────────────────────────────────────
|
|
837
|
+
const providerConfig = {};
|
|
838
|
+
if (options.provider) providerConfig.provider = options.provider;
|
|
839
|
+
if (options.model) providerConfig.model = options.model;
|
|
840
|
+
if (options.apiKey) providerConfig.apiKey = options.apiKey;
|
|
841
|
+
|
|
842
|
+
// ── [6] SYSTEM PROMPT (full production prompt) ─────────────────
|
|
843
|
+
const { buildJulesProductionPrompt } = await import("../agents/jules/config/system-prompt.js");
|
|
844
|
+
const systemPrompt = buildJulesProductionPrompt({
|
|
845
|
+
mode: options.mode,
|
|
846
|
+
framework: ingest?.frameworks?.[0] || "unknown",
|
|
847
|
+
componentCount: ingest?.indexedFiles?.files?.filter(
|
|
848
|
+
f => /\.(tsx|jsx|vue|svelte)$/.test(f.path || ""),
|
|
849
|
+
).length || 0,
|
|
850
|
+
scopeMap,
|
|
851
|
+
ingestSummary: ingest?.summary || {},
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
// ── [7] JULES AGENTIC LOOP (BLIND-FIRST — no baseline) ───────
|
|
855
|
+
emitProgress(onEvent, JULES_DEFINITION, "Starting blind-first deep analysis...");
|
|
856
|
+
let report;
|
|
857
|
+
const gen = julesAuditLoop({
|
|
858
|
+
systemPrompt,
|
|
859
|
+
scopeMap,
|
|
860
|
+
rootPath: targetPath,
|
|
861
|
+
// omarBaseline intentionally NOT passed here — Jules runs blind
|
|
862
|
+
blackboard,
|
|
863
|
+
memory: memoryIndex,
|
|
864
|
+
budget: {
|
|
865
|
+
maxCostUsd: parseFloat(options.maxCost) || 5.0,
|
|
866
|
+
maxOutputTokens: JULES_DEFINITION.budget.maxOutputTokens,
|
|
867
|
+
maxRuntimeMs: JULES_DEFINITION.budget.maxRuntimeMs,
|
|
868
|
+
maxToolCalls: JULES_DEFINITION.budget.maxToolCalls,
|
|
869
|
+
},
|
|
870
|
+
provider: providerConfig,
|
|
871
|
+
mode: options.mode,
|
|
872
|
+
maxTurns: parseInt(options.maxTurns) || 25,
|
|
873
|
+
onEvent,
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
let julesFindings = [];
|
|
877
|
+
for await (const evt of gen) {
|
|
878
|
+
if (evt.event === "agent_complete") {
|
|
879
|
+
report = evt;
|
|
880
|
+
julesFindings = evt.payload?.findings || [];
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// ── [7.5] RUNTIME AUDIT (if --url provided or URL detected) ────
|
|
885
|
+
let runtimeResult = null;
|
|
886
|
+
const runtimeUrl = options.url || null;
|
|
887
|
+
if (runtimeUrl || !options.skipRuntime) {
|
|
888
|
+
emitProgress(onEvent, JULES_DEFINITION, "Checking for deployed URL...");
|
|
889
|
+
try {
|
|
890
|
+
const { runtimeAudit: runRT } = await import("../agents/jules/tools/runtime-audit.js");
|
|
891
|
+
|
|
892
|
+
// Detect URL if not provided
|
|
893
|
+
let targetUrl = runtimeUrl;
|
|
894
|
+
if (!targetUrl) {
|
|
895
|
+
const detected = runRT({ operation: "detect_deployed_url", path: targetPath });
|
|
896
|
+
if (detected.found) {
|
|
897
|
+
targetUrl = detected.primary;
|
|
898
|
+
emitProgress(onEvent, JULES_DEFINITION, "Detected deployed URL: " + targetUrl);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
if (targetUrl) {
|
|
903
|
+
emitProgress(onEvent, JULES_DEFINITION, "Running runtime audit on " + targetUrl + "...");
|
|
904
|
+
|
|
905
|
+
// Response headers (security check)
|
|
906
|
+
const headers = runRT({ operation: "check_response_headers", url: targetUrl });
|
|
907
|
+
if (headers.available && headers.securityFindings) {
|
|
908
|
+
for (const hf of headers.securityFindings) {
|
|
909
|
+
julesFindings.push({
|
|
910
|
+
severity: hf.severity, file: targetUrl, line: 0,
|
|
911
|
+
title: "Missing security header: " + hf.header,
|
|
912
|
+
evidence: "HTTP response from " + targetUrl + " lacks " + hf.header,
|
|
913
|
+
source: "runtime_audit",
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// Network waterfall
|
|
919
|
+
const waterfall = runRT({ operation: "check_network_waterfall", url: targetUrl });
|
|
920
|
+
|
|
921
|
+
// Lighthouse (if available)
|
|
922
|
+
const lighthouse = runRT({ operation: "lighthouse_scan", url: targetUrl, path: targetPath });
|
|
923
|
+
|
|
924
|
+
runtimeResult = { url: targetUrl, headers, waterfall, lighthouse };
|
|
925
|
+
emitProgress(onEvent, JULES_DEFINITION,
|
|
926
|
+
"Runtime audit complete" +
|
|
927
|
+
(lighthouse.available ? " (Lighthouse: perf=" + ((lighthouse.scores?.performance || 0) * 100).toFixed(0) + "%)" : "") +
|
|
928
|
+
(headers.available ? " (" + (headers.securityFindings?.length || 0) + " header findings)" : ""));
|
|
929
|
+
} else {
|
|
930
|
+
emitProgress(onEvent, JULES_DEFINITION, "No deployed URL found — skipping runtime audit");
|
|
931
|
+
}
|
|
932
|
+
} catch (rtErr) {
|
|
933
|
+
emitProgress(onEvent, JULES_DEFINITION, "Runtime audit failed (non-blocking): " + rtErr.message);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
// ── [8] RECONCILIATION (now Jules gets the baseline) ──────────
|
|
938
|
+
emitProgress(onEvent, JULES_DEFINITION, "Reconciling against Omar baseline...");
|
|
939
|
+
const reconciliation = reconcileWithBaseline(julesFindings, omarBaseline);
|
|
940
|
+
|
|
941
|
+
if (onEvent && reconciliation.summary) {
|
|
942
|
+
onEvent({
|
|
943
|
+
stream: "sl_event", event: "reconciliation_complete",
|
|
944
|
+
agent: { id: JULES_DEFINITION.id, persona: JULES_DEFINITION.persona,
|
|
945
|
+
color: JULES_DEFINITION.color, avatar: JULES_DEFINITION.avatar },
|
|
946
|
+
payload: reconciliation.summary,
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// ── [9] FINAL REPORT ──────────────────────────────────────────
|
|
951
|
+
const allFindings = [
|
|
952
|
+
...reconciliation.preserved,
|
|
953
|
+
...reconciliation.newFromJules,
|
|
954
|
+
];
|
|
955
|
+
const severityCounts = { P0: 0, P1: 0, P2: 0, P3: 0 };
|
|
956
|
+
for (const f of allFindings) {
|
|
957
|
+
const sev = (f.severity || "P3").toUpperCase();
|
|
958
|
+
if (severityCounts[sev] !== undefined) severityCounts[sev]++;
|
|
959
|
+
else severityCounts.P3++;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
const reportPayload = {
|
|
963
|
+
command: "audit frontend",
|
|
964
|
+
persona: JULES_DEFINITION.persona,
|
|
965
|
+
targetPath,
|
|
966
|
+
mode: options.mode,
|
|
967
|
+
framework: ingest?.frameworks?.[0] || "unknown",
|
|
968
|
+
findings: allFindings,
|
|
969
|
+
summary: { total: allFindings.length, ...severityCounts, blocking: severityCounts.P0 > 0 || severityCounts.P1 > 0 },
|
|
970
|
+
reconciliation: reconciliation.summary,
|
|
971
|
+
baseline: omarBaseline ? { ran: true, findingCount: omarBaseline.findings?.length || 0 } : { ran: false },
|
|
972
|
+
runtime: runtimeResult ? {
|
|
973
|
+
ran: true, url: runtimeResult.url,
|
|
974
|
+
lighthouse: runtimeResult.lighthouse?.available ? runtimeResult.lighthouse.scores : null,
|
|
975
|
+
headerFindings: runtimeResult.headers?.securityFindings?.length || 0,
|
|
976
|
+
} : { ran: false },
|
|
977
|
+
julesUsage: report?.usage || {},
|
|
978
|
+
signature: JULES_DEFINITION.signature,
|
|
979
|
+
};
|
|
980
|
+
|
|
981
|
+
await fsp.writeFile(
|
|
982
|
+
path.join(artifactDir, "JULES_AUDIT.json"),
|
|
983
|
+
JSON.stringify(reportPayload, null, 2),
|
|
984
|
+
);
|
|
985
|
+
|
|
986
|
+
// Write baseline artifact if it exists
|
|
987
|
+
if (omarBaseline) {
|
|
988
|
+
await fsp.writeFile(
|
|
989
|
+
path.join(artifactDir, "OMAR_BASELINE.json"),
|
|
990
|
+
JSON.stringify(omarBaseline, null, 2),
|
|
991
|
+
);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// Write reconciliation artifact
|
|
995
|
+
await fsp.writeFile(
|
|
996
|
+
path.join(artifactDir, "RECONCILIATION.json"),
|
|
997
|
+
JSON.stringify(reconciliation, null, 2),
|
|
998
|
+
);
|
|
999
|
+
|
|
1000
|
+
if (emitJson) {
|
|
1001
|
+
console.log(JSON.stringify(reportPayload, null, 2));
|
|
1002
|
+
} else if (!emitStream) {
|
|
1003
|
+
process.stderr.write("\n" + JULES_DEFINITION.signature + "\n");
|
|
1004
|
+
process.stderr.write("Report: " + artifactDir + "/JULES_AUDIT.json\n");
|
|
1005
|
+
if (omarBaseline) {
|
|
1006
|
+
process.stderr.write("Baseline: " + artifactDir + "/OMAR_BASELINE.json\n");
|
|
1007
|
+
}
|
|
1008
|
+
process.stderr.write("Reconciliation: " + artifactDir + "/RECONCILIATION.json\n");
|
|
1009
|
+
process.stderr.write("Summary: " + allFindings.length + " findings (P0=" + severityCounts.P0 + " P1=" + severityCounts.P1 + " P2=" + severityCounts.P2 + ")\n");
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// Sync run to dashboard (fire-and-forget)
|
|
1013
|
+
try {
|
|
1014
|
+
const { syncRunToDashboard } = await import("../telemetry/sync.js");
|
|
1015
|
+
const syncResult = await syncRunToDashboard({
|
|
1016
|
+
command: "audit frontend",
|
|
1017
|
+
persona: JULES_DEFINITION.persona,
|
|
1018
|
+
mode: options.mode,
|
|
1019
|
+
framework: ingest?.frameworks?.[0] || "unknown",
|
|
1020
|
+
summary: { total: allFindings.length, ...severityCounts, blocking: severityCounts.P0 > 0 || severityCounts.P1 > 0 },
|
|
1021
|
+
usage: report?.usage || {},
|
|
1022
|
+
stopReason: report?.usage?.stopReason || "completed",
|
|
1023
|
+
reconciliation: reconciliation.summary,
|
|
1024
|
+
runtime: runtimeResult ? { ran: true, url: runtimeResult.url } : { ran: false },
|
|
1025
|
+
});
|
|
1026
|
+
if (!emitJson && !emitStream && syncResult.synced) {
|
|
1027
|
+
process.stderr.write(JULES_DEFINITION.avatar + " Run synced to dashboard" + (syncResult.runId ? " (run:" + syncResult.runId + ")" : "") + "\n");
|
|
1028
|
+
}
|
|
1029
|
+
} catch { /* sync failure never blocks CLI */ }
|
|
1030
|
+
|
|
1031
|
+
if (severityCounts.P0 > 0 || severityCounts.P1 > 0) {
|
|
1032
|
+
process.exitCode = 2;
|
|
1033
|
+
}
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// ── Helpers for Jules invocation ──────────────────────────────────────
|
|
1038
|
+
|
|
1039
|
+
function buildScopeMapFromIngest(ingest, defaultScope) {
|
|
1040
|
+
if (!ingest || !ingest.indexedFiles) {
|
|
1041
|
+
return { primary: [], secondary: [], tertiary: [] };
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
const files = (ingest.indexedFiles.files || []).map(f => ({
|
|
1045
|
+
path: f.path,
|
|
1046
|
+
loc: f.loc || 0,
|
|
1047
|
+
language: f.language || "",
|
|
1048
|
+
}));
|
|
1049
|
+
|
|
1050
|
+
const matchesAny = (filePath, patterns) =>
|
|
1051
|
+
patterns.some(p => {
|
|
1052
|
+
const regex = new RegExp(
|
|
1053
|
+
"^" + p.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*") + "$",
|
|
1054
|
+
);
|
|
1055
|
+
return regex.test(filePath);
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
return {
|
|
1059
|
+
primary: files.filter(f => matchesAny(f.path, defaultScope.primaryPatterns)),
|
|
1060
|
+
secondary: files.filter(f => matchesAny(f.path, defaultScope.secondaryPatterns)),
|
|
1061
|
+
tertiary: files.filter(f => matchesAny(f.path, defaultScope.tertiaryPatterns)),
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
// ── Reconciliation ──────────────────────────────────────────────────
|
|
1066
|
+
|
|
1067
|
+
function reconcileWithBaseline(julesFindings, omarBaseline) {
|
|
1068
|
+
if (!omarBaseline || !omarBaseline.findings || !Array.isArray(omarBaseline.findings)) {
|
|
1069
|
+
return {
|
|
1070
|
+
preserved: julesFindings || [],
|
|
1071
|
+
newFromJules: [],
|
|
1072
|
+
rejectedBaseline: [],
|
|
1073
|
+
corroboratedBaseline: [],
|
|
1074
|
+
summary: {
|
|
1075
|
+
julesCount: (julesFindings || []).length,
|
|
1076
|
+
baselineCount: 0,
|
|
1077
|
+
preserved: (julesFindings || []).length,
|
|
1078
|
+
corroborated: 0,
|
|
1079
|
+
rejected: 0,
|
|
1080
|
+
newFromJules: 0,
|
|
1081
|
+
},
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
const baselineFindings = omarBaseline.findings;
|
|
1086
|
+
const corroborated = [];
|
|
1087
|
+
const rejected = [];
|
|
1088
|
+
const newFromJules = [];
|
|
1089
|
+
|
|
1090
|
+
// Build a fingerprint set from baseline for matching
|
|
1091
|
+
const baselineFingerprints = new Set();
|
|
1092
|
+
for (const bf of baselineFindings) {
|
|
1093
|
+
const fp = (bf.file || "") + ":" + (bf.line || "") + ":" + (bf.severity || "");
|
|
1094
|
+
baselineFingerprints.add(fp);
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// Classify Jules findings
|
|
1098
|
+
const julesMatched = new Set();
|
|
1099
|
+
for (const jf of (julesFindings || [])) {
|
|
1100
|
+
const fp = (jf.file || "") + ":" + (jf.line || "") + ":" + (jf.severity || "");
|
|
1101
|
+
if (baselineFingerprints.has(fp)) {
|
|
1102
|
+
corroborated.push({ ...jf, source: "corroborated", baselineMatch: true });
|
|
1103
|
+
julesMatched.add(fp);
|
|
1104
|
+
} else {
|
|
1105
|
+
newFromJules.push({ ...jf, source: "jules_new" });
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// Baseline findings not corroborated by Jules — preserved (not silently dropped)
|
|
1110
|
+
const preservedBaseline = [];
|
|
1111
|
+
for (const bf of baselineFindings) {
|
|
1112
|
+
const fp = (bf.file || "") + ":" + (bf.line || "") + ":" + (bf.severity || "");
|
|
1113
|
+
if (!julesMatched.has(fp)) {
|
|
1114
|
+
preservedBaseline.push({ ...bf, source: "baseline_preserved" });
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
return {
|
|
1119
|
+
preserved: [...corroborated, ...preservedBaseline],
|
|
1120
|
+
newFromJules,
|
|
1121
|
+
rejectedBaseline: rejected,
|
|
1122
|
+
corroboratedBaseline: corroborated,
|
|
1123
|
+
summary: {
|
|
1124
|
+
julesCount: (julesFindings || []).length,
|
|
1125
|
+
baselineCount: baselineFindings.length,
|
|
1126
|
+
preserved: corroborated.length + preservedBaseline.length,
|
|
1127
|
+
corroborated: corroborated.length,
|
|
1128
|
+
preservedFromBaseline: preservedBaseline.length,
|
|
1129
|
+
rejected: rejected.length,
|
|
1130
|
+
newFromJules: newFromJules.length,
|
|
1131
|
+
},
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// ── Event helpers ───────────────────────────────────────────────────
|
|
1136
|
+
|
|
1137
|
+
function buildEventHandler(emitStream, emitJson, def) {
|
|
1138
|
+
if (emitStream) return (evt) => console.log(JSON.stringify(evt));
|
|
1139
|
+
if (emitJson) return undefined;
|
|
1140
|
+
return (evt) => {
|
|
1141
|
+
if (evt.event === "progress") {
|
|
1142
|
+
process.stderr.write(def.avatar + " " + def.shortName + ": " + (evt.payload?.message || "") + "\n");
|
|
1143
|
+
} else if (evt.event === "finding") {
|
|
1144
|
+
const f = evt.payload;
|
|
1145
|
+
process.stderr.write(def.avatar + " [" + (f.severity || "P3") + "] " + (f.file || "") + ":" + (f.line || "") + " " + (f.title || "") + "\n");
|
|
1146
|
+
} else if (evt.event === "heartbeat") {
|
|
1147
|
+
const h = evt.payload;
|
|
1148
|
+
process.stderr.write(def.avatar + " " + def.shortName + " [" + (h.turnsCompleted || 0) + "/" + (h.turnsMax || "?") + " turns, $" + (h.budgetRemaining?.costUsd?.toFixed(2) || "?") + "]\n");
|
|
1149
|
+
} else if (evt.event === "agent_complete") {
|
|
1150
|
+
process.stderr.write(def.avatar + " " + def.persona + " complete: " + (evt.payload?.total || 0) + " findings (P0=" + (evt.payload?.P0 || 0) + " P1=" + (evt.payload?.P1 || 0) + " P2=" + (evt.payload?.P2 || 0) + ")\n");
|
|
1151
|
+
} else if (evt.event === "reconciliation_complete") {
|
|
1152
|
+
const r = evt.payload;
|
|
1153
|
+
process.stderr.write(def.avatar + " Reconciliation: " + (r.corroborated || 0) + " corroborated, " + (r.newFromJules || 0) + " new, " + (r.preservedFromBaseline || 0) + " baseline preserved\n");
|
|
1154
|
+
}
|
|
1155
|
+
};
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
function emitProgress(onEvent, def, message) {
|
|
1159
|
+
if (onEvent) {
|
|
1160
|
+
onEvent({
|
|
1161
|
+
stream: "sl_event", event: "progress",
|
|
1162
|
+
agent: { id: def.id, persona: def.persona, color: def.color, avatar: def.avatar },
|
|
1163
|
+
payload: { phase: "setup", message },
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
1166
|
+
}
|