iterate-plugin 2.9.4 → 2.11.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 +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/config-loader.js +14 -3
- package/dist/config-write.js +7 -4
- package/dist/evidence.js +67 -1
- package/dist/git-scope.js +35 -6
- package/dist/meta-review.js +19 -5
- package/dist/method-scope.js +5 -1
- package/dist/review-scope.js +12 -8
- package/dist/review.js +76 -24
- package/dist/skill-prompt.js +45 -15
- package/dist/tools/checkpoint.js +10 -3
- package/dist/tools/context.js +16 -4
- package/dist/tools/decision-log.js +29 -9
- package/dist/tools/fix.js +120 -3
- package/dist/tools/prune.js +16 -9
- package/dist/tools/review.js +4 -1
- package/dist/tools/triage.js +9 -6
- package/dist/tools/validate.js +5 -2
- package/lib/client.js +152 -80
- package/lib/parse.js +26 -17
- package/package.json +3 -3
- package/src/client/index.ts +144 -56
- package/src/config-loader.ts +12 -2
- package/src/config-write.ts +6 -4
- package/src/evidence.ts +69 -1
- package/src/git-scope.ts +34 -6
- package/src/meta-review.ts +24 -10
- package/src/method-scope.ts +5 -1
- package/src/review-scope.ts +11 -7
- package/src/review.ts +82 -25
- package/src/skill-prompt.ts +45 -15
- package/src/tools/checkpoint.ts +10 -3
- package/src/tools/context.ts +14 -3
- package/src/tools/decision-log.ts +27 -10
- package/src/tools/fix.ts +114 -3
- package/src/tools/prune.ts +14 -11
- package/src/tools/review.ts +5 -2
- package/src/tools/triage.ts +9 -6
- package/src/tools/validate.ts +5 -2
- package/src/types.ts +12 -0
package/lib/client.js
CHANGED
|
@@ -40,7 +40,10 @@ var SEVERITY_LABEL = {
|
|
|
40
40
|
var SEVERITY_COLOR = {
|
|
41
41
|
critical: "#ef4444",
|
|
42
42
|
high: "#f97316",
|
|
43
|
-
medium
|
|
43
|
+
// medium is used both for dots/fills and as TEXT (stat numbers, table
|
|
44
|
+
// headers); #eab308 is illegible as text on light backgrounds (~1.6:1).
|
|
45
|
+
// amber-600 (#d97706) ~3.2:1 — still short of AA; go darker for legibility.
|
|
46
|
+
medium: "#b45309",
|
|
44
47
|
low: "#6b7280"
|
|
45
48
|
};
|
|
46
49
|
function safeGet(o, key) {
|
|
@@ -194,8 +197,6 @@ function findReportInObject(obj, seen, maxDepth = 20) {
|
|
|
194
197
|
}
|
|
195
198
|
function scanSessionForReport(session) {
|
|
196
199
|
if (!session || typeof session !== "object") return null;
|
|
197
|
-
const direct = findReportInObject(session);
|
|
198
|
-
if (direct) return direct;
|
|
199
200
|
const s = (
|
|
200
201
|
/** @type {Record<string, unknown>} */
|
|
201
202
|
session
|
|
@@ -304,8 +305,6 @@ function findRunSummaryInObject(obj, seen, maxDepth = 20) {
|
|
|
304
305
|
}
|
|
305
306
|
function scanSessionForRunSummary(session) {
|
|
306
307
|
if (!session || typeof session !== "object") return null;
|
|
307
|
-
const direct = findRunSummaryInObject(session);
|
|
308
|
-
if (direct) return direct;
|
|
309
308
|
const s = (
|
|
310
309
|
/** @type {Record<string, unknown>} */
|
|
311
310
|
session
|
|
@@ -503,22 +502,24 @@ function buildTriageState(report) {
|
|
|
503
502
|
return state;
|
|
504
503
|
}
|
|
505
504
|
function hashReport(report) {
|
|
506
|
-
const
|
|
507
|
-
/** @type {Record<string, unknown>} */
|
|
508
|
-
report.convergence ?? {}
|
|
509
|
-
);
|
|
510
|
-
const totalRounds = String(convergence.totalRounds ?? "");
|
|
511
|
-
const findingsCount = String(
|
|
512
|
-
/** @type {Array<unknown>} */
|
|
513
|
-
(report.findings ?? []).length
|
|
514
|
-
);
|
|
515
|
-
const firstFinding = (
|
|
505
|
+
const findings = (
|
|
516
506
|
/** @type {Array<Record<string, unknown>>} */
|
|
517
|
-
|
|
507
|
+
report.findings ?? []
|
|
518
508
|
);
|
|
519
|
-
|
|
520
|
-
const
|
|
521
|
-
|
|
509
|
+
let h = 2166136261;
|
|
510
|
+
const mix = (s) => {
|
|
511
|
+
for (let i = 0; i < s.length; i++) {
|
|
512
|
+
h ^= s.charCodeAt(i);
|
|
513
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
mix(`${String(report.mode ?? "")}|`);
|
|
517
|
+
for (const f of findings) {
|
|
518
|
+
if (!f || typeof f !== "object") continue;
|
|
519
|
+
mix(`${String(f.file ?? "")}|${typeof f.line === "number" ? f.line : 0}|${String(f.dimension ?? "")}|${String(f.summary ?? "")}
|
|
520
|
+
`);
|
|
521
|
+
}
|
|
522
|
+
return `iterate-triage-${h.toString(36)}`;
|
|
522
523
|
}
|
|
523
524
|
function toKnownIntentionalYaml(entries) {
|
|
524
525
|
if (!entries || entries.length === 0) return "";
|
|
@@ -955,6 +956,27 @@ var ITERATE_CSS = `
|
|
|
955
956
|
.iterate-switch[data-on] { background: var(--dsw-alias-brand-primary); border-color: var(--dsw-alias-brand-primary); }
|
|
956
957
|
.iterate-switch[data-on] .iterate-switch-knob { transform: translateX(18px); background: #FFFFFF; }
|
|
957
958
|
|
|
959
|
+
/* Shared keyboard focus ring for every iterate interactive control */
|
|
960
|
+
.iterate-btn:focus-visible, .iterate-vbtn:focus-visible, .iterate-batch-btn:focus-visible,
|
|
961
|
+
.iterate-filter-select:focus-visible, .iterate-filter-search:focus-visible,
|
|
962
|
+
.iterate-finding:focus-visible, .iterate-switch:focus-visible {
|
|
963
|
+
outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 2px;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
/* Dashboard empty/onboarding state */
|
|
967
|
+
.iterate-dashboard-empty { opacity: 0.75; }
|
|
968
|
+
.iterate-empty-hint { font-size: 12px; color: var(--dsw-alias-label-secondary); }
|
|
969
|
+
|
|
970
|
+
/* Convergence-completed progress fill */
|
|
971
|
+
.iterate-progress-fill-done { background: var(--dsw-alias-state-success-primary); }
|
|
972
|
+
|
|
973
|
+
/* Batch scope segmented control */
|
|
974
|
+
.iterate-batch-scope { opacity: 0.6; }
|
|
975
|
+
.iterate-batch-scope-on { opacity: 1; border-color: var(--dsw-alias-brand-primary); color: var(--dsw-alias-label-primary); }
|
|
976
|
+
|
|
977
|
+
/* Overflow dimension chip */
|
|
978
|
+
.iterate-dim-more { opacity: 0.7; font-style: italic; }
|
|
979
|
+
|
|
958
980
|
/* Button variants */
|
|
959
981
|
.iterate-btn[data-ghost] { background: transparent; }
|
|
960
982
|
.iterate-btn[data-danger] { border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary) 45%, transparent); color: var(--dsw-alias-state-error-primary); background: transparent; }
|
|
@@ -1025,13 +1047,12 @@ function removeStorageByPrefix(prefix) {
|
|
|
1025
1047
|
}
|
|
1026
1048
|
function copyText(text) {
|
|
1027
1049
|
if (typeof navigator !== "undefined" && navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
|
|
1028
|
-
navigator.clipboard.writeText(text).then(
|
|
1050
|
+
return navigator.clipboard.writeText(text).then(
|
|
1029
1051
|
() => true,
|
|
1030
1052
|
() => false
|
|
1031
1053
|
);
|
|
1032
|
-
return true;
|
|
1033
1054
|
}
|
|
1034
|
-
return false;
|
|
1055
|
+
return Promise.resolve(false);
|
|
1035
1056
|
}
|
|
1036
1057
|
var SEVERITY_KEYS = ["critical", "high", "medium", "low"];
|
|
1037
1058
|
function coerceSeverity(severity) {
|
|
@@ -1111,7 +1132,12 @@ function TrendChart({ points }) {
|
|
|
1111
1132
|
style: { height: `${Math.max(4, Math.round(p.count / max * 24))}px` }
|
|
1112
1133
|
})
|
|
1113
1134
|
);
|
|
1114
|
-
|
|
1135
|
+
const summary = points.map((p) => `Round ${p.round}: ${p.count}`).join(", ");
|
|
1136
|
+
return React.createElement("div", {
|
|
1137
|
+
className: "iterate-trend",
|
|
1138
|
+
role: "img",
|
|
1139
|
+
"aria-label": `\u5404\u8F6E\u53D1\u73B0\u6570\u91CF\u8D8B\u52BF\uFF1A${summary}`
|
|
1140
|
+
}, ...bars);
|
|
1115
1141
|
}
|
|
1116
1142
|
function ConvergenceDashboard(props) {
|
|
1117
1143
|
const [pulseKey, setPulseKey] = React.useState(0);
|
|
@@ -1124,7 +1150,14 @@ function ConvergenceDashboard(props) {
|
|
|
1124
1150
|
emitRoundPulse(cur, conv?.converged === true);
|
|
1125
1151
|
setPulseKey((k) => k + 1);
|
|
1126
1152
|
}, [report && hashReport(report) + ":" + getCurrentRound(report)]);
|
|
1127
|
-
if (!report)
|
|
1153
|
+
if (!report) {
|
|
1154
|
+
return React.createElement(
|
|
1155
|
+
"div",
|
|
1156
|
+
{ "data-iterate-root": "", "data-iterate": "dashboard", className: "iterate-dashboard iterate-dashboard-empty" },
|
|
1157
|
+
React.createElement("span", { className: "iterate-round-badge" }, "iterate"),
|
|
1158
|
+
React.createElement("span", { className: "iterate-empty-hint" }, "\u8FD0\u884C\u4E00\u6B21\u8BC4\u5BA1\u540E\uFF0C\u8FD9\u91CC\u4F1A\u663E\u793A\u6536\u655B\u8FDB\u5EA6\u4E0E\u53D1\u73B0\u7EDF\u8BA1\u3002\u8BD5\u8BD5\u300Creview this project\u300D\u6216\u300C/iterate review-only\u300D")
|
|
1159
|
+
);
|
|
1160
|
+
}
|
|
1128
1161
|
const round = getCurrentRound(report);
|
|
1129
1162
|
const total = getTotalRounds(report);
|
|
1130
1163
|
const progress = computeConvergenceProgress(report);
|
|
@@ -1143,13 +1176,24 @@ function ConvergenceDashboard(props) {
|
|
|
1143
1176
|
key: "images",
|
|
1144
1177
|
title: "\u4F1A\u8BDD\u4E2D\u68C0\u6D4B\u5230\u7528\u6237\u9644\u5E26\u7684\u56FE\u7247\uFF0C\u8BC4\u5BA1\u5C06\u4F5C\u4E3A\u89C6\u89C9\u8BC1\u636E\u53C2\u8003"
|
|
1145
1178
|
}, `\u9644\u4EF6\u56FE\u7247 ${String(imageCount)}`) : null;
|
|
1146
|
-
const
|
|
1179
|
+
const dimNames = Object.keys(dims);
|
|
1180
|
+
const dimBadges = dimNames.slice(0, 6).map(
|
|
1147
1181
|
(dim) => React.createElement(
|
|
1148
1182
|
"span",
|
|
1149
1183
|
{ key: dim, className: "iterate-dim-badge" },
|
|
1150
1184
|
`${dim} \xB7 ${dims[dim]?.length ?? 0}`
|
|
1151
1185
|
)
|
|
1152
1186
|
);
|
|
1187
|
+
const overflow = dimNames.length - 6;
|
|
1188
|
+
if (overflow > 0) {
|
|
1189
|
+
dimBadges.push(
|
|
1190
|
+
React.createElement(
|
|
1191
|
+
"span",
|
|
1192
|
+
{ key: "+more", className: "iterate-dim-badge iterate-dim-more", title: dimNames.slice(6).join(", ") },
|
|
1193
|
+
`+${overflow} \u66F4\u591A`
|
|
1194
|
+
)
|
|
1195
|
+
);
|
|
1196
|
+
}
|
|
1153
1197
|
const mode = report.mode;
|
|
1154
1198
|
const summary = report.summary;
|
|
1155
1199
|
const isNormal = mode === "normal";
|
|
@@ -1159,6 +1203,18 @@ function ConvergenceDashboard(props) {
|
|
|
1159
1203
|
key: "fixes",
|
|
1160
1204
|
title: "\u672C\u8F6E\u5DF2\u5E94\u7528\u7684\u539F\u5B50\u4FEE\u590D\u6570\uFF08\u6B63\u5E38\u6A21\u5F0F\uFF09"
|
|
1161
1205
|
}, `${String(fixCount)} fixes`) : null;
|
|
1206
|
+
const converged = report.convergence && report.convergence.converged === true;
|
|
1207
|
+
const convChip = converged ? React.createElement("span", {
|
|
1208
|
+
className: "iterate-chip-resume",
|
|
1209
|
+
key: "converged",
|
|
1210
|
+
title: "\u5BA1\u67E5\u5DF2\u6536\u655B\uFF1A\u6700\u540E\u4E00\u8F6E\u672A\u53D1\u73B0\u65B0\u95EE\u9898"
|
|
1211
|
+
}, "\u2713 \u5DF2\u6536\u655B") : null;
|
|
1212
|
+
const sevMetric = (key, label) => React.createElement(
|
|
1213
|
+
"span",
|
|
1214
|
+
{ className: "iterate-metric", key, title: label },
|
|
1215
|
+
React.createElement("span", { className: "iterate-sev-dot", style: { background: SEVERITY_COLOR[key] } }),
|
|
1216
|
+
`${label} ${String(stats[key])}`
|
|
1217
|
+
);
|
|
1162
1218
|
return React.createElement(
|
|
1163
1219
|
"div",
|
|
1164
1220
|
{ "data-iterate-root": "", "data-iterate": "dashboard", className: "iterate-dashboard" },
|
|
@@ -1170,26 +1226,16 @@ function ConvergenceDashboard(props) {
|
|
|
1170
1226
|
React.createElement(
|
|
1171
1227
|
"div",
|
|
1172
1228
|
{ className: "iterate-progress" },
|
|
1173
|
-
React.createElement("div", {
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
{ className: "iterate-metric" },
|
|
1178
|
-
React.createElement("span", { className: "iterate-sev-dot", style: { background: SEVERITY_COLOR.critical } }),
|
|
1179
|
-
stats.critical
|
|
1180
|
-
),
|
|
1181
|
-
React.createElement(
|
|
1182
|
-
"span",
|
|
1183
|
-
{ className: "iterate-metric" },
|
|
1184
|
-
React.createElement("span", { className: "iterate-sev-dot", style: { background: SEVERITY_COLOR.high } }),
|
|
1185
|
-
stats.high
|
|
1186
|
-
),
|
|
1187
|
-
React.createElement(
|
|
1188
|
-
"span",
|
|
1189
|
-
{ className: "iterate-metric" },
|
|
1190
|
-
React.createElement("span", { className: "iterate-sev-dot", style: { background: SEVERITY_COLOR.medium } }),
|
|
1191
|
-
stats.medium
|
|
1229
|
+
React.createElement("div", {
|
|
1230
|
+
className: converged ? "iterate-progress-fill iterate-progress-fill-done" : "iterate-progress-fill",
|
|
1231
|
+
style: { width: `${progress}%` }
|
|
1232
|
+
})
|
|
1192
1233
|
),
|
|
1234
|
+
convChip,
|
|
1235
|
+
sevMetric("critical", "CRIT"),
|
|
1236
|
+
sevMetric("high", "HIGH"),
|
|
1237
|
+
sevMetric("medium", "MED"),
|
|
1238
|
+
sevMetric("low", "LOW"),
|
|
1193
1239
|
fixBadge,
|
|
1194
1240
|
resumeChip,
|
|
1195
1241
|
imageChip,
|
|
@@ -1313,12 +1359,11 @@ function TriagePanel(props) {
|
|
|
1313
1359
|
const [filter, setFilter] = React.useState({ severities: [], dimensions: [], search: "" });
|
|
1314
1360
|
const [selected, setSelected] = React.useState(null);
|
|
1315
1361
|
const [selectAll, setSelectAll] = React.useState(false);
|
|
1316
|
-
|
|
1317
|
-
if (storage) storage.set(storageKey, JSON.stringify(
|
|
1318
|
-
|
|
1319
|
-
};
|
|
1362
|
+
React.useEffect(() => {
|
|
1363
|
+
if (storage) storage.set(storageKey, JSON.stringify(verdicts));
|
|
1364
|
+
}, [storageKey, verdicts]);
|
|
1320
1365
|
const setVerdict = (index, verdict) => {
|
|
1321
|
-
setVerdicts((prev) =>
|
|
1366
|
+
setVerdicts((prev) => ({ ...prev, [String(index)]: verdict }));
|
|
1322
1367
|
};
|
|
1323
1368
|
const { filtered, indices } = filterFindingsWithIndices(findings, filter);
|
|
1324
1369
|
const indicesKey = indices.join(",");
|
|
@@ -1331,21 +1376,20 @@ function TriagePanel(props) {
|
|
|
1331
1376
|
const allIndices = allVerdictKeys(verdicts);
|
|
1332
1377
|
const batchTarget = selectAll ? allIndices : indices;
|
|
1333
1378
|
const applyBatch = (verdict) => {
|
|
1334
|
-
setVerdicts((prev) =>
|
|
1335
|
-
};
|
|
1336
|
-
const applyBatchAll = (verdict) => {
|
|
1337
|
-
setVerdicts((prev) => persistVerdicts(setAllVerdicts(prev, verdict)));
|
|
1379
|
+
setVerdicts((prev) => batchSetVerdict(prev, batchTarget, verdict));
|
|
1338
1380
|
};
|
|
1339
1381
|
const doResetVerdicts = () => {
|
|
1340
|
-
setVerdicts((prev) =>
|
|
1382
|
+
setVerdicts((prev) => setAllVerdicts(prev, "keep"));
|
|
1341
1383
|
setSelectAll(false);
|
|
1342
1384
|
};
|
|
1343
1385
|
React.useEffect(() => {
|
|
1344
1386
|
const doc = typeof document !== "undefined" ? document : null;
|
|
1345
1387
|
if (!doc) return;
|
|
1346
1388
|
const onKeyDown = (ev) => {
|
|
1389
|
+
if (ev.metaKey || ev.ctrlKey || ev.altKey) return;
|
|
1347
1390
|
const t = ev.target;
|
|
1348
1391
|
if (t && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.tagName === "SELECT")) return;
|
|
1392
|
+
if (t && typeof t.isContentEditable === "boolean" && t.isContentEditable) return;
|
|
1349
1393
|
const verdict = keyToVerdict(ev.key);
|
|
1350
1394
|
if (verdict && selected !== null && indices.includes(selected)) {
|
|
1351
1395
|
ev.preventDefault();
|
|
@@ -1376,10 +1420,14 @@ function TriagePanel(props) {
|
|
|
1376
1420
|
const doCopyYaml = () => {
|
|
1377
1421
|
const yaml = toKnownIntentionalYaml(ignored);
|
|
1378
1422
|
if (!yaml) return;
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1423
|
+
copyText(yaml).then((ok) => {
|
|
1424
|
+
if (ok) {
|
|
1425
|
+
setCopied(true);
|
|
1426
|
+
setTimeout(() => setCopied(false), 1600);
|
|
1427
|
+
} else {
|
|
1428
|
+
setPayload(yaml);
|
|
1429
|
+
}
|
|
1430
|
+
});
|
|
1383
1431
|
};
|
|
1384
1432
|
const doBuildInstruction = () => {
|
|
1385
1433
|
const text = buildApplyInstruction(ignored);
|
|
@@ -1408,7 +1456,17 @@ function TriagePanel(props) {
|
|
|
1408
1456
|
key: String(index),
|
|
1409
1457
|
className: "iterate-finding",
|
|
1410
1458
|
"data-selected": isSelected ? "" : void 0,
|
|
1411
|
-
|
|
1459
|
+
role: "option",
|
|
1460
|
+
"aria-selected": isSelected,
|
|
1461
|
+
tabIndex: 0,
|
|
1462
|
+
onClick: () => setSelected(index),
|
|
1463
|
+
onFocus: () => setSelected(index),
|
|
1464
|
+
onKeyDown: (e) => {
|
|
1465
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
1466
|
+
e.preventDefault();
|
|
1467
|
+
setSelected(index);
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1412
1470
|
},
|
|
1413
1471
|
React.createElement(
|
|
1414
1472
|
"div",
|
|
@@ -1435,7 +1493,7 @@ function TriagePanel(props) {
|
|
|
1435
1493
|
React.createElement(
|
|
1436
1494
|
"div",
|
|
1437
1495
|
{ className: "iterate-triage-head" },
|
|
1438
|
-
React.createElement("span", {}, `Iterate \xB7 Findings \u5206\u8BCA (${filtered.length}/${findings.length})`),
|
|
1496
|
+
React.createElement("span", { role: "heading", "aria-level": 3 }, `Iterate \xB7 Findings \u5206\u8BCA (${filtered.length}/${findings.length})`),
|
|
1439
1497
|
React.createElement("span", { className: "iterate-triage-hint" }, "y=\u4FEE\u590D \xB7 n=\u8DF3\u8FC7 \xB7 a=\u5DF2\u77E5\u6709\u610F \xB7 \u2191/\u2193 \u9009\u62E9")
|
|
1440
1498
|
),
|
|
1441
1499
|
React.createElement(
|
|
@@ -1443,7 +1501,7 @@ function TriagePanel(props) {
|
|
|
1443
1501
|
{ className: "iterate-filter" },
|
|
1444
1502
|
React.createElement(
|
|
1445
1503
|
"select",
|
|
1446
|
-
{ className: "iterate-filter-select", value: filter.severities[0] || "", onChange: (e) => setSeverityFilter(e.target.value),
|
|
1504
|
+
{ className: "iterate-filter-select", value: filter.severities[0] || "", onChange: (e) => setSeverityFilter(e.target.value), "aria-label": "\u6309\u4E25\u91CD\u5EA6\u7B5B\u9009" },
|
|
1447
1505
|
React.createElement("option", { value: "" }, "\u5168\u90E8\u4E25\u91CD\u5EA6"),
|
|
1448
1506
|
...options.severities.map(
|
|
1449
1507
|
(s) => React.createElement("option", { key: s.value, value: s.value }, `${severityLabel(s.value)} (${s.count})`)
|
|
@@ -1451,7 +1509,7 @@ function TriagePanel(props) {
|
|
|
1451
1509
|
),
|
|
1452
1510
|
React.createElement(
|
|
1453
1511
|
"select",
|
|
1454
|
-
{ className: "iterate-filter-select", value: filter.dimensions[0] || "", onChange: (e) => setDimensionFilter(e.target.value),
|
|
1512
|
+
{ className: "iterate-filter-select", value: filter.dimensions[0] || "", onChange: (e) => setDimensionFilter(e.target.value), "aria-label": "\u6309\u7EF4\u5EA6\u7B5B\u9009" },
|
|
1455
1513
|
React.createElement("option", { value: "" }, "\u5168\u90E8\u7EF4\u5EA6"),
|
|
1456
1514
|
...options.dimensions.map(
|
|
1457
1515
|
(d) => React.createElement("option", { key: d.value, value: d.value }, `${d.value} (${d.count})`)
|
|
@@ -1461,6 +1519,7 @@ function TriagePanel(props) {
|
|
|
1461
1519
|
className: "iterate-filter-search",
|
|
1462
1520
|
type: "search",
|
|
1463
1521
|
placeholder: "\u641C\u7D22\u6587\u4EF6 / \u6458\u8981\u2026",
|
|
1522
|
+
"aria-label": "\u641C\u7D22\u6587\u4EF6\u6216\u6458\u8981",
|
|
1464
1523
|
value: filter.search,
|
|
1465
1524
|
onChange: (e) => setSearchFilter(e.target.value)
|
|
1466
1525
|
}),
|
|
@@ -1473,20 +1532,20 @@ function TriagePanel(props) {
|
|
|
1473
1532
|
React.createElement(
|
|
1474
1533
|
"div",
|
|
1475
1534
|
{ className: "iterate-batch" },
|
|
1476
|
-
React.createElement("span", { className: "iterate-batch-label" }, "\u6279\u91CF\uFF1A"),
|
|
1477
|
-
React.createElement(
|
|
1478
|
-
"
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
React.createElement("button", { className: "iterate-batch-btn", onClick: () =>
|
|
1488
|
-
React.createElement("button", { className: "iterate-batch-btn", onClick: () =>
|
|
1489
|
-
React.createElement("button", { className: "iterate-batch-btn", onClick: () =>
|
|
1535
|
+
React.createElement("span", { className: "iterate-batch-label" }, "\u6279\u91CF\u4F5C\u7528\u4E8E\uFF1A"),
|
|
1536
|
+
React.createElement("button", {
|
|
1537
|
+
className: selectAll ? "iterate-batch-btn iterate-batch-scope" : "iterate-batch-btn iterate-batch-scope iterate-batch-scope-on",
|
|
1538
|
+
onClick: () => setSelectAll(false),
|
|
1539
|
+
title: "\u6279\u91CF\u6309\u94AE\u4EC5\u4F5C\u7528\u4E8E\u5F53\u524D\u7B5B\u9009\u53EF\u89C1\u7684 findings"
|
|
1540
|
+
}, `\u53EF\u89C1 ${indices.length}`),
|
|
1541
|
+
React.createElement("button", {
|
|
1542
|
+
className: selectAll ? "iterate-batch-btn iterate-batch-scope iterate-batch-scope-on" : "iterate-batch-btn iterate-batch-scope",
|
|
1543
|
+
onClick: () => setSelectAll(true),
|
|
1544
|
+
title: "\u6279\u91CF\u6309\u94AE\u4F5C\u7528\u4E8E\u5168\u90E8 findings"
|
|
1545
|
+
}, `\u5168\u90E8 ${allIndices.length}`),
|
|
1546
|
+
React.createElement("button", { className: "iterate-batch-btn", onClick: () => applyBatch("keep") }, "y"),
|
|
1547
|
+
React.createElement("button", { className: "iterate-batch-btn", onClick: () => applyBatch("skip") }, "n"),
|
|
1548
|
+
React.createElement("button", { className: "iterate-batch-btn", onClick: () => applyBatch("ignore") }, "a"),
|
|
1490
1549
|
React.createElement("button", { className: "iterate-batch-btn", onClick: doResetVerdicts, title: "\u628A\u6240\u6709\u5224\u5B9A\u6062\u590D\u4E3A\u9ED8\u8BA4 y\uFF08\u4FEE\u590D\uFF09" }, "\u91CD\u7F6E")
|
|
1491
1550
|
),
|
|
1492
1551
|
...rows,
|
|
@@ -1497,8 +1556,20 @@ function TriagePanel(props) {
|
|
|
1497
1556
|
React.createElement(
|
|
1498
1557
|
"span",
|
|
1499
1558
|
{ style: { display: "flex", gap: 6 } },
|
|
1500
|
-
React.createElement("button", {
|
|
1501
|
-
|
|
1559
|
+
React.createElement("button", {
|
|
1560
|
+
className: "iterate-btn",
|
|
1561
|
+
"data-primary": "",
|
|
1562
|
+
"data-copied": copied ? "" : void 0,
|
|
1563
|
+
onClick: doCopyYaml,
|
|
1564
|
+
disabled: ignoredCount === 0,
|
|
1565
|
+
title: ignoredCount === 0 ? "\u5F53\u524D\u6CA1\u6709\u6807\u8BB0\u4E3A\u300C\u5DF2\u77E5\u6709\u610F\u300D\u7684 finding" : "\u590D\u5236 known_intentional YAML"
|
|
1566
|
+
}, copied ? "\u5DF2\u590D\u5236" : `\u590D\u5236 known_intentional${ignoredCount > 0 ? `\uFF08${ignoredCount}\uFF09` : ""}`),
|
|
1567
|
+
React.createElement("button", {
|
|
1568
|
+
className: "iterate-btn",
|
|
1569
|
+
onClick: doBuildInstruction,
|
|
1570
|
+
disabled: ignoredCount === 0,
|
|
1571
|
+
title: ignoredCount === 0 ? "\u5F53\u524D\u6CA1\u6709\u6807\u8BB0\u4E3A\u300C\u5DF2\u77E5\u6709\u610F\u300D\u7684 finding" : "\u751F\u6210 iterate_triage \u5E94\u7528\u6307\u4EE4"
|
|
1572
|
+
}, "\u751F\u6210\u5E94\u7528\u6307\u4EE4")
|
|
1502
1573
|
)
|
|
1503
1574
|
),
|
|
1504
1575
|
payload ? React.createElement("div", { className: "iterate-payload" }, payload) : null
|
|
@@ -1607,8 +1678,9 @@ function SettingsPanel(_props) {
|
|
|
1607
1678
|
setTimeout(() => setter(false), 1600);
|
|
1608
1679
|
};
|
|
1609
1680
|
const doCopy = (text, slot) => {
|
|
1610
|
-
copyText(text)
|
|
1611
|
-
|
|
1681
|
+
copyText(text).then((ok) => {
|
|
1682
|
+
if (ok) flashCopied(slot);
|
|
1683
|
+
});
|
|
1612
1684
|
};
|
|
1613
1685
|
const requestClear = () => {
|
|
1614
1686
|
if (confirming) {
|
package/lib/parse.js
CHANGED
|
@@ -24,7 +24,10 @@ export const SEVERITY_LABEL = {
|
|
|
24
24
|
export const SEVERITY_COLOR = {
|
|
25
25
|
critical: '#ef4444',
|
|
26
26
|
high: '#f97316',
|
|
27
|
-
medium
|
|
27
|
+
// medium is used both for dots/fills and as TEXT (stat numbers, table
|
|
28
|
+
// headers); #eab308 is illegible as text on light backgrounds (~1.6:1).
|
|
29
|
+
// amber-600 (#d97706) ~3.2:1 — still short of AA; go darker for legibility.
|
|
30
|
+
medium: '#b45309',
|
|
28
31
|
low: '#6b7280',
|
|
29
32
|
}
|
|
30
33
|
|
|
@@ -250,13 +253,11 @@ export function findReportInObject(obj, seen, maxDepth = 20) {
|
|
|
250
253
|
export function scanSessionForReport(session) {
|
|
251
254
|
if (!session || typeof session !== 'object') return null
|
|
252
255
|
|
|
253
|
-
// Try direct find first
|
|
254
|
-
const direct = findReportInObject(session)
|
|
255
|
-
if (direct) return direct
|
|
256
|
-
|
|
257
|
-
// Try common session structures
|
|
258
256
|
const s = /** @type {Record<string, unknown>} */ (session)
|
|
259
257
|
|
|
258
|
+
// LATEST-first scan: walk the chronological structures in reverse before the
|
|
259
|
+
// generic deep find, so a conversation with several reviews surfaces the
|
|
260
|
+
// most recent report — the generic find would return the FIRST match.
|
|
260
261
|
// Common pattern: session.toolCalls[].result.report
|
|
261
262
|
const toolCalls = safeGet(s, 'toolCalls')
|
|
262
263
|
if (Array.isArray(toolCalls)) {
|
|
@@ -382,11 +383,10 @@ export function findRunSummaryInObject(obj, seen, maxDepth = 20) {
|
|
|
382
383
|
export function scanSessionForRunSummary(session) {
|
|
383
384
|
if (!session || typeof session !== 'object') return null
|
|
384
385
|
|
|
385
|
-
const direct = findRunSummaryInObject(session)
|
|
386
|
-
if (direct) return direct
|
|
387
|
-
|
|
388
386
|
const s = /** @type {Record<string, unknown>} */ (session)
|
|
389
387
|
|
|
388
|
+
// LATEST-first: walk chronological structures in reverse before the generic
|
|
389
|
+
// deep find (which would return the FIRST match, i.e. the oldest run).
|
|
390
390
|
// Common pattern: session.toolCalls[].result contains a run summary.
|
|
391
391
|
const toolCalls = safeGet(s, 'toolCalls')
|
|
392
392
|
if (Array.isArray(toolCalls)) {
|
|
@@ -654,14 +654,23 @@ export function buildTriageState(report) {
|
|
|
654
654
|
* @returns {string}
|
|
655
655
|
*/
|
|
656
656
|
export function hashReport(report) {
|
|
657
|
-
const
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
const
|
|
663
|
-
|
|
664
|
-
|
|
657
|
+
const findings = /** @type {Array<Record<string, unknown>>} */ (report.findings ?? [])
|
|
658
|
+
// FNV-1a over EVERY finding (file|line|dimension|summary) so two different
|
|
659
|
+
// reports never share a verdict store, while re-running the identical review
|
|
660
|
+
// restores the same verdicts.
|
|
661
|
+
let h = 0x811c9dc5
|
|
662
|
+
const mix = (s) => {
|
|
663
|
+
for (let i = 0; i < s.length; i++) {
|
|
664
|
+
h ^= s.charCodeAt(i)
|
|
665
|
+
h = Math.imul(h, 0x01000193) >>> 0
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
mix(`${String(report.mode ?? '')}|`)
|
|
669
|
+
for (const f of findings) {
|
|
670
|
+
if (!f || typeof f !== 'object') continue
|
|
671
|
+
mix(`${String(f.file ?? '')}|${typeof f.line === 'number' ? f.line : 0}|${String(f.dimension ?? '')}|${String(f.summary ?? '')}\n`)
|
|
672
|
+
}
|
|
673
|
+
return `iterate-triage-${h.toString(36)}`
|
|
665
674
|
}
|
|
666
675
|
|
|
667
676
|
// ─── Known-intentional YAML builder ──────────────────────────────────────────
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iterate-plugin",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.11.0",
|
|
4
4
|
"description": "dsh plugin that turns the iterate skill into an autonomous closed-loop harness: plan -> parallel review xN -> atomic fixes -> validate -> loop -> auto-stop, plus a dry-run pure-review mode with multi-round convergence and a meta-review that audits the report and emits a final review report.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -61,11 +61,11 @@
|
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
63
|
"@deepseek-ai/cordis": "4.0.1",
|
|
64
|
-
"@deepseek-ai/dsh-tools": "0.1.
|
|
64
|
+
"@deepseek-ai/dsh-tools": "0.1.1-rc.1",
|
|
65
65
|
"js-yaml": "4.3.1"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
|
-
"@deepseek-ai/dsh-session": "0.1.
|
|
68
|
+
"@deepseek-ai/dsh-session": "0.1.1-rc.1",
|
|
69
69
|
"@types/js-yaml": "4.0.9",
|
|
70
70
|
"@types/node": "22.15.0",
|
|
71
71
|
"@types/react": "19.2.2",
|