iterate-plugin 3.11.0 → 3.12.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 +8 -7
- package/README.zh-CN.md +9 -8
- package/dist/index.js +8 -5
- package/dist/tools/preflight.js +239 -0
- package/lib/client.js +321 -5
- package/lib/parse.js +148 -2
- package/package.json +1 -1
- package/src/client/i18n.ts +40 -1
- package/src/client/index.ts +201 -0
- package/src/index.ts +8 -5
- package/src/tools/preflight.ts +289 -0
package/lib/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* iterate-plugin client source-digest:
|
|
1
|
+
/* iterate-plugin client source-digest: 408ff45e22a5041e34cdeb7ccd786225658f6c1fe52a7bb5233fc35450546683 */
|
|
2
2
|
window.__ModuleLoader__.load({ id: "iterate-plugin", factory: (require) => {
|
|
3
3
|
var module = { exports: {} };
|
|
4
4
|
var exports = module.exports;
|
|
@@ -854,6 +854,62 @@ function scanSessionForDefenseEvents(session) {
|
|
|
854
854
|
const loose = findFirstInObject(session, isDefenseEventsResult);
|
|
855
855
|
return loose ? normalizeDefenseEventsResult(loose) : null;
|
|
856
856
|
}
|
|
857
|
+
function isPreflightReport(obj) {
|
|
858
|
+
if (!obj || typeof obj !== "object") return false;
|
|
859
|
+
const o = (
|
|
860
|
+
/** @type {Record<string, unknown>} */
|
|
861
|
+
obj
|
|
862
|
+
);
|
|
863
|
+
const ready = safeGet(o, "ready");
|
|
864
|
+
const checks = safeGet(o, "checks");
|
|
865
|
+
const blocking = safeGet(o, "blocking");
|
|
866
|
+
const warnings = safeGet(o, "warnings");
|
|
867
|
+
return typeof ready === "boolean" && Array.isArray(checks) && (typeof blocking === "number" || typeof warnings === "number");
|
|
868
|
+
}
|
|
869
|
+
function findPreflightReportInObject(obj) {
|
|
870
|
+
return findFirstInObject(obj, (o) => isPreflightReport(o));
|
|
871
|
+
}
|
|
872
|
+
function normalizePreflightReport(raw) {
|
|
873
|
+
const src = raw && typeof raw === "object" ? (
|
|
874
|
+
/** @type {Record<string, unknown>} */
|
|
875
|
+
raw
|
|
876
|
+
) : {};
|
|
877
|
+
const asNum = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
878
|
+
const asStr = (v) => typeof v === "string" ? v : "";
|
|
879
|
+
const checks = Array.isArray(safeGet(src, "checks")) ? safeGet(src, "checks") : [];
|
|
880
|
+
return {
|
|
881
|
+
ok: safeGet(src, "ok") !== false,
|
|
882
|
+
ready: safeGet(src, "ready") === true,
|
|
883
|
+
blocking: asNum(safeGet(src, "blocking")),
|
|
884
|
+
warnings: asNum(safeGet(src, "warnings")),
|
|
885
|
+
error: asStr(safeGet(src, "error")) || null,
|
|
886
|
+
checks: (
|
|
887
|
+
/** @type {unknown[]} */
|
|
888
|
+
checks.map((c) => {
|
|
889
|
+
const rec = (
|
|
890
|
+
/** @type {Record<string, unknown>} */
|
|
891
|
+
c && typeof c === "object" ? c : {}
|
|
892
|
+
);
|
|
893
|
+
const status = safeGet(rec, "status");
|
|
894
|
+
const detail = safeGet(rec, "detail");
|
|
895
|
+
return {
|
|
896
|
+
id: asStr(safeGet(rec, "id")),
|
|
897
|
+
status: status === "pass" || status === "warn" || status === "fail" ? status : "warn",
|
|
898
|
+
detail: detail && typeof detail === "object" ? detail : {}
|
|
899
|
+
};
|
|
900
|
+
})
|
|
901
|
+
)
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
function scanSessionForPreflight(session) {
|
|
905
|
+
const node = latestToolResultNode(session, "iterate_preflight", findPreflightReportInObject);
|
|
906
|
+
if (node !== null) {
|
|
907
|
+
const found = findPreflightReportInObject(node);
|
|
908
|
+
if (found) return normalizePreflightReport(found);
|
|
909
|
+
}
|
|
910
|
+
const loose = findFirstInObject(session, isPreflightReport);
|
|
911
|
+
return loose ? normalizePreflightReport(loose) : null;
|
|
912
|
+
}
|
|
857
913
|
function isRunSummary(obj) {
|
|
858
914
|
if (!obj || typeof obj !== "object") return false;
|
|
859
915
|
const o = (
|
|
@@ -1280,6 +1336,13 @@ function buildQualityGateClearInstruction() {
|
|
|
1280
1336
|
${JSON.stringify({ operation: "clear" }, null, 2)}
|
|
1281
1337
|
\`\`\``;
|
|
1282
1338
|
}
|
|
1339
|
+
function buildPreflightInstruction() {
|
|
1340
|
+
return `\u8BF7\u8C03\u7528 \`iterate_preflight\` \u8FD0\u884C\u5F00\u8DD1\u524D\u9884\u68C0\uFF08\u53EA\u8BFB\uFF1A\u6821\u9A8C validation.commands\u3001git\u3001\u810F\u5DE5\u4F5C\u533A\u3001\u9648\u65E7\u8D28\u91CF\u95E8\u7981\u4E0E\u4E2D\u65AD\u65AD\u70B9\uFF09\uFF1A
|
|
1341
|
+
|
|
1342
|
+
\`\`\`json
|
|
1343
|
+
${JSON.stringify({}, null, 2)}
|
|
1344
|
+
\`\`\``;
|
|
1345
|
+
}
|
|
1283
1346
|
function buildRollbackInstruction(id) {
|
|
1284
1347
|
return `\u8BF7\u8C03\u7528 \`iterate_rollback\` \u56DE\u6EDA\u4EE5\u4E0B\u4FEE\u590D\uFF1A
|
|
1285
1348
|
|
|
@@ -1555,13 +1618,35 @@ function buildCompletionSummary(report, lang = "zh") {
|
|
|
1555
1618
|
const stats = severityStats(report);
|
|
1556
1619
|
const converged = conv.converged === true;
|
|
1557
1620
|
const totalFindings = stats.critical + stats.high + stats.medium + stats.low;
|
|
1621
|
+
const stopped = typeof conv.stoppedReason === "string" ? conv.stoppedReason : typeof report.stoppedReason === "string" ? report.stoppedReason : "";
|
|
1622
|
+
const reason = stopped ? completionReason(stopped, lang, total) : converged ? lang === "en" ? "converged" : "\u5DF2\u6536\u655B" : lang === "en" ? `reached the round limit ${total}` : `\u5DF2\u8FBE\u6700\u5927\u8F6E\u6570 ${total}`;
|
|
1558
1623
|
if (lang === "en") {
|
|
1559
|
-
|
|
1560
|
-
return `iterate review complete \xB7 ${rounds}/${total} rounds \xB7 ${totalFindings} findings \xB7 ${reason2}`;
|
|
1624
|
+
return `iterate review complete \xB7 ${rounds}/${total} rounds \xB7 ${totalFindings} findings \xB7 ${reason}`;
|
|
1561
1625
|
}
|
|
1562
|
-
const reason = converged ? "\u5DF2\u6536\u655B" : `\u5DF2\u8FBE\u6700\u5927\u8F6E\u6570 ${total}`;
|
|
1563
1626
|
return `iterate \u8BC4\u5BA1\u5B8C\u6210 \xB7 ${rounds}/${total} \u8F6E \xB7 ${totalFindings} \u9879\u53D1\u73B0 \xB7 ${reason}`;
|
|
1564
1627
|
}
|
|
1628
|
+
function completionReason(reason, lang, total) {
|
|
1629
|
+
const zh = {
|
|
1630
|
+
converged: "\u5DF2\u6536\u655B",
|
|
1631
|
+
max_rounds_reached: `\u5DF2\u8FBE\u6700\u5927\u8F6E\u6570 ${total}`,
|
|
1632
|
+
aborted_by_validation: "\u9A8C\u8BC1\u5931\u8D25\u540E\u56DE\u6EDA",
|
|
1633
|
+
aborted_by_config: "\u9A8C\u8BC1\u547D\u4EE4\u672A\u5728\u767D\u540D\u5355",
|
|
1634
|
+
inconclusive: "\u8F6E\u6B21\u7ED3\u8BBA\u4E0D\u660E",
|
|
1635
|
+
schema_invalid: "\u5BA1\u67E5\u8F93\u51FA schema \u6821\u9A8C\u5931\u8D25",
|
|
1636
|
+
no_usable_reviewer_output: "\u65E0\u53EF\u7528\u5BA1\u67E5\u8F93\u51FA"
|
|
1637
|
+
};
|
|
1638
|
+
const en = {
|
|
1639
|
+
converged: "converged",
|
|
1640
|
+
max_rounds_reached: `reached the round limit ${total}`,
|
|
1641
|
+
aborted_by_validation: "rolled back after a validation failure",
|
|
1642
|
+
aborted_by_config: "validation command not allow-listed",
|
|
1643
|
+
inconclusive: "inconclusive rounds",
|
|
1644
|
+
schema_invalid: "review output failed schema validation",
|
|
1645
|
+
no_usable_reviewer_output: "no usable review output"
|
|
1646
|
+
};
|
|
1647
|
+
const table = lang === "en" ? en : zh;
|
|
1648
|
+
return Object.hasOwn(table, reason) ? table[reason] : reason;
|
|
1649
|
+
}
|
|
1565
1650
|
var CONFIG_EDIT_FIELDS = [
|
|
1566
1651
|
{
|
|
1567
1652
|
key: "goal",
|
|
@@ -2658,6 +2743,38 @@ var EN = {
|
|
|
2658
2743
|
\u6E05\u9664\u7C7B\u578B\u7B5B\u9009: "Clear type filter",
|
|
2659
2744
|
"\u9632\u7EBF\uFF1A{a0}": "Defense: {a0}",
|
|
2660
2745
|
\u6CA1\u6709\u5339\u914D\u8BE5\u7C7B\u578B\u7684\u9632\u5FA1\u4E8B\u4EF6: "No defense events of this type",
|
|
2746
|
+
// ── F11 preflight ────────────────────────────────────────────────────────
|
|
2747
|
+
\u5F00\u8DD1\u9884\u68C0: "Preflight",
|
|
2748
|
+
\u5F85\u9884\u68C0: "Not run yet",
|
|
2749
|
+
\u672A\u5C31\u7EEA: "Not ready",
|
|
2750
|
+
"\u590D\u5236 iterate_preflight \u9884\u68C0\u6307\u4EE4\uFF08\u542B JSON \u53C2\u6570\uFF09": "Copy the iterate_preflight readiness instruction (JSON arguments included)",
|
|
2751
|
+
\u8FD0\u884C\u9884\u68C0: "Run preflight",
|
|
2752
|
+
\u5F00\u8DD1\u9884\u68C0\u6E05\u5355: "Preflight checklist",
|
|
2753
|
+
\u9884\u68C0\u7ED3\u679C: "Preflight result",
|
|
2754
|
+
"\u5F00\u8DD1\u524D\u5148\u9884\u68C0\uFF1A\u786E\u8BA4\u9A8C\u8BC1\u547D\u4EE4\u3001Git\u3001\u5DE5\u4F5C\u533A\u3001\u9648\u65E7\u95E8\u7981\u4E0E\u4E2D\u65AD\u65AD\u70B9\u90FD\u5DF2\u5C31\u7EEA\uFF0C\u907F\u514D\u5F00\u8DD1\u540E\u624D\u53D1\u73B0\u73AF\u5883\u95EE\u9898\u3002": "Run preflight first: confirm validation commands, git, the working tree, a stale gate and an interrupted checkpoint are ready, so you do not discover an environment problem mid-run.",
|
|
2755
|
+
\u590D\u5236\u9884\u68C0\u6307\u4EE4: "Copy preflight instruction",
|
|
2756
|
+
\u9A8C\u8BC1\u547D\u4EE4: "Validation commands",
|
|
2757
|
+
"\u5DF2\u914D\u7F6E {a0} \u6761\u9A8C\u8BC1\u547D\u4EE4": "{a0} validation command(s) configured",
|
|
2758
|
+
"\u672A\u914D\u7F6E validation.commands \u2014\u2014 \u672C\u8F6E\u65E0\u6CD5\u8BC1\u660E\u4FEE\u590D\uFF0C\u5C5E\u963B\u65AD\u9879": "validation.commands is not configured \u2014 the run cannot prove fixes (blocking)",
|
|
2759
|
+
"Git \u4ED3\u5E93": "Git repository",
|
|
2760
|
+
"\u5DF2\u68C0\u6D4B\u5230 Git \u4ED3\u5E93": "Git repository detected",
|
|
2761
|
+
"\u4E0D\u662F Git \u4ED3\u5E93 \u2014\u2014 changed-only \u8303\u56F4\u4E0E iterate_diff \u9700\u8981\u5B83": "Not a git repository \u2014 changed-only scope and iterate_diff need one",
|
|
2762
|
+
\u5DE5\u4F5C\u533A: "Working tree",
|
|
2763
|
+
"\u672A\u63D0\u4EA4\u6539\u52A8\uFF1A\u672A\u77E5": "Uncommitted changes: unknown",
|
|
2764
|
+
"{a0} \u4E2A\u672A\u63D0\u4EA4/\u672A\u8DDF\u8E2A\u6587\u4EF6 \u2014\u2014 \u5EFA\u8BAE\u5148 commit \u6216 stash": "{a0} uncommitted/untracked file(s) \u2014 commit or stash first",
|
|
2765
|
+
\u5DE5\u4F5C\u533A\u5E72\u51C0: "Working tree clean",
|
|
2766
|
+
\u9648\u65E7\u95E8\u7981: "Stale gate",
|
|
2767
|
+
"\u78C1\u76D8\u4E0A\u6B8B\u7559 FAIL \u8D28\u91CF\u95E8\u7981 \u2014\u2014 \u5F00\u65B0\u8F6E\u524D\u5148\u6E05\u9664": "A stale FAIL quality gate is persisted \u2014 clear it before a fresh run",
|
|
2768
|
+
"\u8D28\u91CF\u95E8\u7981\uFF1A{a0}": "Quality gate: {a0}",
|
|
2769
|
+
\u65E0\u6301\u4E45\u5316\u8D28\u91CF\u95E8\u7981: "No persisted quality gate",
|
|
2770
|
+
\u4E2D\u65AD\u65AD\u70B9: "Interrupted checkpoint",
|
|
2771
|
+
"\u5B58\u5728\u4E2D\u65AD\u65AD\u70B9\uFF08\u5DF2\u6062\u590D {a0} \u6B21\uFF09\u2014\u2014 \u6062\u590D\u6216\u6E05\u9664\u540E\u518D\u5F00\u8DD1": "An interrupted checkpoint is waiting (resumed {a0}x) \u2014 resume or clear it first",
|
|
2772
|
+
\u65E0\u4E2D\u65AD\u65AD\u70B9: "No interrupted checkpoint",
|
|
2773
|
+
\u914D\u7F6E\u9A8C\u8BC1\u547D\u4EE4: "Configure validation",
|
|
2774
|
+
\u6062\u590D\u65AD\u70B9: "Resume checkpoint",
|
|
2775
|
+
"\u963B\u65AD {a0} \xB7 \u8B66\u544A {a1}": "Blocking {a0} \xB7 warnings {a1}",
|
|
2776
|
+
"\u9884\u68C0\u5931\u8D25\uFF1A{a0}": "Preflight failed: {a0}",
|
|
2777
|
+
"\u9884\u68C0\u53EA\u8BFB\uFF0C\u4E0D\u4F1A\u4FEE\u6539\u914D\u7F6E\u6216\u6587\u4EF6\u3002": "Preflight is read-only; it never modifies config or files.",
|
|
2661
2778
|
// ── copy feedback ────────────────────────────────────────────────────────
|
|
2662
2779
|
\u5DF2\u590D\u5236: "Copied",
|
|
2663
2780
|
\u590D\u5236\u5931\u8D25: "Copy failed"
|
|
@@ -2907,6 +3024,7 @@ var ITERATE_CSS = `
|
|
|
2907
3024
|
.iterate-gate[data-status="pass"] { color: var(--dsw-alias-state-success-primary); background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 12%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-state-success-primary) 28%, transparent); }
|
|
2908
3025
|
.iterate-gate[data-status="fail"] { color: var(--dsw-alias-state-error-primary); background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 28%, transparent); }
|
|
2909
3026
|
.iterate-gate[data-status="pending"] { color: var(--dsw-alias-label-secondary); background: color-mix(in srgb, var(--dsw-alias-label-secondary) 12%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-label-secondary) 28%, transparent); }
|
|
3027
|
+
.iterate-gate[data-status="warn"] { color: var(--dsw-alias-state-warn-primary); background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 12%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary) 28%, transparent); }
|
|
2910
3028
|
|
|
2911
3029
|
/* v3.0: Experience bank hit highlight */
|
|
2912
3030
|
.iterate-exp-hit { display: inline-flex; align-items: center; gap: 6px; padding: 2px 8px; border-radius: 6px; background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 12%, transparent); border: 1px solid color-mix(in srgb, var(--dsw-alias-state-success-primary) 28%, transparent); font-size: 11px; color: var(--dsw-alias-state-success-primary); }
|
|
@@ -4343,7 +4461,9 @@ var OBS_TABS = [
|
|
|
4343
4461
|
// v3.0: Quality Command Center tabs
|
|
4344
4462
|
{ key: "f8", label: { zh: "\u8D28\u91CF\u95E8\u7981", en: "Quality gate" }, hotkey: "8" },
|
|
4345
4463
|
{ key: "f9", label: { zh: "\u7ECF\u9A8C\u94F6\u884C", en: "Experience bank" }, hotkey: "9" },
|
|
4346
|
-
{ key: "f10", label: { zh: "\u9632\u5FA1\u4E8B\u4EF6", en: "Defense events" }, hotkey: "0" }
|
|
4464
|
+
{ key: "f10", label: { zh: "\u9632\u5FA1\u4E8B\u4EF6", en: "Defense events" }, hotkey: "0" },
|
|
4465
|
+
// v3.12: pre-run readiness checklist (validation/git/dirty-tree/stale-gate/checkpoint)
|
|
4466
|
+
{ key: "f11", label: { zh: "\u5F00\u8DD1\u9884\u68C0", en: "Preflight" }, hotkey: "" }
|
|
4347
4467
|
];
|
|
4348
4468
|
var OBS_HOTKEY_TABS = {
|
|
4349
4469
|
1: "f1",
|
|
@@ -4414,6 +4534,10 @@ function latestDefenseEvents(session) {
|
|
|
4414
4534
|
if (!session) return null;
|
|
4415
4535
|
return scanSessionForDefenseEvents(session);
|
|
4416
4536
|
}
|
|
4537
|
+
function latestPreflight(session) {
|
|
4538
|
+
if (!session) return null;
|
|
4539
|
+
return scanSessionForPreflight(session);
|
|
4540
|
+
}
|
|
4417
4541
|
function buildObsFixInstruction(f) {
|
|
4418
4542
|
return buildFixInstruction(f);
|
|
4419
4543
|
}
|
|
@@ -4426,6 +4550,7 @@ function ObservatoryPanel(props) {
|
|
|
4426
4550
|
const qualityGate = latestQualityGate(session);
|
|
4427
4551
|
const experienceBank = latestExperienceBank(session);
|
|
4428
4552
|
const defenseEvents = latestDefenseEvents(session);
|
|
4553
|
+
const preflight = latestPreflight(session);
|
|
4429
4554
|
const [open, setOpen] = React.useState(false);
|
|
4430
4555
|
const [tab, setTab] = React.useState("live");
|
|
4431
4556
|
const [expandedThreads, setExpandedThreads] = React.useState(/* @__PURE__ */ new Set());
|
|
@@ -5577,6 +5702,195 @@ function ObservatoryPanel(props) {
|
|
|
5577
5702
|
eventRows.length > 0 ? React.createElement("div", {}, ...eventRows) : React.createElement("div", { className: "iterate-obs-empty" }, t("\u6CA1\u6709\u5339\u914D\u8BE5\u7C7B\u578B\u7684\u9632\u5FA1\u4E8B\u4EF6"))
|
|
5578
5703
|
);
|
|
5579
5704
|
};
|
|
5705
|
+
const renderPreflight = () => {
|
|
5706
|
+
const preflightInstruction = buildPreflightInstruction();
|
|
5707
|
+
const report = preflight;
|
|
5708
|
+
const hasReport = report !== null && Array.isArray(report.checks);
|
|
5709
|
+
const ready = report !== null && report.ready === true;
|
|
5710
|
+
const blocking = Number(report && report.blocking != null ? report.blocking : 0);
|
|
5711
|
+
const warnings = Number(report && report.warnings != null ? report.warnings : 0);
|
|
5712
|
+
const statusLabel = !hasReport ? t("\u5F85\u9884\u68C0") : ready ? t("\u5C31\u7EEA") : t("\u672A\u5C31\u7EEA");
|
|
5713
|
+
const statusTone = !hasReport ? "pending" : ready ? "pass" : "fail";
|
|
5714
|
+
const headerBar = React.createElement(
|
|
5715
|
+
"div",
|
|
5716
|
+
{ className: "iterate-obs-bar", style: { marginBottom: 8 } },
|
|
5717
|
+
React.createElement("b", {}, t("\u5F00\u8DD1\u9884\u68C0")),
|
|
5718
|
+
React.createElement("span", { className: "iterate-gate", "data-status": statusTone }, statusLabel),
|
|
5719
|
+
React.createElement("button", {
|
|
5720
|
+
className: "iterate-btn",
|
|
5721
|
+
"data-primary": "",
|
|
5722
|
+
"data-copied": copiedKey === "preflight" ? "" : void 0,
|
|
5723
|
+
onClick: () => copyInstruction("preflight", preflightInstruction),
|
|
5724
|
+
title: t("\u590D\u5236 iterate_preflight \u9884\u68C0\u6307\u4EE4\uFF08\u542B JSON \u53C2\u6570\uFF09")
|
|
5725
|
+
}, copiedKey === "preflight" ? t("\u5DF2\u590D\u5236") : t("\u8FD0\u884C\u9884\u68C0"))
|
|
5726
|
+
);
|
|
5727
|
+
if (!hasReport) {
|
|
5728
|
+
return React.createElement(
|
|
5729
|
+
"div",
|
|
5730
|
+
{},
|
|
5731
|
+
headerBar,
|
|
5732
|
+
React.createElement(
|
|
5733
|
+
"div",
|
|
5734
|
+
{ className: "iterate-obs-block" },
|
|
5735
|
+
React.createElement(
|
|
5736
|
+
"div",
|
|
5737
|
+
{ className: "iterate-obs-block-head" },
|
|
5738
|
+
React.createElement("span", {}, t("\u5F00\u8DD1\u9884\u68C0\u6E05\u5355")),
|
|
5739
|
+
React.createElement("span", { className: "iterate-obs-badge" }, "v3.12")
|
|
5740
|
+
),
|
|
5741
|
+
React.createElement(
|
|
5742
|
+
"div",
|
|
5743
|
+
{ className: "iterate-obs-block-body" },
|
|
5744
|
+
React.createElement(
|
|
5745
|
+
"div",
|
|
5746
|
+
{ className: "iterate-obs-msg" },
|
|
5747
|
+
// Gap #4: separate "pulled and empty" from "never pulled".
|
|
5748
|
+
diskEmptyStateText(toolCalledInSession(session, "iterate_preflight"), t("\u9884\u68C0\u7ED3\u679C"), getLang())
|
|
5749
|
+
),
|
|
5750
|
+
React.createElement(
|
|
5751
|
+
"div",
|
|
5752
|
+
{ className: "iterate-obs-msg", style: { marginTop: 6 } },
|
|
5753
|
+
t("\u5F00\u8DD1\u524D\u5148\u9884\u68C0\uFF1A\u786E\u8BA4\u9A8C\u8BC1\u547D\u4EE4\u3001Git\u3001\u5DE5\u4F5C\u533A\u3001\u9648\u65E7\u95E8\u7981\u4E0E\u4E2D\u65AD\u65AD\u70B9\u90FD\u5DF2\u5C31\u7EEA\uFF0C\u907F\u514D\u5F00\u8DD1\u540E\u624D\u53D1\u73B0\u73AF\u5883\u95EE\u9898\u3002")
|
|
5754
|
+
),
|
|
5755
|
+
React.createElement(
|
|
5756
|
+
"div",
|
|
5757
|
+
{ className: "iterate-obs-bar", style: { marginTop: 8 } },
|
|
5758
|
+
React.createElement("button", {
|
|
5759
|
+
className: "iterate-btn",
|
|
5760
|
+
"data-copied": copiedKey === "preflight-instr" ? "" : void 0,
|
|
5761
|
+
onClick: () => copyInstruction("preflight-instr", preflightInstruction),
|
|
5762
|
+
title: t("\u590D\u5236\u9884\u68C0\u6307\u4EE4")
|
|
5763
|
+
}, copiedKey === "preflight-instr" ? t("\u5DF2\u590D\u5236") : t("\u590D\u5236\u9884\u68C0\u6307\u4EE4"))
|
|
5764
|
+
)
|
|
5765
|
+
)
|
|
5766
|
+
)
|
|
5767
|
+
);
|
|
5768
|
+
}
|
|
5769
|
+
if (report && report.ok === false) {
|
|
5770
|
+
return React.createElement(
|
|
5771
|
+
"div",
|
|
5772
|
+
{},
|
|
5773
|
+
headerBar,
|
|
5774
|
+
React.createElement(
|
|
5775
|
+
"div",
|
|
5776
|
+
{ className: "iterate-obs-block" },
|
|
5777
|
+
React.createElement(
|
|
5778
|
+
"div",
|
|
5779
|
+
{ className: "iterate-obs-block-head" },
|
|
5780
|
+
React.createElement("span", {}, t("\u5F00\u8DD1\u9884\u68C0\u6E05\u5355")),
|
|
5781
|
+
React.createElement("span", { className: "iterate-obs-badge" }, "v3.12")
|
|
5782
|
+
),
|
|
5783
|
+
React.createElement(
|
|
5784
|
+
"div",
|
|
5785
|
+
{ className: "iterate-obs-block-body" },
|
|
5786
|
+
React.createElement(
|
|
5787
|
+
"div",
|
|
5788
|
+
{ className: "iterate-obs-msg" },
|
|
5789
|
+
tf("\u9884\u68C0\u5931\u8D25\uFF1A{a0}", { a0: String(report.error || "") })
|
|
5790
|
+
)
|
|
5791
|
+
)
|
|
5792
|
+
)
|
|
5793
|
+
);
|
|
5794
|
+
}
|
|
5795
|
+
const CHECK_META = {
|
|
5796
|
+
validation: {
|
|
5797
|
+
label: t("\u9A8C\u8BC1\u547D\u4EE4"),
|
|
5798
|
+
detail: (d) => {
|
|
5799
|
+
const n = Number(d.commandCount ?? 0);
|
|
5800
|
+
return n > 0 ? tf("\u5DF2\u914D\u7F6E {a0} \u6761\u9A8C\u8BC1\u547D\u4EE4", { a0: n }) : t("\u672A\u914D\u7F6E validation.commands \u2014\u2014 \u672C\u8F6E\u65E0\u6CD5\u8BC1\u660E\u4FEE\u590D\uFF0C\u5C5E\u963B\u65AD\u9879");
|
|
5801
|
+
}
|
|
5802
|
+
},
|
|
5803
|
+
git: {
|
|
5804
|
+
label: t("Git \u4ED3\u5E93"),
|
|
5805
|
+
detail: (d) => d.isRepo === true ? t("\u5DF2\u68C0\u6D4B\u5230 Git \u4ED3\u5E93") : t("\u4E0D\u662F Git \u4ED3\u5E93 \u2014\u2014 changed-only \u8303\u56F4\u4E0E iterate_diff \u9700\u8981\u5B83")
|
|
5806
|
+
},
|
|
5807
|
+
"dirty-tree": {
|
|
5808
|
+
label: t("\u5DE5\u4F5C\u533A"),
|
|
5809
|
+
detail: (d) => {
|
|
5810
|
+
const n = d.changedFiles;
|
|
5811
|
+
if (n === null || n === void 0) return t("\u672A\u63D0\u4EA4\u6539\u52A8\uFF1A\u672A\u77E5");
|
|
5812
|
+
const num = Number(n);
|
|
5813
|
+
return num > 0 ? tf("{a0} \u4E2A\u672A\u63D0\u4EA4/\u672A\u8DDF\u8E2A\u6587\u4EF6 \u2014\u2014 \u5EFA\u8BAE\u5148 commit \u6216 stash", { a0: num }) : t("\u5DE5\u4F5C\u533A\u5E72\u51C0");
|
|
5814
|
+
}
|
|
5815
|
+
},
|
|
5816
|
+
"stale-gate": {
|
|
5817
|
+
label: t("\u9648\u65E7\u95E8\u7981"),
|
|
5818
|
+
detail: (d) => {
|
|
5819
|
+
const s = d.overallStatus;
|
|
5820
|
+
if (s === "fail") return t("\u78C1\u76D8\u4E0A\u6B8B\u7559 FAIL \u8D28\u91CF\u95E8\u7981 \u2014\u2014 \u5F00\u65B0\u8F6E\u524D\u5148\u6E05\u9664");
|
|
5821
|
+
return s ? tf("\u8D28\u91CF\u95E8\u7981\uFF1A{a0}", { a0: String(s) }) : t("\u65E0\u6301\u4E45\u5316\u8D28\u91CF\u95E8\u7981");
|
|
5822
|
+
}
|
|
5823
|
+
},
|
|
5824
|
+
"interrupted-checkpoint": {
|
|
5825
|
+
label: t("\u4E2D\u65AD\u65AD\u70B9"),
|
|
5826
|
+
detail: (d) => {
|
|
5827
|
+
if (d.interrupted === true) {
|
|
5828
|
+
const rc = Number(d.resumeCount ?? 0);
|
|
5829
|
+
return tf("\u5B58\u5728\u4E2D\u65AD\u65AD\u70B9\uFF08\u5DF2\u6062\u590D {a0} \u6B21\uFF09\u2014\u2014 \u6062\u590D\u6216\u6E05\u9664\u540E\u518D\u5F00\u8DD1", { a0: rc });
|
|
5830
|
+
}
|
|
5831
|
+
return t("\u65E0\u4E2D\u65AD\u65AD\u70B9");
|
|
5832
|
+
}
|
|
5833
|
+
}
|
|
5834
|
+
};
|
|
5835
|
+
const fixFor = (id) => {
|
|
5836
|
+
switch (id) {
|
|
5837
|
+
case "validation":
|
|
5838
|
+
return { key: "preflight-fix-validation", label: t("\u914D\u7F6E\u9A8C\u8BC1\u547D\u4EE4"), instruction: buildConfigFieldInstruction("validation.commands") };
|
|
5839
|
+
case "stale-gate":
|
|
5840
|
+
return { key: "preflight-fix-gate", label: t("\u6E05\u9664\u95E8\u7981"), instruction: buildQualityGateClearInstruction() };
|
|
5841
|
+
case "interrupted-checkpoint":
|
|
5842
|
+
return { key: "preflight-fix-checkpoint", label: t("\u6062\u590D\u65AD\u70B9"), instruction: buildCheckpointResumeInstruction() };
|
|
5843
|
+
default:
|
|
5844
|
+
return null;
|
|
5845
|
+
}
|
|
5846
|
+
};
|
|
5847
|
+
const rows = (report.checks || []).map((c, i) => {
|
|
5848
|
+
const id = String(c.id || "");
|
|
5849
|
+
const status = String(c.status || "warn");
|
|
5850
|
+
const meta = CHECK_META[id];
|
|
5851
|
+
const label = meta ? meta.label : id || "?";
|
|
5852
|
+
const detail = meta ? meta.detail(c.detail || {}) : "";
|
|
5853
|
+
const fix = status !== "pass" ? fixFor(id) : null;
|
|
5854
|
+
return React.createElement(
|
|
5855
|
+
"div",
|
|
5856
|
+
{ key: `pf-${i}`, className: "iterate-obs-row" },
|
|
5857
|
+
React.createElement("span", { className: "iterate-gate", "data-status": status }, status.toUpperCase()),
|
|
5858
|
+
React.createElement("b", {}, label),
|
|
5859
|
+
React.createElement("span", { className: "iterate-obs-msg" }, detail),
|
|
5860
|
+
fix ? React.createElement("button", {
|
|
5861
|
+
className: "iterate-btn",
|
|
5862
|
+
"data-copied": copiedKey === fix.key ? "" : void 0,
|
|
5863
|
+
onClick: () => copyInstruction(fix.key, fix.instruction),
|
|
5864
|
+
title: fix.instruction
|
|
5865
|
+
}, copiedKey === fix.key ? t("\u5DF2\u590D\u5236") : fix.label) : null
|
|
5866
|
+
);
|
|
5867
|
+
});
|
|
5868
|
+
return React.createElement(
|
|
5869
|
+
"div",
|
|
5870
|
+
{},
|
|
5871
|
+
headerBar,
|
|
5872
|
+
React.createElement(
|
|
5873
|
+
"div",
|
|
5874
|
+
{ className: "iterate-obs-block" },
|
|
5875
|
+
React.createElement(
|
|
5876
|
+
"div",
|
|
5877
|
+
{ className: "iterate-obs-block-head" },
|
|
5878
|
+
React.createElement("span", {}, t("\u5F00\u8DD1\u9884\u68C0\u6E05\u5355")),
|
|
5879
|
+
React.createElement(
|
|
5880
|
+
"span",
|
|
5881
|
+
{ className: "iterate-obs-head-meta" },
|
|
5882
|
+
tf("\u963B\u65AD {a0} \xB7 \u8B66\u544A {a1}", { a0: blocking, a1: warnings })
|
|
5883
|
+
)
|
|
5884
|
+
),
|
|
5885
|
+
React.createElement(
|
|
5886
|
+
"div",
|
|
5887
|
+
{ className: "iterate-obs-block-body" },
|
|
5888
|
+
React.createElement("div", { className: "iterate-obs-msg" }, t("\u9884\u68C0\u53EA\u8BFB\uFF0C\u4E0D\u4F1A\u4FEE\u6539\u914D\u7F6E\u6216\u6587\u4EF6\u3002")),
|
|
5889
|
+
...rows
|
|
5890
|
+
)
|
|
5891
|
+
)
|
|
5892
|
+
);
|
|
5893
|
+
};
|
|
5580
5894
|
const renderLive = () => {
|
|
5581
5895
|
const liveEntries = manifest.live || [];
|
|
5582
5896
|
if (liveEntries.length === 0) {
|
|
@@ -5673,6 +5987,8 @@ function ObservatoryPanel(props) {
|
|
|
5673
5987
|
return renderExperienceBank();
|
|
5674
5988
|
case "f10":
|
|
5675
5989
|
return renderDefenseEvents();
|
|
5990
|
+
case "f11":
|
|
5991
|
+
return renderPreflight();
|
|
5676
5992
|
default:
|
|
5677
5993
|
return null;
|
|
5678
5994
|
}
|
package/lib/parse.js
CHANGED
|
@@ -1124,6 +1124,88 @@ export function scanSessionForDefenseEvents(session) {
|
|
|
1124
1124
|
return loose ? normalizeDefenseEventsResult(loose) : null
|
|
1125
1125
|
}
|
|
1126
1126
|
|
|
1127
|
+
// ─── Preflight readiness report detection ────────────────────────────────────
|
|
1128
|
+
|
|
1129
|
+
/**
|
|
1130
|
+
* Check whether `obj` is an `iterate_preflight` report.
|
|
1131
|
+
*
|
|
1132
|
+
* Discriminator: a boolean `ready`, an array `checks`, and a numeric
|
|
1133
|
+
* `blocking`/`warnings` counter. The `ok:false` error shape (empty checks)
|
|
1134
|
+
* still matches so the panel can render the failure instead of claiming
|
|
1135
|
+
* "not pulled yet".
|
|
1136
|
+
*
|
|
1137
|
+
* @param {unknown} obj
|
|
1138
|
+
* @returns {boolean}
|
|
1139
|
+
*/
|
|
1140
|
+
export function isPreflightReport(obj) {
|
|
1141
|
+
if (!obj || typeof obj !== 'object') return false
|
|
1142
|
+
const o = /** @type {Record<string, unknown>} */ (obj)
|
|
1143
|
+
const ready = safeGet(o, 'ready')
|
|
1144
|
+
const checks = safeGet(o, 'checks')
|
|
1145
|
+
const blocking = safeGet(o, 'blocking')
|
|
1146
|
+
const warnings = safeGet(o, 'warnings')
|
|
1147
|
+
return (
|
|
1148
|
+
typeof ready === 'boolean' &&
|
|
1149
|
+
Array.isArray(checks) &&
|
|
1150
|
+
(typeof blocking === 'number' || typeof warnings === 'number')
|
|
1151
|
+
)
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
/** Deep-find the first `iterate_preflight` report node inside `obj`. */
|
|
1155
|
+
function findPreflightReportInObject(obj) {
|
|
1156
|
+
return findFirstInObject(obj, (o) => isPreflightReport(o))
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
/**
|
|
1160
|
+
* Normalize an `iterate_preflight` report into a JSON-safe object.
|
|
1161
|
+
*
|
|
1162
|
+
* @param {Record<string, unknown> | null | undefined} raw
|
|
1163
|
+
* @returns {Record<string, unknown>}
|
|
1164
|
+
*/
|
|
1165
|
+
export function normalizePreflightReport(raw) {
|
|
1166
|
+
const src = raw && typeof raw === 'object'
|
|
1167
|
+
? /** @type {Record<string, unknown>} */ (raw)
|
|
1168
|
+
: {}
|
|
1169
|
+
const asNum = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : 0)
|
|
1170
|
+
const asStr = (v) => (typeof v === 'string' ? v : '')
|
|
1171
|
+
const checks = Array.isArray(safeGet(src, 'checks')) ? safeGet(src, 'checks') : []
|
|
1172
|
+
return {
|
|
1173
|
+
ok: safeGet(src, 'ok') !== false,
|
|
1174
|
+
ready: safeGet(src, 'ready') === true,
|
|
1175
|
+
blocking: asNum(safeGet(src, 'blocking')),
|
|
1176
|
+
warnings: asNum(safeGet(src, 'warnings')),
|
|
1177
|
+
error: asStr(safeGet(src, 'error')) || null,
|
|
1178
|
+
checks: (/** @type {unknown[]} */ (checks)).map((c) => {
|
|
1179
|
+
const rec = /** @type {Record<string, unknown>} */ (c && typeof c === 'object' ? c : {})
|
|
1180
|
+
const status = safeGet(rec, 'status')
|
|
1181
|
+
const detail = safeGet(rec, 'detail')
|
|
1182
|
+
return {
|
|
1183
|
+
id: asStr(safeGet(rec, 'id')),
|
|
1184
|
+
status: status === 'pass' || status === 'warn' || status === 'fail' ? status : 'warn',
|
|
1185
|
+
detail: detail && typeof detail === 'object' ? detail : {},
|
|
1186
|
+
}
|
|
1187
|
+
}),
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
/**
|
|
1192
|
+
* Scan a session snapshot for the latest `iterate_preflight` result and return
|
|
1193
|
+
* its normalized report (or null).
|
|
1194
|
+
*
|
|
1195
|
+
* @param {unknown} session
|
|
1196
|
+
* @returns {Record<string, unknown> | null}
|
|
1197
|
+
*/
|
|
1198
|
+
export function scanSessionForPreflight(session) {
|
|
1199
|
+
const node = latestToolResultNode(session, 'iterate_preflight', findPreflightReportInObject)
|
|
1200
|
+
if (node !== null) {
|
|
1201
|
+
const found = findPreflightReportInObject(node)
|
|
1202
|
+
if (found) return normalizePreflightReport(found)
|
|
1203
|
+
}
|
|
1204
|
+
// Same deep-scan fallback as the quality-gate scanner (see above).
|
|
1205
|
+
const loose = findFirstInObject(session, isPreflightReport)
|
|
1206
|
+
return loose ? normalizePreflightReport(loose) : null
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1127
1209
|
// ─── Run-summary / meta-review verdict detection ─────────────────────────────
|
|
1128
1210
|
|
|
1129
1211
|
/**
|
|
@@ -1857,6 +1939,21 @@ export function buildQualityGateClearInstruction() {
|
|
|
1857
1939
|
)
|
|
1858
1940
|
}
|
|
1859
1941
|
|
|
1942
|
+
/**
|
|
1943
|
+
* `iterate_preflight` readiness-check payload (runs the pre-run checklist).
|
|
1944
|
+
*
|
|
1945
|
+
* @returns {string}
|
|
1946
|
+
*/
|
|
1947
|
+
export function buildPreflightInstruction() {
|
|
1948
|
+
return (
|
|
1949
|
+
'请调用 `iterate_preflight` 运行开跑前预检(只读:校验 validation.commands、git、脏工作区、' +
|
|
1950
|
+
'陈旧质量门禁与中断断点):\n\n' +
|
|
1951
|
+
'```json\n' +
|
|
1952
|
+
`${JSON.stringify({}, null, 2)}\n` +
|
|
1953
|
+
'```'
|
|
1954
|
+
)
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1860
1957
|
/**
|
|
1861
1958
|
* `iterate_rollback` payload for one applied fix id.
|
|
1862
1959
|
*
|
|
@@ -2310,14 +2407,63 @@ export function buildCompletionSummary(report, lang = 'zh') {
|
|
|
2310
2407
|
const stats = severityStats(report)
|
|
2311
2408
|
const converged = conv.converged === true
|
|
2312
2409
|
const totalFindings = stats.critical + stats.high + stats.medium + stats.low
|
|
2410
|
+
// The persisted `stoppedReason` is the authoritative "why" — a validation
|
|
2411
|
+
// abort or a schema failure must NOT be reported as "reached the round
|
|
2412
|
+
// limit". A normalized report nests it under `convergence.stoppedReason`
|
|
2413
|
+
// (normalizeReport); a raw harness report may carry it at the top level.
|
|
2414
|
+
// Fall back to the convergence flag only when no reason was stored (older
|
|
2415
|
+
// transcripts predate the field).
|
|
2416
|
+
const stopped = typeof conv.stoppedReason === 'string'
|
|
2417
|
+
? conv.stoppedReason
|
|
2418
|
+
: typeof report.stoppedReason === 'string'
|
|
2419
|
+
? report.stoppedReason
|
|
2420
|
+
: ''
|
|
2421
|
+
const reason = stopped
|
|
2422
|
+
? completionReason(stopped, lang, total)
|
|
2423
|
+
: converged
|
|
2424
|
+
? (lang === 'en' ? 'converged' : '已收敛')
|
|
2425
|
+
: (lang === 'en' ? `reached the round limit ${total}` : `已达最大轮数 ${total}`)
|
|
2313
2426
|
if (lang === 'en') {
|
|
2314
|
-
const reason = converged ? 'converged' : `reached the round limit ${total}`
|
|
2315
2427
|
return `iterate review complete \u00b7 ${rounds}/${total} rounds \u00b7 ${totalFindings} findings \u00b7 ${reason}`
|
|
2316
2428
|
}
|
|
2317
|
-
const reason = converged ? '已收敛' : `已达最大轮数 ${total}`
|
|
2318
2429
|
return `iterate 评审完成 · ${rounds}/${total} 轮 · ${totalFindings} 项发现 · ${reason}`
|
|
2319
2430
|
}
|
|
2320
2431
|
|
|
2432
|
+
/**
|
|
2433
|
+
* Compact "why the run ended" phrase for the completion summary.
|
|
2434
|
+
* Mirrors `stoppedReasonLabel` but drops the "已结束 · " prefix so it reads
|
|
2435
|
+
* inside the one-line summary. Unknown reasons degrade to the raw value.
|
|
2436
|
+
*
|
|
2437
|
+
* @param {string} reason
|
|
2438
|
+
* @param {string} lang
|
|
2439
|
+
* @param {number} total
|
|
2440
|
+
* @returns {string}
|
|
2441
|
+
*/
|
|
2442
|
+
function completionReason(reason, lang, total) {
|
|
2443
|
+
const zh = {
|
|
2444
|
+
converged: '已收敛',
|
|
2445
|
+
max_rounds_reached: `已达最大轮数 ${total}`,
|
|
2446
|
+
aborted_by_validation: '验证失败后回滚',
|
|
2447
|
+
aborted_by_config: '验证命令未在白名单',
|
|
2448
|
+
inconclusive: '轮次结论不明',
|
|
2449
|
+
schema_invalid: '审查输出 schema 校验失败',
|
|
2450
|
+
no_usable_reviewer_output: '无可用审查输出',
|
|
2451
|
+
}
|
|
2452
|
+
const en = {
|
|
2453
|
+
converged: 'converged',
|
|
2454
|
+
max_rounds_reached: `reached the round limit ${total}`,
|
|
2455
|
+
aborted_by_validation: 'rolled back after a validation failure',
|
|
2456
|
+
aborted_by_config: 'validation command not allow-listed',
|
|
2457
|
+
inconclusive: 'inconclusive rounds',
|
|
2458
|
+
schema_invalid: 'review output failed schema validation',
|
|
2459
|
+
no_usable_reviewer_output: 'no usable review output',
|
|
2460
|
+
}
|
|
2461
|
+
const table = lang === 'en' ? en : zh
|
|
2462
|
+
// Object.hasOwn, NOT `table[reason]`: a hand-edited `__proto__` reason must
|
|
2463
|
+
// not resolve through the prototype chain to a non-string.
|
|
2464
|
+
return Object.hasOwn(table, reason) ? table[reason] : reason
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2321
2467
|
// ─── Config edit guidance ───────────────────────────────────────────────────
|
|
2322
2468
|
|
|
2323
2469
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iterate-plugin",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.12.0",
|
|
4
4
|
"description": "dsh plugin that turns the iterate skill into an autonomous closed-loop harness with quality command center and experience bank (v3.3). Features: plan -> parallel review xN -> atomic fixes -> validate -> loop -> auto-stop, plus dry-run pure-review mode, quality gate compute/persist, writable experience bank, defense events stream (record + bilingual labels), and native command buttons — with a live quality command center (F8/F9/F10 render real session data), §8 assign-fix instruction, and task_mode indicator wired end to end.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/client/i18n.ts
CHANGED
|
@@ -446,11 +446,50 @@ export const EN: Record<string, string> = {
|
|
|
446
446
|
'防线:{a0}': 'Defense: {a0}',
|
|
447
447
|
没有匹配该类型的防御事件: 'No defense events of this type',
|
|
448
448
|
|
|
449
|
+
// ── F11 preflight ────────────────────────────────────────────────────────
|
|
450
|
+
开跑预检: 'Preflight',
|
|
451
|
+
待预检: 'Not run yet',
|
|
452
|
+
未就绪: 'Not ready',
|
|
453
|
+
'复制 iterate_preflight 预检指令(含 JSON 参数)':
|
|
454
|
+
'Copy the iterate_preflight readiness instruction (JSON arguments included)',
|
|
455
|
+
运行预检: 'Run preflight',
|
|
456
|
+
开跑预检清单: 'Preflight checklist',
|
|
457
|
+
预检结果: 'Preflight result',
|
|
458
|
+
'开跑前先预检:确认验证命令、Git、工作区、陈旧门禁与中断断点都已就绪,避免开跑后才发现环境问题。':
|
|
459
|
+
'Run preflight first: confirm validation commands, git, the working tree, a stale gate and an interrupted checkpoint are ready, so you do not discover an environment problem mid-run.',
|
|
460
|
+
复制预检指令: 'Copy preflight instruction',
|
|
461
|
+
验证命令: 'Validation commands',
|
|
462
|
+
'已配置 {a0} 条验证命令': '{a0} validation command(s) configured',
|
|
463
|
+
'未配置 validation.commands —— 本轮无法证明修复,属阻断项':
|
|
464
|
+
'validation.commands is not configured — the run cannot prove fixes (blocking)',
|
|
465
|
+
'Git 仓库': 'Git repository',
|
|
466
|
+
'已检测到 Git 仓库': 'Git repository detected',
|
|
467
|
+
'不是 Git 仓库 —— changed-only 范围与 iterate_diff 需要它':
|
|
468
|
+
'Not a git repository — changed-only scope and iterate_diff need one',
|
|
469
|
+
工作区: 'Working tree',
|
|
470
|
+
'未提交改动:未知': 'Uncommitted changes: unknown',
|
|
471
|
+
'{a0} 个未提交/未跟踪文件 —— 建议先 commit 或 stash':
|
|
472
|
+
'{a0} uncommitted/untracked file(s) — commit or stash first',
|
|
473
|
+
工作区干净: 'Working tree clean',
|
|
474
|
+
陈旧门禁: 'Stale gate',
|
|
475
|
+
'磁盘上残留 FAIL 质量门禁 —— 开新轮前先清除':
|
|
476
|
+
'A stale FAIL quality gate is persisted — clear it before a fresh run',
|
|
477
|
+
'质量门禁:{a0}': 'Quality gate: {a0}',
|
|
478
|
+
无持久化质量门禁: 'No persisted quality gate',
|
|
479
|
+
中断断点: 'Interrupted checkpoint',
|
|
480
|
+
'存在中断断点(已恢复 {a0} 次)—— 恢复或清除后再开跑':
|
|
481
|
+
'An interrupted checkpoint is waiting (resumed {a0}x) — resume or clear it first',
|
|
482
|
+
无中断断点: 'No interrupted checkpoint',
|
|
483
|
+
配置验证命令: 'Configure validation',
|
|
484
|
+
恢复断点: 'Resume checkpoint',
|
|
485
|
+
'阻断 {a0} · 警告 {a1}': 'Blocking {a0} · warnings {a1}',
|
|
486
|
+
'预检失败:{a0}': 'Preflight failed: {a0}',
|
|
487
|
+
'预检只读,不会修改配置或文件。': 'Preflight is read-only; it never modifies config or files.',
|
|
488
|
+
|
|
449
489
|
// ── copy feedback ────────────────────────────────────────────────────────
|
|
450
490
|
已复制: 'Copied',
|
|
451
491
|
复制失败: 'Copy failed',
|
|
452
492
|
}
|
|
453
|
-
|
|
454
493
|
let current: Lang = 'zh'
|
|
455
494
|
let storage: LangStorage | null = null
|
|
456
495
|
const listeners = new Set<(lang: Lang) => void>()
|