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/README.md +123 -90
- package/README.zh-CN.md +129 -80
- package/dist/approval-gate.js +0 -17
- package/dist/tools/checkpoint.js +22 -3
- package/dist/tools/defense-events.js +4 -1
- package/dist/tools/defense-store.js +9 -3
- package/dist/tools/experience-bank.js +4 -1
- package/dist/tools/experience-store.js +9 -3
- package/dist/tools/quality-gate.js +4 -1
- package/dist/tools/quality-store.js +9 -3
- package/dist/tools/transcript.js +8 -1
- package/dist/transcript.js +10 -0
- package/lib/client.js +549 -88
- package/lib/parse.js +352 -0
- package/package.json +2 -2
- package/src/approval-gate.ts +0 -26
- package/src/client/index.ts +349 -73
- package/src/tools/checkpoint.ts +22 -3
- package/src/tools/defense-events.ts +4 -1
- package/src/tools/defense-store.ts +13 -4
- package/src/tools/experience-bank.ts +4 -1
- package/src/tools/experience-store.ts +13 -4
- package/src/tools/quality-gate.ts +4 -1
- package/src/tools/quality-store.ts +13 -4
- package/src/tools/transcript.ts +8 -1
- package/src/transcript.ts +11 -0
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
|
|
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
|
-
|
|
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", {
|
|
2939
|
-
React.createElement(
|
|
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
|
-
|
|
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: {
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
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
|
|
2966
|
-
|
|
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-
|
|
2970
|
-
React.createElement(
|
|
2971
|
-
|
|
2972
|
-
|
|
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" }, "\
|
|
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
|
-
|
|
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
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
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
|
-
{
|
|
3462
|
+
{},
|
|
3463
|
+
headerBar,
|
|
3068
3464
|
React.createElement(
|
|
3069
3465
|
"div",
|
|
3070
|
-
{ className: "iterate-obs-block
|
|
3071
|
-
React.createElement(
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
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
|
|
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-
|
|
3081
|
-
React.createElement("
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
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 = () => {
|