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/lib/client.js CHANGED
@@ -1,4 +1,4 @@
1
- /* iterate-plugin client source-digest: 4f3bcac77cef83b80ea8fa2387f254121b9f94f7a2a2ca9422dd6342581c3463 */
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\u70B9\u300C\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u300D\u8BA9\u6A21\u578B\u56DE\u663E iterate_history\uFF0C\u518D\u7528\u300C\u5BFC\u51FA JSON\u300D\u5E76\u6392\u6BD4\u8F83\u4E24\u6B21\u8FD0\u884C": 'Cross-session comparison: this table only covers the current session \u2014 click "Pull disk snapshot" to have the model echo iterate_history, then use "Export JSON" to compare two runs side by side',
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
- return React.createElement(
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\u70B9\u300C\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u300D\u8BA9\u6A21\u578B\u56DE\u663E iterate_history\uFF0C\u518D\u7528\u300C\u5BFC\u51FA JSON\u300D\u5E76\u6392\u6BD4\u8F83\u4E24\u6B21\u8FD0\u884C")
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": "",