@lmzhen/dsh-evolution-merge 0.30.5 → 0.30.7

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
@@ -814,10 +814,15 @@ async function recoverCommitJournals(io, home, now, skillRoot, restoreSource, is
814
814
  }
815
815
  if (!restoredArchived) for (const source of snapshot.sourceBodies) await io.writeText(skillBodyPath(skillRoot, source.name), source.body);
816
816
  }
817
- if (snapshot.create && snapshot.targetBody === null) try {
818
- await io.remove(skillBodyPath(skillRoot, snapshot.target));
819
- } catch {}
820
- else if (snapshot.targetBody !== null) await io.writeText(skillBodyPath(skillRoot, snapshot.target), snapshot.targetBody);
817
+ if (snapshot.create && snapshot.targetBody === null) {
818
+ try {
819
+ await io.remove(skillBodyPath(skillRoot, snapshot.target));
820
+ } catch {}
821
+ try {
822
+ const dir = join(skillRoot, snapshot.target);
823
+ if ((await io.list(dir)).length === 0) await io.remove(dir);
824
+ } catch {}
825
+ } else if (snapshot.targetBody !== null) await io.writeText(skillBodyPath(skillRoot, snapshot.target), snapshot.targetBody);
821
826
  const oldPaths = new Set((snapshot.targetSupport ?? []).map((file) => file.path));
822
827
  for (const file of snapshot.targetSupport ?? []) await io.writeText(skillFilePath(skillRoot, snapshot.target, file.path), file.content);
823
828
  for (const file of snapshot.targetSupportAfter ?? []) if (!oldPaths.has(file.path)) try {
@@ -1527,24 +1532,28 @@ function mergeUsage() {
1527
1532
  //#endregion
1528
1533
  //#region lib/types/read-commands.js
1529
1534
  async function handleReadOnlyMergeCommand(command, io, home) {
1535
+ const withNote = (text, note) => note === void 0 ? text : "note: " + note + "\n" + text;
1530
1536
  if (command.verb === "status") {
1531
1537
  const loaded = await loadMergeState(io, home);
1532
1538
  if (command.id !== void 0) {
1533
1539
  const row = loaded.state.rows.find((item) => item.id === command.id);
1534
- return row === void 0 ? "merge row not found: " + command.id : formatStatus([row]);
1540
+ return withNote(row === void 0 ? "merge row not found: " + command.id : formatStatus([row]), loaded.note);
1535
1541
  }
1536
- return formatStatus(loaded.state.rows);
1542
+ return withNote(formatStatus(loaded.state.rows), loaded.note);
1537
1543
  }
1538
1544
  if (command.verb === "pending") {
1539
1545
  const loaded = await loadMergeState(io, home);
1540
1546
  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");
1547
+ return withNote(command.expired === true ? formatStatus(rows) : formatQueue(rows, "pending"), loaded.note);
1542
1548
  }
1543
1549
  if (command.verb === "candidates") {
1544
1550
  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");
1551
+ return withNote(formatQueue(command.retry === true ? loaded.state.rows.filter((row) => row.state === "approved") : loaded.state.rows, command.retry === true ? "fuse" : "verify"), loaded.note);
1552
+ }
1553
+ if (command.verb === "skipped") {
1554
+ const loaded = await loadMergeState(io, home);
1555
+ return withNote(formatQueue(loaded.state.rows, "skipped"), loaded.note);
1546
1556
  }
1547
- if (command.verb === "skipped") return formatQueue((await loadMergeState(io, home)).state.rows, "skipped");
1548
1557
  return null;
1549
1558
  }
1550
1559
  function mergeCommandHelp() {
@@ -1554,8 +1563,8 @@ function mergeCommandHelp() {
1554
1563
  //#region lib/types/domain/errors.js
1555
1564
  function formatMergeError(error) {
1556
1565
  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";
1566
+ 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(" ");
1567
+ return "merge failed: " + error.message + " (" + error.code + ")\n" + where + "\nnext: fix the target or rerun after the cause is gone";
1559
1568
  }
1560
1569
  //#endregion
1561
1570
  //#region lib/types/merge-command.js
@@ -1767,7 +1776,16 @@ async function runScan(options) {
1767
1776
  if (!created.ok || created.row === null) throw new Error(created.ok ? "candidate row missing" : created.message);
1768
1777
  row = created.row;
1769
1778
  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);
1779
+ if (!placed.ok && placed.current.state === "skipped") {
1780
+ const restored = transition(placed.current, {
1781
+ type: "restore",
1782
+ at: at(options.now)
1783
+ });
1784
+ if (!restored.ok || restored.row === null) throw new Error("cannot restore skipped pair");
1785
+ row = restored.row;
1786
+ const retried = await upsertRow(options.io, options.home, options.now, row, "verify");
1787
+ if (!retried.ok) throw new Error("pair already in " + retried.current.state);
1788
+ } else if (!placed.ok) throw new Error("pair already in " + placed.current.state);
1771
1789
  } catch (error) {
1772
1790
  totals.failed += 1;
1773
1791
  pairs.push({
@@ -3749,7 +3767,7 @@ function createMergeRuntime(deps) {
3749
3767
  const managed = new Set(cards.filter((card) => card.managed === true && card.protectedBy == null).map((card) => card.name));
3750
3768
  const loadedState = await loadMergeState(deps.io, deps.home);
3751
3769
  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));
3770
+ 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
3771
  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
3772
  const result = await runScanCommand({
3755
3773
  ...common,
@@ -72,6 +72,12 @@ export async function recoverCommitJournals(io, home, now, skillRoot, restoreSou
72
72
  await io.remove(skillBodyPath(skillRoot, snapshot.target));
73
73
  }
74
74
  catch { /* already gone */ }
75
+ try {
76
+ const dir = join(skillRoot, snapshot.target);
77
+ if ((await io.list(dir)).length === 0)
78
+ await io.remove(dir);
79
+ }
80
+ catch { /* absent or non-empty */ }
75
81
  }
76
82
  else if (snapshot.targetBody !== null) {
77
83
  await io.writeText(skillBodyPath(skillRoot, snapshot.target), snapshot.targetBody);
@@ -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.7",
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.7",
31
+ "@lmzhen/dsh-evolution-merge-engine": "^0.30.7"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "@deepseek-ai/cordis": "^4.0.5"