@lmzhen/dsh-evolution-merge 0.30.5 → 0.30.6

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/index.js CHANGED
@@ -1527,24 +1527,28 @@ function mergeUsage() {
1527
1527
  //#endregion
1528
1528
  //#region lib/types/read-commands.js
1529
1529
  async function handleReadOnlyMergeCommand(command, io, home) {
1530
+ const withNote = (text, note) => note === void 0 ? text : "note: " + note + "\n" + text;
1530
1531
  if (command.verb === "status") {
1531
1532
  const loaded = await loadMergeState(io, home);
1532
1533
  if (command.id !== void 0) {
1533
1534
  const row = loaded.state.rows.find((item) => item.id === command.id);
1534
- return row === void 0 ? "merge row not found: " + command.id : formatStatus([row]);
1535
+ return withNote(row === void 0 ? "merge row not found: " + command.id : formatStatus([row]), loaded.note);
1535
1536
  }
1536
- return formatStatus(loaded.state.rows);
1537
+ return withNote(formatStatus(loaded.state.rows), loaded.note);
1537
1538
  }
1538
1539
  if (command.verb === "pending") {
1539
1540
  const loaded = await loadMergeState(io, home);
1540
1541
  const rows = command.expired === true ? loaded.state.rows.filter((row) => row.expired !== void 0) : loaded.state.rows;
1541
- return command.expired === true ? formatStatus(rows) : formatQueue(rows, "pending");
1542
+ return withNote(command.expired === true ? formatStatus(rows) : formatQueue(rows, "pending"), loaded.note);
1542
1543
  }
1543
1544
  if (command.verb === "candidates") {
1544
1545
  const loaded = await loadMergeState(io, home);
1545
- return formatQueue(command.retry === true ? loaded.state.rows.filter((row) => row.state === "approved") : loaded.state.rows, command.retry === true ? "fuse" : "verify");
1546
+ return withNote(formatQueue(command.retry === true ? loaded.state.rows.filter((row) => row.state === "approved") : loaded.state.rows, command.retry === true ? "fuse" : "verify"), loaded.note);
1547
+ }
1548
+ if (command.verb === "skipped") {
1549
+ const loaded = await loadMergeState(io, home);
1550
+ return withNote(formatQueue(loaded.state.rows, "skipped"), loaded.note);
1546
1551
  }
1547
- if (command.verb === "skipped") return formatQueue((await loadMergeState(io, home)).state.rows, "skipped");
1548
1552
  return null;
1549
1553
  }
1550
1554
  function mergeCommandHelp() {
@@ -1554,8 +1558,8 @@ function mergeCommandHelp() {
1554
1558
  //#region lib/types/domain/errors.js
1555
1559
  function formatMergeError(error) {
1556
1560
  if (error.kind === "parameter") return "merge: " + error.message + " (" + error.code + ")";
1557
- const detail = "\n" + (error.detail ?? "no additional detail");
1558
- return "merge failed: " + error.message + " (" + error.code + ")" + detail + "\nnext: fix the target or rerun after the cause is gone";
1561
+ const where = error.stage === void 0 && error.runId === void 0 ? error.detail ?? "no additional detail" : [error.stage === void 0 ? void 0 : "stage=" + error.stage, error.runId === void 0 ? void 0 : "run=" + error.runId].filter(Boolean).join(" ");
1562
+ return "merge failed: " + error.message + " (" + error.code + ")\n" + where + "\nnext: fix the target or rerun after the cause is gone";
1559
1563
  }
1560
1564
  //#endregion
1561
1565
  //#region lib/types/merge-command.js
@@ -1767,7 +1771,16 @@ async function runScan(options) {
1767
1771
  if (!created.ok || created.row === null) throw new Error(created.ok ? "candidate row missing" : created.message);
1768
1772
  row = created.row;
1769
1773
  const placed = await upsertRow(options.io, options.home, options.now, row, "verify");
1770
- if (!placed.ok) throw new Error("pair already in " + placed.current.state);
1774
+ if (!placed.ok && placed.current.state === "skipped") {
1775
+ const restored = transition(placed.current, {
1776
+ type: "restore",
1777
+ at: at(options.now)
1778
+ });
1779
+ if (!restored.ok || restored.row === null) throw new Error("cannot restore skipped pair");
1780
+ row = restored.row;
1781
+ const retried = await upsertRow(options.io, options.home, options.now, row, "verify");
1782
+ if (!retried.ok) throw new Error("pair already in " + retried.current.state);
1783
+ } else if (!placed.ok) throw new Error("pair already in " + placed.current.state);
1771
1784
  } catch (error) {
1772
1785
  totals.failed += 1;
1773
1786
  pairs.push({
@@ -3749,7 +3762,7 @@ function createMergeRuntime(deps) {
3749
3762
  const managed = new Set(cards.filter((card) => card.managed === true && card.protectedBy == null).map((card) => card.name));
3750
3763
  const loadedState = await loadMergeState(deps.io, deps.home);
3751
3764
  const skipped = skippedPairKeys(loadedState.state.rows, deps.params.mergeSkippedTtlDays, deps.now());
3752
- const held = new Set(loadedState.state.rows.filter((row) => row.queue === "pending" || row.queue === "fuse" || !canRetry(row)).map((row) => row.pairKey));
3765
+ const held = new Set(loadedState.state.rows.filter((row) => row.queue === "pending" || row.queue === "fuse" || row.queue === "verify" && !canRetry(row)).map((row) => row.pairKey));
3753
3766
  const conflictNotes = loadedState.state.rows.filter((row) => row.state === "pending" || row.state === "approved" || row.state === "fuse" || row.state === "skipped").map((row) => "conflict:" + row.state + ":" + row.pairKey + ":" + row.id);
3754
3767
  const result = await runScanCommand({
3755
3768
  ...common,
@@ -5,6 +5,8 @@ export interface MergeError {
5
5
  readonly code: string;
6
6
  readonly message: string;
7
7
  readonly detail?: string;
8
+ readonly stage?: string;
9
+ readonly runId?: string;
8
10
  }
9
11
  export declare function formatMergeError(error: MergeError): string;
10
12
  //# sourceMappingURL=errors.d.ts.map
@@ -1,7 +1,9 @@
1
1
  export function formatMergeError(error) {
2
2
  if (error.kind === 'parameter')
3
3
  return 'merge: ' + error.message + ' (' + error.code + ')';
4
- const detail = '\n' + (error.detail ?? 'no additional detail');
5
- return 'merge failed: ' + error.message + ' (' + error.code + ')' + detail + '\nnext: fix the target or rerun after the cause is gone';
4
+ const where = error.stage === undefined && error.runId === undefined
5
+ ? (error.detail ?? 'no additional detail')
6
+ : [error.stage === undefined ? undefined : 'stage=' + error.stage, error.runId === undefined ? undefined : 'run=' + error.runId].filter(Boolean).join(' ');
7
+ return 'merge failed: ' + error.message + ' (' + error.code + ')\n' + where + '\nnext: fix the target or rerun after the cause is gone';
6
8
  }
7
9
  //# sourceMappingURL=errors.js.map
@@ -2,27 +2,28 @@ import { loadMergeState } from "./state.js";
2
2
  import { formatQueue, formatStatus } from "./status.js";
3
3
  import { mergeUsage } from "./commands.js";
4
4
  export async function handleReadOnlyMergeCommand(command, io, home) {
5
+ const withNote = (text, note) => note === undefined ? text : 'note: ' + note + '\n' + text;
5
6
  if (command.verb === 'status') {
6
7
  const loaded = await loadMergeState(io, home);
7
8
  if (command.id !== undefined) {
8
9
  const row = loaded.state.rows.find(item => item.id === command.id);
9
- return row === undefined ? 'merge row not found: ' + command.id : formatStatus([row]);
10
+ return withNote(row === undefined ? 'merge row not found: ' + command.id : formatStatus([row]), loaded.note);
10
11
  }
11
- return formatStatus(loaded.state.rows);
12
+ return withNote(formatStatus(loaded.state.rows), loaded.note);
12
13
  }
13
14
  if (command.verb === 'pending') {
14
15
  const loaded = await loadMergeState(io, home);
15
16
  const rows = command.expired === true ? loaded.state.rows.filter(row => row.expired !== undefined) : loaded.state.rows;
16
- return command.expired === true ? formatStatus(rows) : formatQueue(rows, 'pending');
17
+ return withNote(command.expired === true ? formatStatus(rows) : formatQueue(rows, 'pending'), loaded.note);
17
18
  }
18
19
  if (command.verb === 'candidates') {
19
20
  const loaded = await loadMergeState(io, home);
20
21
  const rows = command.retry === true ? loaded.state.rows.filter(row => row.state === 'approved') : loaded.state.rows;
21
- return formatQueue(rows, command.retry === true ? 'fuse' : 'verify');
22
+ return withNote(formatQueue(rows, command.retry === true ? 'fuse' : 'verify'), loaded.note);
22
23
  }
23
24
  if (command.verb === 'skipped') {
24
25
  const loaded = await loadMergeState(io, home);
25
- return formatQueue(loaded.state.rows, 'skipped');
26
+ return withNote(formatQueue(loaded.state.rows, 'skipped'), loaded.note);
26
27
  }
27
28
  return null;
28
29
  }
@@ -104,7 +104,7 @@ export function createMergeRuntime(deps) {
104
104
  const managed = new Set(cards.filter(card => card.managed === true && card.protectedBy == null).map(card => card.name));
105
105
  const loadedState = await loadMergeState(deps.io, deps.home);
106
106
  const skipped = skippedPairKeys(loadedState.state.rows, deps.params.mergeSkippedTtlDays, deps.now());
107
- const held = new Set(loadedState.state.rows.filter(row => row.queue === 'pending' || row.queue === 'fuse' || !canRetry(row)).map(row => row.pairKey));
107
+ const held = new Set(loadedState.state.rows.filter(row => row.queue === 'pending' || row.queue === 'fuse' || (row.queue === 'verify' && !canRetry(row))).map(row => row.pairKey));
108
108
  const conflictNotes = loadedState.state.rows
109
109
  .filter(row => row.state === 'pending' || row.state === 'approved' || row.state === 'fuse' || row.state === 'skipped')
110
110
  .map(row => 'conflict:' + row.state + ':' + row.pairKey + ':' + row.id);
@@ -66,8 +66,18 @@ export async function runScan(options) {
66
66
  throw new Error(created.ok ? 'candidate row missing' : created.message);
67
67
  row = created.row;
68
68
  const placed = await upsertRow(options.io, options.home, options.now, row, 'verify');
69
- if (!placed.ok)
69
+ if (!placed.ok && placed.current.state === 'skipped') {
70
+ const restored = transition(placed.current, { type: 'restore', at: at(options.now) });
71
+ if (!restored.ok || restored.row === null)
72
+ throw new Error('cannot restore skipped pair');
73
+ row = restored.row;
74
+ const retried = await upsertRow(options.io, options.home, options.now, row, 'verify');
75
+ if (!retried.ok)
76
+ throw new Error('pair already in ' + retried.current.state);
77
+ }
78
+ else if (!placed.ok) {
70
79
  throw new Error('pair already in ' + placed.current.state);
80
+ }
71
81
  }
72
82
  catch (error) {
73
83
  totals.failed += 1;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-merge",
3
3
  "description": "Skill auto-merge domain: screening, two-round authoring, queues and commit orchestration (community build)",
4
- "version": "0.30.5",
4
+ "version": "0.30.6",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -27,8 +27,8 @@
27
27
  "license": "MIT",
28
28
  "dependencies": {
29
29
  "@deepseek-ai/schemastery": "^3.18.5",
30
- "@lmzhen/dsh-evolution-core": "^0.30.5",
31
- "@lmzhen/dsh-evolution-merge-engine": "^0.30.5"
30
+ "@lmzhen/dsh-evolution-core": "^0.30.6",
31
+ "@lmzhen/dsh-evolution-merge-engine": "^0.30.6"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "@deepseek-ai/cordis": "^4.0.5"