iterate-plugin 3.15.0 → 3.17.0
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 +4 -2
- package/README.zh-CN.md +3 -1
- package/dist/index.js +5 -4
- package/dist/tools/decision-log.js +21 -2
- package/lib/client.js +443 -7
- package/lib/parse.js +192 -0
- package/package.json +1 -1
- package/src/client/i18n.ts +29 -2
- package/src/client/index.ts +235 -21
- package/src/client/run-compare.ts +239 -0
- package/src/index.ts +5 -4
- package/src/tools/decision-log.ts +20 -2
package/lib/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* iterate-plugin client source-digest:
|
|
1
|
+
/* iterate-plugin client source-digest: 1396cad72b6ae5e0e09c4fac31f0e74b0f17719bb8a1d3c2c30cc749e34b70c1 */
|
|
2
2
|
window.__ModuleLoader__.load({ id: "iterate-plugin", factory: (require) => {
|
|
3
3
|
var module = { exports: {} };
|
|
4
4
|
var exports = module.exports;
|
|
@@ -1343,6 +1343,125 @@ function buildQualityGateClearInstruction() {
|
|
|
1343
1343
|
${JSON.stringify({ operation: "clear" }, null, 2)}
|
|
1344
1344
|
\`\`\``;
|
|
1345
1345
|
}
|
|
1346
|
+
function buildQualityGateComputeInstruction(input) {
|
|
1347
|
+
const src = input && typeof input === "object" ? input : {};
|
|
1348
|
+
const cleanStr = (v) => typeof v === "string" && v.length > 0;
|
|
1349
|
+
const dimensions = Array.isArray(src.dimensions) ? src.dimensions.filter((d) => typeof d === "string" && d.length > 0) : [];
|
|
1350
|
+
const findings = Array.isArray(src.findings) ? src.findings.filter((f) => f && typeof f === "object" && cleanStr(f.dimension) && cleanStr(f.file)).map((f) => ({
|
|
1351
|
+
dimension: f.dimension,
|
|
1352
|
+
severity: typeof f.severity === "string" && f.severity ? f.severity : "medium",
|
|
1353
|
+
file: f.file,
|
|
1354
|
+
...typeof f.line === "number" && Number.isFinite(f.line) && f.line > 0 ? { line: f.line } : {}
|
|
1355
|
+
})) : [];
|
|
1356
|
+
const validationResults = Array.isArray(src.validationResults) ? src.validationResults.filter((v) => v && typeof v === "object" && cleanStr(v.command) && typeof v.exitCode === "number" && Number.isFinite(v.exitCode)).map((v) => ({ command: v.command, exitCode: v.exitCode })) : [];
|
|
1357
|
+
const findingsByRound = {};
|
|
1358
|
+
if (src.findingsByRound && typeof src.findingsByRound === "object" && !Array.isArray(src.findingsByRound)) {
|
|
1359
|
+
for (const [dim, series] of Object.entries(src.findingsByRound)) {
|
|
1360
|
+
const clean = Array.isArray(series) ? series.filter((n) => typeof n === "number" && Number.isFinite(n) && n >= 0) : [];
|
|
1361
|
+
if (clean.length > 0) findingsByRound[dim] = clean;
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
const fixedByDimension = {};
|
|
1365
|
+
if (src.fixedByDimension && typeof src.fixedByDimension === "object" && !Array.isArray(src.fixedByDimension)) {
|
|
1366
|
+
for (const [dim, n] of Object.entries(src.fixedByDimension)) {
|
|
1367
|
+
if (typeof n === "number" && Number.isFinite(n) && n >= 0) fixedByDimension[dim] = n;
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
const payload = { operation: "compute" };
|
|
1371
|
+
if (dimensions.length > 0) payload.dimensions = dimensions;
|
|
1372
|
+
if (findings.length > 0) payload.findings = findings;
|
|
1373
|
+
if (validationResults.length > 0) payload.validationResults = validationResults;
|
|
1374
|
+
if (Object.keys(findingsByRound).length > 0) payload.findingsByRound = findingsByRound;
|
|
1375
|
+
if (Object.keys(fixedByDimension).length > 0) payload.fixedByDimension = fixedByDimension;
|
|
1376
|
+
return `\u8BF7\u8C03\u7528 \`iterate_quality_gate\` \u91CD\u7B97\u8D28\u91CF\u95E8\u7981\u8BC1\u4E66\uFF08\u4ECE\u5F53\u524D\u4F1A\u8BDD\u63A8\u5BFC\u7EF4\u5EA6/\u53D1\u73B0/\u9A8C\u8BC1\u7ED3\u679C\u5E76\u6301\u4E45\u5316\uFF09\uFF1A
|
|
1377
|
+
|
|
1378
|
+
\`\`\`json
|
|
1379
|
+
${JSON.stringify(payload, null, 2)}
|
|
1380
|
+
\`\`\``;
|
|
1381
|
+
}
|
|
1382
|
+
function deriveQualityGateComputeInput(manifest, qualityGate) {
|
|
1383
|
+
const m = manifest && typeof manifest === "object" ? manifest : {};
|
|
1384
|
+
const asArray2 = (v) => Array.isArray(v) ? v : [];
|
|
1385
|
+
const dims = [];
|
|
1386
|
+
for (const r of asArray2(m.rounds)) {
|
|
1387
|
+
for (const t2 of asArray2(r && r.threads)) {
|
|
1388
|
+
const d = t2 && typeof t2.dimension === "string" ? t2.dimension : "";
|
|
1389
|
+
if (d && !dims.includes(d)) dims.push(d);
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
if (dims.length === 0 && qualityGate && Array.isArray(qualityGate.dimensions)) {
|
|
1393
|
+
for (const d of qualityGate.dimensions) {
|
|
1394
|
+
const name2 = d && typeof d.dimension === "string" ? d.dimension : "";
|
|
1395
|
+
if (name2 && !dims.includes(name2)) dims.push(name2);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
const findings = [];
|
|
1399
|
+
for (const f of asArray2(m.findings)) {
|
|
1400
|
+
if (!f || typeof f !== "object") continue;
|
|
1401
|
+
if (typeof f.dimension !== "string" || !f.dimension) continue;
|
|
1402
|
+
if (typeof f.file !== "string" || !f.file) continue;
|
|
1403
|
+
findings.push({
|
|
1404
|
+
dimension: f.dimension,
|
|
1405
|
+
severity: typeof f.severity === "string" && f.severity ? f.severity : "medium",
|
|
1406
|
+
file: f.file,
|
|
1407
|
+
...typeof f.line === "number" && Number.isFinite(f.line) && f.line > 0 ? { line: f.line } : {}
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1410
|
+
const validationResults = [];
|
|
1411
|
+
for (const v of asArray2(m.validations)) {
|
|
1412
|
+
if (!v || typeof v !== "object") continue;
|
|
1413
|
+
if (typeof v.command !== "string" || !v.command) continue;
|
|
1414
|
+
if (typeof v.exitCode !== "number" || !Number.isFinite(v.exitCode)) continue;
|
|
1415
|
+
validationResults.push({ command: v.command, exitCode: v.exitCode });
|
|
1416
|
+
}
|
|
1417
|
+
const rounds = asArray2(m.rounds);
|
|
1418
|
+
const allDims = dims.slice();
|
|
1419
|
+
for (const r of rounds) {
|
|
1420
|
+
for (const t2 of asArray2(r && r.threads)) {
|
|
1421
|
+
const d = t2 && typeof t2.dimension === "string" ? t2.dimension : "";
|
|
1422
|
+
if (d && !allDims.includes(d)) allDims.push(d);
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
const byRound = {};
|
|
1426
|
+
for (const d of allDims) {
|
|
1427
|
+
byRound[d] = [];
|
|
1428
|
+
}
|
|
1429
|
+
for (const r of rounds) {
|
|
1430
|
+
for (const d of allDims) {
|
|
1431
|
+
let n = 0;
|
|
1432
|
+
for (const t2 of asArray2(r && r.threads)) {
|
|
1433
|
+
if (!t2 || t2.dimension !== d) continue;
|
|
1434
|
+
for (const f of asArray2(t2 && t2.findings)) {
|
|
1435
|
+
if (f && typeof f === "object" && typeof f.dimension === "string" && f.dimension === d) n += 1;
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
byRound[d].push(n);
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
const fixedByDimension = {};
|
|
1442
|
+
const findingsIndex = findings;
|
|
1443
|
+
for (const fx of asArray2(m.fixes)) {
|
|
1444
|
+
if (!fx || typeof fx !== "object" || fx.success !== true) continue;
|
|
1445
|
+
const file = typeof fx.file === "string" ? fx.file : "";
|
|
1446
|
+
if (!file) continue;
|
|
1447
|
+
let dim = "";
|
|
1448
|
+
for (const f of findingsIndex) {
|
|
1449
|
+
if (f.file !== file) continue;
|
|
1450
|
+
if (typeof f.line === "number" && typeof fx.line === "number" && f.line !== fx.line) continue;
|
|
1451
|
+
dim = f.dimension;
|
|
1452
|
+
break;
|
|
1453
|
+
}
|
|
1454
|
+
if (!dim) continue;
|
|
1455
|
+
fixedByDimension[dim] = (fixedByDimension[dim] || 0) + 1;
|
|
1456
|
+
}
|
|
1457
|
+
return {
|
|
1458
|
+
dimensions: dims,
|
|
1459
|
+
findings,
|
|
1460
|
+
validationResults,
|
|
1461
|
+
findingsByRound: byRound,
|
|
1462
|
+
fixedByDimension
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
1346
1465
|
function buildPreflightInstruction() {
|
|
1347
1466
|
return `\u8BF7\u8C03\u7528 \`iterate_preflight\` \u8FD0\u884C\u5F00\u8DD1\u524D\u9884\u68C0\uFF08\u53EA\u8BFB\uFF1A\u6821\u9A8C validation.commands\u3001git\u3001\u810F\u5DE5\u4F5C\u533A\u3001\u9648\u65E7\u8D28\u91CF\u95E8\u7981\u4E0E\u4E2D\u65AD\u65AD\u70B9\uFF09\uFF1A
|
|
1348
1467
|
|
|
@@ -2645,7 +2764,26 @@ var EN = {
|
|
|
2645
2764
|
"\u5404\u8F6E\u53D1\u73B0\u6570\u91CF\uFF1A{a0}{a1}": "Findings per round: {a0}{a1}",
|
|
2646
2765
|
" \xB7 \u5F53\u524D Round {a0}": " \xB7 current round {a0}",
|
|
2647
2766
|
"\u8DE8\u8F6E\u5BF9\u6BD4\uFF08\u672C\u4F1A\u8BDD\uFF09": "Cross-round comparison (this session)",
|
|
2648
|
-
"\u8DE8\u4F1A\u8BDD\u5BF9\u6BD4\uFF1A\u672C\u8868\u53EA\u8986\u76D6\u5F53\u524D\u4F1A\u8BDD\uFF1B\
|
|
2767
|
+
"\u8DE8\u4F1A\u8BDD\u5BF9\u6BD4\uFF1A\u672C\u8868\u53EA\u8986\u76D6\u5F53\u524D\u4F1A\u8BDD\uFF1B\u8981\u8DE8\u4E24\u6B21\u8FED\u4EE3\u5BF9\u6BD4\uFF0C\u7528\u4E0B\u65B9\u300C\u5B58\u4E3A\u5BF9\u6BD4\u57FA\u51C6\u300D\u4FDD\u5B58\u672C\u6B21\u8FD0\u884C\uFF0C\u518D\u4E0E\u4E0B\u4E00\u6B21\u8FED\u4EE3\u5BF9\u6BD4\u3002": 'Cross-session comparison: this table only covers the current session \u2014 to compare two iterations, save this run with "Save as comparison baseline" below, then compare the next iteration against it.',
|
|
2768
|
+
\u8DE8\u4F1A\u8BDD\u5BF9\u6BD4: "Cross-session comparison",
|
|
2769
|
+
"\u57FA\u51C6\u5DF2\u4FDD\u5B58\uFF1A{a0}": "Baseline saved: {a0}",
|
|
2770
|
+
"\u5148\u4FDD\u5B58\u5F53\u524D\u8FD0\u884C\u4E3A\u57FA\u51C6\uFF0C\u518D\u8DD1\u4E0B\u4E00\u6B21\u8FED\u4EE3\u540E\u4E00\u952E\u5BF9\u6BD4": "Save the current run as the baseline, then one-click compare the next iteration against it",
|
|
2771
|
+
"\u628A\u5F53\u524D\u8FD0\u884C\u6458\u8981\u4FDD\u5B58\u4E3A\u5BF9\u6BD4\u57FA\u51C6\uFF08\u542B\u8F6E\u6B21/\u53D1\u73B0/\u4FEE\u590D/\u9A8C\u8BC1/\u95E8\u7981\uFF09": "Save the current run summary as the comparison baseline (rounds / findings / fixes / validation / gate)",
|
|
2772
|
+
"\u5F53\u524D\u6CA1\u6709\u53EF\u4FDD\u5B58\u7684\u8FD0\u884C\u6570\u636E": "No run data to save yet",
|
|
2773
|
+
\u5DF2\u5B58\u4E3A\u57FA\u51C6: "Saved as baseline",
|
|
2774
|
+
\u5B58\u4E3A\u5BF9\u6BD4\u57FA\u51C6: "Save as comparison baseline",
|
|
2775
|
+
"\u663E\u793A\u6216\u9690\u85CF\u4E0E\u5DF2\u4FDD\u5B58\u57FA\u51C6\u7684\u5BF9\u6BD4\u8868": "Show or hide the comparison against the saved baseline",
|
|
2776
|
+
\u6536\u8D77\u5BF9\u6BD4: "Hide comparison",
|
|
2777
|
+
\u4E0E\u57FA\u51C6\u5BF9\u6BD4: "Compare with baseline",
|
|
2778
|
+
"\u6E05\u9664\u5DF2\u4FDD\u5B58\u7684\u5BF9\u6BD4\u57FA\u51C6": "Clear the saved comparison baseline",
|
|
2779
|
+
\u6E05\u9664\u57FA\u51C6: "Clear baseline",
|
|
2780
|
+
"\u5F53\u524D\u4F1A\u8BDD\u6CA1\u6709\u8FD0\u884C\u6570\u636E\u53EF\u5BF9\u6BD4": "This session has no run data to compare yet",
|
|
2781
|
+
\u6307\u6807: "Metric",
|
|
2782
|
+
\u57FA\u51C6: "Baseline",
|
|
2783
|
+
\u5F53\u524D: "Current",
|
|
2784
|
+
\u53D8\u5316: "Delta",
|
|
2785
|
+
\u590D\u5236\u5BF9\u6BD4\u8868: "Copy comparison table",
|
|
2786
|
+
"\u590D\u5236\u8DE8\u4F1A\u8BDD\u5BF9\u6BD4\u8868\u4E3A Markdown\uFF08\u53EF\u7C98\u8D34\u8FDB PR / issue / CI \u6CE8\u91CA\uFF09": "Copy the cross-session comparison as a Markdown table (paste into a PR / issue / CI comment)",
|
|
2649
2787
|
// ── F3 finding locations ─────────────────────────────────────────────────
|
|
2650
2788
|
\u6682\u65E0\u53D1\u73B0: "No findings",
|
|
2651
2789
|
"{a0}\uFF08{a1} \u9879\uFF09": "{a0} ({a1} findings)",
|
|
@@ -2712,6 +2850,9 @@ var EN = {
|
|
|
2712
2850
|
\u67E5\u8BE2\u95E8\u7981: "Query gate",
|
|
2713
2851
|
"\u590D\u5236 iterate_quality_gate clear \u6307\u4EE4\u6587\u672C\uFF08\u91CD\u7F6E\u9648\u65E7\u7684 FAIL \u8BC1\u4E66\uFF09": "Copy the iterate_quality_gate clear instruction (resets a stale FAIL certificate)",
|
|
2714
2852
|
\u6E05\u9664\u95E8\u7981: "Clear gate",
|
|
2853
|
+
\u91CD\u7B97\u95E8\u7981: "Recompute gate",
|
|
2854
|
+
"\u590D\u5236 iterate_quality_gate compute \u6307\u4EE4\uFF1A\u4ECE\u5F53\u524D\u4F1A\u8BDD\u63A8\u5BFC\u7EF4\u5EA6/\u53D1\u73B0/\u9A8C\u8BC1\u7ED3\u679C\uFF0C\u4E00\u952E\u91CD\u7B97\u5E76\u6301\u4E45\u5316\u95E8\u7981\u8BC1\u4E66": "Copy the iterate_quality_gate compute instruction: derive dimensions / findings / validation results from this session and recompute + persist the gate certificate",
|
|
2855
|
+
"\u5F53\u524D\u4F1A\u8BDD\u6CA1\u6709\u53EF\u91CD\u7B97\u7684\u95E8\u7981\u6570\u636E\uFF08\u9700\u5148\u6709\u5BA1\u67E5/\u9A8C\u8BC1\u7ED3\u679C\uFF09": "No gate data in this session to recompute (need review / validation results first)",
|
|
2715
2856
|
"\u590D\u5236\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u6307\u4EE4\uFF1A\u8BA9\u6A21\u578B\u56DE\u663E .iterate/ \u4E0B\u7684\u95E8\u7981\u3001\u7ECF\u9A8C\u3001\u9632\u5FA1\u4E8B\u4EF6\u4E0E transcript": "Copy the disk-snapshot instruction: have the model echo the gate, experience, defense events and transcript artifacts under .iterate/",
|
|
2716
2857
|
\u8D28\u91CF\u95E8\u7981\u89C6\u56FE: "Quality gate view",
|
|
2717
2858
|
\u8D28\u91CF\u95E8\u7981\u8BC1\u4E66: "Quality gate certificate",
|
|
@@ -3042,6 +3183,116 @@ function registerIterateContextMenus(service, deps, instructionByEntry) {
|
|
|
3042
3183
|
};
|
|
3043
3184
|
}
|
|
3044
3185
|
|
|
3186
|
+
// src/client/run-compare.ts
|
|
3187
|
+
var RUN_BASELINE_VERSION = 1;
|
|
3188
|
+
var RUN_BASELINE_STORAGE_KEY = "iterate.run-baseline";
|
|
3189
|
+
function buildRunBaseline(input, savedAt) {
|
|
3190
|
+
const convergence = Array.isArray(input.convergence) ? input.convergence : [];
|
|
3191
|
+
const findings = (Array.isArray(input.findings) ? input.findings : []).filter(
|
|
3192
|
+
(f) => typeof f === "object" && f !== null
|
|
3193
|
+
);
|
|
3194
|
+
const fixes = (Array.isArray(input.fixes) ? input.fixes : []).filter(
|
|
3195
|
+
(f) => typeof f === "object" && f !== null
|
|
3196
|
+
);
|
|
3197
|
+
const validations = (Array.isArray(input.validations) ? input.validations : []).filter(
|
|
3198
|
+
(v) => typeof v === "object" && v !== null
|
|
3199
|
+
);
|
|
3200
|
+
let criticalHigh = 0;
|
|
3201
|
+
for (const f of findings) {
|
|
3202
|
+
const sev = String(f && f.severity || "").toLowerCase();
|
|
3203
|
+
if (sev === "critical" || sev === "high") criticalHigh += 1;
|
|
3204
|
+
}
|
|
3205
|
+
let fixed = 0;
|
|
3206
|
+
let failedFixes = 0;
|
|
3207
|
+
for (const x of fixes) {
|
|
3208
|
+
if (x && x.success === true) fixed += 1;
|
|
3209
|
+
else if (x && x.success === false) failedFixes += 1;
|
|
3210
|
+
}
|
|
3211
|
+
let passed = 0;
|
|
3212
|
+
for (const v of validations) {
|
|
3213
|
+
if (v && v.allowed === true) passed += 1;
|
|
3214
|
+
}
|
|
3215
|
+
const validationTotal = validations.length;
|
|
3216
|
+
const validationPassRate = validationTotal > 0 ? Math.round(passed / validationTotal * 100) : null;
|
|
3217
|
+
return {
|
|
3218
|
+
version: RUN_BASELINE_VERSION,
|
|
3219
|
+
savedAt,
|
|
3220
|
+
...input.project ? { project: input.project } : {},
|
|
3221
|
+
...input.mode ? { mode: input.mode } : {},
|
|
3222
|
+
rounds: convergence.length,
|
|
3223
|
+
findings: findings.length,
|
|
3224
|
+
criticalHigh,
|
|
3225
|
+
fixed,
|
|
3226
|
+
failedFixes,
|
|
3227
|
+
validationPassRate,
|
|
3228
|
+
validationTotal,
|
|
3229
|
+
qualityGateStatus: input.qualityGateStatus || null,
|
|
3230
|
+
qualityGateScore: typeof input.qualityGateScore === "number" ? input.qualityGateScore : null,
|
|
3231
|
+
stoppedReason: input.stoppedReason || null,
|
|
3232
|
+
convergence
|
|
3233
|
+
};
|
|
3234
|
+
}
|
|
3235
|
+
function parseRunBaseline(raw) {
|
|
3236
|
+
if (!raw) return null;
|
|
3237
|
+
let parsed;
|
|
3238
|
+
try {
|
|
3239
|
+
parsed = JSON.parse(raw);
|
|
3240
|
+
} catch {
|
|
3241
|
+
return null;
|
|
3242
|
+
}
|
|
3243
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
3244
|
+
const b = parsed;
|
|
3245
|
+
if (b.version !== RUN_BASELINE_VERSION) return null;
|
|
3246
|
+
if (typeof b.savedAt !== "string" || b.savedAt.length === 0) return null;
|
|
3247
|
+
if (typeof b.findings !== "number" || !Number.isFinite(b.findings)) return null;
|
|
3248
|
+
const convergence = Array.isArray(b.convergence) ? b.convergence.filter((n) => typeof n === "number" && Number.isFinite(n)) : [];
|
|
3249
|
+
return {
|
|
3250
|
+
version: RUN_BASELINE_VERSION,
|
|
3251
|
+
savedAt: b.savedAt,
|
|
3252
|
+
...typeof b.project === "string" && b.project ? { project: b.project } : {},
|
|
3253
|
+
...typeof b.mode === "string" && b.mode ? { mode: b.mode } : {},
|
|
3254
|
+
rounds: typeof b.rounds === "number" && Number.isFinite(b.rounds) ? b.rounds : convergence.length,
|
|
3255
|
+
findings: b.findings,
|
|
3256
|
+
criticalHigh: typeof b.criticalHigh === "number" && Number.isFinite(b.criticalHigh) ? b.criticalHigh : 0,
|
|
3257
|
+
fixed: typeof b.fixed === "number" && Number.isFinite(b.fixed) ? b.fixed : 0,
|
|
3258
|
+
failedFixes: typeof b.failedFixes === "number" && Number.isFinite(b.failedFixes) ? b.failedFixes : 0,
|
|
3259
|
+
validationPassRate: typeof b.validationPassRate === "number" && Number.isFinite(b.validationPassRate) ? b.validationPassRate : null,
|
|
3260
|
+
validationTotal: typeof b.validationTotal === "number" && Number.isFinite(b.validationTotal) ? b.validationTotal : 0,
|
|
3261
|
+
qualityGateStatus: typeof b.qualityGateStatus === "string" && b.qualityGateStatus ? b.qualityGateStatus : null,
|
|
3262
|
+
qualityGateScore: typeof b.qualityGateScore === "number" && Number.isFinite(b.qualityGateScore) ? b.qualityGateScore : null,
|
|
3263
|
+
stoppedReason: typeof b.stoppedReason === "string" && b.stoppedReason ? b.stoppedReason : null,
|
|
3264
|
+
convergence
|
|
3265
|
+
};
|
|
3266
|
+
}
|
|
3267
|
+
function compareRuns(baseline, current3) {
|
|
3268
|
+
const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
3269
|
+
const rows = [
|
|
3270
|
+
{ key: "rounds", baseline: num(baseline.rounds), current: num(current3.rounds), higherIsBetter: null },
|
|
3271
|
+
{ key: "findings", baseline: num(baseline.findings), current: num(current3.findings), higherIsBetter: false },
|
|
3272
|
+
{ key: "criticalHigh", baseline: num(baseline.criticalHigh), current: num(current3.criticalHigh), higherIsBetter: false },
|
|
3273
|
+
{ key: "fixed", baseline: num(baseline.fixed), current: num(current3.fixed), higherIsBetter: true },
|
|
3274
|
+
{ key: "failedFixes", baseline: num(baseline.failedFixes), current: num(current3.failedFixes), higherIsBetter: false },
|
|
3275
|
+
{ key: "validationPassRate", baseline: num(baseline.validationPassRate), current: num(current3.validationPassRate), higherIsBetter: true },
|
|
3276
|
+
{ key: "qualityGateScore", baseline: num(baseline.qualityGateScore), current: num(current3.qualityGateScore), higherIsBetter: true }
|
|
3277
|
+
];
|
|
3278
|
+
return rows;
|
|
3279
|
+
}
|
|
3280
|
+
function buildRunCompareMarkdown(rows, labels) {
|
|
3281
|
+
const cell = (v) => v === null ? "\u2014" : String(v);
|
|
3282
|
+
const header = ["\u6307\u6807", "\u57FA\u51C6", "\u5F53\u524D", "\u53D8\u5316"].map((h) => labels[h] ?? h);
|
|
3283
|
+
const lines = [
|
|
3284
|
+
`| ${header.join(" | ")} |`,
|
|
3285
|
+
`| ${header.map(() => "---").join(" | ")} |`
|
|
3286
|
+
];
|
|
3287
|
+
for (const row of rows) {
|
|
3288
|
+
const delta = row.baseline === null || row.current === null ? "\u2014" : String(row.current - row.baseline);
|
|
3289
|
+
lines.push(
|
|
3290
|
+
`| ${labels[row.key] ?? row.key} | ${cell(row.baseline)} | ${cell(row.current)} | ${delta} |`
|
|
3291
|
+
);
|
|
3292
|
+
}
|
|
3293
|
+
return lines.join("\n");
|
|
3294
|
+
}
|
|
3295
|
+
|
|
3045
3296
|
// src/client/index.ts
|
|
3046
3297
|
var React = require("react");
|
|
3047
3298
|
var name = "iterate-plugin";
|
|
@@ -4737,6 +4988,15 @@ var OBS_LIVE_META = {
|
|
|
4737
4988
|
info: { label: { zh: "\u4FE1\u606F", en: "Info" }, color: "var(--dsw-alias-label-secondary)" }
|
|
4738
4989
|
};
|
|
4739
4990
|
var OBS_LIVE_FALLBACK_COLOR = "var(--dsw-alias-label-secondary)";
|
|
4991
|
+
var RUN_COMPARE_METRIC_LABELS = {
|
|
4992
|
+
rounds: { zh: "\u8F6E\u6B21", en: "Rounds" },
|
|
4993
|
+
findings: { zh: "\u53D1\u73B0", en: "Findings" },
|
|
4994
|
+
criticalHigh: { zh: "\u4E25\u91CD\uFF08critical+high\uFF09", en: "Critical + high" },
|
|
4995
|
+
fixed: { zh: "\u4FEE\u590D\u6210\u529F", en: "Fixes applied" },
|
|
4996
|
+
failedFixes: { zh: "\u4FEE\u590D\u5931\u8D25", en: "Failed fixes" },
|
|
4997
|
+
validationPassRate: { zh: "\u9A8C\u8BC1\u901A\u8FC7\u7387", en: "Validation pass rate" },
|
|
4998
|
+
qualityGateScore: { zh: "\u95E8\u7981\u5F97\u5206", en: "Quality gate score" }
|
|
4999
|
+
};
|
|
4740
5000
|
function formatObsLiveTime(ts) {
|
|
4741
5001
|
if (!ts) return "";
|
|
4742
5002
|
let date = null;
|
|
@@ -4804,6 +5064,15 @@ function ObservatoryPanel(props) {
|
|
|
4804
5064
|
);
|
|
4805
5065
|
const [expSearch, setExpSearch] = React.useState("");
|
|
4806
5066
|
const [defenseFilter, setDefenseFilter] = React.useState("");
|
|
5067
|
+
const [baseline, setBaseline] = React.useState(() => {
|
|
5068
|
+
try {
|
|
5069
|
+
return parseRunBaseline(storage2 ? storage2.get(RUN_BASELINE_STORAGE_KEY) : null);
|
|
5070
|
+
} catch {
|
|
5071
|
+
return null;
|
|
5072
|
+
}
|
|
5073
|
+
});
|
|
5074
|
+
const [showCompare, setShowCompare] = React.useState(false);
|
|
5075
|
+
const [baselineFlash, setBaselineFlash] = React.useState(false);
|
|
4807
5076
|
const copyTimer = React.useRef(null);
|
|
4808
5077
|
React.useEffect(() => () => {
|
|
4809
5078
|
if (copyTimer.current) clearTimeout(copyTimer.current);
|
|
@@ -5047,11 +5316,51 @@ function ObservatoryPanel(props) {
|
|
|
5047
5316
|
})
|
|
5048
5317
|
);
|
|
5049
5318
|
};
|
|
5319
|
+
const currentRunSummary = () => {
|
|
5320
|
+
const hasRun = (manifest.convergence || []).length > 0 || (manifest.findings || []).length > 0 || (manifest.fixes || []).length > 0 || typeof manifest.round === "number" && manifest.round > 0;
|
|
5321
|
+
if (!hasRun) return null;
|
|
5322
|
+
return buildRunBaseline(
|
|
5323
|
+
{
|
|
5324
|
+
project: manifest.project || void 0,
|
|
5325
|
+
mode: manifest.mode || void 0,
|
|
5326
|
+
convergence: manifest.convergence || [],
|
|
5327
|
+
findings: manifest.findings || [],
|
|
5328
|
+
fixes: manifest.fixes || [],
|
|
5329
|
+
validations: manifest.validations || [],
|
|
5330
|
+
qualityGateStatus: qualityGate && qualityGate.overallStatus || null,
|
|
5331
|
+
qualityGateScore: qualityGate && typeof qualityGate.overallScore === "number" ? qualityGate.overallScore : null,
|
|
5332
|
+
stoppedReason: manifest.stoppedReason || null
|
|
5333
|
+
},
|
|
5334
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
5335
|
+
);
|
|
5336
|
+
};
|
|
5337
|
+
const saveBaseline = () => {
|
|
5338
|
+
const summary = currentRunSummary();
|
|
5339
|
+
if (!summary || !storage2) return;
|
|
5340
|
+
try {
|
|
5341
|
+
storage2.set(RUN_BASELINE_STORAGE_KEY, JSON.stringify(summary));
|
|
5342
|
+
setBaseline(summary);
|
|
5343
|
+
setShowCompare(true);
|
|
5344
|
+
setBaselineFlash(true);
|
|
5345
|
+
if (copyTimer.current) clearTimeout(copyTimer.current);
|
|
5346
|
+
copyTimer.current = setTimeout(() => setBaselineFlash(false), 1600);
|
|
5347
|
+
} catch (err) {
|
|
5348
|
+
log("failed to persist run baseline", err);
|
|
5349
|
+
}
|
|
5350
|
+
};
|
|
5351
|
+
const clearBaseline = () => {
|
|
5352
|
+
if (storage2) {
|
|
5353
|
+
try {
|
|
5354
|
+
storage2.remove(RUN_BASELINE_STORAGE_KEY);
|
|
5355
|
+
} catch (err) {
|
|
5356
|
+
log("failed to clear run baseline", err);
|
|
5357
|
+
}
|
|
5358
|
+
}
|
|
5359
|
+
setBaseline(null);
|
|
5360
|
+
setShowCompare(false);
|
|
5361
|
+
};
|
|
5050
5362
|
const renderTrend = () => {
|
|
5051
5363
|
const conv = manifest.convergence || [];
|
|
5052
|
-
if (conv.length === 0) {
|
|
5053
|
-
return React.createElement("div", { className: "iterate-obs-empty" }, t("\u6682\u65E0\u6536\u655B\u6570\u636E"));
|
|
5054
|
-
}
|
|
5055
5364
|
const points = conv.map((n, i) => ({ round: i + 1, count: n }));
|
|
5056
5365
|
const report = latestReport(session);
|
|
5057
5366
|
const comparison = buildRoundComparison(report, manifest.validations || [], manifest.fixes || []);
|
|
@@ -5092,7 +5401,7 @@ function ObservatoryPanel(props) {
|
|
|
5092
5401
|
})
|
|
5093
5402
|
)
|
|
5094
5403
|
);
|
|
5095
|
-
|
|
5404
|
+
const trendBody = conv.length === 0 ? React.createElement("div", { className: "iterate-obs-empty" }, t("\u6682\u65E0\u6536\u655B\u6570\u636E")) : React.createElement(
|
|
5096
5405
|
"div",
|
|
5097
5406
|
{},
|
|
5098
5407
|
React.createElement(TrendChart, { points }),
|
|
@@ -5111,12 +5420,125 @@ function ObservatoryPanel(props) {
|
|
|
5111
5420
|
React.createElement(
|
|
5112
5421
|
"span",
|
|
5113
5422
|
{ className: "iterate-obs-head-meta" },
|
|
5114
|
-
t("\u8DE8\u4F1A\u8BDD\u5BF9\u6BD4\uFF1A\u672C\u8868\u53EA\u8986\u76D6\u5F53\u524D\u4F1A\u8BDD\uFF1B\
|
|
5423
|
+
t("\u8DE8\u4F1A\u8BDD\u5BF9\u6BD4\uFF1A\u672C\u8868\u53EA\u8986\u76D6\u5F53\u524D\u4F1A\u8BDD\uFF1B\u8981\u8DE8\u4E24\u6B21\u8FED\u4EE3\u5BF9\u6BD4\uFF0C\u7528\u4E0B\u65B9\u300C\u5B58\u4E3A\u5BF9\u6BD4\u57FA\u51C6\u300D\u4FDD\u5B58\u672C\u6B21\u8FD0\u884C\uFF0C\u518D\u4E0E\u4E0B\u4E00\u6B21\u8FED\u4EE3\u5BF9\u6BD4\u3002")
|
|
5115
5424
|
)
|
|
5116
5425
|
),
|
|
5117
5426
|
React.createElement("div", { className: "iterate-obs-block-body" }, comparisonTable)
|
|
5118
5427
|
) : null
|
|
5119
5428
|
);
|
|
5429
|
+
return React.createElement("div", {}, trendBody, renderCrossSessionCompare());
|
|
5430
|
+
};
|
|
5431
|
+
const renderCrossSessionCompare = () => {
|
|
5432
|
+
const current3 = currentRunSummary();
|
|
5433
|
+
const hasRun = current3 !== null;
|
|
5434
|
+
const metricLabel = (key) => {
|
|
5435
|
+
const m = RUN_COMPARE_METRIC_LABELS[key];
|
|
5436
|
+
return m ? m[getLang()] : key;
|
|
5437
|
+
};
|
|
5438
|
+
const fmtCell = (v, key) => {
|
|
5439
|
+
if (v === null) return "\u2014";
|
|
5440
|
+
return key === "validationPassRate" ? `${v}%` : String(v);
|
|
5441
|
+
};
|
|
5442
|
+
const deltaText = (row) => {
|
|
5443
|
+
if (row.baseline === null || row.current === null) return "\u2014";
|
|
5444
|
+
const d = row.current - row.baseline;
|
|
5445
|
+
return d === 0 ? "0" : `${d > 0 ? "+" : ""}${d}`;
|
|
5446
|
+
};
|
|
5447
|
+
const deltaTint = (row) => {
|
|
5448
|
+
if (row.baseline === null || row.current === null || row.higherIsBetter === null) return void 0;
|
|
5449
|
+
const d = row.current - row.baseline;
|
|
5450
|
+
if (d === 0) return void 0;
|
|
5451
|
+
const improved = row.higherIsBetter ? d > 0 : d < 0;
|
|
5452
|
+
return improved ? { color: "var(--dsw-alias-state-success-primary)", fontWeight: 600 } : { color: "var(--dsw-alias-state-error-primary)", fontWeight: 600 };
|
|
5453
|
+
};
|
|
5454
|
+
let compareBody = null;
|
|
5455
|
+
if (baseline && current3 && showCompare) {
|
|
5456
|
+
const rows = compareRuns(baseline, current3);
|
|
5457
|
+
compareBody = React.createElement(
|
|
5458
|
+
"table",
|
|
5459
|
+
{ className: "iterate-obs-table" },
|
|
5460
|
+
React.createElement(
|
|
5461
|
+
"thead",
|
|
5462
|
+
{},
|
|
5463
|
+
React.createElement(
|
|
5464
|
+
"tr",
|
|
5465
|
+
{},
|
|
5466
|
+
React.createElement("th", {}, t("\u6307\u6807")),
|
|
5467
|
+
React.createElement("th", {}, t("\u57FA\u51C6")),
|
|
5468
|
+
React.createElement("th", {}, t("\u5F53\u524D")),
|
|
5469
|
+
React.createElement("th", {}, t("\u53D8\u5316"))
|
|
5470
|
+
)
|
|
5471
|
+
),
|
|
5472
|
+
React.createElement(
|
|
5473
|
+
"tbody",
|
|
5474
|
+
{},
|
|
5475
|
+
...rows.map(
|
|
5476
|
+
(row) => React.createElement(
|
|
5477
|
+
"tr",
|
|
5478
|
+
{ key: `rc-${row.key}` },
|
|
5479
|
+
React.createElement("td", {}, metricLabel(row.key)),
|
|
5480
|
+
React.createElement("td", {}, fmtCell(row.baseline, row.key)),
|
|
5481
|
+
React.createElement("td", {}, fmtCell(row.current, row.key)),
|
|
5482
|
+
React.createElement("td", { style: deltaTint(row) }, deltaText(row))
|
|
5483
|
+
)
|
|
5484
|
+
)
|
|
5485
|
+
)
|
|
5486
|
+
);
|
|
5487
|
+
}
|
|
5488
|
+
return React.createElement(
|
|
5489
|
+
"div",
|
|
5490
|
+
{ className: "iterate-obs-block", style: { marginTop: 8 } },
|
|
5491
|
+
React.createElement(
|
|
5492
|
+
"div",
|
|
5493
|
+
{ className: "iterate-obs-block-head" },
|
|
5494
|
+
React.createElement("span", {}, t("\u8DE8\u4F1A\u8BDD\u5BF9\u6BD4")),
|
|
5495
|
+
baseline ? React.createElement("span", { className: "iterate-obs-head-meta" }, tf(`\u57FA\u51C6\u5DF2\u4FDD\u5B58\uFF1A{a0}`, { a0: baseline.savedAt })) : React.createElement("span", { className: "iterate-obs-head-meta" }, t("\u5148\u4FDD\u5B58\u5F53\u524D\u8FD0\u884C\u4E3A\u57FA\u51C6\uFF0C\u518D\u8DD1\u4E0B\u4E00\u6B21\u8FED\u4EE3\u540E\u4E00\u952E\u5BF9\u6BD4"))
|
|
5496
|
+
),
|
|
5497
|
+
React.createElement(
|
|
5498
|
+
"div",
|
|
5499
|
+
{ className: "iterate-obs-block-body" },
|
|
5500
|
+
React.createElement(
|
|
5501
|
+
"div",
|
|
5502
|
+
{ className: "iterate-obs-bar", style: { flexWrap: "wrap" } },
|
|
5503
|
+
React.createElement("button", {
|
|
5504
|
+
className: "iterate-btn",
|
|
5505
|
+
"data-primary": "",
|
|
5506
|
+
disabled: !hasRun,
|
|
5507
|
+
title: hasRun ? t("\u628A\u5F53\u524D\u8FD0\u884C\u6458\u8981\u4FDD\u5B58\u4E3A\u5BF9\u6BD4\u57FA\u51C6\uFF08\u542B\u8F6E\u6B21/\u53D1\u73B0/\u4FEE\u590D/\u9A8C\u8BC1/\u95E8\u7981\uFF09") : t("\u5F53\u524D\u6CA1\u6709\u53EF\u4FDD\u5B58\u7684\u8FD0\u884C\u6570\u636E"),
|
|
5508
|
+
onClick: saveBaseline
|
|
5509
|
+
}, baselineFlash ? t("\u5DF2\u5B58\u4E3A\u57FA\u51C6") : t("\u5B58\u4E3A\u5BF9\u6BD4\u57FA\u51C6")),
|
|
5510
|
+
baseline ? React.createElement("button", {
|
|
5511
|
+
className: "iterate-btn",
|
|
5512
|
+
"data-copied": showCompare ? "" : void 0,
|
|
5513
|
+
disabled: !hasRun,
|
|
5514
|
+
title: t("\u663E\u793A\u6216\u9690\u85CF\u4E0E\u5DF2\u4FDD\u5B58\u57FA\u51C6\u7684\u5BF9\u6BD4\u8868"),
|
|
5515
|
+
onClick: () => setShowCompare((v) => !v)
|
|
5516
|
+
}, showCompare ? t("\u6536\u8D77\u5BF9\u6BD4") : t("\u4E0E\u57FA\u51C6\u5BF9\u6BD4")) : null,
|
|
5517
|
+
baseline && current3 && showCompare ? React.createElement("button", {
|
|
5518
|
+
className: "iterate-btn",
|
|
5519
|
+
"data-copied": copiedKey === "rc-copy" ? "" : void 0,
|
|
5520
|
+
onClick: () => copyInstruction("rc-copy", buildRunCompareMarkdown(compareRuns(baseline, current3), {
|
|
5521
|
+
\u6307\u6807: t("\u6307\u6807"),
|
|
5522
|
+
\u57FA\u51C6: t("\u57FA\u51C6"),
|
|
5523
|
+
\u5F53\u524D: t("\u5F53\u524D"),
|
|
5524
|
+
\u53D8\u5316: t("\u53D8\u5316"),
|
|
5525
|
+
...Object.fromEntries(
|
|
5526
|
+
Object.entries(RUN_COMPARE_METRIC_LABELS).map(([k, v]) => [k, v[getLang()]])
|
|
5527
|
+
)
|
|
5528
|
+
})),
|
|
5529
|
+
title: t("\u590D\u5236\u8DE8\u4F1A\u8BDD\u5BF9\u6BD4\u8868\u4E3A Markdown\uFF08\u53EF\u7C98\u8D34\u8FDB PR / issue / CI \u6CE8\u91CA\uFF09")
|
|
5530
|
+
}, copiedKey === "rc-copy" ? t("\u5DF2\u590D\u5236") : t("\u590D\u5236\u5BF9\u6BD4\u8868")) : null,
|
|
5531
|
+
baseline ? React.createElement("button", {
|
|
5532
|
+
className: "iterate-btn",
|
|
5533
|
+
"data-ghost": "",
|
|
5534
|
+
title: t("\u6E05\u9664\u5DF2\u4FDD\u5B58\u7684\u5BF9\u6BD4\u57FA\u51C6"),
|
|
5535
|
+
onClick: clearBaseline
|
|
5536
|
+
}, t("\u6E05\u9664\u57FA\u51C6")) : null
|
|
5537
|
+
),
|
|
5538
|
+
showCompare && baseline && !hasRun ? React.createElement("div", { className: "iterate-obs-empty", style: { marginTop: 8 } }, t("\u5F53\u524D\u4F1A\u8BDD\u6CA1\u6709\u8FD0\u884C\u6570\u636E\u53EF\u5BF9\u6BD4")) : null,
|
|
5539
|
+
compareBody
|
|
5540
|
+
)
|
|
5541
|
+
);
|
|
5120
5542
|
};
|
|
5121
5543
|
const renderFindings = () => {
|
|
5122
5544
|
const findings = manifest.findings || [];
|
|
@@ -5551,6 +5973,12 @@ function ObservatoryPanel(props) {
|
|
|
5551
5973
|
const renderQualityGate = () => {
|
|
5552
5974
|
const gateInstruction = buildQualityGateQueryInstruction();
|
|
5553
5975
|
const gateClearInstruction = buildQualityGateClearInstruction();
|
|
5976
|
+
const computeInput = deriveQualityGateComputeInput(
|
|
5977
|
+
manifest,
|
|
5978
|
+
qualityGate
|
|
5979
|
+
);
|
|
5980
|
+
const computeInstruction = buildQualityGateComputeInstruction(computeInput);
|
|
5981
|
+
const hasComputeData = computeInput.dimensions.length > 0 || computeInput.findings.length > 0 || computeInput.validationResults.length > 0;
|
|
5554
5982
|
const gate = qualityGate;
|
|
5555
5983
|
const dims = gate && gate.dimensions ? gate.dimensions : [];
|
|
5556
5984
|
const hasGate = gate !== null && Boolean(gate.overallStatus || gate.overallScore != null || dims.length > 0);
|
|
@@ -5569,6 +5997,14 @@ function ObservatoryPanel(props) {
|
|
|
5569
5997
|
onClick: () => copyInstruction("qgate", gateInstruction),
|
|
5570
5998
|
title: t("\u590D\u5236 iterate_quality_gate \u67E5\u8BE2\u6307\u4EE4\uFF08\u542B JSON \u53C2\u6570\uFF09")
|
|
5571
5999
|
}, copiedKey === "qgate" ? t("\u5DF2\u590D\u5236") : t("\u67E5\u8BE2\u95E8\u7981")),
|
|
6000
|
+
React.createElement("button", {
|
|
6001
|
+
className: "iterate-btn",
|
|
6002
|
+
"data-primary": "",
|
|
6003
|
+
"data-copied": copiedKey === "qgate-compute" ? "" : void 0,
|
|
6004
|
+
disabled: !hasComputeData,
|
|
6005
|
+
onClick: () => copyInstruction("qgate-compute", computeInstruction),
|
|
6006
|
+
title: hasComputeData ? t("\u590D\u5236 iterate_quality_gate compute \u6307\u4EE4\uFF1A\u4ECE\u5F53\u524D\u4F1A\u8BDD\u63A8\u5BFC\u7EF4\u5EA6/\u53D1\u73B0/\u9A8C\u8BC1\u7ED3\u679C\uFF0C\u4E00\u952E\u91CD\u7B97\u5E76\u6301\u4E45\u5316\u95E8\u7981\u8BC1\u4E66") : t("\u5F53\u524D\u4F1A\u8BDD\u6CA1\u6709\u53EF\u91CD\u7B97\u7684\u95E8\u7981\u6570\u636E\uFF08\u9700\u5148\u6709\u5BA1\u67E5/\u9A8C\u8BC1\u7ED3\u679C\uFF09")
|
|
6007
|
+
}, copiedKey === "qgate-compute" ? t("\u5DF2\u590D\u5236") : t("\u91CD\u7B97\u95E8\u7981")),
|
|
5572
6008
|
React.createElement("button", {
|
|
5573
6009
|
className: "iterate-btn",
|
|
5574
6010
|
"data-danger": "",
|