iterate-plugin 2.7.1 → 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/dist/index.js +6 -4
- package/dist/review.js +6 -1
- package/dist/skill-prompt.js +6 -2
- package/dist/tools/decision-log.js +41 -6
- package/dist/tools/review.js +6 -0
- package/lib/client.js +1490 -695
- package/lib/parse.js +137 -1
- package/package.json +6 -2
- package/src/client/index.ts +1218 -0
- package/src/index.ts +6 -4
- package/src/review.ts +9 -1
- package/src/skill-prompt.ts +6 -2
- package/src/tools/decision-log.ts +43 -6
- package/src/tools/review.ts +7 -0
- package/src/types.ts +3 -0
package/lib/client.js
CHANGED
|
@@ -1,90 +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
|
-
|
|
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");
|
|
84
682
|
}
|
|
85
683
|
|
|
86
|
-
|
|
87
|
-
|
|
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 = `
|
|
88
707
|
[data-iterate-root] { box-sizing: border-box; font-family: var(--dsw-font-sans, system-ui, sans-serif); }
|
|
89
708
|
[data-iterate-root] * , [data-iterate-root] *::before, [data-iterate-root] *::after { box-sizing: border-box; }
|
|
90
709
|
|
|
@@ -159,8 +778,6 @@ const ITERATE_CSS = `
|
|
|
159
778
|
@keyframes iterate-fadein { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
|
|
160
779
|
|
|
161
780
|
.iterate-settings { display: flex; flex-direction: column; gap: 12px; padding: 4px 2px 12px; }
|
|
162
|
-
.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); }
|
|
163
|
-
.iterate-settings-row:last-child { border-bottom: none; }
|
|
164
781
|
.iterate-settings-title { font-size: 14px; font-weight: 600; color: var(--dsw-alias-label-primary); }
|
|
165
782
|
.iterate-settings-desc { font-size: 12px; color: var(--dsw-alias-label-secondary); margin-top: 3px; line-height: 1.5; }
|
|
166
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); }
|
|
@@ -195,738 +812,916 @@ const ITERATE_CSS = `
|
|
|
195
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); }
|
|
196
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); }
|
|
197
814
|
.iterate-capsule[data-ok] { border-color: var(--dsw-alias-state-success-primary); color: var(--dsw-alias-state-success-primary); }
|
|
198
|
-
.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; }
|
|
199
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); }
|
|
200
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; }
|
|
201
817
|
.iterate-batch-check input { margin: 0; cursor: pointer; }
|
|
202
|
-
`
|
|
203
|
-
|
|
204
|
-
// ─── Small helpers ───────────────────────────────────────────────────────────
|
|
205
818
|
|
|
206
|
-
|
|
819
|
+
/* Meta-review verdict banner (dry-run closing result) */
|
|
820
|
+
.iterate-verdict { margin: 10px 0; padding: 10px 14px; border-radius: 12px; border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-1); display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-size: 12px; color: var(--dsw-alias-label-primary); }
|
|
821
|
+
.iterate-verdict-tag { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; border-radius: 999px; font-weight: 600; white-space: nowrap; }
|
|
822
|
+
.iterate-verdict-tag[data-ok] { color: var(--dsw-alias-state-success-primary); background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 14%, transparent); }
|
|
823
|
+
.iterate-verdict-tag[data-warn] { color: var(--dsw-alias-state-warn-primary); background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 14%, transparent); }
|
|
824
|
+
.iterate-verdict-detail { display: inline-flex; align-items: center; gap: 10px; flex-wrap: wrap; color: var(--dsw-alias-label-secondary); }
|
|
825
|
+
.iterate-verdict-item { white-space: nowrap; }
|
|
826
|
+
.iterate-verdict-item b { color: var(--dsw-alias-label-primary); font-weight: 600; }
|
|
827
|
+
|
|
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
|
+
`;
|
|
207
858
|
function log(...args) {
|
|
208
|
-
if (typeof console !==
|
|
209
|
-
console.error(`[${PLUGIN_TAG}]`, ...args)
|
|
859
|
+
if (typeof console !== "undefined" && typeof console.error === "function") {
|
|
860
|
+
console.error(`[${PLUGIN_TAG}]`, ...args);
|
|
210
861
|
}
|
|
211
862
|
}
|
|
212
|
-
|
|
213
|
-
/** Resolve the React runtime (ctx.React first, then window.React). */
|
|
214
|
-
function resolveReact(ctx) {
|
|
215
|
-
if (ctx && ctx.React && typeof ctx.React.createElement === 'function') return ctx.React
|
|
216
|
-
const globalScope = typeof window !== 'undefined' ? window : typeof globalThis !== 'undefined' ? globalThis : null
|
|
217
|
-
if (globalScope && globalScope.React && typeof globalScope.React.createElement === 'function') {
|
|
218
|
-
return globalScope.React
|
|
219
|
-
}
|
|
220
|
-
return null
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/** Safe localStorage wrapper (never throws in private mode / SSR). */
|
|
224
863
|
function createStorage() {
|
|
225
864
|
try {
|
|
226
|
-
const testKey =
|
|
227
|
-
window.localStorage.setItem(testKey,
|
|
228
|
-
window.localStorage.removeItem(testKey)
|
|
865
|
+
const testKey = "__iterate_storage_test__";
|
|
866
|
+
window.localStorage.setItem(testKey, "1");
|
|
867
|
+
window.localStorage.removeItem(testKey);
|
|
229
868
|
return {
|
|
230
|
-
get(key) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
+
};
|
|
235
882
|
} catch {
|
|
236
|
-
const mem = new Map()
|
|
883
|
+
const mem = /* @__PURE__ */ new Map();
|
|
237
884
|
return {
|
|
238
|
-
get(key) {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
+
};
|
|
243
898
|
}
|
|
244
899
|
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Remove every stored key with the given prefix (e.g. all triage verdicts).
|
|
248
|
-
* Returns how many keys were removed.
|
|
249
|
-
*/
|
|
250
900
|
function removeStorageByPrefix(prefix) {
|
|
251
|
-
if (!storage) return 0
|
|
252
|
-
let removed = 0
|
|
901
|
+
if (!storage) return 0;
|
|
902
|
+
let removed = 0;
|
|
253
903
|
try {
|
|
254
904
|
for (const key of storage.keys()) {
|
|
255
905
|
if (key.startsWith(prefix)) {
|
|
256
|
-
storage.remove(key)
|
|
257
|
-
removed
|
|
906
|
+
storage.remove(key);
|
|
907
|
+
removed++;
|
|
258
908
|
}
|
|
259
909
|
}
|
|
260
910
|
} catch (err) {
|
|
261
|
-
log(
|
|
911
|
+
log("failed to clear storage prefix", prefix, err);
|
|
262
912
|
}
|
|
263
|
-
return removed
|
|
913
|
+
return removed;
|
|
264
914
|
}
|
|
265
|
-
|
|
266
|
-
/** Copy text to the clipboard, returning whether it succeeded. */
|
|
267
915
|
function copyText(text) {
|
|
268
|
-
if (typeof navigator !==
|
|
916
|
+
if (typeof navigator !== "undefined" && navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
|
|
269
917
|
navigator.clipboard.writeText(text).then(
|
|
270
918
|
() => true,
|
|
271
|
-
() => false
|
|
272
|
-
)
|
|
273
|
-
return true
|
|
919
|
+
() => false
|
|
920
|
+
);
|
|
921
|
+
return true;
|
|
274
922
|
}
|
|
275
|
-
return false
|
|
923
|
+
return false;
|
|
276
924
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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 = [];
|
|
293
961
|
function emitRoundPulse(round, converged) {
|
|
294
|
-
const payload = { round, converged: converged === true }
|
|
295
|
-
for (const fn of roundPulseListeners.slice()) fn(payload)
|
|
962
|
+
const payload = { round, converged: converged === true };
|
|
963
|
+
for (const fn of roundPulseListeners.slice()) fn(payload);
|
|
296
964
|
}
|
|
297
|
-
|
|
298
|
-
/** Apply the iterate theme skin (idempotent). */
|
|
299
965
|
function applyThemeSkin() {
|
|
300
|
-
if (!themeSvc || typeof themeSvc.overrideTokens !==
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
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);
|
|
304
969
|
}
|
|
305
|
-
|
|
306
|
-
/** Remove the iterate theme skin (idempotent). */
|
|
307
970
|
function clearThemeSkin() {
|
|
308
971
|
if (themeDisposer) {
|
|
309
|
-
try {
|
|
310
|
-
|
|
972
|
+
try {
|
|
973
|
+
themeDisposer();
|
|
974
|
+
} catch (err) {
|
|
975
|
+
log("theme disposer failed", err);
|
|
976
|
+
}
|
|
977
|
+
themeDisposer = null;
|
|
311
978
|
}
|
|
312
979
|
}
|
|
313
|
-
|
|
314
|
-
/** Persist + apply the theme preference. */
|
|
315
980
|
function setThemeEnabled(enabled) {
|
|
316
|
-
themeEnabled = enabled
|
|
317
|
-
if (storage) storage.set(THEME_STORAGE_KEY, enabled ?
|
|
318
|
-
if (enabled) applyThemeSkin()
|
|
319
|
-
else clearThemeSkin()
|
|
981
|
+
themeEnabled = enabled;
|
|
982
|
+
if (storage) storage.set(THEME_STORAGE_KEY, enabled ? "1" : "0");
|
|
983
|
+
if (enabled) applyThemeSkin();
|
|
984
|
+
else clearThemeSkin();
|
|
320
985
|
}
|
|
321
|
-
|
|
322
|
-
// ─── Components (React.createElement trees) ──────────────────────────────────
|
|
323
|
-
|
|
324
|
-
/** Obtain a session snapshot defensively from the slot props. */
|
|
325
986
|
function sessionSnapshot(props) {
|
|
326
|
-
let session = null
|
|
327
|
-
|
|
328
|
-
|
|
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
|
+
}
|
|
329
995
|
}
|
|
330
|
-
if (!session && props && props.session) session = props.session
|
|
331
|
-
return session
|
|
996
|
+
if (!session && props && props.session) session = props.session;
|
|
997
|
+
return session;
|
|
332
998
|
}
|
|
333
|
-
|
|
334
|
-
/** Find the latest report inside a session snapshot (normalized). */
|
|
335
999
|
function latestReport(session) {
|
|
336
|
-
if (!session) return null
|
|
337
|
-
const raw = scanSessionForReport(session) || findReportInObject(session,
|
|
338
|
-
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;
|
|
339
1003
|
}
|
|
340
|
-
|
|
341
|
-
/** Inline bar chart of findings-by-round (pure divs, no SVG needed). */
|
|
342
1004
|
function TrendChart({ points }) {
|
|
343
|
-
if (!points || points.length === 0) return null
|
|
344
|
-
const max = trendMax(points)
|
|
345
|
-
const bars = points.map(
|
|
346
|
-
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", {
|
|
347
1009
|
key: p.round,
|
|
348
|
-
className:
|
|
349
|
-
|
|
350
|
-
title: `Round ${p.round}
|
|
351
|
-
style: { height: `${Math.max(4, Math.round(
|
|
352
|
-
})
|
|
353
|
-
)
|
|
354
|
-
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);
|
|
355
1017
|
}
|
|
356
|
-
|
|
357
|
-
/** Dashboard: live convergence strip above the composer. */
|
|
358
1018
|
function ConvergenceDashboard(props) {
|
|
359
|
-
const [pulseKey, setPulseKey] = React.useState(0)
|
|
360
|
-
const report = latestReport(sessionSnapshot(props))
|
|
361
|
-
|
|
1019
|
+
const [pulseKey, setPulseKey] = React.useState(0);
|
|
1020
|
+
const report = latestReport(sessionSnapshot(props));
|
|
362
1021
|
React.useEffect(() => {
|
|
363
|
-
if (!report) return
|
|
364
|
-
const cur = getCurrentRound(report)
|
|
365
|
-
const conv = report.convergence
|
|
366
|
-
emitRoundPulse(cur, conv
|
|
367
|
-
setPulseKey((k) => k + 1)
|
|
368
|
-
}, [report && hashReport(report) +
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
const
|
|
373
|
-
const
|
|
374
|
-
const
|
|
375
|
-
const
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
const fixBadge = fixCount !== null
|
|
393
|
-
? React.createElement('span', { className: 'iterate-metric', key: 'fixes' },
|
|
394
|
-
'\u{1F527} ' + String(fixCount) + ' fixes',
|
|
395
|
-
)
|
|
396
|
-
: null
|
|
397
|
-
|
|
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;
|
|
398
1051
|
return React.createElement(
|
|
399
|
-
|
|
400
|
-
{
|
|
1052
|
+
"div",
|
|
1053
|
+
{ "data-iterate-root": "", "data-iterate": "dashboard", className: "iterate-dashboard" },
|
|
401
1054
|
React.createElement(
|
|
402
|
-
|
|
403
|
-
{ className:
|
|
404
|
-
`Round ${round} / ${total}
|
|
1055
|
+
"span",
|
|
1056
|
+
{ className: "iterate-round-badge", "data-pulse": pulseKey > 0 ? "" : void 0, key: `round-${round}` },
|
|
1057
|
+
`Round ${round} / ${total}`
|
|
405
1058
|
),
|
|
406
|
-
React.createElement(
|
|
407
|
-
|
|
1059
|
+
React.createElement(
|
|
1060
|
+
"div",
|
|
1061
|
+
{ className: "iterate-progress" },
|
|
1062
|
+
React.createElement("div", { className: "iterate-progress-fill", style: { width: `${progress}%` } })
|
|
408
1063
|
),
|
|
409
|
-
React.createElement(
|
|
410
|
-
|
|
411
|
-
|
|
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
|
|
412
1069
|
),
|
|
413
|
-
React.createElement(
|
|
414
|
-
|
|
415
|
-
|
|
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
|
|
416
1075
|
),
|
|
417
|
-
React.createElement(
|
|
418
|
-
|
|
419
|
-
|
|
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
|
|
420
1081
|
),
|
|
421
1082
|
fixBadge,
|
|
422
1083
|
React.createElement(TrendChart, { points: trend.points }),
|
|
423
|
-
...dimBadges
|
|
424
|
-
)
|
|
1084
|
+
...dimBadges
|
|
1085
|
+
);
|
|
425
1086
|
}
|
|
426
|
-
|
|
427
|
-
/** Stats card (empty-findings case of the turn-tail chain). */
|
|
428
1087
|
function StatsCard(props) {
|
|
429
|
-
const report = props.report
|
|
430
|
-
const [showHistory, setShowHistory] = React.useState(false)
|
|
431
|
-
const stats = severityStats(report)
|
|
432
|
-
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;
|
|
433
1092
|
const rows = [
|
|
434
|
-
{ label:
|
|
435
|
-
{ label:
|
|
436
|
-
{ label:
|
|
437
|
-
{ label:
|
|
438
|
-
].map(
|
|
439
|
-
React.createElement(
|
|
440
|
-
|
|
441
|
-
|
|
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
|
+
)
|
|
442
1133
|
),
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
React.createElement(
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
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")
|
|
455
1145
|
),
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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)
|
|
465
1167
|
),
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
'data-primary': showHistory ? '' : undefined,
|
|
474
|
-
onClick: () => setShowHistory((v) => !v),
|
|
475
|
-
}, showHistory ? '收起历史' : '历史 / 趋势'),
|
|
476
|
-
),
|
|
477
|
-
showHistory
|
|
478
|
-
? React.createElement('div', { className: 'iterate-history-body' },
|
|
479
|
-
React.createElement('table', { className: 'iterate-history-table' },
|
|
480
|
-
React.createElement('thead', {},
|
|
481
|
-
React.createElement('tr', {},
|
|
482
|
-
React.createElement('th', {}, '轮次'),
|
|
483
|
-
React.createElement('th', {}, '发现'),
|
|
484
|
-
React.createElement('th', { style: { color: SEVERITY_COLOR.critical } }, 'CRIT'),
|
|
485
|
-
React.createElement('th', { style: { color: SEVERITY_COLOR.high } }, 'HIGH'),
|
|
486
|
-
React.createElement('th', { style: { color: SEVERITY_COLOR.medium } }, 'MED'),
|
|
487
|
-
React.createElement('th', { style: { color: SEVERITY_COLOR.low } }, 'LOW'),
|
|
488
|
-
),
|
|
489
|
-
),
|
|
490
|
-
React.createElement('tbody', {}, ...historyRows),
|
|
491
|
-
),
|
|
492
|
-
React.createElement('div', { className: 'iterate-history-trendline' },
|
|
493
|
-
React.createElement(TrendChart, { points: trend.points }),
|
|
494
|
-
),
|
|
495
|
-
)
|
|
496
|
-
: null,
|
|
497
|
-
)
|
|
1168
|
+
React.createElement(
|
|
1169
|
+
"div",
|
|
1170
|
+
{ className: "iterate-history-trendline" },
|
|
1171
|
+
React.createElement(TrendChart, { points: trend.points })
|
|
1172
|
+
)
|
|
1173
|
+
) : null
|
|
1174
|
+
);
|
|
498
1175
|
}
|
|
499
|
-
|
|
500
|
-
/** Triage panel: per-finding y / n / a with filters, batch ops, keyboard shortcuts. */
|
|
501
1176
|
function TriagePanel(props) {
|
|
502
|
-
const report = props.report
|
|
503
|
-
const findings = report.findings
|
|
504
|
-
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);
|
|
505
1180
|
const [verdicts, setVerdicts] = React.useState(() => {
|
|
506
|
-
const initial = buildTriageState(report)
|
|
507
|
-
const saved = storage ? storage.get(storageKey) : null
|
|
1181
|
+
const initial = buildTriageState(report);
|
|
1182
|
+
const saved = storage ? storage.get(storageKey) : null;
|
|
508
1183
|
if (saved) {
|
|
509
1184
|
try {
|
|
510
|
-
const parsed = JSON.parse(saved)
|
|
511
|
-
if (parsed && typeof parsed ===
|
|
1185
|
+
const parsed = JSON.parse(saved);
|
|
1186
|
+
if (parsed && typeof parsed === "object") {
|
|
512
1187
|
for (const key of Object.keys(initial)) {
|
|
513
|
-
if (parsed[key] ===
|
|
514
|
-
initial[key] = parsed[key]
|
|
1188
|
+
if (parsed[key] === "keep" || parsed[key] === "skip" || parsed[key] === "ignore") {
|
|
1189
|
+
initial[key] = parsed[key];
|
|
515
1190
|
}
|
|
516
1191
|
}
|
|
517
1192
|
}
|
|
518
|
-
} catch {
|
|
1193
|
+
} catch {
|
|
1194
|
+
}
|
|
519
1195
|
}
|
|
520
|
-
return initial
|
|
521
|
-
})
|
|
522
|
-
const [payload, setPayload] = React.useState(null)
|
|
523
|
-
const [copied, setCopied] = React.useState(false)
|
|
524
|
-
const [filter, setFilter] = React.useState({ severities: [], dimensions: [], search:
|
|
525
|
-
const [selected, setSelected] = React.useState(null)
|
|
526
|
-
const [selectAll, setSelectAll] = React.useState(false)
|
|
527
|
-
|
|
528
|
-
/** 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);
|
|
529
1203
|
const persistVerdicts = (next) => {
|
|
530
|
-
if (storage) storage.set(storageKey, JSON.stringify(next))
|
|
531
|
-
return next
|
|
532
|
-
}
|
|
533
|
-
|
|
1204
|
+
if (storage) storage.set(storageKey, JSON.stringify(next));
|
|
1205
|
+
return next;
|
|
1206
|
+
};
|
|
534
1207
|
const setVerdict = (index, verdict) => {
|
|
535
|
-
setVerdicts((prev) => persistVerdicts({ ...prev, [String(index)]: verdict }))
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
const
|
|
540
|
-
const
|
|
541
|
-
const
|
|
542
|
-
const
|
|
543
|
-
|
|
544
|
-
const
|
|
545
|
-
const
|
|
546
|
-
const
|
|
547
|
-
const clearFilter = () => setFilter({ severities: [], dimensions: [], search: '' })
|
|
548
|
-
|
|
549
|
-
// ── Batch operations (apply to the currently VISIBLE findings, or to ALL
|
|
550
|
-
// findings when the select-all toggle is on) ──────────────────────────
|
|
551
|
-
const allIndices = allVerdictKeys(verdicts)
|
|
552
|
-
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;
|
|
553
1220
|
const applyBatch = (verdict) => {
|
|
554
|
-
setVerdicts((prev) => persistVerdicts(batchSetVerdict(prev, batchTarget, verdict)))
|
|
555
|
-
}
|
|
1221
|
+
setVerdicts((prev) => persistVerdicts(batchSetVerdict(prev, batchTarget, verdict)));
|
|
1222
|
+
};
|
|
556
1223
|
const applyBatchAll = (verdict) => {
|
|
557
|
-
setVerdicts((prev) => persistVerdicts(setAllVerdicts(prev, verdict)))
|
|
558
|
-
}
|
|
1224
|
+
setVerdicts((prev) => persistVerdicts(setAllVerdicts(prev, verdict)));
|
|
1225
|
+
};
|
|
559
1226
|
const doResetVerdicts = () => {
|
|
560
|
-
setVerdicts((prev) => persistVerdicts(setAllVerdicts(prev,
|
|
561
|
-
setSelectAll(false)
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
// ── Keyboard shortcuts (y / n / a on the selected finding, ↑/↓ to move) ──
|
|
1227
|
+
setVerdicts((prev) => persistVerdicts(setAllVerdicts(prev, "keep")));
|
|
1228
|
+
setSelectAll(false);
|
|
1229
|
+
};
|
|
565
1230
|
React.useEffect(() => {
|
|
566
|
-
const doc = typeof document !==
|
|
567
|
-
if (!doc) return
|
|
1231
|
+
const doc = typeof document !== "undefined" ? document : null;
|
|
1232
|
+
if (!doc) return;
|
|
568
1233
|
const onKeyDown = (ev) => {
|
|
569
|
-
const t = ev.target
|
|
570
|
-
if (t && (t.tagName ===
|
|
571
|
-
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);
|
|
572
1237
|
if (verdict && selected !== null && indices.includes(selected)) {
|
|
573
|
-
ev.preventDefault()
|
|
574
|
-
setVerdict(selected, verdict)
|
|
575
|
-
const pos = indices.indexOf(selected)
|
|
576
|
-
const nextIdx = indices[pos + 1]
|
|
577
|
-
if (nextIdx !==
|
|
578
|
-
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;
|
|
579
1244
|
}
|
|
580
|
-
if (ev.key ===
|
|
581
|
-
ev.preventDefault()
|
|
582
|
-
const pos = selected === null ? -1 : indices.indexOf(selected)
|
|
583
|
-
setSelected(indices[Math.min(pos + 1, indices.length - 1)])
|
|
584
|
-
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;
|
|
585
1250
|
}
|
|
586
|
-
if (ev.key ===
|
|
587
|
-
ev.preventDefault()
|
|
588
|
-
const pos = selected === null ? 0 : indices.indexOf(selected)
|
|
589
|
-
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);
|
|
590
1255
|
}
|
|
591
|
-
}
|
|
592
|
-
doc.addEventListener(
|
|
593
|
-
return () => doc.removeEventListener(
|
|
594
|
-
}, [selected, indicesKey])
|
|
595
|
-
|
|
596
|
-
const
|
|
597
|
-
const
|
|
598
|
-
const counts = countVerdicts(verdicts)
|
|
599
|
-
|
|
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);
|
|
600
1263
|
const doCopyYaml = () => {
|
|
601
|
-
const yaml = toKnownIntentionalYaml(ignored)
|
|
602
|
-
if (!yaml) return
|
|
603
|
-
const ok = copyText(yaml)
|
|
604
|
-
setCopied(true)
|
|
605
|
-
setTimeout(() => setCopied(false), 1600)
|
|
606
|
-
if (!ok) setPayload(yaml)
|
|
607
|
-
}
|
|
608
|
-
|
|
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
|
+
};
|
|
609
1271
|
const doBuildInstruction = () => {
|
|
610
|
-
const text = buildApplyInstruction(ignored)
|
|
611
|
-
setPayload(text)
|
|
612
|
-
if (text) copyText(text)
|
|
613
|
-
}
|
|
614
|
-
|
|
1272
|
+
const text = buildApplyInstruction(ignored);
|
|
1273
|
+
setPayload(text);
|
|
1274
|
+
if (text) copyText(text);
|
|
1275
|
+
};
|
|
615
1276
|
const rows = filtered.map((finding, i) => {
|
|
616
|
-
const index = indices[i]
|
|
617
|
-
const severity = finding.severity ||
|
|
618
|
-
const verdict = verdicts[String(index)] ||
|
|
619
|
-
const isSelected = selected === index
|
|
620
|
-
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
|
+
},
|
|
621
1300
|
React.createElement(
|
|
622
|
-
|
|
623
|
-
{
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
},
|
|
630
|
-
label,
|
|
631
|
-
)
|
|
632
|
-
|
|
633
|
-
return React.createElement('div', {
|
|
634
|
-
key: String(index),
|
|
635
|
-
className: 'iterate-finding',
|
|
636
|
-
'data-selected': isSelected ? '' : undefined,
|
|
637
|
-
onClick: () => setSelected(index),
|
|
638
|
-
},
|
|
639
|
-
React.createElement('div', { className: 'iterate-finding-meta' },
|
|
640
|
-
React.createElement('span', { className: 'iterate-sev-dot', style: { background: SEVERITY_COLOR[severity] || SEVERITY_COLOR.low } }),
|
|
641
|
-
React.createElement('span', {}, String(SEVERITY_LABEL[severity] || 'LOW')),
|
|
642
|
-
React.createElement('span', { className: 'iterate-finding-file' }, String(finding.file || '?')),
|
|
643
|
-
finding.line ? React.createElement('span', {}, `:${finding.line}`) : null,
|
|
644
|
-
React.createElement('span', {}, String(finding.dimension || '')),
|
|
645
|
-
),
|
|
646
|
-
React.createElement('div', { className: 'iterate-finding-summary' }, String(finding.summary || '')),
|
|
647
|
-
React.createElement('div', { className: 'iterate-finding-actions' },
|
|
648
|
-
btn('y 修复', 'keep', '保留该 finding,进入修复'),
|
|
649
|
-
btn('n 跳过', 'skip', '跳过该 finding'),
|
|
650
|
-
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 || ""))
|
|
651
1308
|
),
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
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")
|
|
659
1327
|
),
|
|
660
|
-
React.createElement(
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
),
|
|
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
|
+
)
|
|
666
1338
|
),
|
|
667
|
-
React.createElement(
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
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
|
+
)
|
|
672
1346
|
),
|
|
673
|
-
React.createElement(
|
|
674
|
-
className:
|
|
675
|
-
type:
|
|
676
|
-
placeholder:
|
|
1347
|
+
React.createElement("input", {
|
|
1348
|
+
className: "iterate-filter-search",
|
|
1349
|
+
type: "search",
|
|
1350
|
+
placeholder: "\u641C\u7D22\u6587\u4EF6 / \u6458\u8981\u2026",
|
|
677
1351
|
value: filter.search,
|
|
678
|
-
onChange: (e) => setSearchFilter(e.target.value)
|
|
1352
|
+
onChange: (e) => setSearchFilter(e.target.value)
|
|
679
1353
|
}),
|
|
680
|
-
React.createElement(
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
)
|
|
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
|
+
)
|
|
685
1359
|
),
|
|
686
|
-
React.createElement(
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
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"
|
|
691
1369
|
),
|
|
692
|
-
React.createElement(
|
|
693
|
-
React.createElement(
|
|
694
|
-
React.createElement(
|
|
695
|
-
React.createElement(
|
|
696
|
-
React.createElement(
|
|
697
|
-
React.createElement(
|
|
698
|
-
React.createElement(
|
|
699
|
-
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")
|
|
700
1378
|
),
|
|
701
1379
|
...rows,
|
|
702
|
-
React.createElement(
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
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
|
+
)
|
|
708
1390
|
),
|
|
709
|
-
payload
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
1391
|
+
payload ? React.createElement("div", { className: "iterate-payload" }, payload) : null
|
|
1392
|
+
);
|
|
1393
|
+
}
|
|
1394
|
+
function VerdictBanner(props) {
|
|
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);
|
|
1405
|
+
return React.createElement(
|
|
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"
|
|
1412
|
+
),
|
|
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
|
+
);
|
|
713
1423
|
}
|
|
714
|
-
|
|
715
|
-
/** Turn-tail chain entry: triage when findings exist, stats card otherwise. */
|
|
716
1424
|
function TurnTailEntry(props) {
|
|
717
|
-
const candidates = []
|
|
718
|
-
if (props && props.turn) candidates.push(props.turn)
|
|
719
|
-
if (props && props.matched) candidates.push(props.matched)
|
|
720
|
-
if (props && props.data) candidates.push(props.data)
|
|
721
|
-
candidates.push(props)
|
|
722
|
-
|
|
723
|
-
|
|
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;
|
|
1431
|
+
for (const c of candidates) {
|
|
1432
|
+
const run = scanSessionForRunSummary(c);
|
|
1433
|
+
if (run) {
|
|
1434
|
+
verdict = extractVerdict(run);
|
|
1435
|
+
break;
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
let report = null;
|
|
724
1439
|
for (const c of candidates) {
|
|
725
|
-
const raw = findReportInObject(c,
|
|
726
|
-
if (raw) {
|
|
1440
|
+
const raw = findReportInObject(c, void 0, 24) || scanSessionForReport(c);
|
|
1441
|
+
if (raw) {
|
|
1442
|
+
report = normalizeReport(raw);
|
|
1443
|
+
break;
|
|
1444
|
+
}
|
|
727
1445
|
}
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
return React.createElement(StatsCard, { report })
|
|
1446
|
+
const blocks = [];
|
|
1447
|
+
if (verdict) blocks.push(React.createElement(VerdictBanner, { key: "verdict", verdict }));
|
|
1448
|
+
if (report) {
|
|
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));
|
|
734
1451
|
}
|
|
735
|
-
|
|
1452
|
+
if (blocks.length === 0) return null;
|
|
1453
|
+
return React.createElement("div", { "data-iterate-root": "", className: "iterate-turn-tail-root" }, ...blocks);
|
|
736
1454
|
}
|
|
737
|
-
|
|
738
|
-
/** Progress capsule: briefly surfaces round-completion (incl. convergence). */
|
|
739
1455
|
function ProgressCapsule() {
|
|
740
|
-
const [info, setInfo] = React.useState(null)
|
|
1456
|
+
const [info, setInfo] = React.useState(null);
|
|
741
1457
|
React.useEffect(() => {
|
|
742
|
-
let timer = null
|
|
1458
|
+
let timer = null;
|
|
743
1459
|
const listener = (payload) => {
|
|
744
|
-
const converged = payload && payload.converged === true
|
|
745
|
-
const round = payload && typeof payload.round ===
|
|
746
|
-
setInfo({ text: converged ? `Round ${round}
|
|
747
|
-
if (timer) clearTimeout(timer)
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
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);
|
|
752
1467
|
return () => {
|
|
753
|
-
const i = roundPulseListeners.indexOf(listener)
|
|
754
|
-
if (i >= 0) roundPulseListeners.splice(i, 1)
|
|
755
|
-
if (timer) clearTimeout(timer)
|
|
756
|
-
}
|
|
757
|
-
}, [])
|
|
758
|
-
if (!info) return null
|
|
759
|
-
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);
|
|
760
1475
|
}
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
const [
|
|
765
|
-
const [
|
|
766
|
-
const [
|
|
767
|
-
const [
|
|
768
|
-
const [
|
|
769
|
-
const guide = buildConfigEditGuide()
|
|
770
|
-
const statusGuide = buildRuntimeStatusGuide()
|
|
771
|
-
|
|
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();
|
|
772
1486
|
const toggleTheme = () => {
|
|
773
|
-
const next = !enabled
|
|
774
|
-
setEnabled(next)
|
|
775
|
-
setThemeEnabled(next)
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
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")
|
|
796
1529
|
),
|
|
797
|
-
React.createElement(
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
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")
|
|
807
1556
|
),
|
|
808
|
-
React.createElement(
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
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")
|
|
817
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
|
+
)
|
|
818
1583
|
),
|
|
819
|
-
React.createElement(
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
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")
|
|
823
1592
|
),
|
|
824
|
-
React.createElement(
|
|
825
|
-
|
|
826
|
-
|
|
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")
|
|
827
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
|
+
)
|
|
828
1614
|
),
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
:
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
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")
|
|
836
1623
|
),
|
|
837
|
-
React.createElement(
|
|
838
|
-
)
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
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
|
+
);
|
|
843
1657
|
}
|
|
844
|
-
|
|
845
|
-
// ─── Registration ────────────────────────────────────────────────────────────
|
|
846
|
-
|
|
847
|
-
/**
|
|
848
|
-
* Chain selector for conversation.chat.turnTail.
|
|
849
|
-
* Returns a marker when the turn contains an iterate review report, null otherwise.
|
|
850
|
-
*/
|
|
851
1658
|
function selectTurnTail(owner) {
|
|
852
|
-
if (!owner) return null
|
|
853
|
-
|
|
854
|
-
|
|
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;
|
|
855
1663
|
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
style.dataset.plugin = PLUGIN_TAG
|
|
877
|
-
style.dataset.pluginCss = 'iterate-main'
|
|
878
|
-
style.textContent = ITERATE_CSS
|
|
879
|
-
document.head.appendChild(style)
|
|
880
|
-
if (typeof ctx.effect === 'function') {
|
|
881
|
-
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
|
+
});
|
|
882
1684
|
}
|
|
883
1685
|
}
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
log('React or slots service unavailable — slot UI disabled')
|
|
888
|
-
return
|
|
1686
|
+
if (slotsSvc === void 0) {
|
|
1687
|
+
log("slots service unavailable \u2014 slot UI disabled");
|
|
1688
|
+
return;
|
|
889
1689
|
}
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
)
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
slotsSvc.register(
|
|
920
|
-
{ name: 'settings.section', id: 'iterate-settings', order: 30, label: () => 'iterate' },
|
|
921
|
-
(props) => React.createElement(SettingsPanel, props),
|
|
922
|
-
),
|
|
923
|
-
)
|
|
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
|
+
);
|
|
924
1719
|
}
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
if (themeEnabled) applyThemeSkin()
|
|
930
|
-
})
|
|
1720
|
+
if (typeof ctx.on === "function") {
|
|
1721
|
+
ctx.on("theme/change", () => {
|
|
1722
|
+
if (themeEnabled) applyThemeSkin();
|
|
1723
|
+
});
|
|
931
1724
|
}
|
|
932
|
-
}
|
|
1725
|
+
}
|
|
1726
|
+
return module.exports; } });
|
|
1727
|
+
|