sentinelayer-cli 0.4.4 → 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/review/replay.js
CHANGED
|
@@ -1,235 +1,235 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
|
-
import fsp from "node:fs/promises";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
|
|
5
|
-
export const REVIEW_RUN_CONTEXT_FILE = "REVIEW_RUN_CONTEXT.json";
|
|
6
|
-
|
|
7
|
-
function normalizeString(value) {
|
|
8
|
-
return String(value || "").trim();
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function runGit(cwd, args) {
|
|
12
|
-
const result = spawnSync("git", args, {
|
|
13
|
-
cwd,
|
|
14
|
-
encoding: "utf-8",
|
|
15
|
-
});
|
|
16
|
-
if (result.status !== 0) {
|
|
17
|
-
return "";
|
|
18
|
-
}
|
|
19
|
-
return normalizeString(result.stdout);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function normalizePath(value) {
|
|
23
|
-
return String(value || "").replace(/\\/g, "/");
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function parseGitDirtyFiles(rawStatus = "") {
|
|
27
|
-
const lines = String(rawStatus || "")
|
|
28
|
-
.split(/\r?\n/g)
|
|
29
|
-
.map((line) => line.trim())
|
|
30
|
-
.filter(Boolean);
|
|
31
|
-
const files = [];
|
|
32
|
-
for (const line of lines) {
|
|
33
|
-
if (line.length < 4) {
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
const candidate = line.slice(3).trim();
|
|
37
|
-
if (candidate) {
|
|
38
|
-
files.push(normalizePath(candidate));
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return [...new Set(files)].sort((left, right) => left.localeCompare(right));
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function collectGitState(targetPath) {
|
|
45
|
-
const normalizedTargetPath = path.resolve(String(targetPath || "."));
|
|
46
|
-
const commitSha = runGit(normalizedTargetPath, ["rev-parse", "HEAD"]);
|
|
47
|
-
const branch = runGit(normalizedTargetPath, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
48
|
-
const dirtyRaw = runGit(normalizedTargetPath, ["status", "--porcelain"]);
|
|
49
|
-
const dirtyFiles = parseGitDirtyFiles(dirtyRaw);
|
|
50
|
-
return {
|
|
51
|
-
commitSha,
|
|
52
|
-
branch,
|
|
53
|
-
dirty: dirtyFiles.length > 0,
|
|
54
|
-
dirtyFiles,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function normalizeInvocation(invocation = {}) {
|
|
59
|
-
return {
|
|
60
|
-
aiEnabled: Boolean(invocation.aiEnabled),
|
|
61
|
-
aiDryRun: Boolean(invocation.aiDryRun),
|
|
62
|
-
provider: normalizeString(invocation.provider),
|
|
63
|
-
model: normalizeString(invocation.model),
|
|
64
|
-
sessionId: normalizeString(invocation.sessionId),
|
|
65
|
-
aiMaxFindings: normalizeString(invocation.aiMaxFindings) || "20",
|
|
66
|
-
maxCost: normalizeString(invocation.maxCost) || "1.0",
|
|
67
|
-
maxTokens: normalizeString(invocation.maxTokens) || "0",
|
|
68
|
-
maxRuntimeMs: normalizeString(invocation.maxRuntimeMs) || "0",
|
|
69
|
-
maxToolCalls: normalizeString(invocation.maxToolCalls) || "0",
|
|
70
|
-
maxNoProgress: normalizeString(invocation.maxNoProgress) || "3",
|
|
71
|
-
warnAtPercent: normalizeString(invocation.warnAtPercent) || "80",
|
|
72
|
-
outputDir: normalizeString(invocation.outputDir),
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export async function writeReviewRunContext({
|
|
77
|
-
runDirectory,
|
|
78
|
-
runId,
|
|
79
|
-
targetPath,
|
|
80
|
-
mode,
|
|
81
|
-
invocation = {},
|
|
82
|
-
replay = {},
|
|
83
|
-
} = {}) {
|
|
84
|
-
const normalizedRunDirectory = path.resolve(String(runDirectory || "."));
|
|
85
|
-
const context = {
|
|
86
|
-
schemaVersion: "1.0.0",
|
|
87
|
-
generatedAt: new Date().toISOString(),
|
|
88
|
-
runId: normalizeString(runId),
|
|
89
|
-
targetPath: path.resolve(String(targetPath || ".")),
|
|
90
|
-
mode: normalizeString(mode) || "full",
|
|
91
|
-
invocation: normalizeInvocation(invocation),
|
|
92
|
-
gitState: collectGitState(targetPath),
|
|
93
|
-
replay: {
|
|
94
|
-
sourceRunId: normalizeString(replay.sourceRunId),
|
|
95
|
-
replayed: Boolean(replay.replayed),
|
|
96
|
-
},
|
|
97
|
-
};
|
|
98
|
-
const contextPath = path.join(normalizedRunDirectory, REVIEW_RUN_CONTEXT_FILE);
|
|
99
|
-
await fsp.mkdir(normalizedRunDirectory, { recursive: true });
|
|
100
|
-
await fsp.writeFile(contextPath, `${JSON.stringify(context, null, 2)}\n`, "utf-8");
|
|
101
|
-
return {
|
|
102
|
-
context,
|
|
103
|
-
contextPath,
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export async function loadReviewRunContext(runDirectory) {
|
|
108
|
-
const normalizedRunDirectory = path.resolve(String(runDirectory || "."));
|
|
109
|
-
const contextPath = path.join(normalizedRunDirectory, REVIEW_RUN_CONTEXT_FILE);
|
|
110
|
-
const context = JSON.parse(await fsp.readFile(contextPath, "utf-8"));
|
|
111
|
-
return {
|
|
112
|
-
context,
|
|
113
|
-
contextPath,
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function fingerprintFinding(finding = {}) {
|
|
118
|
-
return [
|
|
119
|
-
normalizePath(finding.file),
|
|
120
|
-
String(Number(finding.line || 1)),
|
|
121
|
-
normalizeString(finding.message).toLowerCase().replace(/\s+/g, " "),
|
|
122
|
-
].join("|");
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export function compareUnifiedReports(baseReport = {}, candidateReport = {}) {
|
|
126
|
-
const leftFindings = Array.isArray(baseReport.findings) ? baseReport.findings : [];
|
|
127
|
-
const rightFindings = Array.isArray(candidateReport.findings) ? candidateReport.findings : [];
|
|
128
|
-
|
|
129
|
-
const leftMap = new Map();
|
|
130
|
-
for (const finding of leftFindings) {
|
|
131
|
-
leftMap.set(fingerprintFinding(finding), finding);
|
|
132
|
-
}
|
|
133
|
-
const rightMap = new Map();
|
|
134
|
-
for (const finding of rightFindings) {
|
|
135
|
-
rightMap.set(fingerprintFinding(finding), finding);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const added = [];
|
|
139
|
-
const removed = [];
|
|
140
|
-
const severityChanged = [];
|
|
141
|
-
|
|
142
|
-
for (const [fingerprint, finding] of rightMap.entries()) {
|
|
143
|
-
const existing = leftMap.get(fingerprint);
|
|
144
|
-
if (!existing) {
|
|
145
|
-
added.push({
|
|
146
|
-
fingerprint,
|
|
147
|
-
findingId: finding.findingId,
|
|
148
|
-
severity: finding.severity,
|
|
149
|
-
file: finding.file,
|
|
150
|
-
line: finding.line,
|
|
151
|
-
message: finding.message,
|
|
152
|
-
});
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
if (normalizeString(existing.severity) !== normalizeString(finding.severity)) {
|
|
156
|
-
severityChanged.push({
|
|
157
|
-
fingerprint,
|
|
158
|
-
findingId: finding.findingId,
|
|
159
|
-
fromSeverity: existing.severity,
|
|
160
|
-
toSeverity: finding.severity,
|
|
161
|
-
file: finding.file,
|
|
162
|
-
line: finding.line,
|
|
163
|
-
message: finding.message,
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
for (const [fingerprint, finding] of leftMap.entries()) {
|
|
169
|
-
if (!rightMap.has(fingerprint)) {
|
|
170
|
-
removed.push({
|
|
171
|
-
fingerprint,
|
|
172
|
-
findingId: finding.findingId,
|
|
173
|
-
severity: finding.severity,
|
|
174
|
-
file: finding.file,
|
|
175
|
-
line: finding.line,
|
|
176
|
-
message: finding.message,
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const baseSummary = baseReport.summary || {};
|
|
182
|
-
const candidateSummary = candidateReport.summary || {};
|
|
183
|
-
const summaryDelta = {
|
|
184
|
-
P0: Number(candidateSummary.P0 || 0) - Number(baseSummary.P0 || 0),
|
|
185
|
-
P1: Number(candidateSummary.P1 || 0) - Number(baseSummary.P1 || 0),
|
|
186
|
-
P2: Number(candidateSummary.P2 || 0) - Number(baseSummary.P2 || 0),
|
|
187
|
-
P3: Number(candidateSummary.P3 || 0) - Number(baseSummary.P3 || 0),
|
|
188
|
-
};
|
|
189
|
-
summaryDelta.blockingChanged = Boolean(baseSummary.blocking) !== Boolean(candidateSummary.blocking);
|
|
190
|
-
|
|
191
|
-
return {
|
|
192
|
-
deterministicEquivalent:
|
|
193
|
-
added.length === 0 && removed.length === 0 && severityChanged.length === 0,
|
|
194
|
-
counts: {
|
|
195
|
-
added: added.length,
|
|
196
|
-
removed: removed.length,
|
|
197
|
-
severityChanged: severityChanged.length,
|
|
198
|
-
},
|
|
199
|
-
summaryDelta,
|
|
200
|
-
added,
|
|
201
|
-
removed,
|
|
202
|
-
severityChanged,
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
function sanitizeRunId(value) {
|
|
207
|
-
return normalizeString(value).replace(/[^a-zA-Z0-9._-]+/g, "_");
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
export async function writeReviewComparisonArtifact({
|
|
211
|
-
runDirectory,
|
|
212
|
-
baseRunId,
|
|
213
|
-
candidateRunId,
|
|
214
|
-
comparison,
|
|
215
|
-
} = {}) {
|
|
216
|
-
const normalizedRunDirectory = path.resolve(String(runDirectory || "."));
|
|
217
|
-
const fileName = `REVIEW_COMPARISON_${sanitizeRunId(baseRunId)}_vs_${sanitizeRunId(
|
|
218
|
-
candidateRunId
|
|
219
|
-
)}.json`;
|
|
220
|
-
const artifactPath = path.join(normalizedRunDirectory, fileName);
|
|
221
|
-
const payload = {
|
|
222
|
-
schemaVersion: "1.0.0",
|
|
223
|
-
generatedAt: new Date().toISOString(),
|
|
224
|
-
baseRunId: normalizeString(baseRunId),
|
|
225
|
-
candidateRunId: normalizeString(candidateRunId),
|
|
226
|
-
comparison,
|
|
227
|
-
};
|
|
228
|
-
await fsp.mkdir(normalizedRunDirectory, { recursive: true });
|
|
229
|
-
await fsp.writeFile(artifactPath, `${JSON.stringify(payload, null, 2)}\n`, "utf-8");
|
|
230
|
-
return {
|
|
231
|
-
artifactPath,
|
|
232
|
-
payload,
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import fsp from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
export const REVIEW_RUN_CONTEXT_FILE = "REVIEW_RUN_CONTEXT.json";
|
|
6
|
+
|
|
7
|
+
function normalizeString(value) {
|
|
8
|
+
return String(value || "").trim();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function runGit(cwd, args) {
|
|
12
|
+
const result = spawnSync("git", args, {
|
|
13
|
+
cwd,
|
|
14
|
+
encoding: "utf-8",
|
|
15
|
+
});
|
|
16
|
+
if (result.status !== 0) {
|
|
17
|
+
return "";
|
|
18
|
+
}
|
|
19
|
+
return normalizeString(result.stdout);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function normalizePath(value) {
|
|
23
|
+
return String(value || "").replace(/\\/g, "/");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function parseGitDirtyFiles(rawStatus = "") {
|
|
27
|
+
const lines = String(rawStatus || "")
|
|
28
|
+
.split(/\r?\n/g)
|
|
29
|
+
.map((line) => line.trim())
|
|
30
|
+
.filter(Boolean);
|
|
31
|
+
const files = [];
|
|
32
|
+
for (const line of lines) {
|
|
33
|
+
if (line.length < 4) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const candidate = line.slice(3).trim();
|
|
37
|
+
if (candidate) {
|
|
38
|
+
files.push(normalizePath(candidate));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return [...new Set(files)].sort((left, right) => left.localeCompare(right));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function collectGitState(targetPath) {
|
|
45
|
+
const normalizedTargetPath = path.resolve(String(targetPath || "."));
|
|
46
|
+
const commitSha = runGit(normalizedTargetPath, ["rev-parse", "HEAD"]);
|
|
47
|
+
const branch = runGit(normalizedTargetPath, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
48
|
+
const dirtyRaw = runGit(normalizedTargetPath, ["status", "--porcelain"]);
|
|
49
|
+
const dirtyFiles = parseGitDirtyFiles(dirtyRaw);
|
|
50
|
+
return {
|
|
51
|
+
commitSha,
|
|
52
|
+
branch,
|
|
53
|
+
dirty: dirtyFiles.length > 0,
|
|
54
|
+
dirtyFiles,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function normalizeInvocation(invocation = {}) {
|
|
59
|
+
return {
|
|
60
|
+
aiEnabled: Boolean(invocation.aiEnabled),
|
|
61
|
+
aiDryRun: Boolean(invocation.aiDryRun),
|
|
62
|
+
provider: normalizeString(invocation.provider),
|
|
63
|
+
model: normalizeString(invocation.model),
|
|
64
|
+
sessionId: normalizeString(invocation.sessionId),
|
|
65
|
+
aiMaxFindings: normalizeString(invocation.aiMaxFindings) || "20",
|
|
66
|
+
maxCost: normalizeString(invocation.maxCost) || "1.0",
|
|
67
|
+
maxTokens: normalizeString(invocation.maxTokens) || "0",
|
|
68
|
+
maxRuntimeMs: normalizeString(invocation.maxRuntimeMs) || "0",
|
|
69
|
+
maxToolCalls: normalizeString(invocation.maxToolCalls) || "0",
|
|
70
|
+
maxNoProgress: normalizeString(invocation.maxNoProgress) || "3",
|
|
71
|
+
warnAtPercent: normalizeString(invocation.warnAtPercent) || "80",
|
|
72
|
+
outputDir: normalizeString(invocation.outputDir),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function writeReviewRunContext({
|
|
77
|
+
runDirectory,
|
|
78
|
+
runId,
|
|
79
|
+
targetPath,
|
|
80
|
+
mode,
|
|
81
|
+
invocation = {},
|
|
82
|
+
replay = {},
|
|
83
|
+
} = {}) {
|
|
84
|
+
const normalizedRunDirectory = path.resolve(String(runDirectory || "."));
|
|
85
|
+
const context = {
|
|
86
|
+
schemaVersion: "1.0.0",
|
|
87
|
+
generatedAt: new Date().toISOString(),
|
|
88
|
+
runId: normalizeString(runId),
|
|
89
|
+
targetPath: path.resolve(String(targetPath || ".")),
|
|
90
|
+
mode: normalizeString(mode) || "full",
|
|
91
|
+
invocation: normalizeInvocation(invocation),
|
|
92
|
+
gitState: collectGitState(targetPath),
|
|
93
|
+
replay: {
|
|
94
|
+
sourceRunId: normalizeString(replay.sourceRunId),
|
|
95
|
+
replayed: Boolean(replay.replayed),
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
const contextPath = path.join(normalizedRunDirectory, REVIEW_RUN_CONTEXT_FILE);
|
|
99
|
+
await fsp.mkdir(normalizedRunDirectory, { recursive: true });
|
|
100
|
+
await fsp.writeFile(contextPath, `${JSON.stringify(context, null, 2)}\n`, "utf-8");
|
|
101
|
+
return {
|
|
102
|
+
context,
|
|
103
|
+
contextPath,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function loadReviewRunContext(runDirectory) {
|
|
108
|
+
const normalizedRunDirectory = path.resolve(String(runDirectory || "."));
|
|
109
|
+
const contextPath = path.join(normalizedRunDirectory, REVIEW_RUN_CONTEXT_FILE);
|
|
110
|
+
const context = JSON.parse(await fsp.readFile(contextPath, "utf-8"));
|
|
111
|
+
return {
|
|
112
|
+
context,
|
|
113
|
+
contextPath,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function fingerprintFinding(finding = {}) {
|
|
118
|
+
return [
|
|
119
|
+
normalizePath(finding.file),
|
|
120
|
+
String(Number(finding.line || 1)),
|
|
121
|
+
normalizeString(finding.message).toLowerCase().replace(/\s+/g, " "),
|
|
122
|
+
].join("|");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function compareUnifiedReports(baseReport = {}, candidateReport = {}) {
|
|
126
|
+
const leftFindings = Array.isArray(baseReport.findings) ? baseReport.findings : [];
|
|
127
|
+
const rightFindings = Array.isArray(candidateReport.findings) ? candidateReport.findings : [];
|
|
128
|
+
|
|
129
|
+
const leftMap = new Map();
|
|
130
|
+
for (const finding of leftFindings) {
|
|
131
|
+
leftMap.set(fingerprintFinding(finding), finding);
|
|
132
|
+
}
|
|
133
|
+
const rightMap = new Map();
|
|
134
|
+
for (const finding of rightFindings) {
|
|
135
|
+
rightMap.set(fingerprintFinding(finding), finding);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const added = [];
|
|
139
|
+
const removed = [];
|
|
140
|
+
const severityChanged = [];
|
|
141
|
+
|
|
142
|
+
for (const [fingerprint, finding] of rightMap.entries()) {
|
|
143
|
+
const existing = leftMap.get(fingerprint);
|
|
144
|
+
if (!existing) {
|
|
145
|
+
added.push({
|
|
146
|
+
fingerprint,
|
|
147
|
+
findingId: finding.findingId,
|
|
148
|
+
severity: finding.severity,
|
|
149
|
+
file: finding.file,
|
|
150
|
+
line: finding.line,
|
|
151
|
+
message: finding.message,
|
|
152
|
+
});
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (normalizeString(existing.severity) !== normalizeString(finding.severity)) {
|
|
156
|
+
severityChanged.push({
|
|
157
|
+
fingerprint,
|
|
158
|
+
findingId: finding.findingId,
|
|
159
|
+
fromSeverity: existing.severity,
|
|
160
|
+
toSeverity: finding.severity,
|
|
161
|
+
file: finding.file,
|
|
162
|
+
line: finding.line,
|
|
163
|
+
message: finding.message,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
for (const [fingerprint, finding] of leftMap.entries()) {
|
|
169
|
+
if (!rightMap.has(fingerprint)) {
|
|
170
|
+
removed.push({
|
|
171
|
+
fingerprint,
|
|
172
|
+
findingId: finding.findingId,
|
|
173
|
+
severity: finding.severity,
|
|
174
|
+
file: finding.file,
|
|
175
|
+
line: finding.line,
|
|
176
|
+
message: finding.message,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const baseSummary = baseReport.summary || {};
|
|
182
|
+
const candidateSummary = candidateReport.summary || {};
|
|
183
|
+
const summaryDelta = {
|
|
184
|
+
P0: Number(candidateSummary.P0 || 0) - Number(baseSummary.P0 || 0),
|
|
185
|
+
P1: Number(candidateSummary.P1 || 0) - Number(baseSummary.P1 || 0),
|
|
186
|
+
P2: Number(candidateSummary.P2 || 0) - Number(baseSummary.P2 || 0),
|
|
187
|
+
P3: Number(candidateSummary.P3 || 0) - Number(baseSummary.P3 || 0),
|
|
188
|
+
};
|
|
189
|
+
summaryDelta.blockingChanged = Boolean(baseSummary.blocking) !== Boolean(candidateSummary.blocking);
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
deterministicEquivalent:
|
|
193
|
+
added.length === 0 && removed.length === 0 && severityChanged.length === 0,
|
|
194
|
+
counts: {
|
|
195
|
+
added: added.length,
|
|
196
|
+
removed: removed.length,
|
|
197
|
+
severityChanged: severityChanged.length,
|
|
198
|
+
},
|
|
199
|
+
summaryDelta,
|
|
200
|
+
added,
|
|
201
|
+
removed,
|
|
202
|
+
severityChanged,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function sanitizeRunId(value) {
|
|
207
|
+
return normalizeString(value).replace(/[^a-zA-Z0-9._-]+/g, "_");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export async function writeReviewComparisonArtifact({
|
|
211
|
+
runDirectory,
|
|
212
|
+
baseRunId,
|
|
213
|
+
candidateRunId,
|
|
214
|
+
comparison,
|
|
215
|
+
} = {}) {
|
|
216
|
+
const normalizedRunDirectory = path.resolve(String(runDirectory || "."));
|
|
217
|
+
const fileName = `REVIEW_COMPARISON_${sanitizeRunId(baseRunId)}_vs_${sanitizeRunId(
|
|
218
|
+
candidateRunId
|
|
219
|
+
)}.json`;
|
|
220
|
+
const artifactPath = path.join(normalizedRunDirectory, fileName);
|
|
221
|
+
const payload = {
|
|
222
|
+
schemaVersion: "1.0.0",
|
|
223
|
+
generatedAt: new Date().toISOString(),
|
|
224
|
+
baseRunId: normalizeString(baseRunId),
|
|
225
|
+
candidateRunId: normalizeString(candidateRunId),
|
|
226
|
+
comparison,
|
|
227
|
+
};
|
|
228
|
+
await fsp.mkdir(normalizedRunDirectory, { recursive: true });
|
|
229
|
+
await fsp.writeFile(artifactPath, `${JSON.stringify(payload, null, 2)}\n`, "utf-8");
|
|
230
|
+
return {
|
|
231
|
+
artifactPath,
|
|
232
|
+
payload,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|