iterate-plugin 3.7.0 → 3.9.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 +43 -8
- package/README.zh-CN.md +26 -5
- package/dist/file-lock.js +75 -14
- package/dist/git-scope.js +22 -2
- package/dist/index.js +9 -4
- package/dist/report-render.js +452 -0
- package/dist/session-record.js +246 -0
- package/dist/skill-prompt.js +4 -3
- package/dist/tools/checkpoint.js +24 -17
- package/dist/tools/config.js +42 -33
- package/dist/tools/decision-log.js +13 -93
- package/dist/tools/report.js +239 -0
- package/dist/tools/transcript.js +260 -53
- package/dist/tools/triage.js +52 -45
- package/dist/transcript.js +2 -2
- package/lib/client.js +486 -22
- package/lib/parse.js +29 -6
- package/package.json +38 -13
- package/src/client/i18n.ts +6 -0
- package/src/client/index.ts +66 -4
- package/src/file-lock.ts +84 -14
- package/src/git-scope.ts +21 -2
- package/src/index.ts +9 -4
- package/src/report-render.ts +578 -0
- package/src/session-record.ts +333 -0
- package/src/skill-prompt.ts +4 -3
- package/src/tools/checkpoint.ts +23 -16
- package/src/tools/config.ts +40 -31
- package/src/tools/decision-log.ts +12 -80
- package/src/tools/report.ts +244 -0
- package/src/tools/transcript.ts +271 -52
- package/src/tools/triage.ts +47 -40
- package/src/transcript.ts +2 -2
- package/src/types.ts +1 -1
package/lib/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* iterate-plugin client source-digest:
|
|
1
|
+
/* iterate-plugin client source-digest: 50774433818a03e70b4ac5341a6683c1ccb3d3a5ff93561a29007232a03b3e01 */
|
|
2
2
|
window.__ModuleLoader__.load({ id: "iterate-plugin", factory: (require) => {
|
|
3
3
|
var module = { exports: {} };
|
|
4
4
|
var exports = module.exports;
|
|
@@ -502,18 +502,18 @@ function normalizeTranscript(manifest) {
|
|
|
502
502
|
const asStr = (v) => typeof v === "string" ? v : "";
|
|
503
503
|
const asBool = (v) => v === true;
|
|
504
504
|
const asCount = (v) => typeof v === "number" ? v : 0;
|
|
505
|
-
const
|
|
505
|
+
const asArray2 = (v) => Array.isArray(v) ? (
|
|
506
506
|
/** @type {Array<Record<string, unknown>>} */
|
|
507
507
|
v
|
|
508
508
|
) : [];
|
|
509
|
-
const rounds =
|
|
509
|
+
const rounds = asArray2(safeGet(src, "rounds")).map((r) => ({
|
|
510
510
|
round: asNum(safeGet(r, "round")),
|
|
511
|
-
threads:
|
|
511
|
+
threads: asArray2(safeGet(r, "threads")).map((t2) => ({
|
|
512
512
|
dimension: asStr(safeGet(t2, "dimension")),
|
|
513
513
|
attempt: asNum(safeGet(t2, "attempt")),
|
|
514
|
-
messages:
|
|
515
|
-
readFiles:
|
|
516
|
-
findings:
|
|
514
|
+
messages: asArray2(safeGet(t2, "messages")).map((m) => typeof m === "string" ? m : ""),
|
|
515
|
+
readFiles: asArray2(safeGet(t2, "readFiles")).map((f) => typeof f === "string" ? f : ""),
|
|
516
|
+
findings: asArray2(safeGet(t2, "findings")).map((f) => ({ ...f }))
|
|
517
517
|
}))
|
|
518
518
|
}));
|
|
519
519
|
const cp = safeGet(src, "checkpoint");
|
|
@@ -538,7 +538,7 @@ function normalizeTranscript(manifest) {
|
|
|
538
538
|
// v3.0: harness task_mode (code/iterate), tolerated when absent.
|
|
539
539
|
taskMode: tm === "code" || tm === "iterate" ? tm : null,
|
|
540
540
|
goal: asStr(safeGet(src, "goal")),
|
|
541
|
-
phases:
|
|
541
|
+
phases: asArray2(safeGet(src, "phases")).map((p) => typeof p === "string" ? p : ""),
|
|
542
542
|
round: asNum(safeGet(src, "round")),
|
|
543
543
|
maxRounds: asNum(safeGet(src, "maxRounds")),
|
|
544
544
|
// v3.5: why a FINISHED run stopped (converged / max_rounds_reached /
|
|
@@ -546,17 +546,17 @@ function normalizeTranscript(manifest) {
|
|
|
546
546
|
// observatory badge to render the stop reason instead of a bare "已结束".
|
|
547
547
|
stoppedReason: src.stoppedReason === null || src.stoppedReason === void 0 ? null : asStr(safeGet(src, "stoppedReason")) || null,
|
|
548
548
|
rounds,
|
|
549
|
-
convergence:
|
|
550
|
-
findings:
|
|
551
|
-
fixes:
|
|
552
|
-
live:
|
|
549
|
+
convergence: asArray2(safeGet(src, "convergence")).map((n) => asCount(n)),
|
|
550
|
+
findings: asArray2(safeGet(src, "findings")).map((f) => ({ ...f })),
|
|
551
|
+
fixes: asArray2(safeGet(src, "fixes")).map((f) => ({ ...f })),
|
|
552
|
+
live: asArray2(safeGet(src, "live")).map((e) => ({
|
|
553
553
|
ts: typeof safeGet(e, "ts") === "number" ? String(safeGet(e, "ts")) : asStr(safeGet(e, "ts")),
|
|
554
554
|
type: asStr(safeGet(e, "type")),
|
|
555
555
|
tool: asStr(safeGet(e, "tool")),
|
|
556
556
|
target: asStr(safeGet(e, "target"))
|
|
557
557
|
})),
|
|
558
558
|
checkpoint,
|
|
559
|
-
timeline:
|
|
559
|
+
timeline: asArray2(safeGet(src, "timeline")).map((t2) => ({ ...t2 })),
|
|
560
560
|
// #6: per-round validation rows (command / exitCode / allowed), captured
|
|
561
561
|
// by the workflow and persisted through `iterate_transcript capture`.
|
|
562
562
|
validations: normalizeValidations(safeGet(src, "validations")),
|
|
@@ -1539,8 +1539,11 @@ function stoppedReasonLabel(reason, lang = "zh") {
|
|
|
1539
1539
|
schema_invalid: "Done \xB7 repeated schema validation failures in review output",
|
|
1540
1540
|
no_usable_reviewer_output: "Done \xB7 no usable review output"
|
|
1541
1541
|
};
|
|
1542
|
-
|
|
1543
|
-
|
|
1542
|
+
const table = lang === "en" ? en : zh;
|
|
1543
|
+
if (typeof reason === "string" && Object.hasOwn(table, reason)) return table[reason];
|
|
1544
|
+
const text = typeof reason === "string" && reason !== "" ? reason : "";
|
|
1545
|
+
if (lang === "en") return text ? `Done \xB7 ${text}` : "Done";
|
|
1546
|
+
return text ? `\u5DF2\u7ED3\u675F \xB7 ${text}` : "\u5DF2\u7ED3\u675F";
|
|
1544
1547
|
}
|
|
1545
1548
|
function buildCompletionSummary(report, lang = "zh") {
|
|
1546
1549
|
const conv = (
|
|
@@ -1626,8 +1629,9 @@ function buildConfigEditGuide() {
|
|
|
1626
1629
|
"---------------------",
|
|
1627
1630
|
"\u914D\u7F6E\u6587\u4EF6\uFF1A\u9879\u76EE\u6839\u76EE\u5F55 iterate.config.yaml\u3002",
|
|
1628
1631
|
"",
|
|
1629
|
-
"\u26A0 \u672A\u914D\u7F6E validation.commands \u65F6\u8FED\u4EE3\u4E0D\u88AB\u4FDD\u62A4\uFF1A\
|
|
1630
|
-
" \
|
|
1632
|
+
"\u26A0 \u672A\u914D\u7F6E validation.commands \u65F6\u8FED\u4EE3\u4E0D\u88AB\u4FDD\u62A4\uFF1A\u8BE5\u8F6E\u4F1A\u76F4\u63A5\u4E2D\u6B62\uFF08fail-closed\uFF09\uFF0C",
|
|
1633
|
+
" \u7A7A\u9A8C\u8BC1\u7ED3\u679C/\u9A8C\u8BC1\u672A\u6267\u884C\u7EDD\u4E0D\u5F53\u4F5C\u201C\u9A8C\u8BC1\u901A\u8FC7\u201D\uFF0C\u8FD0\u884C\u4EE5 converged_unverified /",
|
|
1634
|
+
" aborted_by_config \u6536\u5C3E\u800C\u4E0D\u662F plain converged\u3002\u7B2C\u4E00\u6B21\u542F\u52A8\u8FED\u4EE3\u524D\uFF0C",
|
|
1631
1635
|
" \u8BF7\u5148\u7ED9 validation.commands \u914D\u4E0A\u81F3\u5C11\u4E00\u6761\u771F\u5B9E\u547D\u4EE4\uFF08\u5982 npm test\uFF09\u3002",
|
|
1632
1636
|
"",
|
|
1633
1637
|
"\u53EF\u7F16\u8F91\u5B57\u6BB5\uFF1A",
|
|
@@ -1844,7 +1848,7 @@ function pickExportExtras(extra) {
|
|
|
1844
1848
|
}
|
|
1845
1849
|
return out;
|
|
1846
1850
|
}
|
|
1847
|
-
function buildRoundComparison(report, validations) {
|
|
1851
|
+
function buildRoundComparison(report, validations, fixes) {
|
|
1848
1852
|
const rounds = /* @__PURE__ */ new Map();
|
|
1849
1853
|
const rowsFor = (round) => {
|
|
1850
1854
|
const n = Number(round);
|
|
@@ -1861,7 +1865,18 @@ function buildRoundComparison(report, validations) {
|
|
|
1861
1865
|
row.findings += 1;
|
|
1862
1866
|
const sev = typeof f.severity === "string" && f.severity ? f.severity : "unknown";
|
|
1863
1867
|
row.severities[sev] = (row.severities[sev] || 0) + 1;
|
|
1864
|
-
|
|
1868
|
+
}
|
|
1869
|
+
const fixList = Array.isArray(fixes) ? fixes : [];
|
|
1870
|
+
for (const fx of fixList) {
|
|
1871
|
+
if (!fx || typeof fx !== "object") continue;
|
|
1872
|
+
if (
|
|
1873
|
+
/** @type {Record<string, unknown>} */
|
|
1874
|
+
fx.success !== true
|
|
1875
|
+
) continue;
|
|
1876
|
+
rowsFor(
|
|
1877
|
+
/** @type {Record<string, unknown>} */
|
|
1878
|
+
fx.round
|
|
1879
|
+
).fixed += 1;
|
|
1865
1880
|
}
|
|
1866
1881
|
const vals = Array.isArray(validations) ? validations : [];
|
|
1867
1882
|
for (const v of vals) {
|
|
@@ -1897,6 +1912,404 @@ function latestPhase(phases) {
|
|
|
1897
1912
|
return latest;
|
|
1898
1913
|
}
|
|
1899
1914
|
|
|
1915
|
+
// src/report-render.ts
|
|
1916
|
+
var SEVERITY_ORDER2 = ["critical", "high", "medium", "low"];
|
|
1917
|
+
var REPORT_LABELS = {
|
|
1918
|
+
en: {
|
|
1919
|
+
title: "iterate run report",
|
|
1920
|
+
project: "Project",
|
|
1921
|
+
goal: "Goal",
|
|
1922
|
+
mode: "Mode",
|
|
1923
|
+
status: "Status",
|
|
1924
|
+
round: "Round",
|
|
1925
|
+
updated: "Updated",
|
|
1926
|
+
converged: "converged",
|
|
1927
|
+
running: "running",
|
|
1928
|
+
stopped: "stopped",
|
|
1929
|
+
qualityGate: "Quality gate",
|
|
1930
|
+
overall: "Overall",
|
|
1931
|
+
verification: "Verification",
|
|
1932
|
+
dimension: "Dimension",
|
|
1933
|
+
qstatus: "Status",
|
|
1934
|
+
score: "Score",
|
|
1935
|
+
findingsCount: "Findings",
|
|
1936
|
+
fixed: "Fixed",
|
|
1937
|
+
convergence: "Convergence",
|
|
1938
|
+
findings: "Findings",
|
|
1939
|
+
severity: "Severity",
|
|
1940
|
+
location: "Location",
|
|
1941
|
+
summary: "Summary",
|
|
1942
|
+
defensiveType: "Type",
|
|
1943
|
+
none: "_none_",
|
|
1944
|
+
fixes: "Fixes",
|
|
1945
|
+
file: "File",
|
|
1946
|
+
delta: "\u0394",
|
|
1947
|
+
result: "Result",
|
|
1948
|
+
applied: "applied",
|
|
1949
|
+
failed: "failed",
|
|
1950
|
+
validations: "Validations",
|
|
1951
|
+
command: "Command",
|
|
1952
|
+
exit: "Exit",
|
|
1953
|
+
allowed: "Allowed",
|
|
1954
|
+
yes: "yes",
|
|
1955
|
+
no: "no",
|
|
1956
|
+
defenseEvents: "Defense events",
|
|
1957
|
+
experienceBank: "Experience bank",
|
|
1958
|
+
hits: "hits",
|
|
1959
|
+
entries: "entries",
|
|
1960
|
+
generatedAt: "Generated at",
|
|
1961
|
+
notRecorded: "not recorded"
|
|
1962
|
+
},
|
|
1963
|
+
zh: {
|
|
1964
|
+
title: "iterate \u8FD0\u884C\u62A5\u544A",
|
|
1965
|
+
project: "\u9879\u76EE",
|
|
1966
|
+
goal: "\u76EE\u6807",
|
|
1967
|
+
mode: "\u6A21\u5F0F",
|
|
1968
|
+
status: "\u72B6\u6001",
|
|
1969
|
+
round: "\u8F6E\u6B21",
|
|
1970
|
+
updated: "\u66F4\u65B0\u65F6\u95F4",
|
|
1971
|
+
converged: "\u5DF2\u6536\u655B",
|
|
1972
|
+
running: "\u8FD0\u884C\u4E2D",
|
|
1973
|
+
stopped: "\u5DF2\u7ED3\u675F",
|
|
1974
|
+
qualityGate: "\u8D28\u91CF\u95E8\u7981",
|
|
1975
|
+
overall: "\u603B\u4F53",
|
|
1976
|
+
verification: "\u9A8C\u8BC1",
|
|
1977
|
+
dimension: "\u7EF4\u5EA6",
|
|
1978
|
+
qstatus: "\u72B6\u6001",
|
|
1979
|
+
score: "\u5F97\u5206",
|
|
1980
|
+
findingsCount: "\u53D1\u73B0\u6570",
|
|
1981
|
+
fixed: "\u5DF2\u4FEE\u590D",
|
|
1982
|
+
convergence: "\u6536\u655B\u7387",
|
|
1983
|
+
findings: "\u53D1\u73B0",
|
|
1984
|
+
severity: "\u4E25\u91CD\u5EA6",
|
|
1985
|
+
location: "\u4F4D\u7F6E",
|
|
1986
|
+
summary: "\u6458\u8981",
|
|
1987
|
+
defensiveType: "\u7C7B\u578B",
|
|
1988
|
+
none: "\u65E0",
|
|
1989
|
+
fixes: "\u4FEE\u590D",
|
|
1990
|
+
file: "\u6587\u4EF6",
|
|
1991
|
+
delta: "\u53D8\u66F4",
|
|
1992
|
+
result: "\u7ED3\u679C",
|
|
1993
|
+
applied: "\u6210\u529F",
|
|
1994
|
+
failed: "\u5931\u8D25",
|
|
1995
|
+
validations: "\u9A8C\u8BC1",
|
|
1996
|
+
command: "\u547D\u4EE4",
|
|
1997
|
+
exit: "\u9000\u51FA\u7801",
|
|
1998
|
+
allowed: "\u5DF2\u6388\u6743",
|
|
1999
|
+
yes: "\u662F",
|
|
2000
|
+
no: "\u5426",
|
|
2001
|
+
defenseEvents: "\u9632\u5FA1\u4E8B\u4EF6",
|
|
2002
|
+
experienceBank: "\u7ECF\u9A8C\u94F6\u884C",
|
|
2003
|
+
hits: "\u547D\u4E2D",
|
|
2004
|
+
entries: "\u6761\u76EE",
|
|
2005
|
+
generatedAt: "\u751F\u6210\u65F6\u95F4",
|
|
2006
|
+
notRecorded: "\u672A\u8BB0\u5F55"
|
|
2007
|
+
}
|
|
2008
|
+
};
|
|
2009
|
+
function reportLabels(lang) {
|
|
2010
|
+
return lang === "en" ? REPORT_LABELS.en : REPORT_LABELS.zh;
|
|
2011
|
+
}
|
|
2012
|
+
function get(source, key) {
|
|
2013
|
+
if (!source || typeof source !== "object") return void 0;
|
|
2014
|
+
try {
|
|
2015
|
+
return source[key];
|
|
2016
|
+
} catch {
|
|
2017
|
+
return void 0;
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
function asString(value) {
|
|
2021
|
+
return typeof value === "string" ? value : "";
|
|
2022
|
+
}
|
|
2023
|
+
function asNumber(value) {
|
|
2024
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
2025
|
+
}
|
|
2026
|
+
function asArray(value) {
|
|
2027
|
+
return Array.isArray(value) ? value : [];
|
|
2028
|
+
}
|
|
2029
|
+
function asObject(value) {
|
|
2030
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
2031
|
+
}
|
|
2032
|
+
function mdCell(value) {
|
|
2033
|
+
const s = value === null || value === void 0 ? "" : String(value);
|
|
2034
|
+
return s.replace(/\|/g, "\\|").replace(/\r?\n/g, " ").trim();
|
|
2035
|
+
}
|
|
2036
|
+
function findingSeverity(f) {
|
|
2037
|
+
const s = get(f, "severity");
|
|
2038
|
+
return typeof s === "string" && SEVERITY_ORDER2.includes(s) ? s : "low";
|
|
2039
|
+
}
|
|
2040
|
+
function sarifUri(file) {
|
|
2041
|
+
const s = typeof file === "string" ? file.trim() : "";
|
|
2042
|
+
if (!s) return "";
|
|
2043
|
+
return s.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
2044
|
+
}
|
|
2045
|
+
function sarifLevel(severity) {
|
|
2046
|
+
if (severity === "critical" || severity === "high") return "error";
|
|
2047
|
+
if (severity === "medium") return "warning";
|
|
2048
|
+
return "note";
|
|
2049
|
+
}
|
|
2050
|
+
function buildRunReportData(manifest, extra) {
|
|
2051
|
+
const src = asObject(manifest) ?? {};
|
|
2052
|
+
const ex = asObject(extra);
|
|
2053
|
+
const qg = asObject(get(ex, "qualityGate"));
|
|
2054
|
+
const bank = asObject(get(ex, "experienceBank"));
|
|
2055
|
+
const defense = asObject(get(ex, "defenseEvents"));
|
|
2056
|
+
const findings = asArray(get(src, "findings")).map((f) => ({
|
|
2057
|
+
dimension: asString(get(f, "dimension")),
|
|
2058
|
+
file: asString(get(f, "file")),
|
|
2059
|
+
line: asNumber(get(f, "line")),
|
|
2060
|
+
severity: findingSeverity(f),
|
|
2061
|
+
summary: asString(get(f, "summary")),
|
|
2062
|
+
failureScenario: asString(get(f, "failure_scenario")),
|
|
2063
|
+
suggestedFix: asString(get(f, "suggested_fix")),
|
|
2064
|
+
isAtomic: get(f, "is_atomic") === true,
|
|
2065
|
+
acknowledged: get(f, "acknowledged") === true
|
|
2066
|
+
}));
|
|
2067
|
+
findings.sort(
|
|
2068
|
+
(a, b) => SEVERITY_ORDER2.indexOf(a.severity) - SEVERITY_ORDER2.indexOf(b.severity)
|
|
2069
|
+
);
|
|
2070
|
+
const fixes = asArray(get(src, "fixes")).map((f) => ({
|
|
2071
|
+
round: asNumber(get(f, "round")),
|
|
2072
|
+
file: asString(get(f, "file")),
|
|
2073
|
+
summary: asString(get(f, "summary")),
|
|
2074
|
+
linesAdded: asNumber(get(f, "linesAdded")),
|
|
2075
|
+
linesRemoved: asNumber(get(f, "linesRemoved")),
|
|
2076
|
+
success: get(f, "success") !== false
|
|
2077
|
+
}));
|
|
2078
|
+
const validations = asArray(get(src, "validations")).map((v) => ({
|
|
2079
|
+
round: asNumber(get(v, "round")),
|
|
2080
|
+
command: asString(get(v, "command")),
|
|
2081
|
+
exitCode: typeof get(v, "exitCode") === "number" ? asNumber(get(v, "exitCode")) : null,
|
|
2082
|
+
allowed: get(v, "allowed") === true,
|
|
2083
|
+
rejectReason: asString(get(v, "rejectReason"))
|
|
2084
|
+
}));
|
|
2085
|
+
const severityCounts = { critical: 0, high: 0, medium: 0, low: 0 };
|
|
2086
|
+
for (const f of findings) {
|
|
2087
|
+
const key = f.severity;
|
|
2088
|
+
if (key in severityCounts) severityCounts[key] += 1;
|
|
2089
|
+
}
|
|
2090
|
+
const mode = get(src, "mode");
|
|
2091
|
+
const taskMode = get(src, "taskMode");
|
|
2092
|
+
const stoppedReason = get(src, "stoppedReason");
|
|
2093
|
+
return {
|
|
2094
|
+
project: asString(get(src, "project")),
|
|
2095
|
+
goal: asString(get(src, "goal")),
|
|
2096
|
+
mode: mode === "normal" || mode === "dry-run" ? mode : null,
|
|
2097
|
+
taskMode: taskMode === "code" || taskMode === "iterate" ? taskMode : null,
|
|
2098
|
+
round: asNumber(get(src, "round")),
|
|
2099
|
+
maxRounds: asNumber(get(src, "maxRounds")),
|
|
2100
|
+
active: get(src, "active") !== false,
|
|
2101
|
+
stoppedReason: typeof stoppedReason === "string" && stoppedReason ? stoppedReason : null,
|
|
2102
|
+
updatedAt: asString(get(src, "updatedAt")),
|
|
2103
|
+
convergence: asArray(get(src, "convergence")).filter(
|
|
2104
|
+
(n) => typeof n === "number" && Number.isFinite(n) && n >= 0
|
|
2105
|
+
),
|
|
2106
|
+
findings,
|
|
2107
|
+
severityCounts,
|
|
2108
|
+
fixes,
|
|
2109
|
+
validations,
|
|
2110
|
+
qualityGate: qg,
|
|
2111
|
+
experienceBank: bank,
|
|
2112
|
+
defenseEvents: defense
|
|
2113
|
+
};
|
|
2114
|
+
}
|
|
2115
|
+
function reportStatus(data, labels) {
|
|
2116
|
+
if (data.active === true) return labels.running;
|
|
2117
|
+
if (data.stoppedReason) return `${labels.stopped} \xB7 ${data.stoppedReason}`;
|
|
2118
|
+
return labels.stopped;
|
|
2119
|
+
}
|
|
2120
|
+
function renderRunReportMarkdown(manifest, extra, opts) {
|
|
2121
|
+
const data = buildRunReportData(manifest, extra);
|
|
2122
|
+
const o = opts ?? {};
|
|
2123
|
+
const labels = reportLabels(o.lang === "en" ? "en" : "zh");
|
|
2124
|
+
const when = o.now && o.now ? o.now : (/* @__PURE__ */ new Date()).toISOString();
|
|
2125
|
+
const lines = [];
|
|
2126
|
+
lines.push(`# ${labels.title}`);
|
|
2127
|
+
lines.push("");
|
|
2128
|
+
lines.push(`- **${labels.project}**: \`${mdCell(data.project || labels.notRecorded)}\``);
|
|
2129
|
+
if (data.goal) lines.push(`- **${labels.goal}**: ${mdCell(data.goal)}`);
|
|
2130
|
+
lines.push(
|
|
2131
|
+
`- **${labels.mode}**: ${mdCell(data.mode || labels.notRecorded)}${data.taskMode ? ` \xB7 task_mode: ${data.taskMode}` : ""}`
|
|
2132
|
+
);
|
|
2133
|
+
const roundText = data.maxRounds > 0 ? `${data.round}/${data.maxRounds}` : String(data.round);
|
|
2134
|
+
lines.push(`- **${labels.round}**: ${mdCell(roundText)} \xB7 **${labels.status}**: ${mdCell(reportStatus(data, labels))}`);
|
|
2135
|
+
lines.push(`- **${labels.updated}**: ${mdCell(data.updatedAt || when)}`);
|
|
2136
|
+
lines.push(`- **${labels.generatedAt}**: ${mdCell(when)}`);
|
|
2137
|
+
lines.push("");
|
|
2138
|
+
const qg = data.qualityGate;
|
|
2139
|
+
if (qg) {
|
|
2140
|
+
lines.push(`## ${labels.qualityGate}`);
|
|
2141
|
+
lines.push("");
|
|
2142
|
+
const status = typeof qg.overallStatus === "string" ? qg.overallStatus.toUpperCase() : "PENDING";
|
|
2143
|
+
lines.push(`- **${labels.overall}**: ${mdCell(status)} (${labels.score} ${asNumber(qg.overallScore)})`);
|
|
2144
|
+
lines.push(
|
|
2145
|
+
`- **${labels.verification}**: ${asNumber(qg.passedChecks)}/${asNumber(qg.totalChecks)} (${asNumber(qg.verificationPassRate)}%)`
|
|
2146
|
+
);
|
|
2147
|
+
if (typeof qg.failReason === "string" && qg.failReason) {
|
|
2148
|
+
lines.push(`- ${labels.status}: ${mdCell(qg.failReason)}`);
|
|
2149
|
+
}
|
|
2150
|
+
lines.push("");
|
|
2151
|
+
const dims = asArray(qg.dimensions);
|
|
2152
|
+
if (dims.length > 0) {
|
|
2153
|
+
lines.push(
|
|
2154
|
+
`| ${labels.dimension} | ${labels.qstatus} | ${labels.score} | ${labels.findingsCount} | ${labels.fixed} | ${labels.convergence} |`
|
|
2155
|
+
);
|
|
2156
|
+
lines.push("| --- | --- | --- | --- | --- | --- |");
|
|
2157
|
+
for (const d of dims) {
|
|
2158
|
+
lines.push(
|
|
2159
|
+
`| ${mdCell(get(d, "dimension"))} | ${mdCell(get(d, "status"))} | ${mdCell(get(d, "score"))} | ${mdCell(get(d, "findingsCount"))} | ${mdCell(get(d, "fixedCount"))} | ${mdCell(get(d, "convergenceRate"))}% |`
|
|
2160
|
+
);
|
|
2161
|
+
}
|
|
2162
|
+
lines.push("");
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
lines.push(`## ${labels.findings} (${data.findings.length})`);
|
|
2166
|
+
lines.push("");
|
|
2167
|
+
if (data.findings.length === 0) {
|
|
2168
|
+
lines.push(labels.none);
|
|
2169
|
+
} else {
|
|
2170
|
+
lines.push(`| ${labels.severity} | ${labels.dimension} | ${labels.location} | ${labels.summary} |`);
|
|
2171
|
+
lines.push("| --- | --- | --- | --- |");
|
|
2172
|
+
for (const f of data.findings) {
|
|
2173
|
+
const loc = f.file ? `${f.file}${f.line > 1 ? `:${f.line}` : ""}` : "";
|
|
2174
|
+
lines.push(`| ${mdCell(f.severity)} | ${mdCell(f.dimension)} | ${mdCell(loc)} | ${mdCell(f.summary)} |`);
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
lines.push("");
|
|
2178
|
+
lines.push(`## ${labels.fixes} (${data.fixes.length})`);
|
|
2179
|
+
lines.push("");
|
|
2180
|
+
if (data.fixes.length === 0) {
|
|
2181
|
+
lines.push(labels.none);
|
|
2182
|
+
} else {
|
|
2183
|
+
lines.push(`| # | ${labels.file} | ${labels.delta} | ${labels.result} | ${labels.summary} |`);
|
|
2184
|
+
lines.push("| --- | --- | --- | --- | --- |");
|
|
2185
|
+
for (const f of data.fixes) {
|
|
2186
|
+
lines.push(
|
|
2187
|
+
`| ${mdCell(f.round)} | ${mdCell(f.file)} | ${mdCell(`+${f.linesAdded}/-${f.linesRemoved}`)} | ${f.success ? labels.applied : labels.failed} | ${mdCell(f.summary)} |`
|
|
2188
|
+
);
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
lines.push("");
|
|
2192
|
+
if (data.validations.length > 0) {
|
|
2193
|
+
lines.push(`## ${labels.validations}`);
|
|
2194
|
+
lines.push("");
|
|
2195
|
+
lines.push(`| # | ${labels.command} | ${labels.exit} | ${labels.allowed} |`);
|
|
2196
|
+
lines.push("| --- | --- | --- | --- |");
|
|
2197
|
+
for (const v of data.validations) {
|
|
2198
|
+
lines.push(
|
|
2199
|
+
`| ${mdCell(v.round)} | \`${mdCell(v.command)}\` | ${v.exitCode === null ? "\u2014" : mdCell(v.exitCode)} | ${v.allowed ? labels.yes : labels.no} |`
|
|
2200
|
+
);
|
|
2201
|
+
}
|
|
2202
|
+
lines.push("");
|
|
2203
|
+
}
|
|
2204
|
+
const deEvents = asArray(get(data.defenseEvents, "events"));
|
|
2205
|
+
if (deEvents.length > 0) {
|
|
2206
|
+
lines.push(`## ${labels.defenseEvents} (${deEvents.length})`);
|
|
2207
|
+
lines.push("");
|
|
2208
|
+
lines.push(`| ${labels.severity} | ${labels.defensiveType} | ${labels.round} | ${labels.summary} |`);
|
|
2209
|
+
lines.push("| --- | --- | --- | --- |");
|
|
2210
|
+
for (const e of deEvents) {
|
|
2211
|
+
lines.push(
|
|
2212
|
+
`| ${mdCell(get(e, "severity"))} | ${mdCell(get(e, "type"))} | ${mdCell(get(e, "round"))} | ${mdCell(get(e, "description") || get(e, "outcome"))} |`
|
|
2213
|
+
);
|
|
2214
|
+
}
|
|
2215
|
+
lines.push("");
|
|
2216
|
+
}
|
|
2217
|
+
const entries = asArray(get(data.experienceBank, "entries"));
|
|
2218
|
+
if (entries.length > 0) {
|
|
2219
|
+
const totalHits = asNumber(get(data.experienceBank, "totalHits"));
|
|
2220
|
+
lines.push(`## ${labels.experienceBank} (${entries.length} ${labels.entries}, ${totalHits} ${labels.hits})`);
|
|
2221
|
+
lines.push("");
|
|
2222
|
+
for (const e of entries) {
|
|
2223
|
+
lines.push(
|
|
2224
|
+
`- **[${mdCell(get(e, "id"))}]** ${mdCell(get(e, "pattern"))} \u2014 ${mdCell(get(e, "description"))} (${labels.hits}: ${mdCell(get(e, "hitCount"))})`
|
|
2225
|
+
);
|
|
2226
|
+
}
|
|
2227
|
+
lines.push("");
|
|
2228
|
+
}
|
|
2229
|
+
return lines.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
|
|
2230
|
+
}
|
|
2231
|
+
function renderRunReportSarif(manifest, extra, opts) {
|
|
2232
|
+
const data = buildRunReportData(manifest, extra);
|
|
2233
|
+
const o = opts ?? {};
|
|
2234
|
+
const rules = [];
|
|
2235
|
+
const seenRules = /* @__PURE__ */ new Set();
|
|
2236
|
+
for (const f of data.findings) {
|
|
2237
|
+
const id = f.dimension || "review";
|
|
2238
|
+
if (seenRules.has(id)) continue;
|
|
2239
|
+
seenRules.add(id);
|
|
2240
|
+
rules.push({
|
|
2241
|
+
id,
|
|
2242
|
+
name: id,
|
|
2243
|
+
shortDescription: { text: `${id} review dimension` },
|
|
2244
|
+
defaultConfiguration: { level: "warning" }
|
|
2245
|
+
});
|
|
2246
|
+
}
|
|
2247
|
+
const results = data.findings.map((f) => {
|
|
2248
|
+
const result = {
|
|
2249
|
+
ruleId: f.dimension || "review",
|
|
2250
|
+
level: sarifLevel(f.severity),
|
|
2251
|
+
message: { text: f.summary || `${f.severity} finding` },
|
|
2252
|
+
properties: {
|
|
2253
|
+
severity: f.severity,
|
|
2254
|
+
atomic: f.isAtomic,
|
|
2255
|
+
acknowledged: f.acknowledged
|
|
2256
|
+
}
|
|
2257
|
+
};
|
|
2258
|
+
const uri = sarifUri(f.file);
|
|
2259
|
+
if (uri) {
|
|
2260
|
+
result.locations = [
|
|
2261
|
+
{
|
|
2262
|
+
physicalLocation: {
|
|
2263
|
+
artifactLocation: { uri },
|
|
2264
|
+
...f.line > 0 ? { region: { startLine: f.line } } : {}
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
];
|
|
2268
|
+
}
|
|
2269
|
+
if (f.suggestedFix) result.fixes = [{ description: { text: f.suggestedFix } }];
|
|
2270
|
+
return result;
|
|
2271
|
+
});
|
|
2272
|
+
const failedValidations = data.validations.filter((v) => v.exitCode !== 0).length;
|
|
2273
|
+
return {
|
|
2274
|
+
$schema: "https://json.schemastore.org/sarif-2.1.0.json",
|
|
2275
|
+
version: "2.1.0",
|
|
2276
|
+
runs: [
|
|
2277
|
+
{
|
|
2278
|
+
tool: {
|
|
2279
|
+
driver: {
|
|
2280
|
+
name: "iterate-plugin",
|
|
2281
|
+
informationUri: o.informationUri && o.informationUri ? o.informationUri : "https://github.com/jingzhao-l/iterate-plugin",
|
|
2282
|
+
...o.toolVersion ? { version: o.toolVersion } : {},
|
|
2283
|
+
rules
|
|
2284
|
+
}
|
|
2285
|
+
},
|
|
2286
|
+
invocations: [
|
|
2287
|
+
{
|
|
2288
|
+
executionSuccessful: failedValidations === 0 && data.qualityGate?.overallStatus !== "fail",
|
|
2289
|
+
...data.updatedAt ? { startTimeUtc: data.updatedAt } : {},
|
|
2290
|
+
properties: {
|
|
2291
|
+
mode: data.mode,
|
|
2292
|
+
taskMode: data.taskMode,
|
|
2293
|
+
round: data.round,
|
|
2294
|
+
maxRounds: data.maxRounds,
|
|
2295
|
+
stoppedReason: data.stoppedReason,
|
|
2296
|
+
qualityStatus: data.qualityGate ? data.qualityGate.overallStatus ?? null : null,
|
|
2297
|
+
convergence: data.convergence,
|
|
2298
|
+
findingsBySeverity: data.severityCounts
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
],
|
|
2302
|
+
results
|
|
2303
|
+
}
|
|
2304
|
+
]
|
|
2305
|
+
};
|
|
2306
|
+
}
|
|
2307
|
+
function runReportFilename(format, opts) {
|
|
2308
|
+
const ext = format === "sarif" ? "sarif" : "md";
|
|
2309
|
+
const stamp = opts?.now && opts.now ? opts.now.replace(/[:.]/g, "-") : (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2310
|
+
return `iterate-report-${stamp}.${ext}`;
|
|
2311
|
+
}
|
|
2312
|
+
|
|
1900
2313
|
// src/client/i18n.ts
|
|
1901
2314
|
var LANG_STORAGE_KEY = "iterate-ui-lang";
|
|
1902
2315
|
var LANG_NAMES = {
|
|
@@ -2060,6 +2473,10 @@ var EN = {
|
|
|
2060
2473
|
"\u5C06\u89C2\u6D4B\u53F0\u5168\u90E8\u6570\u636E\u5BFC\u51FA\u4E3A JSON\uFF08\u4F18\u5148\u4E0B\u8F7D\uFF0C\u5931\u8D25\u5219\u590D\u5236\uFF09": "Export every observatory panel as JSON (downloads first, falls back to copying)",
|
|
2061
2474
|
\u5DF2\u5BFC\u51FA: "Exported",
|
|
2062
2475
|
"\u5BFC\u51FA JSON": "Export JSON",
|
|
2476
|
+
"\u5BFC\u51FA\u62A5\u544A": "Export report",
|
|
2477
|
+
"\u5BFC\u51FA\u53EF\u7C98\u8D34\u8FDB PR \u7684 Markdown \u8FD0\u884C\u62A5\u544A\uFF08\u4F18\u5148\u4E0B\u8F7D\uFF0C\u5931\u8D25\u5219\u590D\u5236\uFF09": "Export a PR-pasteable Markdown run report (download first, copy on failure)",
|
|
2478
|
+
"\u5BFC\u51FA SARIF": "Export SARIF",
|
|
2479
|
+
"\u5BFC\u51FA SARIF 2.1.0\uFF08\u4F9B CI / GitHub Code Scanning \u6D88\u8D39\uFF09": "Export SARIF 2.1.0 for CI / GitHub Code Scanning",
|
|
2063
2480
|
"\u89C2\u6D4B\u53F0\u9875\u7B7E\uFF08\u5FEB\u6377\u952E 1-9 / 0 / -\uFF09": "Observatory tabs (keys 1-9 / 0 / -)",
|
|
2064
2481
|
"{a0}\uFF08\u6309 {a1} \u5207\u6362\uFF09": "{a0} (press {a1})",
|
|
2065
2482
|
"{a0}\uFF08\u6309 - \u5207\u6362\uFF09": "{a0} (press -)",
|
|
@@ -2841,9 +3258,17 @@ function ConvergenceDashboard(props) {
|
|
|
2841
3258
|
noteSession(session);
|
|
2842
3259
|
}, [session]);
|
|
2843
3260
|
const report = latestReport(session);
|
|
3261
|
+
const pulseSeenRef = React.useRef(null);
|
|
2844
3262
|
React.useEffect(() => {
|
|
2845
3263
|
if (!report) return;
|
|
2846
3264
|
const cur = getCurrentRound(report);
|
|
3265
|
+
const key = hashReport(report) + ":" + String(cur);
|
|
3266
|
+
if (pulseSeenRef.current === null) {
|
|
3267
|
+
pulseSeenRef.current = key;
|
|
3268
|
+
return;
|
|
3269
|
+
}
|
|
3270
|
+
if (pulseSeenRef.current === key) return;
|
|
3271
|
+
pulseSeenRef.current = key;
|
|
2847
3272
|
const conv = report.convergence;
|
|
2848
3273
|
emitRoundPulse(cur, conv?.converged === true);
|
|
2849
3274
|
setPulseKey((k) => k + 1);
|
|
@@ -3146,6 +3571,7 @@ function TriagePanel(props) {
|
|
|
3146
3571
|
setVerdicts((prev) => setAllVerdicts(prev, "keep"));
|
|
3147
3572
|
setSelectAll(false);
|
|
3148
3573
|
};
|
|
3574
|
+
const rootRef = React.useRef(null);
|
|
3149
3575
|
React.useEffect(() => {
|
|
3150
3576
|
const doc = typeof document !== "undefined" ? document : null;
|
|
3151
3577
|
if (!doc) return;
|
|
@@ -3154,7 +3580,7 @@ function TriagePanel(props) {
|
|
|
3154
3580
|
const t2 = ev.target;
|
|
3155
3581
|
if (t2 && (t2.tagName === "INPUT" || t2.tagName === "TEXTAREA" || t2.tagName === "SELECT")) return;
|
|
3156
3582
|
if (t2 && typeof t2.isContentEditable === "boolean" && t2.isContentEditable) return;
|
|
3157
|
-
const rootEl =
|
|
3583
|
+
const rootEl = rootRef.current;
|
|
3158
3584
|
if (!rootEl) return;
|
|
3159
3585
|
const activeEl = doc.activeElement;
|
|
3160
3586
|
if (!activeEl) return;
|
|
@@ -3292,7 +3718,7 @@ function TriagePanel(props) {
|
|
|
3292
3718
|
});
|
|
3293
3719
|
return React.createElement(
|
|
3294
3720
|
"div",
|
|
3295
|
-
{ "data-iterate-root": "", "data-iterate": "triage", className: "iterate-triage" },
|
|
3721
|
+
{ ref: rootRef, "data-iterate-root": "", "data-iterate": "triage", className: "iterate-triage" },
|
|
3296
3722
|
React.createElement(
|
|
3297
3723
|
"div",
|
|
3298
3724
|
{ className: "iterate-triage-head" },
|
|
@@ -4059,6 +4485,29 @@ function ObservatoryPanel(props) {
|
|
|
4059
4485
|
flash();
|
|
4060
4486
|
});
|
|
4061
4487
|
};
|
|
4488
|
+
const exportReport = (format) => {
|
|
4489
|
+
if (!manifest) return;
|
|
4490
|
+
const extra = { qualityGate, experienceBank, defenseEvents };
|
|
4491
|
+
const content = format === "sarif" ? JSON.stringify(renderRunReportSarif(manifest, extra), null, 2) : renderRunReportMarkdown(manifest, extra, { lang: getLang() });
|
|
4492
|
+
const filename = runReportFilename(format);
|
|
4493
|
+
const key = `report-${format}`;
|
|
4494
|
+
const flash = () => {
|
|
4495
|
+
setCopiedKey(key);
|
|
4496
|
+
if (copyTimer.current) clearTimeout(copyTimer.current);
|
|
4497
|
+
copyTimer.current = setTimeout(() => setCopiedKey((cur) => cur === key ? null : cur), 1600);
|
|
4498
|
+
};
|
|
4499
|
+
if (downloadTextFile(filename, content)) {
|
|
4500
|
+
flash();
|
|
4501
|
+
return;
|
|
4502
|
+
}
|
|
4503
|
+
copyText(content).then((ok) => {
|
|
4504
|
+
if (!ok) {
|
|
4505
|
+
setCopyFailText(content);
|
|
4506
|
+
return;
|
|
4507
|
+
}
|
|
4508
|
+
flash();
|
|
4509
|
+
});
|
|
4510
|
+
};
|
|
4062
4511
|
const setAllThreads = (mode) => {
|
|
4063
4512
|
setThreadMode(mode);
|
|
4064
4513
|
setExpandedThreads(/* @__PURE__ */ new Set());
|
|
@@ -4196,7 +4645,7 @@ function ObservatoryPanel(props) {
|
|
|
4196
4645
|
}
|
|
4197
4646
|
const points = conv.map((n, i) => ({ round: i + 1, count: n }));
|
|
4198
4647
|
const report = latestReport(session);
|
|
4199
|
-
const comparison = buildRoundComparison(report, manifest.validations || []);
|
|
4648
|
+
const comparison = buildRoundComparison(report, manifest.validations || [], manifest.fixes || []);
|
|
4200
4649
|
const comparisonTable = comparison.length === 0 ? null : React.createElement(
|
|
4201
4650
|
"table",
|
|
4202
4651
|
{ className: "iterate-obs-table" },
|
|
@@ -5254,6 +5703,21 @@ function ObservatoryPanel(props) {
|
|
|
5254
5703
|
onClick: exportObservatory,
|
|
5255
5704
|
title: t("\u5C06\u89C2\u6D4B\u53F0\u5168\u90E8\u6570\u636E\u5BFC\u51FA\u4E3A JSON\uFF08\u4F18\u5148\u4E0B\u8F7D\uFF0C\u5931\u8D25\u5219\u590D\u5236\uFF09")
|
|
5256
5705
|
}, copiedKey === "export" ? t("\u5DF2\u5BFC\u51FA") : t("\u5BFC\u51FA JSON")),
|
|
5706
|
+
// v3.9: portable report export — Markdown for a PR body, SARIF for
|
|
5707
|
+
// CI / GitHub Code Scanning. Rendered from the same data model as
|
|
5708
|
+
// the `iterate_report` tool.
|
|
5709
|
+
React.createElement("button", {
|
|
5710
|
+
className: "iterate-btn",
|
|
5711
|
+
"data-copied": copiedKey === "report-markdown" ? "" : void 0,
|
|
5712
|
+
onClick: () => exportReport("markdown"),
|
|
5713
|
+
title: t("\u5BFC\u51FA\u53EF\u7C98\u8D34\u8FDB PR \u7684 Markdown \u8FD0\u884C\u62A5\u544A\uFF08\u4F18\u5148\u4E0B\u8F7D\uFF0C\u5931\u8D25\u5219\u590D\u5236\uFF09")
|
|
5714
|
+
}, copiedKey === "report-markdown" ? t("\u5DF2\u5BFC\u51FA") : t("\u5BFC\u51FA\u62A5\u544A")),
|
|
5715
|
+
React.createElement("button", {
|
|
5716
|
+
className: "iterate-btn",
|
|
5717
|
+
"data-copied": copiedKey === "report-sarif" ? "" : void 0,
|
|
5718
|
+
onClick: () => exportReport("sarif"),
|
|
5719
|
+
title: t("\u5BFC\u51FA SARIF 2.1.0\uFF08\u4F9B CI / GitHub Code Scanning \u6D88\u8D39\uFF09")
|
|
5720
|
+
}, copiedKey === "report-sarif" ? t("\u5DF2\u5BFC\u51FA") : t("\u5BFC\u51FA SARIF")),
|
|
5257
5721
|
// Gap #4: `.iterate/` artifacts are unreachable from the browser;
|
|
5258
5722
|
// this copies the one instruction that makes the model echo them
|
|
5259
5723
|
// into the session stream (which is what every panel scans).
|