iterate-plugin 3.2.1 → 3.3.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
@@ -445,12 +445,15 @@ function normalizeTranscript(manifest) {
445
445
  const ng = safeGet(src, "nudge");
446
446
  const nudge = ng && typeof ng === "object" ? { timestamp: asStr(safeGet(ng, "timestamp")), text: asStr(safeGet(ng, "text")) } : null;
447
447
  const ap = safeGet(src, "approval");
448
+ const tm = safeGet(src, "taskMode");
448
449
  return {
449
450
  version: asNum(safeGet(src, "version")),
450
451
  project: asStr(safeGet(src, "project")),
451
452
  updatedAt: asStr(safeGet(src, "updatedAt")),
452
453
  active: asBool(safeGet(src, "active")),
453
454
  mode: asStr(safeGet(src, "mode")) || null,
455
+ // v3.0: harness task_mode (code/iterate), tolerated when absent.
456
+ taskMode: tm === "code" || tm === "iterate" ? tm : null,
454
457
  goal: asStr(safeGet(src, "goal")),
455
458
  phases: asArray(safeGet(src, "phases")).map((p) => typeof p === "string" ? p : ""),
456
459
  round: asNum(safeGet(src, "round")),
@@ -471,6 +474,266 @@ function normalizeTranscript(manifest) {
471
474
  approval: ap && typeof ap === "object" ? { active: asBool(safeGet(ap, "active")), policy: asStr(safeGet(ap, "policy")) || "ask" } : { active: false, policy: "ask" }
472
475
  };
473
476
  }
477
+ var DEFENSE_EVENT_TYPES = ["precondition_failed", "rollback", "invariant_violated", "assumption_falsified"];
478
+ function isQualityGateSnapshot(obj) {
479
+ if (!obj || typeof obj !== "object") return false;
480
+ const o = (
481
+ /** @type {Record<string, unknown>} */
482
+ obj
483
+ );
484
+ const status = safeGet(o, "overallStatus");
485
+ return (status === "pass" || status === "fail" || status === "pending") && typeof safeGet(o, "overallScore") === "number" && Array.isArray(safeGet(o, "dimensions"));
486
+ }
487
+ function isExperienceBankResult(obj) {
488
+ if (!obj || typeof obj !== "object") return false;
489
+ const o = (
490
+ /** @type {Record<string, unknown>} */
491
+ obj
492
+ );
493
+ const entry = safeGet(o, "entry");
494
+ return Array.isArray(safeGet(o, "entries")) && typeof safeGet(o, "count") === "number" || !!entry && typeof entry === "object" && typeof safeGet(o, "operation") === "string";
495
+ }
496
+ function isDefenseEventsResult(obj) {
497
+ if (!obj || typeof obj !== "object") return false;
498
+ const o = (
499
+ /** @type {Record<string, unknown>} */
500
+ obj
501
+ );
502
+ const counts = safeGet(o, "counts");
503
+ return Array.isArray(safeGet(o, "events")) && typeof safeGet(o, "count") === "number" || !!counts && typeof counts === "object" && typeof safeGet(o, "operation") === "string";
504
+ }
505
+ function findFirstInObject(obj, predicate, seen, maxDepth = 20) {
506
+ if (maxDepth <= 0) return null;
507
+ if (!obj || typeof obj !== "object") return null;
508
+ const s = seen || /* @__PURE__ */ new Set();
509
+ if (s.has(obj)) return null;
510
+ s.add(obj);
511
+ if (Array.isArray(obj)) {
512
+ for (const item of obj) {
513
+ const found = findFirstInObject(item, predicate, s, maxDepth - 1);
514
+ if (found) return found;
515
+ }
516
+ return null;
517
+ }
518
+ if (typeof obj === "object") {
519
+ const o = (
520
+ /** @type {Record<string, unknown>} */
521
+ obj
522
+ );
523
+ if (predicate(o)) return o;
524
+ for (const key of safeKeys(o)) {
525
+ const val = safeGet(o, key);
526
+ if (val && typeof val === "object") {
527
+ const found = findFirstInObject(val, predicate, s, maxDepth - 1);
528
+ if (found) return found;
529
+ }
530
+ }
531
+ }
532
+ return null;
533
+ }
534
+ function findQualityGateInObject(obj) {
535
+ return findFirstInObject(obj, (o) => isQualityGateSnapshot(o));
536
+ }
537
+ function findExperienceResultInObject(obj) {
538
+ return findFirstInObject(obj, (o) => isExperienceBankResult(o));
539
+ }
540
+ function findDefenseResultInObject(obj) {
541
+ return findFirstInObject(obj, (o) => isDefenseEventsResult(o));
542
+ }
543
+ function latestToolResultNode(session, toolName) {
544
+ if (!session || typeof session !== "object") return null;
545
+ const s = (
546
+ /** @type {Record<string, unknown>} */
547
+ session
548
+ );
549
+ const toolCalls = safeGet(s, "toolCalls");
550
+ if (Array.isArray(toolCalls)) {
551
+ const calls = (
552
+ /** @type {Array<Record<string, unknown>>} */
553
+ toolCalls
554
+ );
555
+ for (let i = calls.length - 1; i >= 0; i--) {
556
+ const call = calls[i];
557
+ if (!call) continue;
558
+ const tool = String(safeGet(call, "tool") ?? "");
559
+ if (tool !== toolName && !tool.endsWith(toolName)) continue;
560
+ const result = safeGet(call, "result");
561
+ if (result !== void 0 && result !== null) return result;
562
+ const message = safeGet(call, "message");
563
+ if (message !== void 0 && message !== null) return message;
564
+ }
565
+ }
566
+ const messages = safeGet(s, "messages");
567
+ if (Array.isArray(messages)) {
568
+ const msgs = (
569
+ /** @type {Array<Record<string, unknown>>} */
570
+ messages
571
+ );
572
+ for (let i = msgs.length - 1; i >= 0; i--) {
573
+ const msg = msgs[i];
574
+ if (!msg) continue;
575
+ const content = safeGet(msg, "content");
576
+ if (content !== void 0 && content !== null) return content;
577
+ }
578
+ }
579
+ return null;
580
+ }
581
+ function normalizeQualityGateSnapshot(raw) {
582
+ const src = raw && typeof raw === "object" ? (
583
+ /** @type {Record<string, unknown>} */
584
+ raw
585
+ ) : {};
586
+ const asNum = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
587
+ const asStr = (v) => typeof v === "string" ? v : "";
588
+ const status = safeGet(src, "overallStatus");
589
+ const dims = Array.isArray(safeGet(src, "dimensions")) ? safeGet(src, "dimensions") : [];
590
+ return {
591
+ timestamp: asStr(safeGet(src, "timestamp")),
592
+ overallStatus: status === "pass" || status === "fail" || status === "pending" ? status : "pending",
593
+ overallScore: asNum(safeGet(src, "overallScore")),
594
+ verificationPassRate: asNum(safeGet(src, "verificationPassRate")),
595
+ totalChecks: asNum(safeGet(src, "totalChecks")),
596
+ passedChecks: asNum(safeGet(src, "passedChecks")),
597
+ failedChecks: asNum(safeGet(src, "failedChecks")),
598
+ failReason: asStr(safeGet(src, "failReason")) || null,
599
+ totalFindings: asNum(safeGet(src, "totalFindings")),
600
+ criticalCount: asNum(safeGet(src, "criticalCount")),
601
+ highCount: asNum(safeGet(src, "highCount")),
602
+ mediumCount: asNum(safeGet(src, "mediumCount")),
603
+ lowCount: asNum(safeGet(src, "lowCount")),
604
+ dimensions: (
605
+ /** @type {unknown[]} */
606
+ dims.map((d) => {
607
+ const rec = (
608
+ /** @type {Record<string, unknown>} */
609
+ d && typeof d === "object" ? d : {}
610
+ );
611
+ const dimStatus = safeGet(rec, "status");
612
+ return {
613
+ dimension: asStr(safeGet(rec, "dimension")),
614
+ convergenceRate: asNum(safeGet(rec, "convergenceRate")),
615
+ findingsCount: asNum(safeGet(rec, "findingsCount")),
616
+ fixedCount: asNum(safeGet(rec, "fixedCount")),
617
+ score: asNum(safeGet(rec, "score")),
618
+ status: dimStatus === "pass" || dimStatus === "warn" || dimStatus === "fail" ? dimStatus : "warn"
619
+ };
620
+ })
621
+ )
622
+ };
623
+ }
624
+ function scanSessionForQualityGate(session) {
625
+ const node = latestToolResultNode(session, "iterate_quality_gate");
626
+ if (node === null) return null;
627
+ const found = findQualityGateInObject(node);
628
+ if (!found) return null;
629
+ return normalizeQualityGateSnapshot(found);
630
+ }
631
+ function normalizeExperienceBankResult(raw) {
632
+ const src = raw && typeof raw === "object" ? (
633
+ /** @type {Record<string, unknown>} */
634
+ raw
635
+ ) : {};
636
+ const asNum = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
637
+ const asStr = (v) => typeof v === "string" ? v : "";
638
+ const asArr = (v) => Array.isArray(v) ? (
639
+ /** @type {unknown[]} */
640
+ v
641
+ ) : [];
642
+ const entry = safeGet(src, "entry");
643
+ const rawEntries = Array.isArray(safeGet(src, "entries")) ? (
644
+ /** @type {unknown[]} */
645
+ safeGet(src, "entries")
646
+ ) : entry && typeof entry === "object" ? [entry] : [];
647
+ return {
648
+ operation: asStr(safeGet(src, "operation")),
649
+ count: asNum(safeGet(src, "count")),
650
+ totalHits: asNum(safeGet(src, "totalHits")),
651
+ added: safeGet(src, "added") === true,
652
+ entries: rawEntries.map((e) => {
653
+ const rec = (
654
+ /** @type {Record<string, unknown>} */
655
+ e && typeof e === "object" ? e : {}
656
+ );
657
+ return {
658
+ id: asStr(safeGet(rec, "id")),
659
+ timestamp: asStr(safeGet(rec, "timestamp")),
660
+ dimension: asStr(safeGet(rec, "dimension")),
661
+ pattern: asStr(safeGet(rec, "pattern")),
662
+ description: asStr(safeGet(rec, "description")),
663
+ verifiedFix: asStr(safeGet(rec, "verifiedFix")),
664
+ findingSummary: asStr(safeGet(rec, "findingSummary")),
665
+ severity: asStr(safeGet(rec, "severity")),
666
+ hitCount: asNum(safeGet(rec, "hitCount")),
667
+ lastHitAt: asStr(safeGet(rec, "lastHitAt")) || null,
668
+ files: asArr(safeGet(rec, "files")).map((f) => typeof f === "string" ? f : ""),
669
+ tags: asArr(safeGet(rec, "tags")).map((t) => typeof t === "string" ? t : "")
670
+ };
671
+ })
672
+ };
673
+ }
674
+ function scanSessionForExperienceBank(session) {
675
+ const node = latestToolResultNode(session, "iterate_experience");
676
+ if (node === null) return null;
677
+ const found = findExperienceResultInObject(node);
678
+ if (!found) return null;
679
+ return normalizeExperienceBankResult(found);
680
+ }
681
+ function normalizeDefenseEventsResult(raw) {
682
+ const src = raw && typeof raw === "object" ? (
683
+ /** @type {Record<string, unknown>} */
684
+ raw
685
+ ) : {};
686
+ const asNum = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
687
+ const asStr = (v) => typeof v === "string" ? v : "";
688
+ const countsRaw = safeGet(src, "counts") && typeof safeGet(src, "counts") === "object" ? (
689
+ /** @type {Record<string, unknown>} */
690
+ safeGet(src, "counts")
691
+ ) : {};
692
+ const counts = (
693
+ /** @type {Record<string, number>} */
694
+ {}
695
+ );
696
+ for (const type of DEFENSE_EVENT_TYPES) {
697
+ const n = safeGet(countsRaw, type);
698
+ counts[type] = typeof n === "number" && Number.isFinite(n) && n >= 0 ? n : 0;
699
+ }
700
+ const event = safeGet(src, "event");
701
+ const rawEvents = Array.isArray(safeGet(src, "events")) ? (
702
+ /** @type {unknown[]} */
703
+ safeGet(src, "events")
704
+ ) : event && typeof event === "object" ? [event] : [];
705
+ return {
706
+ operation: asStr(safeGet(src, "operation")),
707
+ count: asNum(safeGet(src, "count")),
708
+ language: asStr(safeGet(src, "language")),
709
+ counts,
710
+ events: rawEvents.map((e) => {
711
+ const rec = (
712
+ /** @type {Record<string, unknown>} */
713
+ e && typeof e === "object" ? e : {}
714
+ );
715
+ return {
716
+ id: asStr(safeGet(rec, "id")),
717
+ timestamp: asStr(safeGet(rec, "timestamp")),
718
+ round: asNum(safeGet(rec, "round")),
719
+ type: asStr(safeGet(rec, "type")),
720
+ description: asStr(safeGet(rec, "description")),
721
+ defense: asStr(safeGet(rec, "defense")),
722
+ outcome: asStr(safeGet(rec, "outcome")),
723
+ file: asStr(safeGet(rec, "file")) || null,
724
+ line: asNum(safeGet(rec, "line")) || null,
725
+ severity: asStr(safeGet(rec, "severity"))
726
+ };
727
+ })
728
+ };
729
+ }
730
+ function scanSessionForDefenseEvents(session) {
731
+ const node = latestToolResultNode(session, "iterate_defense_events");
732
+ if (node === null) return null;
733
+ const found = findDefenseResultInObject(node);
734
+ if (!found) return null;
735
+ return normalizeDefenseEventsResult(found);
736
+ }
474
737
  function isRunSummary(obj) {
475
738
  if (!obj || typeof obj !== "object") return false;
476
739
  const o = (
@@ -1814,6 +2077,30 @@ function TriagePanel(props) {
1814
2077
  }
1815
2078
  });
1816
2079
  };
2080
+ const doAssignFindings = () => {
2081
+ const targetAll = selectAll;
2082
+ const scopeIndices = targetAll ? allIndices : indices;
2083
+ const scopeFindings = findings.filter((f, i) => scopeIndices.includes(i)).map((f) => ({
2084
+ file: String(f.file || ""),
2085
+ ...typeof f.line === "number" && f.line > 0 ? { line: f.line } : {},
2086
+ dimension: String(f.dimension || ""),
2087
+ severity: String(f.severity || ""),
2088
+ summary: String(f.summary || ""),
2089
+ ...f.suggested_fix ? { suggested_fix: String(f.suggested_fix) } : {}
2090
+ }));
2091
+ if (scopeFindings.length === 0) return;
2092
+ const cmd = `\u8BF7\u8C03\u7528 \`iterate_fix\` \u6307\u6D3E\u5E76\u4FEE\u590D\u4EE5\u4E0B findings\uFF1A
2093
+
2094
+ \`\`\`json
2095
+ ${JSON.stringify(scopeFindings, null, 2)}
2096
+ \`\`\``;
2097
+ copyText(cmd).then((ok) => {
2098
+ if (ok) {
2099
+ setCmdCopied("assign");
2100
+ setTimeout(() => setCmdCopied(null), 1600);
2101
+ }
2102
+ });
2103
+ };
1817
2104
  const doCopyYaml = () => {
1818
2105
  const yaml = toKnownIntentionalYaml(ignored);
1819
2106
  if (!yaml) return;
@@ -1990,6 +2277,14 @@ function TriagePanel(props) {
1990
2277
  onClick: doTriggerNewRound,
1991
2278
  title: "\u89E6\u53D1\u65B0\u4E00\u8F6E\u8FED\u4EE3\u5BA1\u67E5"
1992
2279
  }, cmdCopied === "new-round" ? "\u5DF2\u590D\u5236" : "\u89E6\u53D1\u65B0\u4E00\u8F6E"),
2280
+ React.createElement("button", {
2281
+ className: "iterate-cmd",
2282
+ "data-primary": "",
2283
+ "data-copied": cmdCopied === "assign" ? "" : void 0,
2284
+ onClick: doAssignFindings,
2285
+ disabled: (selectAll ? allIndices : indices).length === 0,
2286
+ title: selectAll ? `\u6307\u6D3E\u5168\u90E8 ${allIndices.length} \u4E2A findings \u4FEE\u590D` : `\u6307\u6D3E\u5F53\u524D\u53EF\u89C1 ${indices.length} \u4E2A findings \u4FEE\u590D`
2287
+ }, cmdCopied === "assign" ? "\u5DF2\u590D\u5236" : "\u6307\u6D3E\u4FEE\u590D"),
1993
2288
  React.createElement("button", {
1994
2289
  className: "iterate-cmd",
1995
2290
  "data-danger": "",
@@ -2323,6 +2618,18 @@ function latestTranscript(session) {
2323
2618
  const raw = scanSessionForTranscript(session);
2324
2619
  return raw ? normalizeTranscript(raw) : null;
2325
2620
  }
2621
+ function latestQualityGate(session) {
2622
+ if (!session) return null;
2623
+ return scanSessionForQualityGate(session);
2624
+ }
2625
+ function latestExperienceBank(session) {
2626
+ if (!session) return null;
2627
+ return scanSessionForExperienceBank(session);
2628
+ }
2629
+ function latestDefenseEvents(session) {
2630
+ if (!session) return null;
2631
+ return scanSessionForDefenseEvents(session);
2632
+ }
2326
2633
  function buildObsFixInstruction(f) {
2327
2634
  const payload = JSON.stringify({
2328
2635
  file: String(f.file || ""),
@@ -2340,6 +2647,9 @@ ${payload}
2340
2647
  function ObservatoryPanel(props) {
2341
2648
  const session = props && props.session ? props.session : null;
2342
2649
  const manifest = latestTranscript(session);
2650
+ const qualityGate = latestQualityGate(session);
2651
+ const experienceBank = latestExperienceBank(session);
2652
+ const defenseEvents = latestDefenseEvents(session);
2343
2653
  const [open, setOpen] = React.useState(false);
2344
2654
  const [tab, setTab] = React.useState("live");
2345
2655
  const [expandedThreads, setExpandedThreads] = React.useState(/* @__PURE__ */ new Set());
@@ -2355,7 +2665,6 @@ function ObservatoryPanel(props) {
2355
2665
  { severities: [], dimensions: [], search: "" }
2356
2666
  );
2357
2667
  const [expSearch, setExpSearch] = React.useState("");
2358
- const [expResults, setExpResults] = React.useState([]);
2359
2668
  const [defenseFilter, setDefenseFilter] = React.useState("");
2360
2669
  const copyTimer = React.useRef(null);
2361
2670
  React.useEffect(() => () => {
@@ -2913,45 +3222,111 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
2913
3222
  return React.createElement("div", {}, filterBar, ...rows);
2914
3223
  };
2915
3224
  const renderQualityGate = () => {
2916
- const qualityGateInstruction = "\u8BF7\u8C03\u7528 `iterate_quality_gate` \u67E5\u8BE2\u5F53\u524D\u8D28\u91CF\u95E8\u7981\u72B6\u6001";
3225
+ const gateInstruction = "\u8BF7\u8C03\u7528 `iterate_quality_gate` \u67E5\u8BE2\u5F53\u524D\u8D28\u91CF\u95E8\u7981\u72B6\u6001";
3226
+ const gate = qualityGate;
3227
+ const dims = gate && gate.dimensions ? gate.dimensions : [];
3228
+ const hasGate = gate !== null && Boolean(gate.overallStatus || gate.overallScore != null || dims.length > 0);
3229
+ const status = gate && gate.overallStatus ? String(gate.overallStatus) : "pending";
3230
+ const statusLabel = status === "pass" ? "PASS" : status === "fail" ? "FAIL" : "PENDING";
3231
+ const num = (v) => Number(v != null ? v : 0);
3232
+ const headerBar = React.createElement(
3233
+ "div",
3234
+ { className: "iterate-obs-bar", style: { marginBottom: 8 } },
3235
+ React.createElement("b", {}, "\u8D28\u91CF\u95E8\u7981"),
3236
+ React.createElement("button", {
3237
+ className: "iterate-btn",
3238
+ "data-primary": "",
3239
+ "data-copied": copiedKey === "qgate" ? "" : void 0,
3240
+ onClick: () => copyInstruction("qgate", gateInstruction),
3241
+ title: "\u590D\u5236 iterate_quality_gate \u67E5\u8BE2\u6307\u4EE4"
3242
+ }, copiedKey === "qgate" ? "\u5DF2\u590D\u5236" : "\u67E5\u8BE2\u95E8\u7981")
3243
+ );
3244
+ if (!hasGate) {
3245
+ return React.createElement(
3246
+ "div",
3247
+ {},
3248
+ headerBar,
3249
+ React.createElement(
3250
+ "div",
3251
+ { className: "iterate-obs-block" },
3252
+ React.createElement(
3253
+ "div",
3254
+ { className: "iterate-obs-block-head" },
3255
+ React.createElement("span", {}, "\u8D28\u91CF\u95E8\u7981\u89C6\u56FE"),
3256
+ React.createElement("span", { className: "iterate-obs-badge" }, "v3.0")
3257
+ ),
3258
+ React.createElement(
3259
+ "div",
3260
+ { className: "iterate-obs-block-body" },
3261
+ React.createElement("div", { className: "iterate-obs-msg" }, "\u8D28\u91CF\u95E8\u7981\u663E\u793A\u5404\u7EF4\u5EA6\u6536\u655B\u5EA6\u3001\u9A8C\u8BC1\u901A\u8FC7\u7387\u548C\u6574\u4F53 PASS/FAIL \u72B6\u6001\u3002"),
3262
+ React.createElement(
3263
+ "div",
3264
+ { className: "iterate-obs-bar", style: { marginTop: 8 } },
3265
+ React.createElement("button", {
3266
+ className: "iterate-btn",
3267
+ "data-copied": copiedKey === "qgate-instr" ? "" : void 0,
3268
+ onClick: () => copyInstruction("qgate-instr", gateInstruction),
3269
+ title: "\u590D\u5236\u67E5\u8BE2\u6307\u4EE4"
3270
+ }, copiedKey === "qgate-instr" ? "\u5DF2\u590D\u5236" : "\u590D\u5236\u67E5\u8BE2\u6307\u4EE4")
3271
+ )
3272
+ )
3273
+ )
3274
+ );
3275
+ }
2917
3276
  return React.createElement(
2918
3277
  "div",
2919
3278
  {},
2920
- React.createElement(
2921
- "div",
2922
- { className: "iterate-obs-bar", style: { marginBottom: 8 } },
2923
- React.createElement("b", {}, "\u8D28\u91CF\u95E8\u7981"),
2924
- React.createElement("button", {
2925
- className: "iterate-btn",
2926
- "data-primary": "",
2927
- "data-copied": copiedKey === "qgate" ? "" : void 0,
2928
- onClick: () => copyInstruction("qgate", qualityGateInstruction),
2929
- title: "\u590D\u5236 iterate_quality_gate \u67E5\u8BE2\u6307\u4EE4"
2930
- }, copiedKey === "qgate" ? "\u5DF2\u590D\u5236" : "\u67E5\u8BE2\u95E8\u7981")
2931
- ),
3279
+ headerBar,
2932
3280
  React.createElement(
2933
3281
  "div",
2934
3282
  { className: "iterate-obs-block" },
2935
3283
  React.createElement(
2936
3284
  "div",
2937
3285
  { className: "iterate-obs-block-head" },
2938
- React.createElement("span", {}, "\u8D28\u91CF\u95E8\u7981\u89C6\u56FE"),
2939
- React.createElement("span", { className: "iterate-obs-badge" }, "v3.0")
3286
+ React.createElement("span", { className: "iterate-gate", "data-status": status }, statusLabel),
3287
+ React.createElement(
3288
+ "span",
3289
+ { className: "iterate-obs-head-meta" },
3290
+ `\u6574\u4F53\u8BC4\u5206 ${num(gate && gate.overallScore)} \xB7 \u9A8C\u8BC1 ${num(gate && gate.passedChecks)}/${num(gate && gate.totalChecks)} \xB7 \u901A\u8FC7\u7387 ${num(gate && gate.verificationPassRate)}%`
3291
+ )
2940
3292
  ),
2941
3293
  React.createElement(
2942
3294
  "div",
2943
3295
  { className: "iterate-obs-block-body" },
2944
- React.createElement("div", { className: "iterate-obs-msg" }, "\u8D28\u91CF\u95E8\u7981\u663E\u793A\u5404\u7EF4\u5EA6\u6536\u655B\u5EA6\u3001\u9A8C\u8BC1\u901A\u8FC7\u7387\u548C\u6574\u4F53 PASS/FAIL \u72B6\u6001\u3002"),
3296
+ dims.length > 0 ? React.createElement(
3297
+ "div",
3298
+ { className: "iterate-obs-bar", style: { flexWrap: "wrap", gap: 6 } },
3299
+ ...dims.map(
3300
+ (d, i) => React.createElement(
3301
+ "span",
3302
+ { key: `gdim-${i}`, className: "iterate-obs-chip" },
3303
+ `${String(d.dimension || "?")} \xB7 \u6536\u655B ${num(d.convergenceRate)}% \xB7 \u8BC4\u5206 ${num(d.score)}`
3304
+ )
3305
+ )
3306
+ ) : null,
2945
3307
  React.createElement(
2946
3308
  "div",
2947
- { className: "iterate-obs-bar", style: { marginTop: 8 } },
2948
- React.createElement("button", {
2949
- className: "iterate-btn",
2950
- "data-copied": copiedKey === "qgate-instr" ? "" : void 0,
2951
- onClick: () => copyInstruction("qgate-instr", qualityGateInstruction),
2952
- title: "\u590D\u5236\u67E5\u8BE2\u6307\u4EE4"
2953
- }, copiedKey === "qgate-instr" ? "\u5DF2\u590D\u5236" : "\u590D\u5236\u67E5\u8BE2\u6307\u4EE4")
2954
- )
3309
+ { className: "iterate-obs-bar", style: { flexWrap: "wrap" } },
3310
+ ...dims.map((d, i) => {
3311
+ const fill = Math.min(100, Math.max(0, num(d.score)));
3312
+ return React.createElement(
3313
+ "div",
3314
+ { key: `gbar-${i}`, className: "iterate-dim-score" },
3315
+ React.createElement("span", {}, String(d.dimension || "?")),
3316
+ React.createElement(
3317
+ "div",
3318
+ { className: "iterate-dim-score-bar" },
3319
+ React.createElement("div", {
3320
+ className: "iterate-dim-score-fill",
3321
+ "data-status": String(d.status || "warn"),
3322
+ style: { width: `${fill}%` }
3323
+ })
3324
+ ),
3325
+ React.createElement("span", {}, `\u8BC4\u5206 ${num(d.score)} \xB7 \u53D1\u73B0 ${num(d.findingsCount)} \xB7 \u5DF2\u4FEE\u590D ${num(d.fixedCount)}`)
3326
+ );
3327
+ })
3328
+ ),
3329
+ gate && gate.failReason ? React.createElement("div", { className: "iterate-obs-msg", style: { marginTop: 6 } }, `\u5931\u8D25\u539F\u56E0\uFF1A${String(gate.failReason)}`) : null
2955
3330
  )
2956
3331
  )
2957
3332
  );
@@ -2962,14 +3337,43 @@ ${JSON.stringify({ operation: "nudge", text: null }, null, 2)}
2962
3337
  \`\`\`json
2963
3338
  ${JSON.stringify({ operation: "search", query: expSearch }, null, 2)}
2964
3339
  \`\`\`` : "\u8BF7\u8C03\u7528 `iterate_experience` \u5217\u51FA\u6240\u6709\u7ECF\u9A8C";
2965
- const resultRows = expResults.map((entry, i) => {
2966
- const e = entry;
3340
+ const expEntries = experienceBank && experienceBank.entries ? experienceBank.entries : [];
3341
+ const query = expSearch.trim().toLowerCase();
3342
+ const visible = query ? expEntries.filter(
3343
+ (e) => [e.pattern, e.description, e.id, e.dimension, e.severity, ...e.tags || []].filter(Boolean).some((t) => String(t).toLowerCase().includes(query))
3344
+ ) : expEntries;
3345
+ const resultRows = visible.map((e, i) => {
3346
+ const adoptInstruction = `\u8BF7\u8C03\u7528 \`iterate_experience\` \u91C7\u7EB3\u7ECF\u9A8C\u5E76\u5E94\u7528\u5DF2\u9A8C\u8BC1\u4FEE\u6CD5\uFF1A
3347
+
3348
+ \`\`\`json
3349
+ ${JSON.stringify({ operation: "get", id: e.id }, null, 2)}
3350
+ \`\`\``;
2967
3351
  return React.createElement(
2968
3352
  "div",
2969
- { key: `exp-${i}`, className: "iterate-obs-row" },
2970
- React.createElement("span", { className: "iterate-obs-chip" }, String(e.dimension || "")),
2971
- React.createElement("span", { className: "iterate-obs-msg" }, String(e.pattern || "")),
2972
- React.createElement("span", { className: "iterate-exp-hit" }, `\u547D\u4E2D ${e.hitCount ?? 0} \u6B21`)
3353
+ { key: `exp-${i}`, className: "iterate-obs-block" },
3354
+ React.createElement(
3355
+ "div",
3356
+ { className: "iterate-obs-block-head" },
3357
+ React.createElement("span", { className: "iterate-obs-chip" }, String(e.dimension || "")),
3358
+ React.createElement("span", {}, String(e.pattern || "?")),
3359
+ React.createElement("span", { className: "iterate-exp-hit" }, `\u547D\u4E2D ${e.hitCount ?? 0} \u6B21`)
3360
+ ),
3361
+ React.createElement(
3362
+ "div",
3363
+ { className: "iterate-obs-block-body" },
3364
+ React.createElement("div", { className: "iterate-obs-msg" }, String(e.description || "")),
3365
+ React.createElement(
3366
+ "div",
3367
+ { className: "iterate-obs-bar", style: { marginTop: 6 } },
3368
+ e.verifiedFix ? React.createElement("button", {
3369
+ className: "iterate-btn",
3370
+ "data-primary": "",
3371
+ "data-copied": copiedKey === `exp-adopt-${i}` ? "" : void 0,
3372
+ onClick: () => copyInstruction(`exp-adopt-${i}`, adoptInstruction),
3373
+ title: "\u590D\u5236\u91C7\u7EB3\u6307\u4EE4\uFF08\u67E5\u8BE2\u8BE5\u7ECF\u9A8C\u5E76\u5E94\u7528\u5230\u5F53\u524D\u4E0A\u4E0B\u6587\uFF09"
3374
+ }, copiedKey === `exp-adopt-${i}` ? "\u5DF2\u590D\u5236" : "\u91C7\u7EB3") : null
3375
+ )
3376
+ )
2973
3377
  );
2974
3378
  });
2975
3379
  return React.createElement(
@@ -2979,6 +3383,7 @@ ${JSON.stringify({ operation: "search", query: expSearch }, null, 2)}
2979
3383
  "div",
2980
3384
  { className: "iterate-obs-bar", style: { marginBottom: 8 } },
2981
3385
  React.createElement("b", {}, "\u7ECF\u9A8C\u94F6\u884C"),
3386
+ React.createElement("span", { className: "iterate-obs-head-meta" }, `\u4F1A\u8BDD\u5185 ${expEntries.length} \u6761`),
2982
3387
  React.createElement("button", {
2983
3388
  className: "iterate-btn",
2984
3389
  "data-primary": "",
@@ -3006,7 +3411,7 @@ ${JSON.stringify({ operation: "search", query: expSearch }, null, 2)}
3006
3411
  title: "\u590D\u5236\u641C\u7D22\u6307\u4EE4"
3007
3412
  }, copiedKey === "exp-search" ? "\u5DF2\u590D\u5236" : "\u641C\u7D22")
3008
3413
  ),
3009
- resultRows.length > 0 ? React.createElement("div", {}, ...resultRows) : React.createElement("div", { className: "iterate-obs-empty" }, "\u8F93\u5165\u5173\u952E\u8BCD\u641C\u7D22\u5386\u53F2\u7ECF\u9A8C\uFF0C\u6216\u70B9\u51FB\u300C\u5217\u51FA\u7ECF\u9A8C\u300D\u67E5\u770B\u5168\u90E8")
3414
+ resultRows.length > 0 ? React.createElement("div", {}, ...resultRows) : expEntries.length > 0 ? React.createElement("div", { className: "iterate-obs-empty" }, "\u6CA1\u6709\u5339\u914D\u300C" + expSearch + "\u300D\u7684\u7ECF\u9A8C") : React.createElement("div", { className: "iterate-obs-empty" }, "\u672C\u6B21\u4F1A\u8BDD\u6682\u65E0\u7ECF\u9A8C\u8BB0\u5F55\uFF0C\u53EF\u70B9\u51FB\u300C\u5217\u51FA\u7ECF\u9A8C\u300D\u67E5\u8BE2\u5386\u53F2\u7ECF\u9A8C")
3010
3415
  );
3011
3416
  };
3012
3417
  const renderDefenseEvents = () => {
@@ -3022,71 +3427,127 @@ ${JSON.stringify({ operation: "list", type: defenseFilter || void 0 }, null, 2)}
3022
3427
  { value: "invariant_violated", label: "\u4E0D\u53D8\u91CF\u8FDD\u53CD" },
3023
3428
  { value: "assumption_falsified", label: "\u5047\u8BBE\u88AB\u8BC1\u4F2A" }
3024
3429
  ];
3025
- return React.createElement(
3430
+ const TYPE_LABEL = {
3431
+ precondition_failed: "\u524D\u7F6E\u6821\u9A8C\u5931\u8D25",
3432
+ rollback: "\u56DE\u6EDA",
3433
+ invariant_violated: "\u4E0D\u53D8\u91CF\u8FDD\u53CD",
3434
+ assumption_falsified: "\u5047\u8BBE\u88AB\u8BC1\u4F2A"
3435
+ };
3436
+ const counts = defenseEvents && defenseEvents.counts || {};
3437
+ const allEvents = defenseEvents && defenseEvents.events ? defenseEvents.events : [];
3438
+ const totalCount = typeOptions.slice(1).reduce((acc, o) => acc + (counts[o.value] || 0), 0);
3439
+ const visibleEvents = defenseFilter ? allEvents.filter((e) => e.type === defenseFilter) : allEvents;
3440
+ const headerBar = React.createElement(
3026
3441
  "div",
3027
- {},
3028
- React.createElement(
3029
- "div",
3030
- { className: "iterate-obs-bar", style: { marginBottom: 8 } },
3031
- React.createElement("b", {}, "\u9632\u5FA1\u4E8B\u4EF6"),
3032
- React.createElement("button", {
3033
- className: "iterate-btn",
3034
- "data-primary": "",
3035
- "data-copied": copiedKey === "defense-list" ? "" : void 0,
3036
- onClick: () => copyInstruction("defense-list", "\u8BF7\u8C03\u7528 `iterate_defense_events` \u5217\u51FA\u6240\u6709\u9632\u5FA1\u4E8B\u4EF6"),
3037
- title: "\u590D\u5236\u5217\u51FA\u9632\u5FA1\u4E8B\u4EF6\u6307\u4EE4"
3038
- }, copiedKey === "defense-list" ? "\u5DF2\u590D\u5236" : "\u5217\u51FA\u4E8B\u4EF6"),
3039
- React.createElement("button", {
3040
- className: "iterate-btn",
3041
- "data-copied": copiedKey === "defense-counts" ? "" : void 0,
3042
- onClick: () => copyInstruction("defense-counts", "\u8BF7\u8C03\u7528 `iterate_defense_events` \u67E5\u8BE2\u4E8B\u4EF6\u7EDF\u8BA1"),
3043
- title: "\u590D\u5236\u7EDF\u8BA1\u6307\u4EE4"
3044
- }, copiedKey === "defense-counts" ? "\u5DF2\u590D\u5236" : "\u7EDF\u8BA1")
3045
- ),
3046
- React.createElement(
3047
- "div",
3048
- { className: "iterate-obs-bar", style: { marginBottom: 8 } },
3049
- React.createElement(
3050
- "select",
3051
- {
3052
- className: "iterate-filter-select",
3053
- value: defenseFilter,
3054
- "aria-label": "\u6309\u4E8B\u4EF6\u7C7B\u578B\u7B5B\u9009",
3055
- onChange: (e) => setDefenseFilter(e.target.value)
3056
- },
3057
- ...typeOptions.map((opt) => React.createElement("option", { key: opt.value, value: opt.value }, opt.label))
3058
- ),
3059
- defenseFilter ? React.createElement("button", {
3060
- className: "iterate-filter-clear",
3061
- onClick: () => setDefenseFilter(""),
3062
- title: "\u6E05\u9664\u7C7B\u578B\u7B5B\u9009"
3063
- }, "\u6E05\u9664") : null
3064
- ),
3065
- React.createElement(
3442
+ { className: "iterate-obs-bar", style: { marginBottom: 8 } },
3443
+ React.createElement("b", {}, "\u9632\u5FA1\u4E8B\u4EF6"),
3444
+ React.createElement("span", { className: "iterate-obs-head-meta" }, `\u4F1A\u8BDD\u5185 ${allEvents.length} \u6761 / \u7D2F\u8BA1 ${totalCount}`),
3445
+ React.createElement("button", {
3446
+ className: "iterate-btn",
3447
+ "data-primary": "",
3448
+ "data-copied": copiedKey === "defense-list" ? "" : void 0,
3449
+ onClick: () => copyInstruction("defense-list", "\u8BF7\u8C03\u7528 `iterate_defense_events` \u5217\u51FA\u6240\u6709\u9632\u5FA1\u4E8B\u4EF6"),
3450
+ title: "\u590D\u5236\u5217\u51FA\u9632\u5FA1\u4E8B\u4EF6\u6307\u4EE4"
3451
+ }, copiedKey === "defense-list" ? "\u5DF2\u590D\u5236" : "\u5217\u51FA\u4E8B\u4EF6"),
3452
+ React.createElement("button", {
3453
+ className: "iterate-btn",
3454
+ "data-copied": copiedKey === "defense-counts" ? "" : void 0,
3455
+ onClick: () => copyInstruction("defense-counts", "\u8BF7\u8C03\u7528 `iterate_defense_events` \u67E5\u8BE2\u4E8B\u4EF6\u7EDF\u8BA1"),
3456
+ title: "\u590D\u5236\u7EDF\u8BA1\u6307\u4EE4"
3457
+ }, copiedKey === "defense-counts" ? "\u5DF2\u590D\u5236" : "\u7EDF\u8BA1")
3458
+ );
3459
+ if (allEvents.length === 0 && totalCount === 0) {
3460
+ return React.createElement(
3066
3461
  "div",
3067
- { className: "iterate-obs-block" },
3462
+ {},
3463
+ headerBar,
3068
3464
  React.createElement(
3069
3465
  "div",
3070
- { className: "iterate-obs-block-head" },
3071
- React.createElement("span", {}, "\u9632\u5FA1\u4E8B\u4EF6\u6D41"),
3072
- React.createElement("span", { className: "iterate-obs-badge" }, "v3.0")
3073
- ),
3074
- React.createElement(
3466
+ { className: "iterate-obs-block" },
3467
+ React.createElement(
3468
+ "div",
3469
+ { className: "iterate-obs-block-head" },
3470
+ React.createElement("span", {}, "\u9632\u5FA1\u4E8B\u4EF6\u6D41"),
3471
+ React.createElement("span", { className: "iterate-obs-badge" }, "v3.0")
3472
+ ),
3473
+ React.createElement(
3474
+ "div",
3475
+ { className: "iterate-obs-block-body" },
3476
+ React.createElement("div", { className: "iterate-obs-msg" }, "\u9632\u5FA1\u4E8B\u4EF6\u5305\u62EC\uFF1A\u524D\u7F6E\u6821\u9A8C\u5931\u8D25\u3001\u56DE\u6EDA\u3001\u4E0D\u53D8\u91CF\u8FDD\u53CD\u3001\u5047\u8BBE\u88AB\u8BC1\u4F2A\u3002"),
3477
+ React.createElement(
3478
+ "div",
3479
+ { className: "iterate-obs-bar", style: { marginTop: 8 } },
3480
+ React.createElement("button", {
3481
+ className: "iterate-btn",
3482
+ "data-copied": copiedKey === "defense-instr" ? "" : void 0,
3483
+ onClick: () => copyInstruction("defense-instr", defenseInstruction),
3484
+ title: "\u590D\u5236\u67E5\u8BE2\u6307\u4EE4"
3485
+ }, copiedKey === "defense-instr" ? "\u5DF2\u590D\u5236" : "\u590D\u5236\u67E5\u8BE2\u6307\u4EE4")
3486
+ )
3487
+ )
3488
+ )
3489
+ );
3490
+ }
3491
+ const filterBar = React.createElement(
3492
+ "div",
3493
+ { className: "iterate-obs-bar", style: { marginBottom: 8 } },
3494
+ React.createElement(
3495
+ "select",
3496
+ {
3497
+ className: "iterate-filter-select",
3498
+ value: defenseFilter,
3499
+ "aria-label": "\u6309\u4E8B\u4EF6\u7C7B\u578B\u7B5B\u9009",
3500
+ onChange: (e) => setDefenseFilter(e.target.value)
3501
+ },
3502
+ ...typeOptions.map((opt) => React.createElement("option", { key: opt.value, value: opt.value }, opt.label))
3503
+ ),
3504
+ defenseFilter ? React.createElement("button", {
3505
+ className: "iterate-filter-clear",
3506
+ onClick: () => setDefenseFilter(""),
3507
+ title: "\u6E05\u9664\u7C7B\u578B\u7B5B\u9009"
3508
+ }, "\u6E05\u9664") : null
3509
+ );
3510
+ let eventRows = [];
3511
+ if (visibleEvents.length > 0) {
3512
+ eventRows = visibleEvents.map((e, i) => {
3513
+ const type = String(e.type || "unknown");
3514
+ const loc = `${String(e.file || "?")}${typeof e.line === "number" && e.line > 0 ? `:${e.line}` : ""}`;
3515
+ return React.createElement(
3075
3516
  "div",
3076
- { className: "iterate-obs-block-body" },
3077
- React.createElement("div", { className: "iterate-obs-msg" }, "\u9632\u5FA1\u4E8B\u4EF6\u5305\u62EC\uFF1A\u524D\u7F6E\u6821\u9A8C\u5931\u8D25\u3001\u56DE\u6EDA\u3001\u4E0D\u53D8\u91CF\u8FDD\u53CD\u3001\u5047\u8BBE\u88AB\u8BC1\u4F2A\u3002"),
3517
+ { key: `def-${i}`, className: "iterate-obs-block" },
3078
3518
  React.createElement(
3079
3519
  "div",
3080
- { className: "iterate-obs-bar", style: { marginTop: 8 } },
3081
- React.createElement("button", {
3082
- className: "iterate-btn",
3083
- "data-copied": copiedKey === "defense-instr" ? "" : void 0,
3084
- onClick: () => copyInstruction("defense-instr", defenseInstruction),
3085
- title: "\u590D\u5236\u67E5\u8BE2\u6307\u4EE4"
3086
- }, copiedKey === "defense-instr" ? "\u5DF2\u590D\u5236" : "\u590D\u5236\u67E5\u8BE2\u6307\u4EE4")
3520
+ { className: "iterate-obs-block-head" },
3521
+ React.createElement("span", { className: "iterate-defense-badge", "data-type": type }, TYPE_LABEL[type] || type),
3522
+ typeof e.round === "number" ? React.createElement("span", { className: "iterate-obs-head-meta" }, `Round ${e.round}`) : null,
3523
+ React.createElement("span", { className: "iterate-obs-head-meta" }, loc)
3524
+ ),
3525
+ React.createElement(
3526
+ "div",
3527
+ { className: "iterate-obs-block-body" },
3528
+ React.createElement("div", { className: "iterate-obs-msg" }, String(e.description || "")),
3529
+ e.defense ? React.createElement("div", { className: "iterate-obs-msg", style: { marginTop: 4 } }, `\u9632\u7EBF\uFF1A${String(e.defense)}`) : null
3530
+ )
3531
+ );
3532
+ });
3533
+ }
3534
+ return React.createElement(
3535
+ "div",
3536
+ {},
3537
+ headerBar,
3538
+ filterBar,
3539
+ React.createElement(
3540
+ "div",
3541
+ { className: "iterate-obs-bar", style: { flexWrap: "wrap", gap: 6, marginBottom: 8 } },
3542
+ ...typeOptions.slice(1).map(
3543
+ (opt) => React.createElement(
3544
+ "span",
3545
+ { key: `cnt-${opt.value}`, className: "iterate-defense-badge", "data-type": opt.value },
3546
+ `${opt.label} ${counts[opt.value] || 0}`
3087
3547
  )
3088
3548
  )
3089
- )
3549
+ ),
3550
+ eventRows.length > 0 ? React.createElement("div", {}, ...eventRows) : React.createElement("div", { className: "iterate-obs-empty" }, "\u6CA1\u6709\u5339\u914D\u8BE5\u7C7B\u578B\u7684\u9632\u5FA1\u4E8B\u4EF6")
3090
3551
  );
3091
3552
  };
3092
3553
  const renderLive = () => {