@lmzhen/dsh-evolution-merge 0.28.0 → 0.30.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.
Files changed (115) hide show
  1. package/README.md +21 -18
  2. package/lib/index.js +1583 -1209
  3. package/lib/types/adapters/channel.js +29 -0
  4. package/lib/types/adapters/engine-screen.d.ts +55 -0
  5. package/lib/types/adapters/engine-screen.js +45 -0
  6. package/lib/types/adapters/library.js +14 -0
  7. package/lib/types/adapters/model-runner.d.ts +28 -0
  8. package/lib/types/adapters/model-runner.js +34 -0
  9. package/lib/types/adapters/select.d.ts +3 -0
  10. package/lib/types/adapters/select.js +35 -0
  11. package/lib/types/adapters/undo.js +35 -0
  12. package/lib/types/approve-command.js +42 -0
  13. package/lib/types/authoring-flow.d.ts +7 -2
  14. package/lib/types/authoring-flow.js +48 -0
  15. package/lib/types/channel-port.d.ts +2 -1
  16. package/lib/types/channel-port.js +74 -0
  17. package/lib/types/commands.d.ts +9 -0
  18. package/lib/types/commands.js +79 -0
  19. package/lib/types/commit-assembly.d.ts +2 -0
  20. package/lib/types/commit-assembly.js +48 -0
  21. package/lib/types/commit-execute.js +52 -0
  22. package/lib/types/commit-plan.js +56 -0
  23. package/lib/types/commit-receipt.d.ts +6 -1
  24. package/lib/types/commit-receipt.js +85 -0
  25. package/lib/types/contracts.d.ts +21 -7
  26. package/lib/types/contracts.js +57 -0
  27. package/lib/types/coordinator.d.ts +11 -0
  28. package/lib/types/coordinator.js +131 -0
  29. package/lib/types/decision.js +61 -0
  30. package/lib/types/doctor.d.ts +1 -1
  31. package/lib/types/doctor.js +50 -0
  32. package/lib/types/domain/claim.d.ts +10 -0
  33. package/lib/types/domain/claim.js +12 -0
  34. package/lib/types/domain/commit-journal-store.d.ts +6 -0
  35. package/lib/types/domain/commit-journal-store.js +22 -0
  36. package/lib/types/domain/commit-journal.d.ts +16 -0
  37. package/lib/types/domain/commit-journal.js +25 -0
  38. package/lib/types/domain/errors.d.ts +10 -0
  39. package/lib/types/domain/errors.js +7 -0
  40. package/lib/types/domain/failure-route.d.ts +25 -0
  41. package/lib/types/domain/failure-route.js +22 -0
  42. package/lib/types/domain/indexes.d.ts +21 -0
  43. package/lib/types/domain/indexes.js +15 -0
  44. package/lib/types/domain/ledger.d.ts +17 -0
  45. package/lib/types/domain/ledger.js +65 -0
  46. package/lib/types/domain/markdown.d.ts +2 -0
  47. package/lib/types/domain/markdown.js +57 -0
  48. package/lib/types/domain/migrate-v1.d.ts +41 -0
  49. package/lib/types/domain/migrate-v1.js +23 -0
  50. package/lib/types/domain/pair-index-store.d.ts +6 -0
  51. package/lib/types/domain/pair-index-store.js +25 -0
  52. package/lib/types/domain/pair-index.d.ts +9 -0
  53. package/lib/types/domain/pair-index.js +28 -0
  54. package/lib/types/domain/resolve-pair.d.ts +11 -0
  55. package/lib/types/domain/resolve-pair.js +17 -0
  56. package/lib/types/domain/row.d.ts +113 -0
  57. package/lib/types/domain/row.js +111 -0
  58. package/lib/types/domain/run-record.d.ts +45 -0
  59. package/lib/types/domain/run-record.js +24 -0
  60. package/lib/types/domain/state.d.ts +57 -0
  61. package/lib/types/domain/state.js +65 -0
  62. package/lib/types/gate-cache.js +36 -0
  63. package/lib/types/index.d.ts +6 -7
  64. package/lib/types/index.js +241 -0
  65. package/lib/types/ladder.js +162 -0
  66. package/lib/types/merge-command.d.ts +3 -0
  67. package/lib/types/merge-command.js +43 -0
  68. package/lib/types/merge-routes.d.ts +15 -4
  69. package/lib/types/merge-routes.js +99 -0
  70. package/lib/types/merge-tool.js +69 -0
  71. package/lib/types/new-child-authoring.d.ts +1 -1
  72. package/lib/types/new-child-authoring.js +83 -0
  73. package/lib/types/pair-command.js +18 -0
  74. package/lib/types/params.d.ts +1 -0
  75. package/lib/types/params.js +49 -0
  76. package/lib/types/pending-actions.js +28 -0
  77. package/lib/types/prompts.d.ts +7 -5
  78. package/lib/types/prompts.js +60 -0
  79. package/lib/types/read-commands.js +32 -0
  80. package/lib/types/report.js +70 -0
  81. package/lib/types/resume-command.js +57 -0
  82. package/lib/types/round2-outcome.js +50 -0
  83. package/lib/types/routes.d.ts +20 -2
  84. package/lib/types/routes.js +75 -0
  85. package/lib/types/rules.js +55 -0
  86. package/lib/types/run-shell.d.ts +7 -0
  87. package/lib/types/run-shell.js +69 -0
  88. package/lib/types/runtime.d.ts +5 -4
  89. package/lib/types/runtime.js +269 -0
  90. package/lib/types/scan-command.d.ts +1 -0
  91. package/lib/types/scan-command.js +30 -0
  92. package/lib/types/scan-ports.d.ts +1 -1
  93. package/lib/types/scan-ports.js +93 -0
  94. package/lib/types/scan-run.d.ts +4 -0
  95. package/lib/types/scan-run.js +186 -0
  96. package/lib/types/skill-card.d.ts +12 -0
  97. package/lib/types/skill-card.js +2 -0
  98. package/lib/types/skipped.js +24 -0
  99. package/lib/types/stage-tool-registry.js +32 -0
  100. package/lib/types/stage-tool.js +31 -0
  101. package/lib/types/staged-tree.js +75 -0
  102. package/lib/types/staging-files.js +86 -0
  103. package/lib/types/staging.d.ts +13 -0
  104. package/lib/types/staging.js +149 -0
  105. package/lib/types/state.js +103 -0
  106. package/lib/types/status.d.ts +2 -1
  107. package/lib/types/status.js +28 -0
  108. package/lib/types/supersede.js +14 -0
  109. package/lib/types/undo-merge.js +23 -0
  110. package/package.json +4 -7
  111. package/lib/types/adapters/model.d.ts +0 -31
  112. package/lib/types/gate-filter.d.ts +0 -31
  113. package/lib/types/propose.d.ts +0 -26
  114. package/lib/types/screening-port.d.ts +0 -47
  115. package/lib/types/screening.d.ts +0 -30
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Merge doctor rows: state, pending, gate cache and staging are each probed read-only.
3
+ * A missing file is an empty feature, not a failure; an unreadable file is a warning the
4
+ * operator can act on. This module never repairs or writes.
5
+ */
6
+ import { join } from 'node:path';
7
+ import { probeList } from '@lmzhen/dsh-evolution-core';
8
+ import { loadMergeState } from "./state.js";
9
+ import { countRows } from "./status.js";
10
+ import { stagingRoot } from "./staging.js";
11
+ export const MERGE_DOCTOR_SCHEMA_VERSION = 3;
12
+ export async function mergeDoctor(io, home, configuredRoot) {
13
+ const rows = [];
14
+ const loaded = await loadMergeState(io, home);
15
+ const counts = countRows(loaded.state.rows);
16
+ rows.push({
17
+ key: 'state',
18
+ label: '合并状态',
19
+ status: loaded.note === undefined ? 'ok' : 'warn',
20
+ detail: loaded.note ?? 'pairs ' + String(loaded.state.rows.length) + ';verify ' + String(counts.verify) + ' / pending ' + String(counts.pending) + ' / fuse ' + String(counts.fuse) + ' / skipped ' + String(counts.skipped),
21
+ });
22
+ const modelRoot = configuredRoot !== undefined && configuredRoot !== '' ? configuredRoot : process.env.SKILL_MERGE_MODELS_ROOT;
23
+ if (modelRoot === undefined || modelRoot === '') {
24
+ rows.push({ key: 'models', label: '合并模型', status: 'warn', detail: '未设置 SKILL_MERGE_MODELS_ROOT;合并扫描会 fail loud' });
25
+ }
26
+ else {
27
+ const int8 = join(modelRoot, 'potion-multilingual-128M-int8');
28
+ const pca = join(modelRoot, 'potion-multilingual-128M-pca64-int8');
29
+ const ok = await io.exists(int8) && await io.exists(pca);
30
+ rows.push({ key: 'models', label: '合并模型', status: ok ? 'ok' : 'warn', detail: ok ? modelRoot : '缺少 int8/pca64:' + modelRoot });
31
+ }
32
+ const runsDir = join(home, 'merge', 'runs');
33
+ const listed = await probeList(io, runsDir);
34
+ rows.push({
35
+ key: 'staging',
36
+ label: '暂存树',
37
+ status: listed.kind === 'unknown' ? 'unknown' : 'ok',
38
+ detail: listed.kind === 'unknown'
39
+ ? '无法列出 ' + runsDir + ':' + listed.reason
40
+ : listed.kind === 'absent' ? '0 个 run 目录' : String(listed.value.length) + ' 个 run 目录',
41
+ });
42
+ rows.push({
43
+ key: 'staging-path',
44
+ label: '暂存路径',
45
+ status: 'ok',
46
+ detail: stagingRoot(home, '<runId>'),
47
+ });
48
+ return rows;
49
+ }
50
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1,10 @@
1
+ /** Pure approve-claim lease. No IO. */
2
+ export interface Claim {
3
+ readonly id: string;
4
+ readonly token: string;
5
+ readonly expiresAt: number;
6
+ }
7
+ export declare function acquireClaim(current: Claim | undefined, now: number, leaseMs: number, token: string): Claim | undefined;
8
+ export declare function releaseClaim(current: Claim | undefined, token: string): Claim | undefined;
9
+ export declare function isClaimExpired(current: Claim | undefined, now: number): boolean;
10
+ //# sourceMappingURL=claim.d.ts.map
@@ -0,0 +1,12 @@
1
+ export function acquireClaim(current, now, leaseMs, token) {
2
+ if (current !== undefined && current.expiresAt > now)
3
+ return undefined;
4
+ return { id: 'claim', token, expiresAt: now + Math.max(1, leaseMs) };
5
+ }
6
+ export function releaseClaim(current, token) {
7
+ return current?.token === token ? undefined : current;
8
+ }
9
+ export function isClaimExpired(current, now) {
10
+ return current !== undefined && current.expiresAt <= now;
11
+ }
12
+ //# sourceMappingURL=claim.js.map
@@ -0,0 +1,6 @@
1
+ import type { EvolutionIoLike } from '@lmzhen/dsh-evolution-core';
2
+ import type { CommitJournal } from './commit-journal.ts';
3
+ export declare function journalPath(home: string, runId: string): string;
4
+ export declare function writeJournal(io: EvolutionIoLike, home: string, journal: CommitJournal): Promise<string>;
5
+ export declare function readJournal(io: EvolutionIoLike, home: string, runId: string): Promise<CommitJournal | null>;
6
+ //# sourceMappingURL=commit-journal-store.d.ts.map
@@ -0,0 +1,22 @@
1
+ /** Durable commit journal: <home>/merge/journals/<run>.json. */
2
+ import { join } from 'node:path';
3
+ export function journalPath(home, runId) {
4
+ return join(home, 'merge', 'journals', runId.replace(/[^A-Za-z0-9_-]+/g, '_') + '.json');
5
+ }
6
+ export async function writeJournal(io, home, journal) {
7
+ const path = journalPath(home, journal.runId);
8
+ await io.writeText(path, JSON.stringify(journal, null, 2));
9
+ return path;
10
+ }
11
+ export async function readJournal(io, home, runId) {
12
+ const raw = await io.readText(journalPath(home, runId));
13
+ if (raw === null)
14
+ return null;
15
+ try {
16
+ return JSON.parse(raw);
17
+ }
18
+ catch {
19
+ return null;
20
+ }
21
+ }
22
+ //# sourceMappingURL=commit-journal-store.js.map
@@ -0,0 +1,16 @@
1
+ /** Pure commit-journal phases for target-first merge. */
2
+ export type JournalPhase = 'prepared' | 'target-written' | 'sources-archived' | 'committed' | 'rolled-back' | 'failed';
3
+ export interface CommitJournal {
4
+ readonly runId: string;
5
+ readonly unitId?: string;
6
+ readonly target: string;
7
+ readonly create: boolean;
8
+ readonly sources: readonly string[];
9
+ readonly phase: JournalPhase;
10
+ readonly updatedAt: string;
11
+ }
12
+ export declare function advanceJournal(journal: CommitJournal, phase: JournalPhase, at: string): CommitJournal;
13
+ export type JournalRecovery = 'none' | 'retry-commit' | 'rollback-target' | 'rollback-sources';
14
+ /** What a restart may do with the last durable phase. */
15
+ export declare function recoveryFor(journal: CommitJournal): JournalRecovery;
16
+ //# sourceMappingURL=commit-journal.d.ts.map
@@ -0,0 +1,25 @@
1
+ const NEXT = {
2
+ prepared: ['target-written', 'failed'],
3
+ 'target-written': ['sources-archived', 'rolled-back', 'failed'],
4
+ 'sources-archived': ['committed', 'rolled-back', 'failed'],
5
+ committed: [],
6
+ 'rolled-back': [],
7
+ failed: [],
8
+ };
9
+ export function advanceJournal(journal, phase, at) {
10
+ if (!NEXT[journal.phase].includes(phase))
11
+ throw new Error('invalid journal transition: ' + journal.phase + ' -> ' + phase);
12
+ return { ...journal, phase, updatedAt: at };
13
+ }
14
+ /** What a restart may do with the last durable phase. */
15
+ export function recoveryFor(journal) {
16
+ switch (journal.phase) {
17
+ case 'prepared': return 'retry-commit';
18
+ case 'target-written': return 'rollback-target';
19
+ case 'sources-archived': return 'rollback-sources';
20
+ case 'committed':
21
+ case 'rolled-back':
22
+ case 'failed': return 'none';
23
+ }
24
+ }
25
+ //# sourceMappingURL=commit-journal.js.map
@@ -0,0 +1,10 @@
1
+ /** Pure merge error rendering: parameter errors are two lines, runtime/target failures three. */
2
+ export type MergeErrorKind = 'parameter' | 'runtime' | 'target' | 'approval';
3
+ export interface MergeError {
4
+ readonly kind: MergeErrorKind;
5
+ readonly code: string;
6
+ readonly message: string;
7
+ readonly detail?: string;
8
+ }
9
+ export declare function formatMergeError(error: MergeError): string;
10
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1,7 @@
1
+ export function formatMergeError(error) {
2
+ if (error.kind === 'parameter')
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';
6
+ }
7
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1,25 @@
1
+ /** Pure failure routing for merge errors. */
2
+ export type MergeFailureRoute = {
3
+ readonly kind: 'parameter';
4
+ readonly reportOnly: false;
5
+ } | {
6
+ readonly kind: 'source-gone';
7
+ readonly reportOnly: true;
8
+ } | {
9
+ readonly kind: 'target-taken';
10
+ readonly reportOnly: true;
11
+ } | {
12
+ readonly kind: 'validation';
13
+ readonly reportOnly: true;
14
+ } | {
15
+ readonly kind: 'commit';
16
+ readonly reportOnly: true;
17
+ } | {
18
+ readonly kind: 'schema';
19
+ readonly reportOnly: true;
20
+ } | {
21
+ readonly kind: 'runtime';
22
+ readonly reportOnly: true;
23
+ };
24
+ export declare function routeFailure(code: string): MergeFailureRoute;
25
+ //# sourceMappingURL=failure-route.d.ts.map
@@ -0,0 +1,22 @@
1
+ const PARAMETER = new Set(['bad-pair', 'bad-target', 'bad-flag', 'usage']);
2
+ const SOURCE_GONE = new Set(['source-gone', 'source-missing']);
3
+ const TARGET_TAKEN = new Set(['target-taken', 'target-exists']);
4
+ const VALIDATION = new Set(['validation-failed', 'invalid-decision']);
5
+ const COMMIT = new Set(['commit-failed', 'commit-refused']);
6
+ const SCHEMA = new Set(['schema-invalid', 'schema-version']);
7
+ export function routeFailure(code) {
8
+ if (PARAMETER.has(code))
9
+ return { kind: 'parameter', reportOnly: false };
10
+ if (SOURCE_GONE.has(code))
11
+ return { kind: 'source-gone', reportOnly: true };
12
+ if (TARGET_TAKEN.has(code))
13
+ return { kind: 'target-taken', reportOnly: true };
14
+ if (VALIDATION.has(code))
15
+ return { kind: 'validation', reportOnly: true };
16
+ if (COMMIT.has(code))
17
+ return { kind: 'commit', reportOnly: true };
18
+ if (SCHEMA.has(code))
19
+ return { kind: 'schema', reportOnly: true };
20
+ return { kind: 'runtime', reportOnly: true };
21
+ }
22
+ //# sourceMappingURL=failure-route.js.map
@@ -0,0 +1,21 @@
1
+ /** Pure pairKey result index and archived-history projection. No IO. */
2
+ export interface PairIndexEntryV2 {
3
+ readonly pairKey: string;
4
+ readonly runId: string;
5
+ readonly result: 'merged' | 'kept' | 'failed' | 'cancelled' | 'undone';
6
+ readonly target?: string;
7
+ readonly reportRef?: string;
8
+ }
9
+ export type HistoryKind = 'merged' | 'deleted' | 'split' | 'other';
10
+ export interface HistoryRecord {
11
+ readonly name: string;
12
+ readonly kind: HistoryKind;
13
+ readonly reason: string;
14
+ readonly lastVersion: number;
15
+ readonly archivedAt: string;
16
+ readonly sourceRunId?: string;
17
+ }
18
+ export declare function putPair(index: ReadonlyMap<string, PairIndexEntryV2>, entry: PairIndexEntryV2): Map<string, PairIndexEntryV2>;
19
+ export declare function historyRecord(input: HistoryRecord): HistoryRecord;
20
+ export declare function summarizeHistory(records: readonly HistoryRecord[]): Record<HistoryKind, number>;
21
+ //# sourceMappingURL=indexes.d.ts.map
@@ -0,0 +1,15 @@
1
+ export function putPair(index, entry) {
2
+ const next = new Map(index);
3
+ next.set(entry.pairKey, entry);
4
+ return next;
5
+ }
6
+ export function historyRecord(input) {
7
+ return { ...input, lastVersion: Math.max(1, Math.trunc(input.lastVersion)) };
8
+ }
9
+ export function summarizeHistory(records) {
10
+ const counts = { merged: 0, deleted: 0, split: 0, other: 0 };
11
+ for (const record of records)
12
+ counts[record.kind] += 1;
13
+ return counts;
14
+ }
15
+ //# sourceMappingURL=indexes.js.map
@@ -0,0 +1,17 @@
1
+ import type { MergeRowV2 } from './row.ts';
2
+ import { type MergeQueue } from './state.ts';
3
+ export interface LedgerV2 {
4
+ readonly schemaVersion: 2;
5
+ readonly rows: readonly MergeRowV2[];
6
+ }
7
+ export interface LedgerParseResult {
8
+ readonly ok: boolean;
9
+ readonly ledger: LedgerV2;
10
+ readonly note?: string;
11
+ }
12
+ export declare function parseLedger(raw: string | null): LedgerParseResult;
13
+ export declare function upsert(rows: readonly MergeRowV2[], row: MergeRowV2): readonly MergeRowV2[];
14
+ export declare function remove(rows: readonly MergeRowV2[], pairKey: string): readonly MergeRowV2[];
15
+ export declare function rowsOfQueue(rows: readonly MergeRowV2[], queue: MergeQueue): readonly MergeRowV2[];
16
+ export declare function activeCounts(rows: readonly MergeRowV2[]): Record<MergeQueue, number>;
17
+ //# sourceMappingURL=ledger.d.ts.map
@@ -0,0 +1,65 @@
1
+ /** Pure v2 ledger normalization/projection. No IO. */
2
+ import { migrateV1Row } from "./migrate-v1.js";
3
+ import { queueOf } from "./state.js";
4
+ function isRow(value) {
5
+ const row = value;
6
+ return typeof row.id === 'string' && typeof row.pairKey === 'string' && typeof row.a === 'string' && typeof row.b === 'string'
7
+ && typeof row.state === 'string' && typeof row.createdAt === 'string' && typeof row.updatedAt === 'string';
8
+ }
9
+ export function parseLedger(raw) {
10
+ if (raw === null)
11
+ return { ok: true, ledger: { schemaVersion: 2, rows: [] } };
12
+ let parsed;
13
+ try {
14
+ parsed = JSON.parse(raw);
15
+ }
16
+ catch {
17
+ return { ok: false, ledger: { schemaVersion: 2, rows: [] }, note: 'invalid json' };
18
+ }
19
+ const body = parsed;
20
+ if (!Array.isArray(body.rows))
21
+ return { ok: false, ledger: { schemaVersion: 2, rows: [] }, note: 'rows missing' };
22
+ const rows = [];
23
+ let skipped = 0;
24
+ for (const value of body.rows) {
25
+ if (body.schemaVersion === 1) {
26
+ const legacy = value;
27
+ if (typeof legacy.id !== 'string' || typeof legacy.pairKey !== 'string' || typeof legacy.a !== 'string' || typeof legacy.b !== 'string' || typeof legacy.state !== 'string' || typeof legacy.createdAt !== 'string' || typeof legacy.updatedAt !== 'string') {
28
+ skipped += 1;
29
+ continue;
30
+ }
31
+ const row = migrateV1Row(legacy);
32
+ rows.push(row);
33
+ continue;
34
+ }
35
+ if (!isRow(value)) {
36
+ skipped += 1;
37
+ continue;
38
+ }
39
+ rows.push(value);
40
+ }
41
+ const note = skipped === 0 ? undefined : String(skipped) + ' invalid row(s) skipped';
42
+ return { ok: true, ledger: { schemaVersion: 2, rows }, ...(note === undefined ? {} : { note }) };
43
+ }
44
+ export function upsert(rows, row) {
45
+ const index = rows.findIndex(current => current.pairKey === row.pairKey);
46
+ if (index < 0)
47
+ return [...rows, row];
48
+ return rows.map((current, at) => at === index ? row : current);
49
+ }
50
+ export function remove(rows, pairKey) {
51
+ return rows.filter(row => row.pairKey !== pairKey);
52
+ }
53
+ export function rowsOfQueue(rows, queue) {
54
+ return rows.filter(row => queueOf(row.state) === queue);
55
+ }
56
+ export function activeCounts(rows) {
57
+ const counts = { verify: 0, pending: 0, fuse: 0, skipped: 0 };
58
+ for (const row of rows) {
59
+ if (row.expired !== undefined)
60
+ continue;
61
+ counts[queueOf(row.state)] += 1;
62
+ }
63
+ return counts;
64
+ }
65
+ //# sourceMappingURL=ledger.js.map
@@ -0,0 +1,2 @@
1
+ export declare function renderMarkdown(source: string): string;
2
+ //# sourceMappingURL=markdown.d.ts.map
@@ -0,0 +1,57 @@
1
+ /** Minimal safe Markdown renderer for the read-only body route. */
2
+ function escapeHtml(text) {
3
+ return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
4
+ }
5
+ export function renderMarkdown(source) {
6
+ const fence = String.fromCharCode(96, 96, 96);
7
+ const lines = source.split(/\r?\n/);
8
+ const out = [];
9
+ let inCode = false;
10
+ let inList = false;
11
+ for (const line of lines) {
12
+ if (line.startsWith(fence)) {
13
+ if (inCode) {
14
+ out.push('</code></pre>');
15
+ inCode = false;
16
+ }
17
+ else {
18
+ out.push('<pre><code>');
19
+ inCode = true;
20
+ }
21
+ continue;
22
+ }
23
+ if (inCode) {
24
+ out.push(escapeHtml(line));
25
+ continue;
26
+ }
27
+ const heading = /^(#{1,6})\s+(.*)$/.exec(line);
28
+ if (heading !== null) {
29
+ if (inList) {
30
+ out.push('</ul>');
31
+ inList = false;
32
+ }
33
+ const level = heading[1]?.length ?? 1;
34
+ out.push('<h' + String(level) + '>' + escapeHtml(heading[2] ?? '') + '</h' + String(level) + '>');
35
+ continue;
36
+ }
37
+ if (/^[-*]\s+/.test(line)) {
38
+ if (!inList) {
39
+ out.push('<ul>');
40
+ inList = true;
41
+ }
42
+ out.push('<li>' + escapeHtml(line.replace(/^[-*]\s+/, '')) + '</li>');
43
+ continue;
44
+ }
45
+ if (inList) {
46
+ out.push('</ul>');
47
+ inList = false;
48
+ }
49
+ out.push(line.trim() === '' ? '' : '<p>' + escapeHtml(line) + '</p>');
50
+ }
51
+ if (inList)
52
+ out.push('</ul>');
53
+ if (inCode)
54
+ out.push('</code></pre>');
55
+ return out.join('\n');
56
+ }
57
+ //# sourceMappingURL=markdown.js.map
@@ -0,0 +1,41 @@
1
+ /** Pure v1→v2 row migration. No IO. */
2
+ import type { MergeState } from './state.ts';
3
+ export interface LegacyMergeRow {
4
+ readonly id: string;
5
+ readonly pairKey: string;
6
+ readonly a: string;
7
+ readonly b: string;
8
+ readonly state: 'verify' | 'pending' | 'fuse' | 'skipped';
9
+ readonly queue?: 'pending' | 'retry' | 'skipped';
10
+ readonly failure?: unknown;
11
+ readonly decision?: unknown;
12
+ readonly runId?: string;
13
+ readonly unitId?: string;
14
+ readonly origin?: 'agent' | 'human';
15
+ readonly attempts?: number;
16
+ readonly stagingRef?: string;
17
+ readonly stagingExpiresAt?: number;
18
+ readonly createdAt: string;
19
+ readonly updatedAt: string;
20
+ }
21
+ export interface V2Row {
22
+ readonly schemaVersion: 2;
23
+ readonly id: string;
24
+ readonly pairKey: string;
25
+ readonly a: string;
26
+ readonly b: string;
27
+ readonly state: MergeState;
28
+ readonly queue: 'verify' | 'pending' | 'fuse' | 'skipped';
29
+ readonly failure?: unknown;
30
+ readonly decision?: unknown;
31
+ readonly runId?: string;
32
+ readonly unitId?: string;
33
+ readonly origin?: 'agent' | 'human';
34
+ readonly attempts?: number;
35
+ readonly stagingRef?: string;
36
+ readonly stagingExpiresAt?: number;
37
+ readonly createdAt: string;
38
+ readonly updatedAt: string;
39
+ }
40
+ export declare function migrateV1Row(row: LegacyMergeRow): V2Row;
41
+ //# sourceMappingURL=migrate-v1.d.ts.map
@@ -0,0 +1,23 @@
1
+ export function migrateV1Row(row) {
2
+ const queue = row.state === 'pending' ? 'pending' : row.state === 'skipped' ? 'skipped' : row.state === 'fuse' ? 'fuse' : 'verify';
3
+ return {
4
+ schemaVersion: 2,
5
+ id: row.id,
6
+ pairKey: row.pairKey,
7
+ a: row.a,
8
+ b: row.b,
9
+ state: row.state,
10
+ queue,
11
+ ...(row.failure === undefined ? {} : { failure: row.failure }),
12
+ ...(row.decision === undefined ? {} : { decision: row.decision }),
13
+ ...(row.runId === undefined ? {} : { runId: row.runId }),
14
+ ...(row.unitId === undefined ? {} : { unitId: row.unitId }),
15
+ ...(row.origin === undefined ? {} : { origin: row.origin }),
16
+ ...(row.attempts === undefined ? {} : { attempts: row.attempts }),
17
+ ...(row.stagingRef === undefined ? {} : { stagingRef: row.stagingRef }),
18
+ ...(row.stagingExpiresAt === undefined ? {} : { stagingExpiresAt: row.stagingExpiresAt }),
19
+ createdAt: row.createdAt,
20
+ updatedAt: row.updatedAt,
21
+ };
22
+ }
23
+ //# sourceMappingURL=migrate-v1.js.map
@@ -0,0 +1,6 @@
1
+ import type { EvolutionIoLike } from '@lmzhen/dsh-evolution-core';
2
+ import type { PairIndexEntryV2 } from './indexes.ts';
3
+ export declare function pairIndexPath(home: string): string;
4
+ export declare function loadPairIndex(io: EvolutionIoLike, home: string): Promise<Map<string, PairIndexEntryV2>>;
5
+ export declare function recordPair(io: EvolutionIoLike, home: string, entry: PairIndexEntryV2): Promise<void>;
6
+ //# sourceMappingURL=pair-index-store.d.ts.map
@@ -0,0 +1,25 @@
1
+ /** Durable pairKey index at <home>/merge/pair-index.json. */
2
+ import { join } from 'node:path';
3
+ import { transactIo } from '@lmzhen/dsh-evolution-core';
4
+ import { parsePairIndex, serializePairIndex } from "./pair-index.js";
5
+ export function pairIndexPath(home) {
6
+ return join(home, 'merge', 'pair-index.json');
7
+ }
8
+ export async function loadPairIndex(io, home) {
9
+ let raw = null;
10
+ try {
11
+ raw = await io.readText(pairIndexPath(home));
12
+ }
13
+ catch {
14
+ return new Map();
15
+ }
16
+ return parsePairIndex(raw);
17
+ }
18
+ export async function recordPair(io, home, entry) {
19
+ await transactIo(io, pairIndexPath(home), (current) => {
20
+ const index = parsePairIndex(current);
21
+ index.set(entry.pairKey, entry);
22
+ return serializePairIndex(index);
23
+ });
24
+ }
25
+ //# sourceMappingURL=pair-index-store.js.map
@@ -0,0 +1,9 @@
1
+ /** Pure pairKey index serialization. No IO. */
2
+ import type { PairIndexEntryV2 } from './indexes.ts';
3
+ export interface PairIndexFile {
4
+ readonly schemaVersion: 1;
5
+ readonly entries: readonly PairIndexEntryV2[];
6
+ }
7
+ export declare function parsePairIndex(raw: string | null): Map<string, PairIndexEntryV2>;
8
+ export declare function serializePairIndex(index: ReadonlyMap<string, PairIndexEntryV2>): string;
9
+ //# sourceMappingURL=pair-index.d.ts.map
@@ -0,0 +1,28 @@
1
+ export function parsePairIndex(raw) {
2
+ const index = new Map();
3
+ if (raw === null)
4
+ return index;
5
+ let parsed;
6
+ try {
7
+ parsed = JSON.parse(raw);
8
+ }
9
+ catch {
10
+ return index;
11
+ }
12
+ const body = parsed;
13
+ if (!Array.isArray(body.entries))
14
+ return index;
15
+ for (const value of body.entries) {
16
+ const entry = value;
17
+ if (typeof entry.pairKey !== 'string' || typeof entry.runId !== 'string' || typeof entry.result !== 'string')
18
+ continue;
19
+ if (!['merged', 'kept', 'failed', 'cancelled', 'undone'].includes(entry.result))
20
+ continue;
21
+ index.set(entry.pairKey, entry);
22
+ }
23
+ return index;
24
+ }
25
+ export function serializePairIndex(index) {
26
+ return JSON.stringify({ schemaVersion: 1, entries: [...index.values()] }, null, 2);
27
+ }
28
+ //# sourceMappingURL=pair-index.js.map
@@ -0,0 +1,11 @@
1
+ /** Pure pairKey lookup over run records. No IO. */
2
+ import type { PairIndexEntryV2 } from './indexes.ts';
3
+ import type { PairRecord } from './run-record.ts';
4
+ export interface PairRunLike {
5
+ readonly id: string;
6
+ readonly pairKey?: string;
7
+ readonly pairs: readonly PairRecord[];
8
+ readonly resultRef?: string;
9
+ }
10
+ export declare function findPairRun(runs: readonly PairRunLike[], pairKey: string): PairIndexEntryV2 | undefined;
11
+ //# sourceMappingURL=resolve-pair.d.ts.map
@@ -0,0 +1,17 @@
1
+ export function findPairRun(runs, pairKey) {
2
+ for (const run of runs) {
3
+ if (run.pairKey === pairKey) {
4
+ return { pairKey, runId: run.id, result: 'merged', ...(run.resultRef === undefined ? {} : { reportRef: run.resultRef }) };
5
+ }
6
+ const pair = run.pairs.find(item => item.pairKey === pairKey);
7
+ if (pair !== undefined) {
8
+ return {
9
+ pairKey, runId: run.id, result: pair.result === 'undone' ? 'undone' : pair.result === 'kept' ? 'kept' : pair.result === 'failed' ? 'failed' : pair.result === 'cancelled' ? 'cancelled' : 'merged',
10
+ ...(pair.target?.actual === undefined ? {} : { target: pair.target.actual }),
11
+ ...(run.resultRef === undefined ? {} : { reportRef: run.resultRef }),
12
+ };
13
+ }
14
+ }
15
+ return undefined;
16
+ }
17
+ //# sourceMappingURL=resolve-pair.js.map