iterate-plugin 2.7.2 → 2.7.3
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 +8 -0
- package/README.zh-CN.md +8 -0
- package/lib/client.js +1474 -734
- package/package.json +6 -2
- package/src/client/index.ts +1218 -0
package/lib/client.js
CHANGED
|
@@ -1,92 +1,709 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
1
|
+
window.__ModuleLoader__.load({ id: "iterate-plugin", factory: (require) => {
|
|
2
|
+
var module = { exports: {} };
|
|
3
|
+
var exports = module.exports;
|
|
4
|
+
"use strict";
|
|
5
|
+
var __create = Object.create;
|
|
6
|
+
var __defProp = Object.defineProperty;
|
|
7
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
8
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
9
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
10
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name2 in all)
|
|
13
|
+
__defProp(target, name2, { get: all[name2], enumerable: true });
|
|
14
|
+
};
|
|
15
|
+
var __copyProps = (to, from, except, desc) => {
|
|
16
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
17
|
+
for (let key of __getOwnPropNames(from))
|
|
18
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
19
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
+
mod
|
|
30
|
+
));
|
|
31
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
|
+
|
|
33
|
+
// src/client/index.ts
|
|
34
|
+
var index_exports = {};
|
|
35
|
+
__export(index_exports, {
|
|
36
|
+
apply: () => apply,
|
|
37
|
+
inject: () => inject,
|
|
38
|
+
name: () => name
|
|
39
|
+
});
|
|
40
|
+
module.exports = __toCommonJS(index_exports);
|
|
41
|
+
var React = __toESM(require("react"), 1);
|
|
42
|
+
|
|
43
|
+
// lib/parse.js
|
|
44
|
+
var SEVERITY_ORDER = ["critical", "high", "medium", "low"];
|
|
45
|
+
var SEVERITY_LABEL = {
|
|
46
|
+
critical: "CRIT",
|
|
47
|
+
high: "HIGH",
|
|
48
|
+
medium: "MED",
|
|
49
|
+
low: "LOW"
|
|
50
|
+
};
|
|
51
|
+
var SEVERITY_COLOR = {
|
|
52
|
+
critical: "#ef4444",
|
|
53
|
+
high: "#f97316",
|
|
54
|
+
medium: "#eab308",
|
|
55
|
+
low: "#6b7280"
|
|
56
|
+
};
|
|
57
|
+
function isReviewReport(obj) {
|
|
58
|
+
if (!obj || typeof obj !== "object") return false;
|
|
59
|
+
const o = (
|
|
60
|
+
/** @type {Record<string, unknown>} */
|
|
61
|
+
obj
|
|
62
|
+
);
|
|
63
|
+
return typeof o.convergence === "object" && o.convergence !== null && Array.isArray(o.findings) && Array.isArray(o.rounds);
|
|
64
|
+
}
|
|
65
|
+
function findReportInObject(obj, seen, maxDepth = 20) {
|
|
66
|
+
if (maxDepth <= 0) return null;
|
|
67
|
+
if (!obj || typeof obj !== "object") return null;
|
|
68
|
+
const s = seen || /* @__PURE__ */ new Set();
|
|
69
|
+
if (s.has(obj)) return null;
|
|
70
|
+
s.add(obj);
|
|
71
|
+
if (isReviewReport(obj)) return (
|
|
72
|
+
/** @type {Record<string, unknown>} */
|
|
73
|
+
obj
|
|
74
|
+
);
|
|
75
|
+
if (Array.isArray(obj)) {
|
|
76
|
+
for (const item of obj) {
|
|
77
|
+
const found = findReportInObject(item, s, maxDepth - 1);
|
|
78
|
+
if (found) return found;
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
const o = (
|
|
83
|
+
/** @type {Record<string, unknown>} */
|
|
84
|
+
obj
|
|
85
|
+
);
|
|
86
|
+
for (const key of Object.keys(o)) {
|
|
87
|
+
const val = o[key];
|
|
88
|
+
if (val && typeof val === "object") {
|
|
89
|
+
const found = findReportInObject(val, s, maxDepth - 1);
|
|
90
|
+
if (found) return found;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
function scanSessionForReport(session) {
|
|
96
|
+
if (!session || typeof session !== "object") return null;
|
|
97
|
+
const direct = findReportInObject(session);
|
|
98
|
+
if (direct) return direct;
|
|
99
|
+
const s = (
|
|
100
|
+
/** @type {Record<string, unknown>} */
|
|
101
|
+
session
|
|
102
|
+
);
|
|
103
|
+
if (Array.isArray(s.toolCalls)) {
|
|
104
|
+
const calls = (
|
|
105
|
+
/** @type {Array<Record<string, unknown>>} */
|
|
106
|
+
s.toolCalls
|
|
107
|
+
);
|
|
108
|
+
for (let i = calls.length - 1; i >= 0; i--) {
|
|
109
|
+
const call = calls[i];
|
|
110
|
+
if (!call) continue;
|
|
111
|
+
if (call.tool === "iterate_review" || String(call.tool ?? "").endsWith("iterate_review")) {
|
|
112
|
+
const result = call.result;
|
|
113
|
+
if (result && typeof result === "object") {
|
|
114
|
+
const r = (
|
|
115
|
+
/** @type {Record<string, unknown>} */
|
|
116
|
+
result
|
|
117
|
+
);
|
|
118
|
+
if (r.report && typeof r.report === "object") {
|
|
119
|
+
return (
|
|
120
|
+
/** @type {Record<string, unknown>} */
|
|
121
|
+
r.report
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (Array.isArray(s.messages)) {
|
|
129
|
+
const msgs = (
|
|
130
|
+
/** @type {Array<Record<string, unknown>>} */
|
|
131
|
+
s.messages
|
|
132
|
+
);
|
|
133
|
+
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
134
|
+
const msg = msgs[i];
|
|
135
|
+
if (!msg || !Array.isArray(msg.tool_calls)) continue;
|
|
136
|
+
const calls = (
|
|
137
|
+
/** @type {Array<Record<string, unknown>>} */
|
|
138
|
+
msg.tool_calls
|
|
139
|
+
);
|
|
140
|
+
for (const call of calls) {
|
|
141
|
+
if (!call) continue;
|
|
142
|
+
const fn = call.function;
|
|
143
|
+
if (fn && typeof fn === "object") {
|
|
144
|
+
const f = (
|
|
145
|
+
/** @type {Record<string, unknown>} */
|
|
146
|
+
fn
|
|
147
|
+
);
|
|
148
|
+
if (String(f.name ?? "").endsWith("iterate_review")) {
|
|
149
|
+
try {
|
|
150
|
+
const args = JSON.parse(String(f.arguments ?? "{}"));
|
|
151
|
+
const found = findReportInObject(args);
|
|
152
|
+
if (found) return found;
|
|
153
|
+
} catch {
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
function isRunSummary(obj) {
|
|
163
|
+
if (!obj || typeof obj !== "object") return false;
|
|
164
|
+
const o = (
|
|
165
|
+
/** @type {Record<string, unknown>} */
|
|
166
|
+
obj
|
|
167
|
+
);
|
|
168
|
+
const final = o.finalReport;
|
|
169
|
+
return !!final && typeof final === "object" && (final.verdict === "approved" || final.verdict === "needs_revision");
|
|
170
|
+
}
|
|
171
|
+
function findRunSummaryInObject(obj, seen, maxDepth = 20) {
|
|
172
|
+
if (maxDepth <= 0) return null;
|
|
173
|
+
if (!obj || typeof obj !== "object") return null;
|
|
174
|
+
const s = seen || /* @__PURE__ */ new Set();
|
|
175
|
+
if (s.has(obj)) return null;
|
|
176
|
+
s.add(obj);
|
|
177
|
+
if (isRunSummary(obj)) return (
|
|
178
|
+
/** @type {Record<string, unknown>} */
|
|
179
|
+
obj
|
|
180
|
+
);
|
|
181
|
+
if (Array.isArray(obj)) {
|
|
182
|
+
for (const item of obj) {
|
|
183
|
+
const found = findRunSummaryInObject(item, s, maxDepth - 1);
|
|
184
|
+
if (found) return found;
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
const o = (
|
|
189
|
+
/** @type {Record<string, unknown>} */
|
|
190
|
+
obj
|
|
191
|
+
);
|
|
192
|
+
for (const key of Object.keys(o)) {
|
|
193
|
+
const val = o[key];
|
|
194
|
+
if (val && typeof val === "object") {
|
|
195
|
+
const found = findRunSummaryInObject(val, s, maxDepth - 1);
|
|
196
|
+
if (found) return found;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
function scanSessionForRunSummary(session) {
|
|
202
|
+
if (!session || typeof session !== "object") return null;
|
|
203
|
+
const direct = findRunSummaryInObject(session);
|
|
204
|
+
if (direct) return direct;
|
|
205
|
+
const s = (
|
|
206
|
+
/** @type {Record<string, unknown>} */
|
|
207
|
+
session
|
|
208
|
+
);
|
|
209
|
+
if (Array.isArray(s.toolCalls)) {
|
|
210
|
+
const calls = (
|
|
211
|
+
/** @type {Array<Record<string, unknown>>} */
|
|
212
|
+
s.toolCalls
|
|
213
|
+
);
|
|
214
|
+
for (let i = calls.length - 1; i >= 0; i--) {
|
|
215
|
+
const call = calls[i];
|
|
216
|
+
if (!call) continue;
|
|
217
|
+
if (call.tool === "workflow" || String(call.tool ?? "").endsWith("workflow")) {
|
|
218
|
+
const found = findRunSummaryInObject(call.result, void 0, 24);
|
|
219
|
+
if (found) return found;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (Array.isArray(s.messages)) {
|
|
224
|
+
const msgs = (
|
|
225
|
+
/** @type {Array<Record<string, unknown>>} */
|
|
226
|
+
s.messages
|
|
227
|
+
);
|
|
228
|
+
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
229
|
+
const msg = msgs[i];
|
|
230
|
+
if (!msg) continue;
|
|
231
|
+
const found = findRunSummaryInObject(msg.content);
|
|
232
|
+
if (found) return found;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
function extractVerdict(runSummary) {
|
|
238
|
+
if (!isRunSummary(runSummary)) return null;
|
|
239
|
+
const o = (
|
|
240
|
+
/** @type {Record<string, unknown>} */
|
|
241
|
+
runSummary
|
|
242
|
+
);
|
|
243
|
+
const final = (
|
|
244
|
+
/** @type {Record<string, unknown>} */
|
|
245
|
+
o.finalReport
|
|
246
|
+
);
|
|
247
|
+
const meta = final.metaReview && typeof final.metaReview === "object" ? (
|
|
248
|
+
/** @type {Record<string, unknown>} */
|
|
249
|
+
final.metaReview
|
|
250
|
+
) : {};
|
|
251
|
+
const issues = Array.isArray(meta.issues) ? meta.issues : [];
|
|
252
|
+
const roundsVal = o.rounds;
|
|
253
|
+
const totalRounds = typeof roundsVal === "number" ? roundsVal : Array.isArray(roundsVal) ? roundsVal.length : 0;
|
|
254
|
+
return {
|
|
255
|
+
verdict: final.verdict === "needs_revision" ? "needs_revision" : "approved",
|
|
256
|
+
reportIssues: issues.length,
|
|
257
|
+
checksRun: typeof meta.checksRun === "number" ? meta.checksRun : 0,
|
|
258
|
+
converged: o.converged === true,
|
|
259
|
+
totalRounds,
|
|
260
|
+
totalFindings: typeof o.totalFindings === "number" ? o.totalFindings : 0
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
function normalizeReport(report) {
|
|
264
|
+
const convergence = (
|
|
265
|
+
/** @type {Record<string, unknown>} */
|
|
266
|
+
report.convergence ?? {}
|
|
267
|
+
);
|
|
268
|
+
const rounds = (
|
|
269
|
+
/** @type {Array<unknown>} */
|
|
270
|
+
report.rounds ?? []
|
|
271
|
+
);
|
|
272
|
+
const findings = (
|
|
273
|
+
/** @type {Array<Record<string, unknown>>} */
|
|
274
|
+
report.findings ?? []
|
|
275
|
+
);
|
|
276
|
+
const totalRounds = typeof convergence.totalRounds === "number" ? convergence.totalRounds : rounds.length;
|
|
277
|
+
const normalizedConvergence = {
|
|
278
|
+
totalRounds,
|
|
279
|
+
findingsByRound: Array.isArray(convergence.findingsByRound) ? convergence.findingsByRound : rounds.map((r) => {
|
|
280
|
+
const rr = (
|
|
281
|
+
/** @type {Record<string, unknown>} */
|
|
282
|
+
r
|
|
283
|
+
);
|
|
284
|
+
return Array.isArray(rr?.findings) ? rr.findings.length : 0;
|
|
285
|
+
}),
|
|
286
|
+
converged: convergence.converged === true,
|
|
287
|
+
stoppedReason: convergence.stoppedReason ?? (rounds.length < totalRounds ? "converged" : "max_rounds_reached")
|
|
288
|
+
};
|
|
289
|
+
let summary = report.summary;
|
|
290
|
+
if (!summary || typeof summary !== "object") {
|
|
291
|
+
summary = computeSummaryFromFindings(findings);
|
|
292
|
+
} else {
|
|
293
|
+
const s = (
|
|
294
|
+
/** @type {Record<string, unknown>} */
|
|
295
|
+
summary
|
|
296
|
+
);
|
|
297
|
+
const computed = computeSummaryFromFindings(findings);
|
|
298
|
+
summary = {
|
|
299
|
+
totalFindings: typeof s.totalFindings === "number" ? s.totalFindings : findings.length,
|
|
300
|
+
critical: typeof s.critical === "number" ? s.critical : computed.critical,
|
|
301
|
+
high: typeof s.high === "number" ? s.high : computed.high,
|
|
302
|
+
medium: typeof s.medium === "number" ? s.medium : computed.medium,
|
|
303
|
+
low: typeof s.low === "number" ? s.low : computed.low,
|
|
304
|
+
byDimension: s.byDimension && typeof s.byDimension === "object" ? s.byDimension : computed.byDimension,
|
|
305
|
+
...typeof s.fixedCount === "number" ? { fixedCount: s.fixedCount } : {}
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
mode: report.mode ?? "dry-run",
|
|
310
|
+
goal: report.goal ?? "",
|
|
311
|
+
dimensions: Array.isArray(report.dimensions) ? report.dimensions : [],
|
|
312
|
+
maxReviewRounds: report.maxReviewRounds ?? totalRounds,
|
|
313
|
+
rounds,
|
|
314
|
+
findings,
|
|
315
|
+
convergence: normalizedConvergence,
|
|
316
|
+
summary
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
function computeSummaryFromFindings(findings) {
|
|
320
|
+
const counts = { critical: 0, high: 0, medium: 0, low: 0 };
|
|
321
|
+
const byDimension = {};
|
|
322
|
+
for (const f of findings) {
|
|
323
|
+
const sev = String(f.severity ?? "low");
|
|
324
|
+
if (sev in counts) counts[sev]++;
|
|
325
|
+
const dim = String(f.dimension ?? "unknown");
|
|
326
|
+
byDimension[dim] = (byDimension[dim] ?? 0) + 1;
|
|
327
|
+
}
|
|
328
|
+
return {
|
|
329
|
+
totalFindings: findings.length,
|
|
330
|
+
critical: counts.critical,
|
|
331
|
+
high: counts.high,
|
|
332
|
+
medium: counts.medium,
|
|
333
|
+
low: counts.low,
|
|
334
|
+
byDimension
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
function computeConvergenceProgress(report) {
|
|
338
|
+
const convergence = (
|
|
339
|
+
/** @type {Record<string, unknown>} */
|
|
340
|
+
report.convergence ?? {}
|
|
341
|
+
);
|
|
342
|
+
const totalRounds = typeof convergence.totalRounds === "number" ? convergence.totalRounds : 1;
|
|
343
|
+
const currentRounds = (
|
|
344
|
+
/** @type {Array<unknown>} */
|
|
345
|
+
(report.rounds ?? []).length
|
|
346
|
+
);
|
|
347
|
+
if (!(totalRounds > 0)) return 0;
|
|
348
|
+
return Math.min(100, Math.round(currentRounds / totalRounds * 100));
|
|
349
|
+
}
|
|
350
|
+
function getCurrentRound(report) {
|
|
351
|
+
return (
|
|
352
|
+
/** @type {Array<unknown>} */
|
|
353
|
+
(report.rounds ?? []).length
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
function getTotalRounds(report) {
|
|
357
|
+
const convergence = (
|
|
358
|
+
/** @type {Record<string, unknown>} */
|
|
359
|
+
report.convergence ?? {}
|
|
360
|
+
);
|
|
361
|
+
return typeof convergence.totalRounds === "number" ? convergence.totalRounds : 1;
|
|
362
|
+
}
|
|
363
|
+
function severityStats(report) {
|
|
364
|
+
const findings = (
|
|
365
|
+
/** @type {Array<Record<string, unknown>>} */
|
|
366
|
+
report.findings ?? []
|
|
367
|
+
);
|
|
368
|
+
const counts = { critical: 0, high: 0, medium: 0, low: 0 };
|
|
369
|
+
for (const f of findings) {
|
|
370
|
+
const sev = String(f.severity ?? "low");
|
|
371
|
+
if (sev in counts) counts[sev]++;
|
|
372
|
+
}
|
|
373
|
+
return counts;
|
|
374
|
+
}
|
|
375
|
+
function groupByDimension(report) {
|
|
376
|
+
const findings = (
|
|
377
|
+
/** @type {Array<Record<string, unknown>>} */
|
|
378
|
+
report.findings ?? []
|
|
379
|
+
);
|
|
380
|
+
const groups = {};
|
|
381
|
+
for (const f of findings) {
|
|
382
|
+
const dim = String(f.dimension ?? "unknown");
|
|
383
|
+
if (!groups[dim]) groups[dim] = [];
|
|
384
|
+
groups[dim].push(f);
|
|
385
|
+
}
|
|
386
|
+
return groups;
|
|
387
|
+
}
|
|
388
|
+
function buildTriageState(report) {
|
|
389
|
+
const findings = (
|
|
390
|
+
/** @type {Array<unknown>} */
|
|
391
|
+
report.findings ?? []
|
|
392
|
+
);
|
|
393
|
+
const state = {};
|
|
394
|
+
for (let i = 0; i < findings.length; i++) {
|
|
395
|
+
state[String(i)] = "keep";
|
|
396
|
+
}
|
|
397
|
+
return state;
|
|
398
|
+
}
|
|
399
|
+
function hashReport(report) {
|
|
400
|
+
const convergence = (
|
|
401
|
+
/** @type {Record<string, unknown>} */
|
|
402
|
+
report.convergence ?? {}
|
|
403
|
+
);
|
|
404
|
+
const totalRounds = String(convergence.totalRounds ?? "");
|
|
405
|
+
const findingsCount = String(
|
|
406
|
+
/** @type {Array<unknown>} */
|
|
407
|
+
(report.findings ?? []).length
|
|
408
|
+
);
|
|
409
|
+
const firstFinding = (
|
|
410
|
+
/** @type {Array<Record<string, unknown>>} */
|
|
411
|
+
(report.findings ?? [])[0]
|
|
412
|
+
);
|
|
413
|
+
const firstSummary = firstFinding ? String(firstFinding.summary ?? "") : "";
|
|
414
|
+
const mode = String(report.mode ?? "");
|
|
415
|
+
return `iterate-triage-${mode}-${totalRounds}-${findingsCount}-${firstSummary.slice(0, 20)}`;
|
|
416
|
+
}
|
|
417
|
+
function toKnownIntentionalYaml(entries) {
|
|
418
|
+
if (!entries || entries.length === 0) return "";
|
|
419
|
+
const lines = ["known_intentional:"];
|
|
420
|
+
for (const e of entries) {
|
|
421
|
+
lines.push(` - file: ${JSON.stringify(e.file)}`);
|
|
422
|
+
if (e.line !== void 0 && e.line > 0) {
|
|
423
|
+
lines.push(` line: ${e.line}`);
|
|
424
|
+
}
|
|
425
|
+
lines.push(` dimension: ${JSON.stringify(e.dimension)}`);
|
|
426
|
+
lines.push(` reason: ${JSON.stringify(e.reason)}`);
|
|
427
|
+
}
|
|
428
|
+
return lines.join("\n");
|
|
429
|
+
}
|
|
430
|
+
function buildApplyInstruction(entries) {
|
|
431
|
+
if (!entries || entries.length === 0) return "";
|
|
432
|
+
const payload = JSON.stringify(
|
|
433
|
+
{
|
|
434
|
+
operation: "apply",
|
|
435
|
+
entries: entries.map((e) => ({
|
|
436
|
+
file: e.file,
|
|
437
|
+
...e.line !== void 0 ? { line: e.line } : {},
|
|
438
|
+
dimension: e.dimension,
|
|
439
|
+
reason: e.reason
|
|
440
|
+
}))
|
|
441
|
+
},
|
|
442
|
+
null,
|
|
443
|
+
2
|
|
444
|
+
);
|
|
445
|
+
return `Please call \`iterate_triage\` with the following payload to apply the triage verdicts:
|
|
446
|
+
|
|
447
|
+
\`\`\`json
|
|
448
|
+
${payload}
|
|
449
|
+
\`\`\``;
|
|
450
|
+
}
|
|
451
|
+
function collectIgnoredEntries(triageState, findings) {
|
|
452
|
+
const entries = [];
|
|
453
|
+
for (const [idx, verdict] of Object.entries(triageState)) {
|
|
454
|
+
if (verdict !== "ignore") continue;
|
|
455
|
+
const finding = findings[Number(idx)];
|
|
456
|
+
if (!finding) continue;
|
|
457
|
+
entries.push({
|
|
458
|
+
file: String(finding.file ?? ""),
|
|
459
|
+
...typeof finding.line === "number" && finding.line > 0 ? { line: finding.line } : {},
|
|
460
|
+
dimension: String(finding.dimension ?? ""),
|
|
461
|
+
reason: String(finding.summary ?? "")
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
return entries;
|
|
465
|
+
}
|
|
466
|
+
function normalizeFindingFilter(filter) {
|
|
467
|
+
const f = filter && typeof filter === "object" ? filter : {};
|
|
468
|
+
const severities = Array.isArray(f.severities) ? f.severities.filter((s) => SEVERITY_ORDER.includes(String(s))) : [];
|
|
469
|
+
const dimensions = Array.isArray(f.dimensions) ? f.dimensions.filter((d) => typeof d === "string" && d.length > 0) : [];
|
|
470
|
+
const search = typeof f.search === "string" ? f.search.trim().toLowerCase() : "";
|
|
471
|
+
return { severities, dimensions, search };
|
|
472
|
+
}
|
|
473
|
+
function findingMatches(finding, filter) {
|
|
474
|
+
const f = normalizeFindingFilter(filter);
|
|
475
|
+
const sev = String(finding.severity ?? "low");
|
|
476
|
+
if (f.severities.length > 0 && !f.severities.includes(sev)) return false;
|
|
477
|
+
const dim = String(finding.dimension ?? "");
|
|
478
|
+
if (f.dimensions.length > 0 && !f.dimensions.includes(dim)) return false;
|
|
479
|
+
if (f.search) {
|
|
480
|
+
const haystack = [
|
|
481
|
+
String(finding.file ?? ""),
|
|
482
|
+
String(finding.summary ?? ""),
|
|
483
|
+
String(finding.dimension ?? ""),
|
|
484
|
+
String(finding.suggested_fix ?? "")
|
|
485
|
+
].join(" ").toLowerCase();
|
|
486
|
+
if (haystack.indexOf(f.search) < 0) return false;
|
|
487
|
+
}
|
|
488
|
+
return true;
|
|
489
|
+
}
|
|
490
|
+
function filterFindingsWithIndices(findings, filter) {
|
|
491
|
+
const f = normalizeFindingFilter(filter);
|
|
492
|
+
const list = Array.isArray(findings) ? findings : [];
|
|
493
|
+
const filtered = [];
|
|
494
|
+
const indices = [];
|
|
495
|
+
for (let i = 0; i < list.length; i++) {
|
|
496
|
+
if (findingMatches(list[i], f)) {
|
|
497
|
+
filtered.push(list[i]);
|
|
498
|
+
indices.push(i);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return { filtered, indices };
|
|
502
|
+
}
|
|
503
|
+
function buildFilterOptions(findings) {
|
|
504
|
+
const list = Array.isArray(findings) ? findings : [];
|
|
505
|
+
const severities = SEVERITY_ORDER.map((value) => ({ value, count: 0 }));
|
|
506
|
+
const dimCounts = {};
|
|
507
|
+
for (const f of list) {
|
|
508
|
+
const sev = String(f.severity ?? "low");
|
|
509
|
+
const sv = severities.find((s) => s.value === sev);
|
|
510
|
+
if (sv) sv.count++;
|
|
511
|
+
const dim = String(f.dimension ?? "unknown");
|
|
512
|
+
dimCounts[dim] = (dimCounts[dim] ?? 0) + 1;
|
|
513
|
+
}
|
|
514
|
+
return {
|
|
515
|
+
severities: severities.map((s) => ({ ...s })),
|
|
516
|
+
dimensions: Object.keys(dimCounts).map((value) => ({ value, count: dimCounts[value] }))
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
function countVerdicts(triageState) {
|
|
520
|
+
const counts = { keep: 0, skip: 0, ignore: 0 };
|
|
521
|
+
for (const v of Object.values(triageState ?? {})) {
|
|
522
|
+
if (v === "keep" || v === "skip" || v === "ignore") counts[v]++;
|
|
523
|
+
}
|
|
524
|
+
return counts;
|
|
525
|
+
}
|
|
526
|
+
function batchSetVerdict(triageState, indices, verdict) {
|
|
527
|
+
if (verdict !== "keep" && verdict !== "skip" && verdict !== "ignore") return triageState;
|
|
528
|
+
if (!Array.isArray(indices) || indices.length === 0) return triageState;
|
|
529
|
+
const next = { ...triageState };
|
|
530
|
+
for (const idx of indices) {
|
|
531
|
+
if (typeof idx === "number" && Number.isInteger(idx) && idx >= 0) {
|
|
532
|
+
next[String(idx)] = verdict;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
return next;
|
|
536
|
+
}
|
|
537
|
+
function setAllVerdicts(triageState, verdict, indices) {
|
|
538
|
+
const targets = Array.isArray(indices) ? indices : Object.keys(triageState ?? {}).map(Number);
|
|
539
|
+
return batchSetVerdict(triageState, targets, verdict);
|
|
540
|
+
}
|
|
541
|
+
function buildRoundHistory(report) {
|
|
542
|
+
const rounds = Array.isArray(report.rounds) ? report.rounds : [];
|
|
543
|
+
return rounds.map((r) => {
|
|
544
|
+
const rr = (
|
|
545
|
+
/** @type {Record<string, unknown>} */
|
|
546
|
+
r
|
|
547
|
+
);
|
|
548
|
+
const findings = Array.isArray(rr.findings) ? rr.findings : [];
|
|
549
|
+
const sev = severityStats({ findings });
|
|
550
|
+
return {
|
|
551
|
+
round: typeof rr.round === "number" ? rr.round : 0,
|
|
552
|
+
count: findings.length,
|
|
553
|
+
critical: sev.critical,
|
|
554
|
+
high: sev.high,
|
|
555
|
+
medium: sev.medium,
|
|
556
|
+
low: sev.low
|
|
557
|
+
};
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
function buildFindingTrend(report) {
|
|
561
|
+
const conv = (
|
|
562
|
+
/** @type {Record<string, unknown>} */
|
|
563
|
+
report.convergence ?? {}
|
|
564
|
+
);
|
|
565
|
+
if (Array.isArray(conv.findingsByRound)) {
|
|
566
|
+
return conv.findingsByRound.map((n, i) => ({ round: i + 1, count: typeof n === "number" ? n : 0 }));
|
|
567
|
+
}
|
|
568
|
+
return buildRoundHistory(report).map((h) => ({ round: h.round, count: h.count }));
|
|
569
|
+
}
|
|
570
|
+
function computeTrendMetrics(report) {
|
|
571
|
+
const conv = (
|
|
572
|
+
/** @type {Record<string, unknown>} */
|
|
573
|
+
report.convergence ?? {}
|
|
574
|
+
);
|
|
575
|
+
const points = buildFindingTrend(report);
|
|
576
|
+
const total = points.reduce((sum, p) => sum + p.count, 0);
|
|
577
|
+
const firstRound = points.length > 0 ? points[0].count : 0;
|
|
578
|
+
const lastRound = points.length > 0 ? points[points.length - 1].count : 0;
|
|
579
|
+
const reductionPercent = firstRound > 0 ? Math.round((firstRound - lastRound) / firstRound * 100) : 0;
|
|
580
|
+
return {
|
|
581
|
+
points,
|
|
582
|
+
total,
|
|
583
|
+
firstRound,
|
|
584
|
+
lastRound,
|
|
585
|
+
reductionPercent,
|
|
586
|
+
converged: conv.converged === true
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
function trendMax(points) {
|
|
590
|
+
let max = 1;
|
|
591
|
+
for (const p of Array.isArray(points) ? points : []) {
|
|
592
|
+
if (typeof p.count === "number" && p.count > max) max = p.count;
|
|
593
|
+
}
|
|
594
|
+
return max;
|
|
595
|
+
}
|
|
596
|
+
function buildCompletionSummary(report) {
|
|
597
|
+
const conv = (
|
|
598
|
+
/** @type {Record<string, unknown>} */
|
|
599
|
+
report.convergence ?? {}
|
|
600
|
+
);
|
|
601
|
+
const rounds = getCurrentRound(report);
|
|
602
|
+
const total = getTotalRounds(report);
|
|
603
|
+
const stats = severityStats(report);
|
|
604
|
+
const converged = conv.converged === true;
|
|
605
|
+
const reason = converged ? "\u5DF2\u6536\u655B" : `\u5DF2\u8FBE\u6700\u5927\u8F6E\u6570 ${total}`;
|
|
606
|
+
const totalFindings = stats.critical + stats.high + stats.medium + stats.low;
|
|
607
|
+
return `iterate \u8BC4\u5BA1\u5B8C\u6210 \xB7 ${rounds}/${total} \u8F6E \xB7 ${totalFindings} \u9879\u53D1\u73B0 \xB7 ${reason}`;
|
|
608
|
+
}
|
|
609
|
+
var CONFIG_EDIT_FIELDS = [
|
|
610
|
+
{ key: "goal", label: "\u76EE\u6807", hint: "\u4E00\u53E5\u8BDD\u63CF\u8FF0\u672C\u6B21\u8FED\u4EE3\u76EE\u6807\uFF08\u5B57\u7B26\u4E32\uFF09" },
|
|
611
|
+
{ key: "dimensions", label: "\u5BA1\u67E5\u7EF4\u5EA6", hint: '\u6570\u7EC4\uFF0C\u5982 ["correctness","security"]' },
|
|
612
|
+
{ key: "max_rounds", label: "\u6700\u5927\u8F6E\u6570", hint: "\u6B63\u6574\u6570" },
|
|
613
|
+
{ key: "review.scope", label: "\u5BA1\u67E5\u8303\u56F4", hint: '"full" \u6216 "changed-only"' },
|
|
614
|
+
{ key: "atomic.max_lines", label: "\u539F\u5B50\u4FEE\u590D\u4E0A\u9650\u884C\u6570", hint: "\u6B63\u6574\u6570" },
|
|
615
|
+
{ key: "git.push_per_round", label: "\u6BCF\u8F6E\u63A8\u9001", hint: "true / false" }
|
|
616
|
+
];
|
|
617
|
+
function buildConfigEditGuide() {
|
|
618
|
+
const lines = [
|
|
619
|
+
"iterate \u914D\u7F6E\u7F16\u8F91\u6307\u5F15",
|
|
620
|
+
"---------------------",
|
|
621
|
+
"\u914D\u7F6E\u6587\u4EF6\uFF1A\u9879\u76EE\u6839\u76EE\u5F55 iterate.config.yaml\u3002",
|
|
622
|
+
"",
|
|
623
|
+
"\u53EF\u7F16\u8F91\u5B57\u6BB5\uFF1A",
|
|
624
|
+
...CONFIG_EDIT_FIELDS.map((f) => `- ${f.key}\uFF08${f.label}\uFF09\uFF1A${f.hint}`),
|
|
625
|
+
"",
|
|
626
|
+
"\u8BA9\u6A21\u578B\u5E2E\u4F60\u6539\uFF1A",
|
|
627
|
+
'1. \u8C03\u7528 iterate_config({ operation: "read" }) \u67E5\u770B\u5F53\u524D\u914D\u7F6E\uFF1B',
|
|
628
|
+
"2. \u8BF4\u660E\u60F3\u6539\u7684\u5B57\u6BB5\uFF0C\u4F8B\u5982\u300C\u628A max_rounds \u6539\u6210 5\uFF0Cdimensions \u53EA\u4FDD\u7559 correctness \u548C security\u300D\uFF1B",
|
|
629
|
+
'3. \u6A21\u578B\u4F1A\u8C03\u7528 iterate_config({ operation: "write", updates: {...} }) \u5199\u5165\uFF0C\u5199\u5165\u524D\u81EA\u52A8\u5907\u4EFD\uFF0C\u5931\u8D25\u81EA\u52A8\u56DE\u6EDA\u3002'
|
|
630
|
+
];
|
|
631
|
+
return lines.join("\n");
|
|
632
|
+
}
|
|
633
|
+
var VERDICT_SHORTCUTS = {
|
|
634
|
+
y: "keep",
|
|
635
|
+
Y: "keep",
|
|
636
|
+
n: "skip",
|
|
637
|
+
N: "skip",
|
|
638
|
+
a: "ignore",
|
|
639
|
+
A: "ignore"
|
|
640
|
+
};
|
|
641
|
+
function keyToVerdict(key) {
|
|
642
|
+
return VERDICT_SHORTCUTS[key] ?? null;
|
|
643
|
+
}
|
|
644
|
+
function allVerdictKeys(triageState) {
|
|
645
|
+
const state = triageState && typeof triageState === "object" ? triageState : {};
|
|
646
|
+
return Object.keys(state).map(Number).filter((n) => Number.isInteger(n) && n >= 0).sort((a, b) => a - b);
|
|
647
|
+
}
|
|
648
|
+
var RUNTIME_ARTIFACTS = [
|
|
649
|
+
{
|
|
650
|
+
key: "decision-log.jsonl",
|
|
651
|
+
label: "\u51B3\u7B56\u65E5\u5FD7",
|
|
652
|
+
hint: "\u8FFD\u52A0\u5F0F JSONL\uFF0C\u8BB0\u5F55\u6BCF\u8F6E plan / review / fix / revert / validation \u51B3\u7B56"
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
key: "checkpoint.json",
|
|
656
|
+
label: "\u8FED\u4EE3\u65AD\u70B9",
|
|
657
|
+
hint: "\u957F\u8FED\u4EE3\u7684\u8FDB\u5EA6\u5FEB\u7167\uFF0C\u4E2D\u65AD\u540E\u53EF\u6062\u590D\uFF08iterate_checkpoint\uFF09"
|
|
658
|
+
},
|
|
659
|
+
{
|
|
660
|
+
key: "fixes/registry.json",
|
|
661
|
+
label: "\u4FEE\u590D\u6CE8\u518C\u8868",
|
|
662
|
+
hint: "\u6BCF\u4E2A\u539F\u5B50\u4FEE\u590D\u7684 id / diff / \u5907\u4EFD\u8DEF\u5F84\uFF08iterate_fix / iterate_diff\uFF09"
|
|
663
|
+
},
|
|
664
|
+
{
|
|
665
|
+
key: "fixes/*.bak",
|
|
666
|
+
label: "\u4FEE\u590D\u5907\u4EFD",
|
|
667
|
+
hint: "\u6BCF\u6B21\u4FEE\u590D\u524D\u7684\u539F\u6587\u4EF6\u5907\u4EFD\uFF0C\u56DE\u6EDA\u4F9D\u8D56\uFF08iterate_rollback\uFF09"
|
|
668
|
+
}
|
|
669
|
+
];
|
|
670
|
+
function buildRuntimeStatusGuide() {
|
|
671
|
+
const lines = [
|
|
672
|
+
"iterate \u8FD0\u884C\u65F6\u72B6\u6001\u6982\u89C8",
|
|
673
|
+
"----------------------",
|
|
674
|
+
"\u6240\u6709\u8FD0\u884C\u65F6\u4EA7\u7269\u4F4D\u4E8E\u9879\u76EE\u6839\u76EE\u5F55 .iterate/ \u4E0B\uFF1A",
|
|
675
|
+
"",
|
|
676
|
+
...RUNTIME_ARTIFACTS.map((a) => `- ${a.key}\uFF08${a.label}\uFF09\uFF1A${a.hint}`),
|
|
677
|
+
"",
|
|
678
|
+
"\u67E5\u770B\u72B6\u6001\uFF1A\u8BA9\u6A21\u578B\u8C03\u7528 iterate_status\uFF08\u6C47\u603B\uFF09\u6216 iterate_history\uFF08\u660E\u7EC6\uFF09\u3002",
|
|
679
|
+
"\u6E05\u7406\u72B6\u6001\uFF1A\u8BA9\u6A21\u578B\u8C03\u7528 iterate_prune\uFF08\u9ED8\u8BA4 dry-run\uFF0C\u53EA\u62A5\u544A\u4E0D\u5220\u9664\uFF0C\u663E\u5F0F dryRun:false \u624D\u771F\u6B63\u6E05\u7406\uFF09\u3002"
|
|
680
|
+
];
|
|
681
|
+
return lines.join("\n");
|
|
86
682
|
}
|
|
87
683
|
|
|
88
|
-
|
|
89
|
-
|
|
684
|
+
// src/client/index.ts
|
|
685
|
+
var name = "iterate-plugin";
|
|
686
|
+
var inject = ["slots", "theme"];
|
|
687
|
+
var PLUGIN_TAG = "iterate-ui";
|
|
688
|
+
var TRIAGE_STORAGE_PREFIX = "iterate.triage.";
|
|
689
|
+
var THEME_STORAGE_KEY = "iterate.theme.enabled";
|
|
690
|
+
var THEME_SOURCE = "iterate";
|
|
691
|
+
var ITERATE_TOKENS = {
|
|
692
|
+
"--dsw-alias-bg-base": { light: "#FAF8F5", dark: "#171412" },
|
|
693
|
+
"--dsw-alias-bg-layer-1": { light: "#FFFFFF", dark: "#1F1B17" },
|
|
694
|
+
"--dsw-alias-bg-layer-2": { light: "#F4F1EA", dark: "#27221C" },
|
|
695
|
+
"--dsw-alias-bg-overlay": { light: "rgba(255,255,255,0.96)", dark: "rgba(23,20,18,0.96)" },
|
|
696
|
+
"--dsw-alias-border-l1": { light: "#E8E2D8", dark: "#332C25" },
|
|
697
|
+
"--dsw-alias-border-l2": { light: "#DDD5C9", dark: "#3C342B" },
|
|
698
|
+
"--dsw-alias-brand-primary": { light: "#B45309", dark: "#F59E0B" },
|
|
699
|
+
"--dsw-alias-label-primary": { light: "#1C1917", dark: "#F5F0EA" },
|
|
700
|
+
"--dsw-alias-label-secondary": { light: "#57534E", dark: "#A9A29B" },
|
|
701
|
+
"--dsw-alias-state-error-primary": { light: "#DC2626", dark: "#F87171" },
|
|
702
|
+
"--dsw-alias-state-success-primary": { light: "#15803D", dark: "#4ADE80" },
|
|
703
|
+
"--dsw-alias-state-warn-primary": { light: "#D97706", dark: "#FBBF24" },
|
|
704
|
+
"--dsw-specific-sidebar-fill": { light: "#F5F2EC", dark: "#14110E" }
|
|
705
|
+
};
|
|
706
|
+
var ITERATE_CSS = `
|
|
90
707
|
[data-iterate-root] { box-sizing: border-box; font-family: var(--dsw-font-sans, system-ui, sans-serif); }
|
|
91
708
|
[data-iterate-root] * , [data-iterate-root] *::before, [data-iterate-root] *::after { box-sizing: border-box; }
|
|
92
709
|
|
|
@@ -161,8 +778,6 @@ const ITERATE_CSS = `
|
|
|
161
778
|
@keyframes iterate-fadein { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
|
|
162
779
|
|
|
163
780
|
.iterate-settings { display: flex; flex-direction: column; gap: 12px; padding: 4px 2px 12px; }
|
|
164
|
-
.iterate-settings-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 0; border-bottom: 1px solid var(--dsw-alias-border-l1); }
|
|
165
|
-
.iterate-settings-row:last-child { border-bottom: none; }
|
|
166
781
|
.iterate-settings-title { font-size: 14px; font-weight: 600; color: var(--dsw-alias-label-primary); }
|
|
167
782
|
.iterate-settings-desc { font-size: 12px; color: var(--dsw-alias-label-secondary); margin-top: 3px; line-height: 1.5; }
|
|
168
783
|
.iterate-chip { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; border-radius: 6px; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); font-size: 11px; color: var(--dsw-alias-label-secondary); }
|
|
@@ -197,7 +812,6 @@ const ITERATE_CSS = `
|
|
|
197
812
|
.iterate-completion[data-ok] { border: 1px solid var(--dsw-alias-state-success-primary); color: var(--dsw-alias-state-success-primary); background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 10%, transparent); }
|
|
198
813
|
.iterate-completion[data-warn] { border: 1px solid var(--dsw-alias-state-warn-primary); color: var(--dsw-alias-state-warn-primary); background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 10%, transparent); }
|
|
199
814
|
.iterate-capsule[data-ok] { border-color: var(--dsw-alias-state-success-primary); color: var(--dsw-alias-state-success-primary); }
|
|
200
|
-
.iterate-settings-guide { white-space: pre-wrap; padding: 8px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px; background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); font-family: var(--dsw-font-mono, ui-monospace, monospace); font-size: 11px; max-height: 180px; overflow: auto; }
|
|
201
815
|
.iterate-chip[data-ok] { border-color: var(--dsw-alias-state-success-primary); color: var(--dsw-alias-state-success-primary); background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 10%, transparent); }
|
|
202
816
|
.iterate-batch-check { display: inline-flex; align-items: center; gap: 4px; padding: 3px 8px; border-radius: 6px; border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-secondary); font-size: 11px; cursor: pointer; }
|
|
203
817
|
.iterate-batch-check input { margin: 0; cursor: pointer; }
|
|
@@ -210,778 +824,904 @@ const ITERATE_CSS = `
|
|
|
210
824
|
.iterate-verdict-detail { display: inline-flex; align-items: center; gap: 10px; flex-wrap: wrap; color: var(--dsw-alias-label-secondary); }
|
|
211
825
|
.iterate-verdict-item { white-space: nowrap; }
|
|
212
826
|
.iterate-verdict-item b { color: var(--dsw-alias-label-primary); font-weight: 600; }
|
|
213
|
-
`
|
|
214
|
-
|
|
215
|
-
// ─── Small helpers ───────────────────────────────────────────────────────────
|
|
216
827
|
|
|
217
|
-
|
|
828
|
+
/* Settings page redesign: grouped cards + switch + code blocks. */
|
|
829
|
+
.iterate-settings { display: flex; flex-direction: column; gap: 14px; padding: 6px 2px 16px; }
|
|
830
|
+
.iterate-scard { border: 1px solid var(--dsw-alias-border-l1); border-radius: 12px; background: var(--dsw-alias-bg-layer-1); padding: 14px 16px; }
|
|
831
|
+
.iterate-scard-head { display: flex; align-items: center; justify-content: space-between; gap: 14px; }
|
|
832
|
+
.iterate-scard-title { font-size: 14px; font-weight: 650; color: var(--dsw-alias-label-primary); letter-spacing: -0.01em; }
|
|
833
|
+
.iterate-scard-desc { font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-secondary); margin-top: 4px; }
|
|
834
|
+
.iterate-scard-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
|
|
835
|
+
|
|
836
|
+
/* Status pill */
|
|
837
|
+
.iterate-pill { display: inline-flex; align-items: center; gap: 7px; padding: 3px 11px; border-radius: 999px; font-size: 11px; font-weight: 600; white-space: nowrap; color: var(--dsw-alias-state-success-primary); background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 12%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-state-success-primary) 28%, transparent); }
|
|
838
|
+
.iterate-pill-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
|
|
839
|
+
|
|
840
|
+
/* Accessibility-switch toggle */
|
|
841
|
+
.iterate-switch { position: relative; width: 42px; height: 24px; border-radius: 999px; padding: 0; cursor: pointer; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); transition: background-color 160ms ease, border-color 160ms ease; }
|
|
842
|
+
.iterate-switch:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 2px; }
|
|
843
|
+
.iterate-switch-knob { position: absolute; top: 3px; left: 3px; width: 16px; height: 16px; border-radius: 50%; background: var(--dsw-alias-label-secondary); transition: transform 160ms ease, background-color 160ms ease; }
|
|
844
|
+
.iterate-switch[data-on] { background: var(--dsw-alias-brand-primary); border-color: var(--dsw-alias-brand-primary); }
|
|
845
|
+
.iterate-switch[data-on] .iterate-switch-knob { transform: translateX(18px); background: #FFFFFF; }
|
|
846
|
+
|
|
847
|
+
/* Button variants */
|
|
848
|
+
.iterate-btn[data-ghost] { background: transparent; }
|
|
849
|
+
.iterate-btn[data-danger] { border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary) 45%, transparent); color: var(--dsw-alias-state-error-primary); background: transparent; }
|
|
850
|
+
.iterate-btn[data-danger]:hover { background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent); }
|
|
851
|
+
.iterate-btn[data-confirm] { border-color: var(--dsw-alias-state-error-primary); color: #FFFFFF; background: var(--dsw-alias-state-error-primary); }
|
|
852
|
+
|
|
853
|
+
/* Collapsible guide / status code blocks */
|
|
854
|
+
.iterate-guide { margin-top: 4px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 10px; overflow: hidden; background: var(--dsw-alias-bg-layer-2); }
|
|
855
|
+
.iterate-guide-bar { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 8px 12px; background: var(--dsw-alias-bg-layer-2); border-bottom: 1px solid var(--dsw-alias-border-l1); font-size: 12px; color: var(--dsw-alias-label-secondary); }
|
|
856
|
+
.iterate-guide-body { padding: 12px 14px; font-family: var(--dsw-font-mono, ui-monospace, monospace); font-size: 11.5px; line-height: 1.75; white-space: pre-wrap; color: var(--dsw-alias-label-primary); max-height: 260px; overflow: auto; }
|
|
857
|
+
`;
|
|
218
858
|
function log(...args) {
|
|
219
|
-
if (typeof console !==
|
|
220
|
-
console.error(`[${PLUGIN_TAG}]`, ...args)
|
|
859
|
+
if (typeof console !== "undefined" && typeof console.error === "function") {
|
|
860
|
+
console.error(`[${PLUGIN_TAG}]`, ...args);
|
|
221
861
|
}
|
|
222
862
|
}
|
|
223
|
-
|
|
224
|
-
/** Resolve the React runtime (ctx.React first, then window.React). */
|
|
225
|
-
function resolveReact(ctx) {
|
|
226
|
-
if (ctx && ctx.React && typeof ctx.React.createElement === 'function') return ctx.React
|
|
227
|
-
const globalScope = typeof window !== 'undefined' ? window : typeof globalThis !== 'undefined' ? globalThis : null
|
|
228
|
-
if (globalScope && globalScope.React && typeof globalScope.React.createElement === 'function') {
|
|
229
|
-
return globalScope.React
|
|
230
|
-
}
|
|
231
|
-
return null
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/** Safe localStorage wrapper (never throws in private mode / SSR). */
|
|
235
863
|
function createStorage() {
|
|
236
864
|
try {
|
|
237
|
-
const testKey =
|
|
238
|
-
window.localStorage.setItem(testKey,
|
|
239
|
-
window.localStorage.removeItem(testKey)
|
|
865
|
+
const testKey = "__iterate_storage_test__";
|
|
866
|
+
window.localStorage.setItem(testKey, "1");
|
|
867
|
+
window.localStorage.removeItem(testKey);
|
|
240
868
|
return {
|
|
241
|
-
get(key) {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
869
|
+
get(key) {
|
|
870
|
+
return window.localStorage.getItem(key);
|
|
871
|
+
},
|
|
872
|
+
set(key, value) {
|
|
873
|
+
window.localStorage.setItem(key, value);
|
|
874
|
+
},
|
|
875
|
+
remove(key) {
|
|
876
|
+
window.localStorage.removeItem(key);
|
|
877
|
+
},
|
|
878
|
+
keys() {
|
|
879
|
+
return Object.keys(window.localStorage);
|
|
880
|
+
}
|
|
881
|
+
};
|
|
246
882
|
} catch {
|
|
247
|
-
const mem = new Map()
|
|
883
|
+
const mem = /* @__PURE__ */ new Map();
|
|
248
884
|
return {
|
|
249
|
-
get(key) {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
885
|
+
get(key) {
|
|
886
|
+
return mem.has(key) ? mem.get(key) : null;
|
|
887
|
+
},
|
|
888
|
+
set(key, value) {
|
|
889
|
+
mem.set(key, value);
|
|
890
|
+
},
|
|
891
|
+
remove(key) {
|
|
892
|
+
mem.delete(key);
|
|
893
|
+
},
|
|
894
|
+
keys() {
|
|
895
|
+
return [...mem.keys()];
|
|
896
|
+
}
|
|
897
|
+
};
|
|
254
898
|
}
|
|
255
899
|
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Remove every stored key with the given prefix (e.g. all triage verdicts).
|
|
259
|
-
* Returns how many keys were removed.
|
|
260
|
-
*/
|
|
261
900
|
function removeStorageByPrefix(prefix) {
|
|
262
|
-
if (!storage) return 0
|
|
263
|
-
let removed = 0
|
|
901
|
+
if (!storage) return 0;
|
|
902
|
+
let removed = 0;
|
|
264
903
|
try {
|
|
265
904
|
for (const key of storage.keys()) {
|
|
266
905
|
if (key.startsWith(prefix)) {
|
|
267
|
-
storage.remove(key)
|
|
268
|
-
removed
|
|
906
|
+
storage.remove(key);
|
|
907
|
+
removed++;
|
|
269
908
|
}
|
|
270
909
|
}
|
|
271
910
|
} catch (err) {
|
|
272
|
-
log(
|
|
911
|
+
log("failed to clear storage prefix", prefix, err);
|
|
273
912
|
}
|
|
274
|
-
return removed
|
|
913
|
+
return removed;
|
|
275
914
|
}
|
|
276
|
-
|
|
277
|
-
/** Copy text to the clipboard, returning whether it succeeded. */
|
|
278
915
|
function copyText(text) {
|
|
279
|
-
if (typeof navigator !==
|
|
916
|
+
if (typeof navigator !== "undefined" && navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
|
|
280
917
|
navigator.clipboard.writeText(text).then(
|
|
281
918
|
() => true,
|
|
282
|
-
() => false
|
|
283
|
-
)
|
|
284
|
-
return true
|
|
919
|
+
() => false
|
|
920
|
+
);
|
|
921
|
+
return true;
|
|
285
922
|
}
|
|
286
|
-
return false
|
|
923
|
+
return false;
|
|
287
924
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
925
|
+
var SEVERITY_KEYS = ["critical", "high", "medium", "low"];
|
|
926
|
+
function coerceSeverity(severity) {
|
|
927
|
+
return SEVERITY_KEYS.includes(severity ?? "") ? severity : "low";
|
|
928
|
+
}
|
|
929
|
+
function severityColor(severity) {
|
|
930
|
+
return SEVERITY_COLOR[coerceSeverity(severity)];
|
|
931
|
+
}
|
|
932
|
+
function severityLabel(severity) {
|
|
933
|
+
return SEVERITY_LABEL[coerceSeverity(severity)];
|
|
934
|
+
}
|
|
935
|
+
function readSlots(ctx) {
|
|
936
|
+
if (ctx && ctx.slots && typeof ctx.slots.inject === "function" && typeof ctx.slots.register === "function") {
|
|
937
|
+
return ctx.slots;
|
|
938
|
+
}
|
|
939
|
+
const raw = ctx && typeof ctx.get === "function" ? ctx.get("slots", false) : void 0;
|
|
940
|
+
if (raw && typeof raw.inject === "function" && typeof raw.register === "function") {
|
|
941
|
+
return raw;
|
|
942
|
+
}
|
|
943
|
+
return void 0;
|
|
944
|
+
}
|
|
945
|
+
function readTheme(ctx) {
|
|
946
|
+
if (ctx && ctx.theme && typeof ctx.theme.overrideTokens === "function") {
|
|
947
|
+
return ctx.theme;
|
|
948
|
+
}
|
|
949
|
+
const raw = ctx && typeof ctx.get === "function" ? ctx.get("theme", false) : void 0;
|
|
950
|
+
if (raw && typeof raw.overrideTokens === "function") {
|
|
951
|
+
return raw;
|
|
952
|
+
}
|
|
953
|
+
return void 0;
|
|
954
|
+
}
|
|
955
|
+
var slotsSvc = void 0;
|
|
956
|
+
var themeSvc = void 0;
|
|
957
|
+
var storage = null;
|
|
958
|
+
var themeDisposer = null;
|
|
959
|
+
var themeEnabled = true;
|
|
960
|
+
var roundPulseListeners = [];
|
|
304
961
|
function emitRoundPulse(round, converged) {
|
|
305
|
-
const payload = { round, converged: converged === true }
|
|
306
|
-
for (const fn of roundPulseListeners.slice()) fn(payload)
|
|
962
|
+
const payload = { round, converged: converged === true };
|
|
963
|
+
for (const fn of roundPulseListeners.slice()) fn(payload);
|
|
307
964
|
}
|
|
308
|
-
|
|
309
|
-
/** Apply the iterate theme skin (idempotent). */
|
|
310
965
|
function applyThemeSkin() {
|
|
311
|
-
if (!themeSvc || typeof themeSvc.overrideTokens !==
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
themeDisposer = themeSvc.overrideTokens(THEME_SOURCE, ITERATE_TOKENS)
|
|
966
|
+
if (!themeSvc || typeof themeSvc.overrideTokens !== "function") return;
|
|
967
|
+
clearThemeSkin();
|
|
968
|
+
themeDisposer = themeSvc.overrideTokens(THEME_SOURCE, ITERATE_TOKENS);
|
|
315
969
|
}
|
|
316
|
-
|
|
317
|
-
/** Remove the iterate theme skin (idempotent). */
|
|
318
970
|
function clearThemeSkin() {
|
|
319
971
|
if (themeDisposer) {
|
|
320
|
-
try {
|
|
321
|
-
|
|
972
|
+
try {
|
|
973
|
+
themeDisposer();
|
|
974
|
+
} catch (err) {
|
|
975
|
+
log("theme disposer failed", err);
|
|
976
|
+
}
|
|
977
|
+
themeDisposer = null;
|
|
322
978
|
}
|
|
323
979
|
}
|
|
324
|
-
|
|
325
|
-
/** Persist + apply the theme preference. */
|
|
326
980
|
function setThemeEnabled(enabled) {
|
|
327
|
-
themeEnabled = enabled
|
|
328
|
-
if (storage) storage.set(THEME_STORAGE_KEY, enabled ?
|
|
329
|
-
if (enabled) applyThemeSkin()
|
|
330
|
-
else clearThemeSkin()
|
|
981
|
+
themeEnabled = enabled;
|
|
982
|
+
if (storage) storage.set(THEME_STORAGE_KEY, enabled ? "1" : "0");
|
|
983
|
+
if (enabled) applyThemeSkin();
|
|
984
|
+
else clearThemeSkin();
|
|
331
985
|
}
|
|
332
|
-
|
|
333
|
-
// ─── Components (React.createElement trees) ──────────────────────────────────
|
|
334
|
-
|
|
335
|
-
/** Obtain a session snapshot defensively from the slot props. */
|
|
336
986
|
function sessionSnapshot(props) {
|
|
337
|
-
let session = null
|
|
338
|
-
|
|
339
|
-
|
|
987
|
+
let session = null;
|
|
988
|
+
const useSession = props && typeof props.useSession === "function" ? props.useSession : null;
|
|
989
|
+
if (useSession) {
|
|
990
|
+
try {
|
|
991
|
+
session = useSession();
|
|
992
|
+
} catch (err) {
|
|
993
|
+
log("useSession failed", err);
|
|
994
|
+
}
|
|
340
995
|
}
|
|
341
|
-
if (!session && props && props.session) session = props.session
|
|
342
|
-
return session
|
|
996
|
+
if (!session && props && props.session) session = props.session;
|
|
997
|
+
return session;
|
|
343
998
|
}
|
|
344
|
-
|
|
345
|
-
/** Find the latest report inside a session snapshot (normalized). */
|
|
346
999
|
function latestReport(session) {
|
|
347
|
-
if (!session) return null
|
|
348
|
-
const raw = scanSessionForReport(session) || findReportInObject(session,
|
|
349
|
-
return raw ? normalizeReport(raw) : null
|
|
1000
|
+
if (!session) return null;
|
|
1001
|
+
const raw = scanSessionForReport(session) || findReportInObject(session, void 0, 24);
|
|
1002
|
+
return raw ? normalizeReport(raw) : null;
|
|
350
1003
|
}
|
|
351
|
-
|
|
352
|
-
/** Inline bar chart of findings-by-round (pure divs, no SVG needed). */
|
|
353
1004
|
function TrendChart({ points }) {
|
|
354
|
-
if (!points || points.length === 0) return null
|
|
355
|
-
const max = trendMax(points)
|
|
356
|
-
const bars = points.map(
|
|
357
|
-
React.createElement(
|
|
1005
|
+
if (!points || points.length === 0) return null;
|
|
1006
|
+
const max = trendMax(points);
|
|
1007
|
+
const bars = points.map(
|
|
1008
|
+
(p) => React.createElement("div", {
|
|
358
1009
|
key: p.round,
|
|
359
|
-
className:
|
|
360
|
-
|
|
361
|
-
title: `Round ${p.round}
|
|
362
|
-
style: { height: `${Math.max(4, Math.round(
|
|
363
|
-
})
|
|
364
|
-
)
|
|
365
|
-
return React.createElement(
|
|
1010
|
+
className: "iterate-trend-bar",
|
|
1011
|
+
"data-hot": p.count > 0 ? "" : void 0,
|
|
1012
|
+
title: `Round ${p.round}\uFF1A${p.count} \u9879`,
|
|
1013
|
+
style: { height: `${Math.max(4, Math.round(p.count / max * 24))}px` }
|
|
1014
|
+
})
|
|
1015
|
+
);
|
|
1016
|
+
return React.createElement("div", { className: "iterate-trend", title: "\u5404\u8F6E\u53D1\u73B0\u6570\u91CF\u8D8B\u52BF" }, ...bars);
|
|
366
1017
|
}
|
|
367
|
-
|
|
368
|
-
/** Dashboard: live convergence strip above the composer. */
|
|
369
1018
|
function ConvergenceDashboard(props) {
|
|
370
|
-
const [pulseKey, setPulseKey] = React.useState(0)
|
|
371
|
-
const report = latestReport(sessionSnapshot(props))
|
|
372
|
-
|
|
1019
|
+
const [pulseKey, setPulseKey] = React.useState(0);
|
|
1020
|
+
const report = latestReport(sessionSnapshot(props));
|
|
373
1021
|
React.useEffect(() => {
|
|
374
|
-
if (!report) return
|
|
375
|
-
const cur = getCurrentRound(report)
|
|
376
|
-
const conv = report.convergence
|
|
377
|
-
emitRoundPulse(cur, conv
|
|
378
|
-
setPulseKey((k) => k + 1)
|
|
379
|
-
}, [report && hashReport(report) +
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
const
|
|
384
|
-
const
|
|
385
|
-
const
|
|
386
|
-
const
|
|
387
|
-
const
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
const fixCount = isNormal && summary && typeof summary.fixedCount === 'number' ? summary.fixedCount : null
|
|
404
|
-
const fixBadge = fixCount !== null
|
|
405
|
-
? React.createElement('span', {
|
|
406
|
-
className: 'iterate-metric',
|
|
407
|
-
key: 'fixes',
|
|
408
|
-
title: '本轮已应用的原子修复数(正常模式)',
|
|
409
|
-
}, `${String(fixCount)} fixes`)
|
|
410
|
-
: null
|
|
411
|
-
|
|
1022
|
+
if (!report) return;
|
|
1023
|
+
const cur = getCurrentRound(report);
|
|
1024
|
+
const conv = report.convergence;
|
|
1025
|
+
emitRoundPulse(cur, conv?.converged === true);
|
|
1026
|
+
setPulseKey((k) => k + 1);
|
|
1027
|
+
}, [report && hashReport(report) + ":" + getCurrentRound(report)]);
|
|
1028
|
+
if (!report) return null;
|
|
1029
|
+
const round = getCurrentRound(report);
|
|
1030
|
+
const total = getTotalRounds(report);
|
|
1031
|
+
const progress = computeConvergenceProgress(report);
|
|
1032
|
+
const stats = severityStats(report);
|
|
1033
|
+
const dims = groupByDimension(report);
|
|
1034
|
+
const trend = computeTrendMetrics(report);
|
|
1035
|
+
const dimBadges = Object.keys(dims).slice(0, 6).map(
|
|
1036
|
+
(dim) => React.createElement(
|
|
1037
|
+
"span",
|
|
1038
|
+
{ key: dim, className: "iterate-dim-badge" },
|
|
1039
|
+
`${dim} \xB7 ${dims[dim]?.length ?? 0}`
|
|
1040
|
+
)
|
|
1041
|
+
);
|
|
1042
|
+
const mode = report.mode;
|
|
1043
|
+
const summary = report.summary;
|
|
1044
|
+
const isNormal = mode === "normal";
|
|
1045
|
+
const fixCount = isNormal && summary && typeof summary.fixedCount === "number" ? summary.fixedCount : null;
|
|
1046
|
+
const fixBadge = fixCount !== null ? React.createElement("span", {
|
|
1047
|
+
className: "iterate-metric",
|
|
1048
|
+
key: "fixes",
|
|
1049
|
+
title: "\u672C\u8F6E\u5DF2\u5E94\u7528\u7684\u539F\u5B50\u4FEE\u590D\u6570\uFF08\u6B63\u5E38\u6A21\u5F0F\uFF09"
|
|
1050
|
+
}, `${String(fixCount)} fixes`) : null;
|
|
412
1051
|
return React.createElement(
|
|
413
|
-
|
|
414
|
-
{
|
|
1052
|
+
"div",
|
|
1053
|
+
{ "data-iterate-root": "", "data-iterate": "dashboard", className: "iterate-dashboard" },
|
|
415
1054
|
React.createElement(
|
|
416
|
-
|
|
417
|
-
{ className:
|
|
418
|
-
`Round ${round} / ${total}
|
|
1055
|
+
"span",
|
|
1056
|
+
{ className: "iterate-round-badge", "data-pulse": pulseKey > 0 ? "" : void 0, key: `round-${round}` },
|
|
1057
|
+
`Round ${round} / ${total}`
|
|
419
1058
|
),
|
|
420
|
-
React.createElement(
|
|
421
|
-
|
|
1059
|
+
React.createElement(
|
|
1060
|
+
"div",
|
|
1061
|
+
{ className: "iterate-progress" },
|
|
1062
|
+
React.createElement("div", { className: "iterate-progress-fill", style: { width: `${progress}%` } })
|
|
422
1063
|
),
|
|
423
|
-
React.createElement(
|
|
424
|
-
|
|
425
|
-
|
|
1064
|
+
React.createElement(
|
|
1065
|
+
"span",
|
|
1066
|
+
{ className: "iterate-metric" },
|
|
1067
|
+
React.createElement("span", { className: "iterate-sev-dot", style: { background: SEVERITY_COLOR.critical } }),
|
|
1068
|
+
stats.critical
|
|
426
1069
|
),
|
|
427
|
-
React.createElement(
|
|
428
|
-
|
|
429
|
-
|
|
1070
|
+
React.createElement(
|
|
1071
|
+
"span",
|
|
1072
|
+
{ className: "iterate-metric" },
|
|
1073
|
+
React.createElement("span", { className: "iterate-sev-dot", style: { background: SEVERITY_COLOR.high } }),
|
|
1074
|
+
stats.high
|
|
430
1075
|
),
|
|
431
|
-
React.createElement(
|
|
432
|
-
|
|
433
|
-
|
|
1076
|
+
React.createElement(
|
|
1077
|
+
"span",
|
|
1078
|
+
{ className: "iterate-metric" },
|
|
1079
|
+
React.createElement("span", { className: "iterate-sev-dot", style: { background: SEVERITY_COLOR.medium } }),
|
|
1080
|
+
stats.medium
|
|
434
1081
|
),
|
|
435
1082
|
fixBadge,
|
|
436
1083
|
React.createElement(TrendChart, { points: trend.points }),
|
|
437
|
-
...dimBadges
|
|
438
|
-
)
|
|
1084
|
+
...dimBadges
|
|
1085
|
+
);
|
|
439
1086
|
}
|
|
440
|
-
|
|
441
|
-
/** Stats card (empty-findings case of the turn-tail chain). */
|
|
442
1087
|
function StatsCard(props) {
|
|
443
|
-
const report = props.report
|
|
444
|
-
const [showHistory, setShowHistory] = React.useState(false)
|
|
445
|
-
const stats = severityStats(report)
|
|
446
|
-
const total = stats.critical + stats.high + stats.medium + stats.low
|
|
1088
|
+
const report = props.report;
|
|
1089
|
+
const [showHistory, setShowHistory] = React.useState(false);
|
|
1090
|
+
const stats = severityStats(report);
|
|
1091
|
+
const total = stats.critical + stats.high + stats.medium + stats.low;
|
|
447
1092
|
const rows = [
|
|
448
|
-
{ label:
|
|
449
|
-
{ label:
|
|
450
|
-
{ label:
|
|
451
|
-
{ label:
|
|
452
|
-
].map(
|
|
453
|
-
React.createElement(
|
|
454
|
-
|
|
455
|
-
|
|
1093
|
+
{ label: "Critical", value: stats.critical, color: SEVERITY_COLOR.critical },
|
|
1094
|
+
{ label: "High", value: stats.high, color: SEVERITY_COLOR.high },
|
|
1095
|
+
{ label: "Medium", value: stats.medium, color: SEVERITY_COLOR.medium },
|
|
1096
|
+
{ label: "Low", value: stats.low, color: SEVERITY_COLOR.low }
|
|
1097
|
+
].map(
|
|
1098
|
+
(r) => React.createElement(
|
|
1099
|
+
"div",
|
|
1100
|
+
{ key: r.label, className: "iterate-stat" },
|
|
1101
|
+
React.createElement("div", { className: "iterate-stat-num", style: { color: r.color } }, String(r.value)),
|
|
1102
|
+
React.createElement("div", { className: "iterate-stat-label" }, r.label)
|
|
1103
|
+
)
|
|
1104
|
+
);
|
|
1105
|
+
const history = buildRoundHistory(report);
|
|
1106
|
+
const trend = computeTrendMetrics(report);
|
|
1107
|
+
const historyRows = history.map(
|
|
1108
|
+
(h) => React.createElement(
|
|
1109
|
+
"tr",
|
|
1110
|
+
{ key: h.round },
|
|
1111
|
+
React.createElement("td", { className: "iterate-history-num" }, `Round ${h.round}`),
|
|
1112
|
+
React.createElement("td", {}, String(h.count)),
|
|
1113
|
+
React.createElement("td", { style: { color: SEVERITY_COLOR.critical } }, String(h.critical)),
|
|
1114
|
+
React.createElement("td", { style: { color: SEVERITY_COLOR.high } }, String(h.high)),
|
|
1115
|
+
React.createElement("td", { style: { color: SEVERITY_COLOR.medium } }, String(h.medium)),
|
|
1116
|
+
React.createElement("td", { style: { color: SEVERITY_COLOR.low } }, String(h.low))
|
|
1117
|
+
)
|
|
1118
|
+
);
|
|
1119
|
+
const trendLine = `\u9996\u8F6E ${trend.firstRound} \u2192 \u672B\u8F6E ${trend.lastRound} \u9879\uFF0C\u964D\u5E45 ${trend.reductionPercent}%${trend.converged ? "\uFF0C\u5DF2\u6536\u655B" : ""}`;
|
|
1120
|
+
const completion = buildCompletionSummary(report);
|
|
1121
|
+
return React.createElement(
|
|
1122
|
+
"div",
|
|
1123
|
+
{ "data-iterate-root": "", "data-iterate": "stats", className: "iterate-stats" },
|
|
1124
|
+
React.createElement(
|
|
1125
|
+
"div",
|
|
1126
|
+
{ className: "iterate-triage-head" },
|
|
1127
|
+
React.createElement("span", {}, "Iterate \xB7 \u6536\u655B\u7EDF\u8BA1"),
|
|
1128
|
+
React.createElement(
|
|
1129
|
+
"span",
|
|
1130
|
+
{ className: "iterate-triage-hint" },
|
|
1131
|
+
`Round ${getCurrentRound(report)}/${getTotalRounds(report)} \xB7 ${total} findings \xB7 ${report.convergence && report.convergence.converged ? "\u5DF2\u6536\u655B" : "\u672A\u6536\u655B"}`
|
|
1132
|
+
)
|
|
456
1133
|
),
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
React.createElement(
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
React.createElement('td', { style: { color: SEVERITY_COLOR.low } }, String(h.low)),
|
|
1134
|
+
React.createElement("div", { className: "iterate-stats-grid" }, ...rows),
|
|
1135
|
+
React.createElement("div", { className: "iterate-completion", "data-ok": trend.converged ? "" : void 0, "data-warn": trend.converged ? void 0 : "" }, completion),
|
|
1136
|
+
React.createElement(
|
|
1137
|
+
"div",
|
|
1138
|
+
{ className: "iterate-triage-foot", style: { marginTop: 8, borderRadius: 8 } },
|
|
1139
|
+
React.createElement("span", {}, trendLine),
|
|
1140
|
+
React.createElement("button", {
|
|
1141
|
+
className: "iterate-btn",
|
|
1142
|
+
"data-primary": showHistory ? "" : void 0,
|
|
1143
|
+
onClick: () => setShowHistory((v) => !v)
|
|
1144
|
+
}, showHistory ? "\u6536\u8D77\u5386\u53F2" : "\u5386\u53F2 / \u8D8B\u52BF")
|
|
469
1145
|
),
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
1146
|
+
showHistory ? React.createElement(
|
|
1147
|
+
"div",
|
|
1148
|
+
{ className: "iterate-history-body" },
|
|
1149
|
+
React.createElement(
|
|
1150
|
+
"table",
|
|
1151
|
+
{ className: "iterate-history-table" },
|
|
1152
|
+
React.createElement(
|
|
1153
|
+
"thead",
|
|
1154
|
+
{},
|
|
1155
|
+
React.createElement(
|
|
1156
|
+
"tr",
|
|
1157
|
+
{},
|
|
1158
|
+
React.createElement("th", {}, "\u8F6E\u6B21"),
|
|
1159
|
+
React.createElement("th", {}, "\u53D1\u73B0"),
|
|
1160
|
+
React.createElement("th", { style: { color: SEVERITY_COLOR.critical } }, "CRIT"),
|
|
1161
|
+
React.createElement("th", { style: { color: SEVERITY_COLOR.high } }, "HIGH"),
|
|
1162
|
+
React.createElement("th", { style: { color: SEVERITY_COLOR.medium } }, "MED"),
|
|
1163
|
+
React.createElement("th", { style: { color: SEVERITY_COLOR.low } }, "LOW")
|
|
1164
|
+
)
|
|
1165
|
+
),
|
|
1166
|
+
React.createElement("tbody", {}, ...historyRows)
|
|
479
1167
|
),
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
'data-primary': showHistory ? '' : undefined,
|
|
488
|
-
onClick: () => setShowHistory((v) => !v),
|
|
489
|
-
}, showHistory ? '收起历史' : '历史 / 趋势'),
|
|
490
|
-
),
|
|
491
|
-
showHistory
|
|
492
|
-
? React.createElement('div', { className: 'iterate-history-body' },
|
|
493
|
-
React.createElement('table', { className: 'iterate-history-table' },
|
|
494
|
-
React.createElement('thead', {},
|
|
495
|
-
React.createElement('tr', {},
|
|
496
|
-
React.createElement('th', {}, '轮次'),
|
|
497
|
-
React.createElement('th', {}, '发现'),
|
|
498
|
-
React.createElement('th', { style: { color: SEVERITY_COLOR.critical } }, 'CRIT'),
|
|
499
|
-
React.createElement('th', { style: { color: SEVERITY_COLOR.high } }, 'HIGH'),
|
|
500
|
-
React.createElement('th', { style: { color: SEVERITY_COLOR.medium } }, 'MED'),
|
|
501
|
-
React.createElement('th', { style: { color: SEVERITY_COLOR.low } }, 'LOW'),
|
|
502
|
-
),
|
|
503
|
-
),
|
|
504
|
-
React.createElement('tbody', {}, ...historyRows),
|
|
505
|
-
),
|
|
506
|
-
React.createElement('div', { className: 'iterate-history-trendline' },
|
|
507
|
-
React.createElement(TrendChart, { points: trend.points }),
|
|
508
|
-
),
|
|
509
|
-
)
|
|
510
|
-
: null,
|
|
511
|
-
)
|
|
1168
|
+
React.createElement(
|
|
1169
|
+
"div",
|
|
1170
|
+
{ className: "iterate-history-trendline" },
|
|
1171
|
+
React.createElement(TrendChart, { points: trend.points })
|
|
1172
|
+
)
|
|
1173
|
+
) : null
|
|
1174
|
+
);
|
|
512
1175
|
}
|
|
513
|
-
|
|
514
|
-
/** Triage panel: per-finding y / n / a with filters, batch ops, keyboard shortcuts. */
|
|
515
1176
|
function TriagePanel(props) {
|
|
516
|
-
const report = props.report
|
|
517
|
-
const findings = report.findings
|
|
518
|
-
const storageKey = TRIAGE_STORAGE_PREFIX + hashReport(report)
|
|
1177
|
+
const report = props.report;
|
|
1178
|
+
const findings = report.findings || [];
|
|
1179
|
+
const storageKey = TRIAGE_STORAGE_PREFIX + hashReport(report);
|
|
519
1180
|
const [verdicts, setVerdicts] = React.useState(() => {
|
|
520
|
-
const initial = buildTriageState(report)
|
|
521
|
-
const saved = storage ? storage.get(storageKey) : null
|
|
1181
|
+
const initial = buildTriageState(report);
|
|
1182
|
+
const saved = storage ? storage.get(storageKey) : null;
|
|
522
1183
|
if (saved) {
|
|
523
1184
|
try {
|
|
524
|
-
const parsed = JSON.parse(saved)
|
|
525
|
-
if (parsed && typeof parsed ===
|
|
1185
|
+
const parsed = JSON.parse(saved);
|
|
1186
|
+
if (parsed && typeof parsed === "object") {
|
|
526
1187
|
for (const key of Object.keys(initial)) {
|
|
527
|
-
if (parsed[key] ===
|
|
528
|
-
initial[key] = parsed[key]
|
|
1188
|
+
if (parsed[key] === "keep" || parsed[key] === "skip" || parsed[key] === "ignore") {
|
|
1189
|
+
initial[key] = parsed[key];
|
|
529
1190
|
}
|
|
530
1191
|
}
|
|
531
1192
|
}
|
|
532
|
-
} catch {
|
|
1193
|
+
} catch {
|
|
1194
|
+
}
|
|
533
1195
|
}
|
|
534
|
-
return initial
|
|
535
|
-
})
|
|
536
|
-
const [payload, setPayload] = React.useState(null)
|
|
537
|
-
const [copied, setCopied] = React.useState(false)
|
|
538
|
-
const [filter, setFilter] = React.useState({ severities: [], dimensions: [], search:
|
|
539
|
-
const [selected, setSelected] = React.useState(null)
|
|
540
|
-
const [selectAll, setSelectAll] = React.useState(false)
|
|
541
|
-
|
|
542
|
-
/** Persist + return the next verdicts state. */
|
|
1196
|
+
return initial;
|
|
1197
|
+
});
|
|
1198
|
+
const [payload, setPayload] = React.useState(null);
|
|
1199
|
+
const [copied, setCopied] = React.useState(false);
|
|
1200
|
+
const [filter, setFilter] = React.useState({ severities: [], dimensions: [], search: "" });
|
|
1201
|
+
const [selected, setSelected] = React.useState(null);
|
|
1202
|
+
const [selectAll, setSelectAll] = React.useState(false);
|
|
543
1203
|
const persistVerdicts = (next) => {
|
|
544
|
-
if (storage) storage.set(storageKey, JSON.stringify(next))
|
|
545
|
-
return next
|
|
546
|
-
}
|
|
547
|
-
|
|
1204
|
+
if (storage) storage.set(storageKey, JSON.stringify(next));
|
|
1205
|
+
return next;
|
|
1206
|
+
};
|
|
548
1207
|
const setVerdict = (index, verdict) => {
|
|
549
|
-
setVerdicts((prev) => persistVerdicts({ ...prev, [String(index)]: verdict }))
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
const
|
|
554
|
-
const
|
|
555
|
-
const
|
|
556
|
-
const
|
|
557
|
-
|
|
558
|
-
const
|
|
559
|
-
const
|
|
560
|
-
const
|
|
561
|
-
const clearFilter = () => setFilter({ severities: [], dimensions: [], search: '' })
|
|
562
|
-
|
|
563
|
-
// ── Batch operations (apply to the currently VISIBLE findings, or to ALL
|
|
564
|
-
// findings when the select-all toggle is on) ──────────────────────────
|
|
565
|
-
const allIndices = allVerdictKeys(verdicts)
|
|
566
|
-
const batchTarget = selectAll ? allIndices : indices
|
|
1208
|
+
setVerdicts((prev) => persistVerdicts({ ...prev, [String(index)]: verdict }));
|
|
1209
|
+
};
|
|
1210
|
+
const { filtered, indices } = filterFindingsWithIndices(findings, filter);
|
|
1211
|
+
const indicesKey = indices.join(",");
|
|
1212
|
+
const options = buildFilterOptions(findings);
|
|
1213
|
+
const isFilterActive = filter.severities.length > 0 || filter.dimensions.length > 0 || filter.search !== "";
|
|
1214
|
+
const setSeverityFilter = (value) => setFilter((f) => ({ ...f, severities: value ? [value] : [] }));
|
|
1215
|
+
const setDimensionFilter = (value) => setFilter((f) => ({ ...f, dimensions: value ? [value] : [] }));
|
|
1216
|
+
const setSearchFilter = (value) => setFilter((f) => ({ ...f, search: value }));
|
|
1217
|
+
const clearFilter = () => setFilter({ severities: [], dimensions: [], search: "" });
|
|
1218
|
+
const allIndices = allVerdictKeys(verdicts);
|
|
1219
|
+
const batchTarget = selectAll ? allIndices : indices;
|
|
567
1220
|
const applyBatch = (verdict) => {
|
|
568
|
-
setVerdicts((prev) => persistVerdicts(batchSetVerdict(prev, batchTarget, verdict)))
|
|
569
|
-
}
|
|
1221
|
+
setVerdicts((prev) => persistVerdicts(batchSetVerdict(prev, batchTarget, verdict)));
|
|
1222
|
+
};
|
|
570
1223
|
const applyBatchAll = (verdict) => {
|
|
571
|
-
setVerdicts((prev) => persistVerdicts(setAllVerdicts(prev, verdict)))
|
|
572
|
-
}
|
|
1224
|
+
setVerdicts((prev) => persistVerdicts(setAllVerdicts(prev, verdict)));
|
|
1225
|
+
};
|
|
573
1226
|
const doResetVerdicts = () => {
|
|
574
|
-
setVerdicts((prev) => persistVerdicts(setAllVerdicts(prev,
|
|
575
|
-
setSelectAll(false)
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
// ── Keyboard shortcuts (y / n / a on the selected finding, ↑/↓ to move) ──
|
|
1227
|
+
setVerdicts((prev) => persistVerdicts(setAllVerdicts(prev, "keep")));
|
|
1228
|
+
setSelectAll(false);
|
|
1229
|
+
};
|
|
579
1230
|
React.useEffect(() => {
|
|
580
|
-
const doc = typeof document !==
|
|
581
|
-
if (!doc) return
|
|
1231
|
+
const doc = typeof document !== "undefined" ? document : null;
|
|
1232
|
+
if (!doc) return;
|
|
582
1233
|
const onKeyDown = (ev) => {
|
|
583
|
-
const t = ev.target
|
|
584
|
-
if (t && (t.tagName ===
|
|
585
|
-
const verdict = keyToVerdict(ev.key)
|
|
1234
|
+
const t = ev.target;
|
|
1235
|
+
if (t && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.tagName === "SELECT")) return;
|
|
1236
|
+
const verdict = keyToVerdict(ev.key);
|
|
586
1237
|
if (verdict && selected !== null && indices.includes(selected)) {
|
|
587
|
-
ev.preventDefault()
|
|
588
|
-
setVerdict(selected, verdict)
|
|
589
|
-
const pos = indices.indexOf(selected)
|
|
590
|
-
const nextIdx = indices[pos + 1]
|
|
591
|
-
if (nextIdx !==
|
|
592
|
-
return
|
|
1238
|
+
ev.preventDefault();
|
|
1239
|
+
setVerdict(selected, verdict);
|
|
1240
|
+
const pos = indices.indexOf(selected);
|
|
1241
|
+
const nextIdx = indices[pos + 1];
|
|
1242
|
+
if (nextIdx !== void 0) setSelected(nextIdx);
|
|
1243
|
+
return;
|
|
593
1244
|
}
|
|
594
|
-
if (ev.key ===
|
|
595
|
-
ev.preventDefault()
|
|
596
|
-
const pos = selected === null ? -1 : indices.indexOf(selected)
|
|
597
|
-
setSelected(indices[Math.min(pos + 1, indices.length - 1)])
|
|
598
|
-
return
|
|
1245
|
+
if (ev.key === "ArrowDown" && indices.length > 0) {
|
|
1246
|
+
ev.preventDefault();
|
|
1247
|
+
const pos = selected === null ? -1 : indices.indexOf(selected);
|
|
1248
|
+
setSelected(indices[Math.min(pos + 1, indices.length - 1)] ?? null);
|
|
1249
|
+
return;
|
|
599
1250
|
}
|
|
600
|
-
if (ev.key ===
|
|
601
|
-
ev.preventDefault()
|
|
602
|
-
const pos = selected === null ? 0 : indices.indexOf(selected)
|
|
603
|
-
setSelected(indices[Math.max(pos - 1, 0)])
|
|
1251
|
+
if (ev.key === "ArrowUp" && indices.length > 0) {
|
|
1252
|
+
ev.preventDefault();
|
|
1253
|
+
const pos = selected === null ? 0 : indices.indexOf(selected);
|
|
1254
|
+
setSelected(indices[Math.max(pos - 1, 0)] ?? null);
|
|
604
1255
|
}
|
|
605
|
-
}
|
|
606
|
-
doc.addEventListener(
|
|
607
|
-
return () => doc.removeEventListener(
|
|
608
|
-
}, [selected, indicesKey])
|
|
609
|
-
|
|
610
|
-
const
|
|
611
|
-
const
|
|
612
|
-
const counts = countVerdicts(verdicts)
|
|
613
|
-
|
|
1256
|
+
};
|
|
1257
|
+
doc.addEventListener("keydown", onKeyDown);
|
|
1258
|
+
return () => doc.removeEventListener("keydown", onKeyDown);
|
|
1259
|
+
}, [selected, indicesKey]);
|
|
1260
|
+
const ignored = collectIgnoredEntries(verdicts, findings);
|
|
1261
|
+
const ignoredCount = ignored.length;
|
|
1262
|
+
const counts = countVerdicts(verdicts);
|
|
614
1263
|
const doCopyYaml = () => {
|
|
615
|
-
const yaml = toKnownIntentionalYaml(ignored)
|
|
616
|
-
if (!yaml) return
|
|
617
|
-
const ok = copyText(yaml)
|
|
618
|
-
setCopied(true)
|
|
619
|
-
setTimeout(() => setCopied(false), 1600)
|
|
620
|
-
if (!ok) setPayload(yaml)
|
|
621
|
-
}
|
|
622
|
-
|
|
1264
|
+
const yaml = toKnownIntentionalYaml(ignored);
|
|
1265
|
+
if (!yaml) return;
|
|
1266
|
+
const ok = copyText(yaml);
|
|
1267
|
+
setCopied(true);
|
|
1268
|
+
setTimeout(() => setCopied(false), 1600);
|
|
1269
|
+
if (!ok) setPayload(yaml);
|
|
1270
|
+
};
|
|
623
1271
|
const doBuildInstruction = () => {
|
|
624
|
-
const text = buildApplyInstruction(ignored)
|
|
625
|
-
setPayload(text)
|
|
626
|
-
if (text) copyText(text)
|
|
627
|
-
}
|
|
628
|
-
|
|
1272
|
+
const text = buildApplyInstruction(ignored);
|
|
1273
|
+
setPayload(text);
|
|
1274
|
+
if (text) copyText(text);
|
|
1275
|
+
};
|
|
629
1276
|
const rows = filtered.map((finding, i) => {
|
|
630
|
-
const index = indices[i]
|
|
631
|
-
const severity = finding.severity ||
|
|
632
|
-
const verdict = verdicts[String(index)] ||
|
|
633
|
-
const isSelected = selected === index
|
|
634
|
-
const btn = (label, value, title) =>
|
|
1277
|
+
const index = indices[i];
|
|
1278
|
+
const severity = finding.severity || "low";
|
|
1279
|
+
const verdict = verdicts[String(index)] || "keep";
|
|
1280
|
+
const isSelected = selected === index;
|
|
1281
|
+
const btn = (label, value, title) => React.createElement(
|
|
1282
|
+
"button",
|
|
1283
|
+
{
|
|
1284
|
+
key: value,
|
|
1285
|
+
className: "iterate-vbtn",
|
|
1286
|
+
"data-active": verdict === value ? value : void 0,
|
|
1287
|
+
title,
|
|
1288
|
+
onClick: () => setVerdict(index, value)
|
|
1289
|
+
},
|
|
1290
|
+
label
|
|
1291
|
+
);
|
|
1292
|
+
return React.createElement(
|
|
1293
|
+
"div",
|
|
1294
|
+
{
|
|
1295
|
+
key: String(index),
|
|
1296
|
+
className: "iterate-finding",
|
|
1297
|
+
"data-selected": isSelected ? "" : void 0,
|
|
1298
|
+
onClick: () => setSelected(index)
|
|
1299
|
+
},
|
|
635
1300
|
React.createElement(
|
|
636
|
-
|
|
637
|
-
{
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
},
|
|
644
|
-
label,
|
|
645
|
-
)
|
|
646
|
-
|
|
647
|
-
return React.createElement('div', {
|
|
648
|
-
key: String(index),
|
|
649
|
-
className: 'iterate-finding',
|
|
650
|
-
'data-selected': isSelected ? '' : undefined,
|
|
651
|
-
onClick: () => setSelected(index),
|
|
652
|
-
},
|
|
653
|
-
React.createElement('div', { className: 'iterate-finding-meta' },
|
|
654
|
-
React.createElement('span', { className: 'iterate-sev-dot', style: { background: SEVERITY_COLOR[severity] || SEVERITY_COLOR.low } }),
|
|
655
|
-
React.createElement('span', {}, String(SEVERITY_LABEL[severity] || 'LOW')),
|
|
656
|
-
React.createElement('span', { className: 'iterate-finding-file' }, String(finding.file || '?')),
|
|
657
|
-
finding.line ? React.createElement('span', {}, `:${finding.line}`) : null,
|
|
658
|
-
React.createElement('span', {}, String(finding.dimension || '')),
|
|
659
|
-
),
|
|
660
|
-
React.createElement('div', { className: 'iterate-finding-summary' }, String(finding.summary || '')),
|
|
661
|
-
React.createElement('div', { className: 'iterate-finding-actions' },
|
|
662
|
-
btn('y 修复', 'keep', '保留该 finding,进入修复'),
|
|
663
|
-
btn('n 跳过', 'skip', '跳过该 finding'),
|
|
664
|
-
btn('a 已知有意', 'ignore', '标记为已知有意并写入 known_intentional'),
|
|
1301
|
+
"div",
|
|
1302
|
+
{ className: "iterate-finding-meta" },
|
|
1303
|
+
React.createElement("span", { className: "iterate-sev-dot", style: { background: severityColor(severity) } }),
|
|
1304
|
+
React.createElement("span", {}, severityLabel(severity)),
|
|
1305
|
+
React.createElement("span", { className: "iterate-finding-file" }, String(finding.file || "?")),
|
|
1306
|
+
finding.line ? React.createElement("span", {}, `:${finding.line}`) : null,
|
|
1307
|
+
React.createElement("span", {}, String(finding.dimension || ""))
|
|
665
1308
|
),
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
1309
|
+
React.createElement("div", { className: "iterate-finding-summary" }, String(finding.summary || "")),
|
|
1310
|
+
React.createElement(
|
|
1311
|
+
"div",
|
|
1312
|
+
{ className: "iterate-finding-actions" },
|
|
1313
|
+
btn("y \u4FEE\u590D", "keep", "\u4FDD\u7559\u8BE5 finding\uFF0C\u8FDB\u5165\u4FEE\u590D"),
|
|
1314
|
+
btn("n \u8DF3\u8FC7", "skip", "\u8DF3\u8FC7\u8BE5 finding"),
|
|
1315
|
+
btn("a \u5DF2\u77E5\u6709\u610F", "ignore", "\u6807\u8BB0\u4E3A\u5DF2\u77E5\u6709\u610F\u5E76\u5199\u5165 known_intentional")
|
|
1316
|
+
)
|
|
1317
|
+
);
|
|
1318
|
+
});
|
|
1319
|
+
return React.createElement(
|
|
1320
|
+
"div",
|
|
1321
|
+
{ "data-iterate-root": "", "data-iterate": "triage", className: "iterate-triage" },
|
|
1322
|
+
React.createElement(
|
|
1323
|
+
"div",
|
|
1324
|
+
{ className: "iterate-triage-head" },
|
|
1325
|
+
React.createElement("span", {}, `Iterate \xB7 Findings \u5206\u8BCA (${filtered.length}/${findings.length})`),
|
|
1326
|
+
React.createElement("span", { className: "iterate-triage-hint" }, "y=\u4FEE\u590D \xB7 n=\u8DF3\u8FC7 \xB7 a=\u5DF2\u77E5\u6709\u610F \xB7 \u2191/\u2193 \u9009\u62E9")
|
|
673
1327
|
),
|
|
674
|
-
React.createElement(
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
),
|
|
1328
|
+
React.createElement(
|
|
1329
|
+
"div",
|
|
1330
|
+
{ className: "iterate-filter" },
|
|
1331
|
+
React.createElement(
|
|
1332
|
+
"select",
|
|
1333
|
+
{ className: "iterate-filter-select", value: filter.severities[0] || "", onChange: (e) => setSeverityFilter(e.target.value), title: "\u6309\u4E25\u91CD\u5EA6\u7B5B\u9009" },
|
|
1334
|
+
React.createElement("option", { value: "" }, "\u5168\u90E8\u4E25\u91CD\u5EA6"),
|
|
1335
|
+
...options.severities.map(
|
|
1336
|
+
(s) => React.createElement("option", { key: s.value, value: s.value }, `${severityLabel(s.value)} (${s.count})`)
|
|
1337
|
+
)
|
|
680
1338
|
),
|
|
681
|
-
React.createElement(
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
1339
|
+
React.createElement(
|
|
1340
|
+
"select",
|
|
1341
|
+
{ className: "iterate-filter-select", value: filter.dimensions[0] || "", onChange: (e) => setDimensionFilter(e.target.value), title: "\u6309\u7EF4\u5EA6\u7B5B\u9009" },
|
|
1342
|
+
React.createElement("option", { value: "" }, "\u5168\u90E8\u7EF4\u5EA6"),
|
|
1343
|
+
...options.dimensions.map(
|
|
1344
|
+
(d) => React.createElement("option", { key: d.value, value: d.value }, `${d.value} (${d.count})`)
|
|
1345
|
+
)
|
|
686
1346
|
),
|
|
687
|
-
React.createElement(
|
|
688
|
-
className:
|
|
689
|
-
type:
|
|
690
|
-
placeholder:
|
|
1347
|
+
React.createElement("input", {
|
|
1348
|
+
className: "iterate-filter-search",
|
|
1349
|
+
type: "search",
|
|
1350
|
+
placeholder: "\u641C\u7D22\u6587\u4EF6 / \u6458\u8981\u2026",
|
|
691
1351
|
value: filter.search,
|
|
692
|
-
onChange: (e) => setSearchFilter(e.target.value)
|
|
1352
|
+
onChange: (e) => setSearchFilter(e.target.value)
|
|
693
1353
|
}),
|
|
694
|
-
React.createElement(
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
)
|
|
1354
|
+
React.createElement(
|
|
1355
|
+
"span",
|
|
1356
|
+
{ className: "iterate-filter-count" },
|
|
1357
|
+
isFilterActive ? React.createElement("button", { className: "iterate-batch-btn", onClick: clearFilter }, `\u6E05\u9664\u7B5B\u9009\uFF08\u663E\u793A ${filtered.length}\uFF09`) : `\u5171 ${findings.length} \u9879`
|
|
1358
|
+
)
|
|
699
1359
|
),
|
|
700
|
-
React.createElement(
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
1360
|
+
React.createElement(
|
|
1361
|
+
"div",
|
|
1362
|
+
{ className: "iterate-batch" },
|
|
1363
|
+
React.createElement("span", { className: "iterate-batch-label" }, "\u6279\u91CF\uFF1A"),
|
|
1364
|
+
React.createElement(
|
|
1365
|
+
"label",
|
|
1366
|
+
{ className: "iterate-batch-check", title: "\u52FE\u9009\u540E\u6279\u91CF\u6309\u94AE\u4F5C\u7528\u4E8E\u5168\u90E8 findings\uFF0C\u5426\u5219\u4EC5\u5F53\u524D\u53EF\u89C1" },
|
|
1367
|
+
React.createElement("input", { type: "checkbox", checked: selectAll, onChange: (e) => setSelectAll(e.target.checked) }),
|
|
1368
|
+
selectAll ? `\u5168\u90E8 ${allIndices.length}` : "\u5168\u9009"
|
|
705
1369
|
),
|
|
706
|
-
React.createElement(
|
|
707
|
-
React.createElement(
|
|
708
|
-
React.createElement(
|
|
709
|
-
React.createElement(
|
|
710
|
-
React.createElement(
|
|
711
|
-
React.createElement(
|
|
712
|
-
React.createElement(
|
|
713
|
-
React.createElement(
|
|
1370
|
+
React.createElement("button", { className: "iterate-batch-btn", onClick: () => applyBatch("keep") }, "\u5168\u90E8 y"),
|
|
1371
|
+
React.createElement("button", { className: "iterate-batch-btn", onClick: () => applyBatch("skip") }, "\u5168\u90E8 n"),
|
|
1372
|
+
React.createElement("button", { className: "iterate-batch-btn", onClick: () => applyBatch("ignore") }, "\u5168\u90E8 a"),
|
|
1373
|
+
React.createElement("span", { className: "iterate-batch-label", style: { marginLeft: 8 } }, "\u5168\u90E8\uFF1A"),
|
|
1374
|
+
React.createElement("button", { className: "iterate-batch-btn", onClick: () => applyBatchAll("keep") }, "y"),
|
|
1375
|
+
React.createElement("button", { className: "iterate-batch-btn", onClick: () => applyBatchAll("skip") }, "n"),
|
|
1376
|
+
React.createElement("button", { className: "iterate-batch-btn", onClick: () => applyBatchAll("ignore") }, "a"),
|
|
1377
|
+
React.createElement("button", { className: "iterate-batch-btn", onClick: doResetVerdicts, title: "\u628A\u6240\u6709\u5224\u5B9A\u6062\u590D\u4E3A\u9ED8\u8BA4 y\uFF08\u4FEE\u590D\uFF09" }, "\u91CD\u7F6E")
|
|
714
1378
|
),
|
|
715
1379
|
...rows,
|
|
716
|
-
React.createElement(
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
1380
|
+
React.createElement(
|
|
1381
|
+
"div",
|
|
1382
|
+
{ className: "iterate-triage-foot" },
|
|
1383
|
+
React.createElement("span", {}, `y ${counts.keep} \xB7 n ${counts.skip} \xB7 a ${counts.ignore} \xB7 \u5F85\u5199\u56DE known_intentional\uFF1A${ignoredCount} \u6761`),
|
|
1384
|
+
React.createElement(
|
|
1385
|
+
"span",
|
|
1386
|
+
{ style: { display: "flex", gap: 6 } },
|
|
1387
|
+
React.createElement("button", { className: "iterate-btn", "data-primary": "", "data-copied": copied ? "" : void 0, onClick: doCopyYaml }, copied ? "\u5DF2\u590D\u5236" : "\u590D\u5236 known_intentional"),
|
|
1388
|
+
React.createElement("button", { className: "iterate-btn", onClick: doBuildInstruction }, "\u751F\u6210\u5E94\u7528\u6307\u4EE4")
|
|
1389
|
+
)
|
|
722
1390
|
),
|
|
723
|
-
payload
|
|
724
|
-
|
|
725
|
-
: null,
|
|
726
|
-
)
|
|
1391
|
+
payload ? React.createElement("div", { className: "iterate-payload" }, payload) : null
|
|
1392
|
+
);
|
|
727
1393
|
}
|
|
728
|
-
|
|
729
|
-
/** Meta-review verdict banner: surfaces the closing dry-run audit result. */
|
|
730
1394
|
function VerdictBanner(props) {
|
|
731
|
-
const verdict = props.verdict
|
|
732
|
-
if (!verdict) return null
|
|
733
|
-
const ok = verdict.verdict ===
|
|
734
|
-
const item = (num, unit) =>
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
1395
|
+
const verdict = props.verdict;
|
|
1396
|
+
if (!verdict) return null;
|
|
1397
|
+
const ok = verdict.verdict === "approved";
|
|
1398
|
+
const item = (num, unit) => React.createElement(
|
|
1399
|
+
"span",
|
|
1400
|
+
{ className: "iterate-verdict-item" },
|
|
1401
|
+
React.createElement("b", {}, String(num)),
|
|
1402
|
+
` ${unit}`
|
|
1403
|
+
);
|
|
1404
|
+
const phrase = (text) => React.createElement("span", { className: "iterate-verdict-item" }, text);
|
|
739
1405
|
return React.createElement(
|
|
740
|
-
|
|
741
|
-
{
|
|
742
|
-
React.createElement(
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
item(verdict.totalRounds, '轮'),
|
|
747
|
-
item(verdict.checksRun, '项审查'),
|
|
748
|
-
ok
|
|
749
|
-
? phrase('报告通过全部一致性检查')
|
|
750
|
-
: item(verdict.reportIssues, '处报告缺陷'),
|
|
751
|
-
phrase(verdict.converged ? '已收敛' : '未收敛'),
|
|
1406
|
+
"div",
|
|
1407
|
+
{ "data-iterate-root": "", "data-iterate": "verdict", className: "iterate-verdict" },
|
|
1408
|
+
React.createElement(
|
|
1409
|
+
"span",
|
|
1410
|
+
{ className: "iterate-verdict-tag", "data-ok": ok ? "" : void 0, "data-warn": ok ? void 0 : "" },
|
|
1411
|
+
ok ? "\u62A5\u544A\u5DF2\u6279\u51C6" : "\u62A5\u544A\u9700\u4FEE\u8BA2"
|
|
752
1412
|
),
|
|
753
|
-
|
|
1413
|
+
React.createElement(
|
|
1414
|
+
"span",
|
|
1415
|
+
{ className: "iterate-verdict-detail" },
|
|
1416
|
+
item(verdict.totalFindings, "\u9879\u53D1\u73B0"),
|
|
1417
|
+
item(verdict.totalRounds, "\u8F6E"),
|
|
1418
|
+
item(verdict.checksRun, "\u9879\u5BA1\u67E5"),
|
|
1419
|
+
ok ? phrase("\u62A5\u544A\u901A\u8FC7\u5168\u90E8\u4E00\u81F4\u6027\u68C0\u67E5") : item(verdict.reportIssues, "\u5904\u62A5\u544A\u7F3A\u9677"),
|
|
1420
|
+
phrase(verdict.converged ? "\u5DF2\u6536\u655B" : "\u672A\u6536\u655B")
|
|
1421
|
+
)
|
|
1422
|
+
);
|
|
754
1423
|
}
|
|
755
|
-
|
|
756
|
-
/** Turn-tail chain entry: triage when findings exist, stats card otherwise. */
|
|
757
1424
|
function TurnTailEntry(props) {
|
|
758
|
-
const candidates = []
|
|
759
|
-
if (props && props.turn) candidates.push(props.turn)
|
|
760
|
-
if (props && props.matched) candidates.push(props.matched)
|
|
761
|
-
if (props && props.data) candidates.push(props.data)
|
|
762
|
-
candidates.push(props)
|
|
763
|
-
|
|
764
|
-
// Meta-review verdict (dry-run closing result), found before the report.
|
|
765
|
-
let verdict = null
|
|
1425
|
+
const candidates = [];
|
|
1426
|
+
if (props && props.turn) candidates.push(props.turn);
|
|
1427
|
+
if (props && props.matched) candidates.push(props.matched);
|
|
1428
|
+
if (props && props.data) candidates.push(props.data);
|
|
1429
|
+
candidates.push(props);
|
|
1430
|
+
let verdict = null;
|
|
766
1431
|
for (const c of candidates) {
|
|
767
|
-
const run = scanSessionForRunSummary(c)
|
|
768
|
-
if (run) {
|
|
1432
|
+
const run = scanSessionForRunSummary(c);
|
|
1433
|
+
if (run) {
|
|
1434
|
+
verdict = extractVerdict(run);
|
|
1435
|
+
break;
|
|
1436
|
+
}
|
|
769
1437
|
}
|
|
770
|
-
|
|
771
|
-
let report = null
|
|
1438
|
+
let report = null;
|
|
772
1439
|
for (const c of candidates) {
|
|
773
|
-
const raw = findReportInObject(c,
|
|
774
|
-
if (raw) {
|
|
1440
|
+
const raw = findReportInObject(c, void 0, 24) || scanSessionForReport(c);
|
|
1441
|
+
if (raw) {
|
|
1442
|
+
report = normalizeReport(raw);
|
|
1443
|
+
break;
|
|
1444
|
+
}
|
|
775
1445
|
}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
if (verdict) blocks.push(React.createElement(VerdictBanner, { key: 'verdict', verdict }))
|
|
1446
|
+
const blocks = [];
|
|
1447
|
+
if (verdict) blocks.push(React.createElement(VerdictBanner, { key: "verdict", verdict }));
|
|
779
1448
|
if (report) {
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
// of Hooks and crash when the findings/empty branch flips between renders).
|
|
783
|
-
const panel = !report.findings || report.findings.length === 0
|
|
784
|
-
? React.createElement(StatsCard, { report })
|
|
785
|
-
: React.createElement(TriagePanel, { report })
|
|
786
|
-
blocks.push(React.createElement('div', { key: 'report' }, panel))
|
|
1449
|
+
const panel = !report.findings || report.findings.length === 0 ? React.createElement(StatsCard, { report }) : React.createElement(TriagePanel, { report });
|
|
1450
|
+
blocks.push(React.createElement("div", { key: "report" }, panel));
|
|
787
1451
|
}
|
|
788
|
-
if (blocks.length === 0) return null
|
|
789
|
-
return React.createElement(
|
|
1452
|
+
if (blocks.length === 0) return null;
|
|
1453
|
+
return React.createElement("div", { "data-iterate-root": "", className: "iterate-turn-tail-root" }, ...blocks);
|
|
790
1454
|
}
|
|
791
|
-
|
|
792
|
-
/** Progress capsule: briefly surfaces round-completion (incl. convergence). */
|
|
793
1455
|
function ProgressCapsule() {
|
|
794
|
-
const [info, setInfo] = React.useState(null)
|
|
1456
|
+
const [info, setInfo] = React.useState(null);
|
|
795
1457
|
React.useEffect(() => {
|
|
796
|
-
let timer = null
|
|
1458
|
+
let timer = null;
|
|
797
1459
|
const listener = (payload) => {
|
|
798
|
-
const converged = payload && payload.converged === true
|
|
799
|
-
const round = payload && typeof payload.round ===
|
|
800
|
-
setInfo({ text: converged ? `Round ${round}
|
|
801
|
-
if (timer) clearTimeout(timer)
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
roundPulseListeners.push(listener)
|
|
1460
|
+
const converged = payload && payload.converged === true;
|
|
1461
|
+
const round = payload && typeof payload.round === "number" ? payload.round : "?";
|
|
1462
|
+
setInfo({ text: converged ? `Round ${round} \u5B8C\u6210 \xB7 \u5DF2\u6536\u655B` : `Round ${round} \u5B8C\u6210`, ok: converged });
|
|
1463
|
+
if (timer) clearTimeout(timer);
|
|
1464
|
+
timer = setTimeout(() => setInfo(null), converged ? 3600 : 2400);
|
|
1465
|
+
};
|
|
1466
|
+
roundPulseListeners.push(listener);
|
|
806
1467
|
return () => {
|
|
807
|
-
const i = roundPulseListeners.indexOf(listener)
|
|
808
|
-
if (i >= 0) roundPulseListeners.splice(i, 1)
|
|
809
|
-
if (timer) clearTimeout(timer)
|
|
810
|
-
}
|
|
811
|
-
}, [])
|
|
812
|
-
if (!info) return null
|
|
813
|
-
return React.createElement(
|
|
1468
|
+
const i = roundPulseListeners.indexOf(listener);
|
|
1469
|
+
if (i >= 0) roundPulseListeners.splice(i, 1);
|
|
1470
|
+
if (timer) clearTimeout(timer);
|
|
1471
|
+
};
|
|
1472
|
+
}, []);
|
|
1473
|
+
if (!info) return null;
|
|
1474
|
+
return React.createElement("div", { "data-iterate-root": "", className: "iterate-capsule", "data-ok": info.ok ? "" : void 0 }, info.text);
|
|
814
1475
|
}
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
const [
|
|
819
|
-
const [
|
|
820
|
-
const [
|
|
821
|
-
const [
|
|
822
|
-
const [
|
|
823
|
-
const guide = buildConfigEditGuide()
|
|
824
|
-
const statusGuide = buildRuntimeStatusGuide()
|
|
825
|
-
|
|
1476
|
+
function SettingsPanel(_props) {
|
|
1477
|
+
const [enabled, setEnabled] = React.useState(themeEnabled);
|
|
1478
|
+
const [guideCopied, setGuideCopied] = React.useState(false);
|
|
1479
|
+
const [statusCopied, setStatusCopied] = React.useState(false);
|
|
1480
|
+
const [showGuide, setShowGuide] = React.useState(false);
|
|
1481
|
+
const [showStatus, setShowStatus] = React.useState(false);
|
|
1482
|
+
const [confirming, setConfirming] = React.useState(false);
|
|
1483
|
+
const [clearedInfo, setClearedInfo] = React.useState(null);
|
|
1484
|
+
const guide = buildConfigEditGuide();
|
|
1485
|
+
const statusGuide = buildRuntimeStatusGuide();
|
|
826
1486
|
const toggleTheme = () => {
|
|
827
|
-
const next = !enabled
|
|
828
|
-
setEnabled(next)
|
|
829
|
-
setThemeEnabled(next)
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
1487
|
+
const next = !enabled;
|
|
1488
|
+
setEnabled(next);
|
|
1489
|
+
setThemeEnabled(next);
|
|
1490
|
+
};
|
|
1491
|
+
const flashCopied = (slot) => {
|
|
1492
|
+
const setter = slot === "guide" ? setGuideCopied : setStatusCopied;
|
|
1493
|
+
setter(true);
|
|
1494
|
+
setTimeout(() => setter(false), 1600);
|
|
1495
|
+
};
|
|
1496
|
+
const doCopy = (text, slot) => {
|
|
1497
|
+
copyText(text);
|
|
1498
|
+
flashCopied(slot);
|
|
1499
|
+
};
|
|
1500
|
+
const requestClear = () => {
|
|
1501
|
+
if (confirming) {
|
|
1502
|
+
const count = removeStorageByPrefix(TRIAGE_STORAGE_PREFIX);
|
|
1503
|
+
setClearedInfo(count);
|
|
1504
|
+
setConfirming(false);
|
|
1505
|
+
setTimeout(() => setClearedInfo(null), 3e3);
|
|
1506
|
+
return;
|
|
1507
|
+
}
|
|
1508
|
+
setConfirming(true);
|
|
1509
|
+
setTimeout(() => setConfirming(false), 3e3);
|
|
1510
|
+
};
|
|
1511
|
+
const clearButton = clearedInfo !== null ? React.createElement("button", { className: "iterate-btn", "data-copied": "", disabled: true }, `\u5DF2\u6E05\u9664 ${clearedInfo} \u6761`) : React.createElement("button", {
|
|
1512
|
+
className: "iterate-btn",
|
|
1513
|
+
"data-danger": confirming ? void 0 : "",
|
|
1514
|
+
"data-confirm": confirming ? "" : void 0,
|
|
1515
|
+
onClick: requestClear,
|
|
1516
|
+
title: confirming ? "\u518D\u6B21\u70B9\u51FB\u4EE5\u786E\u8BA4\u6E05\u9664\u5168\u90E8\u5224\u5B9A" : "\u6E05\u9664\u6240\u6709\u5206\u8BCA\u5224\u5B9A\u8BB0\u5F55\uFF08\u9700\u8981\u4E8C\u6B21\u786E\u8BA4\uFF09"
|
|
1517
|
+
}, confirming ? "\u786E\u8BA4\u6E05\u9664\uFF1F" : "\u6E05\u9664\u5206\u8BCA");
|
|
1518
|
+
const themeCard = React.createElement(
|
|
1519
|
+
"div",
|
|
1520
|
+
{ key: "theme", className: "iterate-scard" },
|
|
1521
|
+
React.createElement(
|
|
1522
|
+
"div",
|
|
1523
|
+
{ className: "iterate-scard-head" },
|
|
1524
|
+
React.createElement(
|
|
1525
|
+
"div",
|
|
1526
|
+
{},
|
|
1527
|
+
React.createElement("div", { className: "iterate-settings-title" }, "\u8FED\u4EE3\u4E3B\u9898"),
|
|
1528
|
+
React.createElement("div", { className: "iterate-settings-desc" }, "\u542F\u7528\u6696\u7425\u73C0\u914D\u8272\u7684 iterate \u4E13\u5C5E\u76AE\u80A4\uFF0C\u8986\u76D6 dsh \u9ED8\u8BA4\u4E3B\u9898\u4EE4\u724C\u3002")
|
|
850
1529
|
),
|
|
851
|
-
React.createElement(
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
1530
|
+
React.createElement(
|
|
1531
|
+
"div",
|
|
1532
|
+
{ className: "iterate-scard-actions" },
|
|
1533
|
+
React.createElement("span", { className: "iterate-chip", "data-ok": enabled ? "" : void 0 }, enabled ? "\u5DF2\u542F\u7528" : "\u5DF2\u5173\u95ED"),
|
|
1534
|
+
React.createElement("button", {
|
|
1535
|
+
role: "switch",
|
|
1536
|
+
"aria-checked": enabled,
|
|
1537
|
+
"aria-label": "\u5F00\u5173 iterate \u4E3B\u9898",
|
|
1538
|
+
className: "iterate-switch",
|
|
1539
|
+
"data-on": enabled ? "" : void 0,
|
|
1540
|
+
onClick: toggleTheme
|
|
1541
|
+
}, React.createElement("span", { className: "iterate-switch-knob" }))
|
|
1542
|
+
)
|
|
1543
|
+
)
|
|
1544
|
+
);
|
|
1545
|
+
const dataCard = React.createElement(
|
|
1546
|
+
"div",
|
|
1547
|
+
{ key: "data", className: "iterate-scard" },
|
|
1548
|
+
React.createElement(
|
|
1549
|
+
"div",
|
|
1550
|
+
{ className: "iterate-scard-head" },
|
|
1551
|
+
React.createElement(
|
|
1552
|
+
"div",
|
|
1553
|
+
{},
|
|
1554
|
+
React.createElement("div", { className: "iterate-settings-title" }, "\u5206\u8BCA\u6301\u4E45\u5316"),
|
|
1555
|
+
React.createElement("div", { className: "iterate-settings-desc" }, "y / n / a \u5224\u5B9A\u4FDD\u5B58\u5728\u672C\u5730\u6D4F\u89C8\u5668\uFF08localStorage\uFF09\uFF0C\u5237\u65B0\u4F1A\u8BDD\u540E\u4ECD\u4FDD\u7559\u3002")
|
|
861
1556
|
),
|
|
862
|
-
React.createElement(
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
1557
|
+
React.createElement(
|
|
1558
|
+
"div",
|
|
1559
|
+
{ className: "iterate-scard-actions" },
|
|
1560
|
+
React.createElement("span", { className: "iterate-chip" }, "\u672C\u5730\u4FDD\u5B58"),
|
|
1561
|
+
clearButton
|
|
1562
|
+
)
|
|
1563
|
+
)
|
|
1564
|
+
);
|
|
1565
|
+
const guideCard = React.createElement(
|
|
1566
|
+
"div",
|
|
1567
|
+
{ key: "guide", className: "iterate-scard" },
|
|
1568
|
+
React.createElement(
|
|
1569
|
+
"div",
|
|
1570
|
+
{ className: "iterate-scard-head" },
|
|
1571
|
+
React.createElement(
|
|
1572
|
+
"div",
|
|
1573
|
+
{},
|
|
1574
|
+
React.createElement("div", { className: "iterate-settings-title" }, "\u914D\u7F6E\u7BA1\u7406"),
|
|
1575
|
+
React.createElement("div", { className: "iterate-settings-desc" }, "\u76EE\u6807 / \u7EF4\u5EA6 / \u6700\u5927\u8F6E\u6570\u5199\u5728\u9879\u76EE\u7684 iterate.config.yaml\uFF0C\u590D\u5236\u6307\u5F15\u53EF\u8BA9\u6A21\u578B\u6309\u9700\u8C03\u6574\u3002")
|
|
871
1576
|
),
|
|
1577
|
+
React.createElement(
|
|
1578
|
+
"div",
|
|
1579
|
+
{ className: "iterate-scard-actions" },
|
|
1580
|
+
React.createElement("button", { className: "iterate-btn", "data-primary": "", "data-copied": guideCopied ? "" : void 0, onClick: () => doCopy(guide, "guide") }, guideCopied ? "\u5DF2\u590D\u5236" : "\u590D\u5236\u6307\u5F15"),
|
|
1581
|
+
React.createElement("button", { className: "iterate-btn", "data-ghost": "", onClick: () => setShowGuide((v) => !v) }, showGuide ? "\u6536\u8D77" : "\u5C55\u5F00")
|
|
1582
|
+
)
|
|
872
1583
|
),
|
|
873
|
-
React.createElement(
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
1584
|
+
showGuide ? React.createElement(
|
|
1585
|
+
"div",
|
|
1586
|
+
{ className: "iterate-guide" },
|
|
1587
|
+
React.createElement(
|
|
1588
|
+
"div",
|
|
1589
|
+
{ className: "iterate-guide-bar" },
|
|
1590
|
+
React.createElement("span", {}, "iterate.config.yaml \u53EF\u7F16\u8F91\u5B57\u6BB5"),
|
|
1591
|
+
React.createElement("button", { className: "iterate-btn", "data-ghost": "", onClick: () => doCopy(guide, "guide") }, "\u590D\u5236")
|
|
877
1592
|
),
|
|
878
|
-
React.createElement(
|
|
879
|
-
|
|
880
|
-
|
|
1593
|
+
React.createElement("div", { className: "iterate-guide-body" }, guide)
|
|
1594
|
+
) : null
|
|
1595
|
+
);
|
|
1596
|
+
const statusCard = React.createElement(
|
|
1597
|
+
"div",
|
|
1598
|
+
{ key: "status", className: "iterate-scard" },
|
|
1599
|
+
React.createElement(
|
|
1600
|
+
"div",
|
|
1601
|
+
{ className: "iterate-scard-head" },
|
|
1602
|
+
React.createElement(
|
|
1603
|
+
"div",
|
|
1604
|
+
{},
|
|
1605
|
+
React.createElement("div", { className: "iterate-settings-title" }, "\u72B6\u6001\u6982\u89C8"),
|
|
1606
|
+
React.createElement("div", { className: "iterate-settings-desc" }, "\u67E5\u770B\u8FD0\u884C\u65F6\u4EA7\u7269\u5E03\u5C40\u4E0E\u6E05\u7406\u6307\u5F15\u3002iterate_status / iterate_history / iterate_prune \u5DE5\u5177\u7528\u4E8E\u67E5\u770B\u548C\u7BA1\u7406\u3002")
|
|
881
1607
|
),
|
|
1608
|
+
React.createElement(
|
|
1609
|
+
"div",
|
|
1610
|
+
{ className: "iterate-scard-actions" },
|
|
1611
|
+
React.createElement("button", { className: "iterate-btn", "data-primary": "", "data-copied": statusCopied ? "" : void 0, onClick: () => doCopy(statusGuide, "status") }, statusCopied ? "\u5DF2\u590D\u5236" : "\u590D\u5236\u6307\u5F15"),
|
|
1612
|
+
React.createElement("button", { className: "iterate-btn", "data-ghost": "", onClick: () => setShowStatus((v) => !v) }, showStatus ? "\u6536\u8D77" : "\u5C55\u5F00")
|
|
1613
|
+
)
|
|
882
1614
|
),
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
:
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
React.createElement(
|
|
1615
|
+
showStatus ? React.createElement(
|
|
1616
|
+
"div",
|
|
1617
|
+
{ className: "iterate-guide" },
|
|
1618
|
+
React.createElement(
|
|
1619
|
+
"div",
|
|
1620
|
+
{ className: "iterate-guide-bar" },
|
|
1621
|
+
React.createElement("span", {}, "\u8FD0\u884C\u65F6\u5E03\u5C40\u4E0E\u6E05\u7406"),
|
|
1622
|
+
React.createElement("button", { className: "iterate-btn", "data-ghost": "", onClick: () => doCopy(statusGuide, "status") }, "\u590D\u5236")
|
|
890
1623
|
),
|
|
891
|
-
React.createElement(
|
|
892
|
-
)
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
1624
|
+
React.createElement("div", { className: "iterate-guide-body" }, statusGuide)
|
|
1625
|
+
) : null
|
|
1626
|
+
);
|
|
1627
|
+
const banner = React.createElement(
|
|
1628
|
+
"div",
|
|
1629
|
+
{ key: "banner", className: "iterate-scard" },
|
|
1630
|
+
React.createElement(
|
|
1631
|
+
"div",
|
|
1632
|
+
{ className: "iterate-scard-head" },
|
|
1633
|
+
React.createElement(
|
|
1634
|
+
"div",
|
|
1635
|
+
{},
|
|
1636
|
+
React.createElement("div", { className: "iterate-settings-title" }, "iterate"),
|
|
1637
|
+
React.createElement("div", { className: "iterate-settings-desc" }, "\u4E3A\u6BCF\u6B21\u4EE3\u7801\u8BC4\u5BA1\u751F\u6210 Review \u62A5\u544A\u4E0E\u4EA4\u4E92\u5F0F\u5206\u8BCA\u89C6\u56FE\uFF0C\u4E13\u6CE8 AI \u81EA\u6211\u5BA1\u67E5\u4E0E\u4FEE\u6B63\u3002")
|
|
1638
|
+
),
|
|
1639
|
+
React.createElement(
|
|
1640
|
+
"span",
|
|
1641
|
+
{ className: "iterate-pill" },
|
|
1642
|
+
React.createElement("span", { className: "iterate-pill-dot" }),
|
|
1643
|
+
"\u5C31\u7EEA"
|
|
1644
|
+
)
|
|
1645
|
+
)
|
|
1646
|
+
);
|
|
1647
|
+
return React.createElement(
|
|
1648
|
+
"div",
|
|
1649
|
+
{ "data-iterate-root": "", "data-iterate": "settings", className: "iterate-settings" },
|
|
1650
|
+
React.createElement("div", { className: "iterate-settings-title", style: { fontSize: 15, fontWeight: 700 } }, "iterate \u8BBE\u7F6E"),
|
|
1651
|
+
banner,
|
|
1652
|
+
themeCard,
|
|
1653
|
+
dataCard,
|
|
1654
|
+
guideCard,
|
|
1655
|
+
statusCard
|
|
1656
|
+
);
|
|
897
1657
|
}
|
|
898
|
-
|
|
899
|
-
// ─── Registration ────────────────────────────────────────────────────────────
|
|
900
|
-
|
|
901
|
-
/**
|
|
902
|
-
* Chain selector for conversation.chat.turnTail.
|
|
903
|
-
* Returns a marker when the turn contains an iterate review report, null otherwise.
|
|
904
|
-
*/
|
|
905
1658
|
function selectTurnTail(owner) {
|
|
906
|
-
if (!owner) return null
|
|
907
|
-
if (findReportInObject(owner.turn,
|
|
908
|
-
if (scanSessionForRunSummary(owner.turn)) return { matched: true }
|
|
909
|
-
return null
|
|
1659
|
+
if (!owner) return null;
|
|
1660
|
+
if (findReportInObject(owner.turn, void 0, 24) || scanSessionForReport(owner.turn)) return { matched: true };
|
|
1661
|
+
if (scanSessionForRunSummary(owner.turn)) return { matched: true };
|
|
1662
|
+
return null;
|
|
910
1663
|
}
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
style.dataset.plugin = PLUGIN_TAG
|
|
932
|
-
style.dataset.pluginCss = 'iterate-main'
|
|
933
|
-
style.textContent = ITERATE_CSS
|
|
934
|
-
document.head.appendChild(style)
|
|
935
|
-
if (typeof ctx.effect === 'function') {
|
|
936
|
-
ctx.effect(() => { try { style.remove() } catch { /* noop */ } })
|
|
1664
|
+
function apply(ctx) {
|
|
1665
|
+
slotsSvc = readSlots(ctx);
|
|
1666
|
+
themeSvc = readTheme(ctx);
|
|
1667
|
+
storage = createStorage();
|
|
1668
|
+
const savedTheme = storage.get(THEME_STORAGE_KEY);
|
|
1669
|
+
themeEnabled = savedTheme === null ? true : savedTheme === "1";
|
|
1670
|
+
if (themeEnabled) applyThemeSkin();
|
|
1671
|
+
if (typeof document !== "undefined" && document.createElement && document.head) {
|
|
1672
|
+
const style = document.createElement("style");
|
|
1673
|
+
style.dataset.plugin = PLUGIN_TAG;
|
|
1674
|
+
style.dataset.pluginCss = "iterate-main";
|
|
1675
|
+
style.textContent = ITERATE_CSS;
|
|
1676
|
+
document.head.appendChild(style);
|
|
1677
|
+
if (typeof ctx.effect === "function") {
|
|
1678
|
+
ctx.effect(() => {
|
|
1679
|
+
try {
|
|
1680
|
+
style.remove();
|
|
1681
|
+
} catch {
|
|
1682
|
+
}
|
|
1683
|
+
});
|
|
937
1684
|
}
|
|
938
1685
|
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
log('React or slots service unavailable — slot UI disabled')
|
|
943
|
-
return
|
|
1686
|
+
if (slotsSvc === void 0) {
|
|
1687
|
+
log("slots service unavailable \u2014 slot UI disabled");
|
|
1688
|
+
return;
|
|
944
1689
|
}
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
)
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
slotsSvc.register(
|
|
975
|
-
{ name: 'settings.section', id: 'iterate-settings', order: 30, label: () => 'iterate' },
|
|
976
|
-
(props) => React.createElement(SettingsPanel, props),
|
|
977
|
-
),
|
|
978
|
-
)
|
|
1690
|
+
if (typeof slotsSvc.inject === "function") {
|
|
1691
|
+
slotsSvc.inject(
|
|
1692
|
+
"conversation.input.dock",
|
|
1693
|
+
() => slotsSvc?.register(
|
|
1694
|
+
{ name: "conversation.input.dock", id: "iterate-dashboard", order: 90 },
|
|
1695
|
+
(props) => React.createElement(ConvergenceDashboard, props)
|
|
1696
|
+
)
|
|
1697
|
+
);
|
|
1698
|
+
slotsSvc.inject(
|
|
1699
|
+
"conversation.chat.turnTail",
|
|
1700
|
+
() => slotsSvc?.register(
|
|
1701
|
+
{ name: "conversation.chat.turnTail", id: "iterate-turn-tail", select: selectTurnTail },
|
|
1702
|
+
(props) => React.createElement(TurnTailEntry, props)
|
|
1703
|
+
)
|
|
1704
|
+
);
|
|
1705
|
+
slotsSvc.inject(
|
|
1706
|
+
"shell.overlay",
|
|
1707
|
+
() => slotsSvc?.register(
|
|
1708
|
+
{ name: "shell.overlay", id: "iterate-progress", order: 0 },
|
|
1709
|
+
(props) => React.createElement(ProgressCapsule, props)
|
|
1710
|
+
)
|
|
1711
|
+
);
|
|
1712
|
+
slotsSvc.inject(
|
|
1713
|
+
"settings.section",
|
|
1714
|
+
() => slotsSvc?.register(
|
|
1715
|
+
{ name: "settings.section", id: "iterate-settings", order: 30, label: () => "iterate" },
|
|
1716
|
+
(props) => React.createElement(SettingsPanel, props)
|
|
1717
|
+
)
|
|
1718
|
+
);
|
|
979
1719
|
}
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
if (themeEnabled) applyThemeSkin()
|
|
985
|
-
})
|
|
1720
|
+
if (typeof ctx.on === "function") {
|
|
1721
|
+
ctx.on("theme/change", () => {
|
|
1722
|
+
if (themeEnabled) applyThemeSkin();
|
|
1723
|
+
});
|
|
986
1724
|
}
|
|
987
|
-
}
|
|
1725
|
+
}
|
|
1726
|
+
return module.exports; } });
|
|
1727
|
+
|