iterate-plugin 3.12.0 → 3.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -1,4 +1,4 @@
1
- /* iterate-plugin client source-digest: 408ff45e22a5041e34cdeb7ccd786225658f6c1fe52a7bb5233fc35450546683 */
1
+ /* iterate-plugin client source-digest: 5b22267e6de6fb0564d60874b3de1211755cdeb398f6c7ffa87dfa1007e6796f */
2
2
  window.__ModuleLoader__.load({ id: "iterate-plugin", factory: (require) => {
3
3
  var module = { exports: {} };
4
4
  var exports = module.exports;
@@ -1250,6 +1250,13 @@ var START_INSTRUCTIONS = {
1250
1250
  full: START_INSTRUCTION_FULL,
1251
1251
  reviewOnly: START_INSTRUCTION_REVIEW_ONLY
1252
1252
  };
1253
+ function buildScopedReviewInstruction(scope) {
1254
+ const hasFile = scope && typeof scope.file === "string" && scope.file.length > 0;
1255
+ const hasDir = !hasFile && scope && typeof scope.dir === "string" && scope.dir.length > 0;
1256
+ if (!hasFile && !hasDir) return "";
1257
+ const subject = hasFile ? `\u6587\u4EF6 \`${scope.file}\`${typeof scope.line === "number" && scope.line > 0 ? `\uFF08\u7B2C ${scope.line} \u884C\uFF09` : ""}` : `\u76EE\u5F55 \`${scope.dir}\``;
1258
+ return `\u5F00\u59CB\u4E00\u6B21\u4EC5\u8BC4\u5BA1\uFF08dry-run\uFF0C\u4E0D\u4FEE\u6539\u4EFB\u4F55\u6587\u4EF6\uFF09\uFF0C\u5BA1\u67E5\u8303\u56F4\u9650\u5B9A\u5728${subject}\uFF1A\u8BF7\u8C03\u7528 \`workflow\` \u5DE5\u5177\u8FD0\u884C iterate \u5DE5\u4F5C\u6D41\uFF0Cmode: "dry-run"\uFF0C\u6309\u7CFB\u7EDF\u63D0\u793A\u7684 Iterate Workflow \u6267\u884C plan \u2192 \u8BC4\u5BA1 \u2192 \u5206\u8BCA \u2192 \`iterate_review aggregate\` / \`meta-review\` \u7EC8\u62A5\u5E76 \`iterate_transcript\` capture\uFF1B\u8BC4\u5BA1\u65F6\u628A\u8303\u56F4\u9650\u5B9A\u5728${subject}\uFF0C\u5176\u4F59\u6587\u4EF6\u4EC5\u4F5C\u4E0A\u4E0B\u6587\u3001\u4E0D\u5F97\u7ED9\u51FA\u4FEE\u590D\u3002\u5168\u7A0B\u4E0D\u8981\u8C03\u7528 \`iterate_fix\` / \`iterate_rollback\`\u3002`;
1259
+ }
1253
1260
  function fixFindingPayload(finding) {
1254
1261
  const f = finding && typeof finding === "object" ? finding : {};
1255
1262
  const file = String(f.file ?? "");
@@ -1343,6 +1350,18 @@ function buildPreflightInstruction() {
1343
1350
  ${JSON.stringify({}, null, 2)}
1344
1351
  \`\`\``;
1345
1352
  }
1353
+ function buildValidationApplyInstruction(commands) {
1354
+ const list = Array.isArray(commands) ? commands.filter((c) => typeof c === "string" && c.length > 0) : [];
1355
+ const payload = {
1356
+ operation: "write",
1357
+ updates: { validation: { commands: { default: list } } }
1358
+ };
1359
+ return `\u8BF7\u8C03\u7528 \`iterate_config\` \u628A\u68C0\u6D4B\u5230\u7684\u9A8C\u8BC1\u547D\u4EE4\u5199\u5165 \`validation.commands.default\`\uFF08\u4F1A\u5148\u5907\u4EFD\u914D\u7F6E\uFF09\uFF1A
1360
+
1361
+ \`\`\`json
1362
+ ${JSON.stringify(payload, null, 2)}
1363
+ \`\`\``;
1364
+ }
1346
1365
  function buildRollbackInstruction(id) {
1347
1366
  return `\u8BF7\u8C03\u7528 \`iterate_rollback\` \u56DE\u6EDA\u4EE5\u4E0B\u4FEE\u590D\uFF1A
1348
1367
 
@@ -2755,6 +2774,7 @@ var EN = {
2755
2774
  \u590D\u5236\u9884\u68C0\u6307\u4EE4: "Copy preflight instruction",
2756
2775
  \u9A8C\u8BC1\u547D\u4EE4: "Validation commands",
2757
2776
  "\u5DF2\u914D\u7F6E {a0} \u6761\u9A8C\u8BC1\u547D\u4EE4": "{a0} validation command(s) configured",
2777
+ "\u672A\u914D\u7F6E validation.commands\uFF08\u963B\u65AD\u9879\uFF09\u2014\u2014 \u68C0\u6D4B\u5230 {a0} \u9879\u76EE\uFF0C\u5EFA\u8BAE\uFF1A{a1}": "validation.commands is not configured (blocking) \u2014 detected {a0} project; suggested: {a1}",
2758
2778
  "\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
2779
  "Git \u4ED3\u5E93": "Git repository",
2760
2780
  "\u5DF2\u68C0\u6D4B\u5230 Git \u4ED3\u5E93": "Git repository detected",
@@ -2771,6 +2791,7 @@ var EN = {
2771
2791
  "\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
2792
  \u65E0\u4E2D\u65AD\u65AD\u70B9: "No interrupted checkpoint",
2773
2793
  \u914D\u7F6E\u9A8C\u8BC1\u547D\u4EE4: "Configure validation",
2794
+ \u5E94\u7528\u5EFA\u8BAE: "Apply suggestion",
2774
2795
  \u6062\u590D\u65AD\u70B9: "Resume checkpoint",
2775
2796
  "\u963B\u65AD {a0} \xB7 \u8B66\u544A {a1}": "Blocking {a0} \xB7 warnings {a1}",
2776
2797
  "\u9884\u68C0\u5931\u8D25\uFF1A{a0}": "Preflight failed: {a0}",
@@ -2844,6 +2865,135 @@ function subscribeSession(listener) {
2844
2865
  };
2845
2866
  }
2846
2867
 
2868
+ // src/client/context-menu.ts
2869
+ var OBS_OPEN_EVENT = "iterate-plugin:open-observatory";
2870
+ function openObservatory(detail) {
2871
+ if (typeof window === "undefined" || typeof window.dispatchEvent !== "function") return false;
2872
+ try {
2873
+ window.dispatchEvent(new CustomEvent(OBS_OPEN_EVENT, { detail: detail ?? {} }));
2874
+ return true;
2875
+ } catch {
2876
+ return false;
2877
+ }
2878
+ }
2879
+ var LABELS = {
2880
+ "iterate.open-panel": { zh: "\u6253\u5F00 iterate \u8D28\u91CF\u6307\u6325\u4E2D\u5FC3", en: "Open iterate command center" },
2881
+ "iterate.preflight": { zh: "\u590D\u5236\u5F00\u8DD1\u9884\u68C0\u6307\u4EE4", en: "Copy preflight instruction" },
2882
+ "iterate.dry-run": { zh: "\u590D\u5236 dry-run \u5BA1\u67E5\u6307\u4EE4", en: "Copy dry-run review instruction" },
2883
+ "iterate.resume": { zh: "\u590D\u5236\u65AD\u70B9\u6062\u590D\u6307\u4EE4", en: "Copy checkpoint-resume instruction" },
2884
+ "iterate.pull": { zh: "\u590D\u5236\u62C9\u53D6\u78C1\u76D8\u5FEB\u7167\u6307\u4EE4", en: "Copy disk-snapshot pull instruction" },
2885
+ "iterate.review-file": { zh: "\u590D\u5236\u300C\u4EC5\u5BA1\u67E5\u6B64\u6587\u4EF6\u300D\u6307\u4EE4", en: 'Copy "review this file only" instruction' },
2886
+ "iterate.review-dir": { zh: "\u590D\u5236\u300C\u4EC5\u5BA1\u67E5\u6B64\u76EE\u5F55\u300D\u6307\u4EE4", en: 'Copy "review this directory only" instruction' }
2887
+ };
2888
+ function pick(id, lang) {
2889
+ const entry = LABELS[id];
2890
+ if (!entry) return id;
2891
+ return lang === "zh" ? entry.zh : entry.en;
2892
+ }
2893
+ var COPY_FAILURE = {
2894
+ zh: "\u590D\u5236\u5931\u8D25\uFF1A\u526A\u8D34\u677F\u88AB\u62D2\u7EDD\u3002\u8BF7\u6539\u4E3A\u5728\u89C2\u6D4B\u53F0\u9762\u677F\u91CC\u7528\u540C\u4E00\u6307\u4EE4\u7684\u300C\u590D\u5236\u300D\u6309\u94AE\uFF0C\u6216\u624B\u52A8\u9009\u4E2D\u6307\u4EE4\u6587\u672C\u590D\u5236\u3002",
2895
+ en: 'Copy failed: clipboard denied. Use the matching "Copy" button in the observatory panel, or select the instruction text manually.'
2896
+ };
2897
+ function buildSessionMenuEntries(lang, opts) {
2898
+ return [
2899
+ { id: "iterate.open-panel", label: pick("iterate.open-panel", lang), openTab: "live" },
2900
+ { id: "iterate.preflight", label: pick("iterate.preflight", lang), separatorBefore: true },
2901
+ { id: "iterate.dry-run", label: pick("iterate.dry-run", lang) },
2902
+ { id: "iterate.resume", label: pick("iterate.resume", lang), disabled: opts?.hasCheckpoint === false },
2903
+ { id: "iterate.pull", label: pick("iterate.pull", lang), separatorBefore: true }
2904
+ ];
2905
+ }
2906
+ function buildFileMenuEntries(lang) {
2907
+ return [
2908
+ { id: "iterate.review-file", label: pick("iterate.review-file", lang) },
2909
+ { id: "iterate.open-panel", label: pick("iterate.open-panel", lang) }
2910
+ ];
2911
+ }
2912
+ function buildDirectoryMenuEntries(lang) {
2913
+ return [
2914
+ { id: "iterate.review-dir", label: pick("iterate.review-dir", lang) },
2915
+ { id: "iterate.preflight", label: pick("iterate.preflight", lang), separatorBefore: true },
2916
+ { id: "iterate.open-panel", label: pick("iterate.open-panel", lang) }
2917
+ ];
2918
+ }
2919
+ function toAction(entry, deps, lang) {
2920
+ const action = {
2921
+ id: entry.id,
2922
+ label: entry.label,
2923
+ danger: entry.danger,
2924
+ separatorBefore: entry.separatorBefore
2925
+ };
2926
+ if (entry.disabled) action.disabled = true;
2927
+ if (entry.openTab) {
2928
+ action.run = () => {
2929
+ deps.openPanel({ tab: entry.openTab });
2930
+ deps.notify(lang === "zh" ? "\u5DF2\u6253\u5F00 iterate \u89C2\u6D4B\u53F0\u3002" : "Iterate observatory opened.", true);
2931
+ };
2932
+ return action;
2933
+ }
2934
+ if (entry.instruction) {
2935
+ const text = entry.instruction;
2936
+ const failureLabel = COPY_FAILURE[lang];
2937
+ action.failureLabel = failureLabel;
2938
+ action.run = async () => {
2939
+ const ok = await deps.copy(text);
2940
+ if (!ok) throw new Error(failureLabel);
2941
+ deps.notify(lang === "zh" ? "\u6307\u4EE4\u5DF2\u590D\u5236\uFF0C\u7C98\u8D34\u7ED9\u6A21\u578B\u5373\u53EF\u3002" : "Instruction copied \u2014 paste it to the model.", true);
2942
+ };
2943
+ }
2944
+ return action;
2945
+ }
2946
+ function registerIterateContextMenus(service, deps, instructionByEntry) {
2947
+ const unregister = [];
2948
+ const register = (target, order, build) => {
2949
+ try {
2950
+ const dispose = service.register({
2951
+ id: `iterate-plugin.${target}`,
2952
+ target,
2953
+ order,
2954
+ resolve: (t2, _signal) => {
2955
+ const lang = getLang();
2956
+ return build(lang, t2).map((entry) => {
2957
+ const full = { ...entry, instruction: entry.instruction ?? instructionByEntry(entry.id) };
2958
+ return toAction(full, deps, lang);
2959
+ });
2960
+ }
2961
+ });
2962
+ unregister.push(dispose);
2963
+ } catch (err) {
2964
+ if (typeof console !== "undefined" && typeof console.error === "function") {
2965
+ console.error("[iterate-ui] context-menu registration failed for target", target, err);
2966
+ }
2967
+ }
2968
+ };
2969
+ register("session", 300, (_lang, t2) => {
2970
+ const target = t2;
2971
+ return buildSessionMenuEntries(getLang(), { hasCheckpoint: target?.value?.sessionId ? true : void 0 });
2972
+ });
2973
+ register("file", 300, (_lang, t2) => {
2974
+ const target = t2;
2975
+ return buildFileMenuEntries(getLang()).map((e) => ({
2976
+ ...e,
2977
+ instruction: e.id === "iterate.review-file" ? instructionByEntry(`file:${target?.value?.path ?? ""}${typeof target?.value?.line === "number" ? `:${target.value.line}` : ""}`) : e.instruction
2978
+ }));
2979
+ });
2980
+ register("directory", 300, (_lang, t2) => {
2981
+ const target = t2;
2982
+ return buildDirectoryMenuEntries(getLang()).map((e) => ({
2983
+ ...e,
2984
+ instruction: e.id === "iterate.review-dir" ? instructionByEntry(`dir:${target?.value?.path ?? ""}`) : e.instruction
2985
+ }));
2986
+ });
2987
+ return () => {
2988
+ for (const dispose of unregister.splice(0)) {
2989
+ try {
2990
+ dispose();
2991
+ } catch {
2992
+ }
2993
+ }
2994
+ };
2995
+ }
2996
+
2847
2997
  // src/client/index.ts
2848
2998
  var React = require("react");
2849
2999
  var name = "iterate-plugin";
@@ -3280,12 +3430,42 @@ function readTheme(ctx) {
3280
3430
  }
3281
3431
  return void 0;
3282
3432
  }
3433
+ function readContextMenus(ctx) {
3434
+ if (!ctx || typeof ctx.get !== "function") return void 0;
3435
+ const raw = ctx.get("contextMenus", false);
3436
+ if (!raw || typeof raw !== "object") return void 0;
3437
+ const svc = raw;
3438
+ if (typeof svc.register !== "function") return void 0;
3439
+ if (typeof svc.notify !== "function") return void 0;
3440
+ return svc;
3441
+ }
3442
+ function sessionIdOfSession(session) {
3443
+ if (!session || typeof session !== "object") return null;
3444
+ const s = session;
3445
+ const candidates = [
3446
+ s.sessionId,
3447
+ s.id,
3448
+ s.header,
3449
+ s.session
3450
+ ];
3451
+ for (const c of candidates) {
3452
+ if (typeof c === "string" && c) return c;
3453
+ if (c && typeof c === "object") {
3454
+ const o = c;
3455
+ for (const k of ["sessionId", "id"]) {
3456
+ if (typeof o[k] === "string" && o[k]) return o[k];
3457
+ }
3458
+ }
3459
+ }
3460
+ return null;
3461
+ }
3283
3462
  var slotsSvc = void 0;
3284
3463
  var themeSvc = void 0;
3285
3464
  var storage2 = null;
3286
3465
  var themeDisposer = null;
3287
3466
  var themeListener = null;
3288
3467
  var themeEnabled = true;
3468
+ var menuDisposer = null;
3289
3469
  var roundPulseListeners = [];
3290
3470
  function emitRoundPulse(round, converged) {
3291
3471
  const payload = { round, converged: converged === true };
@@ -4593,6 +4773,21 @@ function ObservatoryPanel(props) {
4593
4773
  doc.addEventListener("keydown", onKeyDown);
4594
4774
  return () => doc.removeEventListener("keydown", onKeyDown);
4595
4775
  }, [open]);
4776
+ const panelSessionId = sessionIdOfSession(session);
4777
+ React.useEffect(() => {
4778
+ if (typeof window === "undefined" || typeof window.addEventListener !== "function") return;
4779
+ const onOpen = (ev) => {
4780
+ const detail = ev.detail;
4781
+ const detailSession = detail && typeof detail.sessionId === "string" && detail.sessionId ? detail.sessionId : null;
4782
+ if (detailSession && panelSessionId && detailSession !== panelSessionId) return;
4783
+ setOpen(true);
4784
+ const target = detail && typeof detail.tab === "string" ? detail.tab : "live";
4785
+ const valid = OBS_TABS.some((x) => x.key === target);
4786
+ setTab(valid ? target : "live");
4787
+ };
4788
+ window.addEventListener(OBS_OPEN_EVENT, onOpen);
4789
+ return () => window.removeEventListener(OBS_OPEN_EVENT, onOpen);
4790
+ }, [panelSessionId]);
4596
4791
  const copyInstruction = (key, text) => {
4597
4792
  if (!text) return;
4598
4793
  copyText(text).then((ok) => {
@@ -5797,7 +5992,15 @@ function ObservatoryPanel(props) {
5797
5992
  label: t("\u9A8C\u8BC1\u547D\u4EE4"),
5798
5993
  detail: (d) => {
5799
5994
  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");
5995
+ if (n > 0) return tf("\u5DF2\u914D\u7F6E {a0} \u6761\u9A8C\u8BC1\u547D\u4EE4", { a0: n });
5996
+ const suggested = Array.isArray(d.suggestedCommands) ? d.suggestedCommands.map((c) => String(c)).filter((c) => c.length > 0) : [];
5997
+ if (suggested.length > 0) {
5998
+ return tf("\u672A\u914D\u7F6E validation.commands\uFF08\u963B\u65AD\u9879\uFF09\u2014\u2014 \u68C0\u6D4B\u5230 {a0} \u9879\u76EE\uFF0C\u5EFA\u8BAE\uFF1A{a1}", {
5999
+ a0: String(d.ecosystem || ""),
6000
+ a1: suggested.join(" \xB7 ")
6001
+ });
6002
+ }
6003
+ return t("\u672A\u914D\u7F6E validation.commands \u2014\u2014 \u672C\u8F6E\u65E0\u6CD5\u8BC1\u660E\u4FEE\u590D\uFF0C\u5C5E\u963B\u65AD\u9879");
5801
6004
  }
5802
6005
  },
5803
6006
  git: {
@@ -5832,10 +6035,19 @@ function ObservatoryPanel(props) {
5832
6035
  }
5833
6036
  }
5834
6037
  };
5835
- const fixFor = (id) => {
6038
+ const fixFor = (id, detail) => {
5836
6039
  switch (id) {
5837
- case "validation":
6040
+ case "validation": {
6041
+ const suggested = Array.isArray(detail.suggestedCommands) ? detail.suggestedCommands.map((c) => String(c)).filter((c) => c.length > 0) : [];
6042
+ if (suggested.length > 0) {
6043
+ return {
6044
+ key: "preflight-fix-validation",
6045
+ label: t("\u5E94\u7528\u5EFA\u8BAE"),
6046
+ instruction: buildValidationApplyInstruction(suggested)
6047
+ };
6048
+ }
5838
6049
  return { key: "preflight-fix-validation", label: t("\u914D\u7F6E\u9A8C\u8BC1\u547D\u4EE4"), instruction: buildConfigFieldInstruction("validation.commands") };
6050
+ }
5839
6051
  case "stale-gate":
5840
6052
  return { key: "preflight-fix-gate", label: t("\u6E05\u9664\u95E8\u7981"), instruction: buildQualityGateClearInstruction() };
5841
6053
  case "interrupted-checkpoint":
@@ -5849,14 +6061,14 @@ function ObservatoryPanel(props) {
5849
6061
  const status = String(c.status || "warn");
5850
6062
  const meta = CHECK_META[id];
5851
6063
  const label = meta ? meta.label : id || "?";
5852
- const detail = meta ? meta.detail(c.detail || {}) : "";
5853
- const fix = status !== "pass" ? fixFor(id) : null;
6064
+ const detailText = meta ? meta.detail(c.detail || {}) : "";
6065
+ const fix = status !== "pass" ? fixFor(id, c.detail || {}) : null;
5854
6066
  return React.createElement(
5855
6067
  "div",
5856
6068
  { key: `pf-${i}`, className: "iterate-obs-row" },
5857
6069
  React.createElement("span", { className: "iterate-gate", "data-status": status }, status.toUpperCase()),
5858
6070
  React.createElement("b", {}, label),
5859
- React.createElement("span", { className: "iterate-obs-msg" }, detail),
6071
+ React.createElement("span", { className: "iterate-obs-msg" }, detailText),
5860
6072
  fix ? React.createElement("button", {
5861
6073
  className: "iterate-btn",
5862
6074
  "data-copied": copiedKey === fix.key ? "" : void 0,
@@ -6186,6 +6398,54 @@ function apply(ctx) {
6186
6398
  )
6187
6399
  );
6188
6400
  }
6401
+ {
6402
+ const menuSvc = readContextMenus(ctx);
6403
+ if (menuSvc) {
6404
+ const instructionByEntry = (id) => {
6405
+ if (id === "iterate.preflight") return buildPreflightInstruction();
6406
+ if (id === "iterate.dry-run") return START_INSTRUCTIONS.reviewOnly;
6407
+ if (id === "iterate.resume") return buildCheckpointResumeInstruction();
6408
+ if (id === "iterate.pull") return buildDiskSnapshotInstruction();
6409
+ if (id.startsWith("file:")) {
6410
+ const rest = id.slice("file:".length);
6411
+ const colon = rest.lastIndexOf(":");
6412
+ const path = colon > 0 ? rest.slice(0, colon) : rest;
6413
+ const line = colon > 0 ? Number(rest.slice(colon + 1)) : void 0;
6414
+ return buildScopedReviewInstruction({ file: path, ...Number.isInteger(line) && line > 0 ? { line } : {} });
6415
+ }
6416
+ if (id.startsWith("dir:")) return buildScopedReviewInstruction({ dir: id.slice("dir:".length) });
6417
+ return "";
6418
+ };
6419
+ if (menuDisposer) {
6420
+ try {
6421
+ menuDisposer();
6422
+ } catch {
6423
+ }
6424
+ menuDisposer = null;
6425
+ }
6426
+ menuDisposer = registerIterateContextMenus(
6427
+ menuSvc,
6428
+ {
6429
+ copy: (text) => copyText(text),
6430
+ openPanel: (detail) => {
6431
+ openObservatory(detail);
6432
+ },
6433
+ notify: (text, success, action) => {
6434
+ menuSvc.notify(text, success, action);
6435
+ }
6436
+ },
6437
+ instructionByEntry
6438
+ );
6439
+ if (typeof ctx.effect === "function") {
6440
+ ctx.effect(() => {
6441
+ if (menuDisposer) {
6442
+ menuDisposer();
6443
+ menuDisposer = null;
6444
+ }
6445
+ });
6446
+ }
6447
+ }
6448
+ }
6189
6449
  registerThemeListener(ctx);
6190
6450
  }
6191
6451
  return module.exports; } });
package/lib/parse.js CHANGED
@@ -1788,6 +1788,34 @@ export const START_INSTRUCTIONS = {
1788
1788
  reviewOnly: START_INSTRUCTION_REVIEW_ONLY,
1789
1789
  }
1790
1790
 
1791
+ /**
1792
+ * Scoped dry-run review instruction (right-click menu: file / directory).
1793
+ *
1794
+ * `iterate_review plan` has no per-call `scope` parameter, so a single-file or
1795
+ * single-directory review is expressed as a directed instruction the model
1796
+ * resolves through the `workflow` tool exactly like the full dry-run flow, but
1797
+ * with the review scope explicitly narrowed to the clicked subject. The user
1798
+ * never has to type the path themselves.
1799
+ *
1800
+ * @param {{ file?: string, line?: number, dir?: string }} scope
1801
+ * @returns {string} empty when no scope is given (defensive; never throw)
1802
+ */
1803
+ export function buildScopedReviewInstruction(scope) {
1804
+ const hasFile = scope && typeof scope.file === 'string' && scope.file.length > 0
1805
+ const hasDir = !hasFile && scope && typeof scope.dir === 'string' && scope.dir.length > 0
1806
+ if (!hasFile && !hasDir) return ''
1807
+ const subject = hasFile
1808
+ ? `文件 \`${scope.file}\`${typeof scope.line === 'number' && scope.line > 0 ? `(第 ${scope.line} 行)` : ''}`
1809
+ : `目录 \`${scope.dir}\``
1810
+ return (
1811
+ `开始一次仅评审(dry-run,不修改任何文件),审查范围限定在${subject}:` +
1812
+ '请调用 `workflow` 工具运行 iterate 工作流,mode: "dry-run",' +
1813
+ `按系统提示的 Iterate Workflow 执行 plan → 评审 → 分诊 → \`iterate_review aggregate\` / \`meta-review\` 终报并 \`iterate_transcript\` capture;` +
1814
+ `评审时把范围限定在${subject},其余文件仅作上下文、不得给出修复。` +
1815
+ '全程不要调用 `iterate_fix` / `iterate_rollback`。'
1816
+ )
1817
+ }
1818
+
1791
1819
  /** Normalize an arbitrary finding-ish node into the payload field set. */
1792
1820
  function fixFindingPayload(finding) {
1793
1821
  const f = finding && typeof finding === 'object' ? finding : {}
@@ -1954,6 +1982,30 @@ export function buildPreflightInstruction() {
1954
1982
  )
1955
1983
  }
1956
1984
 
1985
+ /**
1986
+ * `iterate_config` write instruction that installs the validation commands the
1987
+ * preflight panel detected. Copies the exact command set the user saw, so the
1988
+ * model applies it deterministically instead of re-detecting (and drifting).
1989
+ *
1990
+ * @param {string[]} commands
1991
+ * @returns {string}
1992
+ */
1993
+ export function buildValidationApplyInstruction(commands) {
1994
+ const list = Array.isArray(commands)
1995
+ ? commands.filter((c) => typeof c === 'string' && c.length > 0)
1996
+ : []
1997
+ const payload = {
1998
+ operation: 'write',
1999
+ updates: { validation: { commands: { default: list } } },
2000
+ }
2001
+ return (
2002
+ '请调用 `iterate_config` 把检测到的验证命令写入 `validation.commands.default`(会先备份配置):\n\n' +
2003
+ '```json\n' +
2004
+ `${JSON.stringify(payload, null, 2)}\n` +
2005
+ '```'
2006
+ )
2007
+ }
2008
+
1957
2009
  /**
1958
2010
  * `iterate_rollback` payload for one applied fix id.
1959
2011
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iterate-plugin",
3
- "version": "3.12.0",
3
+ "version": "3.14.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",
@@ -54,7 +54,9 @@
54
54
  "0.2.1-rc.2": "compatible",
55
55
  "0.2.1-rc.3": "compatible",
56
56
  "0.2.1-rc.4": "compatible",
57
- "0.2.2-alpha.2": "compatible"
57
+ "0.2.1-rc.5": "compatible",
58
+ "0.2.2-alpha.2": "compatible",
59
+ "0.2.2-alpha.3": "compatible"
58
60
  }
59
61
  },
60
62
  "client": {
@@ -95,10 +97,10 @@
95
97
  "js-yaml": "4.3.2"
96
98
  },
97
99
  "peerDependencies": {
98
- "@deepseek-ai/cordis": "^4.0.5-alpha.2 || ~4.0.6-alpha.1",
99
- "@deepseek-ai/dsh-session": "0.1.1-rc.1 || 0.1.2-alpha.4 || 0.1.2-alpha.5 || 0.1.2-rc.1 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.6-alpha.1 || 0.1.6-alpha.2 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4 || 0.2.2-alpha.2",
100
- "@deepseek-ai/dsh-tools": "0.1.1-rc.1 || 0.1.2-alpha.4 || 0.1.2-alpha.5 || 0.1.2-rc.1 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.6-alpha.1 || 0.1.6-alpha.2 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4 || 0.2.2-alpha.2",
101
- "@deepseek-ai/dsh-util-values": "0.1.2-alpha.5 || 0.1.2-rc.1 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.6-alpha.1 || 0.1.6-alpha.2 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4 || 0.2.2-alpha.2"
100
+ "@deepseek-ai/cordis": "^4.0.5-alpha.2 || ~4.0.6-alpha.1 || ~4.0.6-alpha.2",
101
+ "@deepseek-ai/dsh-session": "0.1.1-rc.1 || 0.1.2-alpha.4 || 0.1.2-alpha.5 || 0.1.2-rc.1 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.6-alpha.1 || 0.1.6-alpha.2 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4 || 0.2.1-rc.5 || 0.2.2-alpha.2 || 0.2.2-alpha.3",
102
+ "@deepseek-ai/dsh-tools": "0.1.1-rc.1 || 0.1.2-alpha.4 || 0.1.2-alpha.5 || 0.1.2-rc.1 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.6-alpha.1 || 0.1.6-alpha.2 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4 || 0.2.1-rc.5 || 0.2.2-alpha.2 || 0.2.2-alpha.3",
103
+ "@deepseek-ai/dsh-util-values": "0.1.2-alpha.5 || 0.1.2-rc.1 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.6-alpha.1 || 0.1.6-alpha.2 || 0.1.7-alpha.1 || 0.1.7-alpha.2 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-alpha.1 || 0.2.1-alpha.2 || 0.2.1-alpha.3 || 0.2.1-alpha.4 || 0.2.1-alpha.5 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4 || 0.2.1-rc.5 || 0.2.2-alpha.2 || 0.2.2-alpha.3"
102
104
  },
103
105
  "peerDependenciesMeta": {
104
106
  "@deepseek-ai/cordis": {
@@ -115,20 +117,20 @@
115
117
  }
116
118
  },
117
119
  "devDependencies": {
118
- "@deepseek-ai/cordis": "4.0.6-alpha.1",
119
- "@deepseek-ai/dsh-agent": "0.2.2-alpha.2",
120
- "@deepseek-ai/dsh-jobs": "0.2.2-alpha.2",
121
- "@deepseek-ai/dsh-llm": "0.2.2-alpha.2",
122
- "@deepseek-ai/dsh-ptc-runtime": "0.2.2-alpha.2",
123
- "@deepseek-ai/dsh-sandbox": "0.2.2-alpha.2",
124
- "@deepseek-ai/dsh-sandbox-policy": "0.2.2-alpha.2",
125
- "@deepseek-ai/dsh-scope": "0.2.2-alpha.2",
126
- "@deepseek-ai/dsh-session": "0.2.2-alpha.2",
127
- "@deepseek-ai/dsh-system-prompt": "0.2.2-alpha.2",
128
- "@deepseek-ai/dsh-tools": "0.2.2-alpha.2",
129
- "@deepseek-ai/dsh-user-approval": "0.2.2-alpha.2",
130
- "@deepseek-ai/dsh-util-values": "0.2.2-alpha.2",
131
- "@deepseek-ai/dsh-working-directory": "0.2.2-alpha.2",
120
+ "@deepseek-ai/cordis": "4.0.6-alpha.2",
121
+ "@deepseek-ai/dsh-agent": "0.2.2-alpha.3",
122
+ "@deepseek-ai/dsh-jobs": "0.2.2-alpha.3",
123
+ "@deepseek-ai/dsh-llm": "0.2.2-alpha.3",
124
+ "@deepseek-ai/dsh-ptc-runtime": "0.2.2-alpha.3",
125
+ "@deepseek-ai/dsh-sandbox": "0.2.2-alpha.3",
126
+ "@deepseek-ai/dsh-sandbox-policy": "0.2.2-alpha.3",
127
+ "@deepseek-ai/dsh-scope": "0.2.2-alpha.3",
128
+ "@deepseek-ai/dsh-session": "0.2.2-alpha.3",
129
+ "@deepseek-ai/dsh-system-prompt": "0.2.2-alpha.3",
130
+ "@deepseek-ai/dsh-tools": "0.2.2-alpha.3",
131
+ "@deepseek-ai/dsh-user-approval": "0.2.2-alpha.3",
132
+ "@deepseek-ai/dsh-util-values": "0.2.2-alpha.3",
133
+ "@deepseek-ai/dsh-working-directory": "0.2.2-alpha.3",
132
134
  "@types/js-yaml": "4.0.9",
133
135
  "@types/node": "22.15.0",
134
136
  "@types/react": "19.2.2",