@git-stunts/git-warp 11.2.1 → 11.3.3

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 (111) hide show
  1. package/bin/cli/commands/check.js +2 -2
  2. package/bin/cli/commands/doctor/checks.js +12 -12
  3. package/bin/cli/commands/doctor/index.js +2 -2
  4. package/bin/cli/commands/doctor/types.js +1 -1
  5. package/bin/cli/commands/history.js +12 -5
  6. package/bin/cli/commands/install-hooks.js +5 -5
  7. package/bin/cli/commands/materialize.js +2 -2
  8. package/bin/cli/commands/patch.js +142 -0
  9. package/bin/cli/commands/path.js +4 -4
  10. package/bin/cli/commands/query.js +54 -13
  11. package/bin/cli/commands/registry.js +4 -0
  12. package/bin/cli/commands/seek.js +17 -11
  13. package/bin/cli/commands/tree.js +230 -0
  14. package/bin/cli/commands/trust.js +3 -3
  15. package/bin/cli/commands/verify-audit.js +8 -7
  16. package/bin/cli/commands/view.js +6 -5
  17. package/bin/cli/infrastructure.js +26 -12
  18. package/bin/cli/shared.js +2 -2
  19. package/bin/cli/types.js +19 -8
  20. package/bin/presenters/index.js +35 -9
  21. package/bin/presenters/json.js +14 -12
  22. package/bin/presenters/text.js +155 -33
  23. package/index.d.ts +82 -22
  24. package/package.json +3 -2
  25. package/src/domain/WarpGraph.js +4 -1
  26. package/src/domain/crdt/ORSet.js +8 -8
  27. package/src/domain/errors/EmptyMessageError.js +2 -2
  28. package/src/domain/errors/ForkError.js +1 -1
  29. package/src/domain/errors/IndexError.js +1 -1
  30. package/src/domain/errors/OperationAbortedError.js +1 -1
  31. package/src/domain/errors/QueryError.js +1 -1
  32. package/src/domain/errors/SchemaUnsupportedError.js +1 -1
  33. package/src/domain/errors/ShardCorruptionError.js +2 -2
  34. package/src/domain/errors/ShardLoadError.js +2 -2
  35. package/src/domain/errors/ShardValidationError.js +4 -4
  36. package/src/domain/errors/StorageError.js +2 -2
  37. package/src/domain/errors/SyncError.js +1 -1
  38. package/src/domain/errors/TraversalError.js +1 -1
  39. package/src/domain/errors/TrustError.js +1 -1
  40. package/src/domain/errors/WarpError.js +2 -2
  41. package/src/domain/errors/WormholeError.js +1 -1
  42. package/src/domain/services/AuditReceiptService.js +6 -6
  43. package/src/domain/services/AuditVerifierService.js +52 -38
  44. package/src/domain/services/BitmapIndexBuilder.js +3 -3
  45. package/src/domain/services/BitmapIndexReader.js +28 -19
  46. package/src/domain/services/BoundaryTransitionRecord.js +18 -17
  47. package/src/domain/services/CheckpointSerializerV5.js +17 -16
  48. package/src/domain/services/CheckpointService.js +2 -2
  49. package/src/domain/services/CommitDagTraversalService.js +13 -13
  50. package/src/domain/services/DagPathFinding.js +7 -7
  51. package/src/domain/services/DagTopology.js +1 -1
  52. package/src/domain/services/DagTraversal.js +1 -1
  53. package/src/domain/services/HealthCheckService.js +1 -1
  54. package/src/domain/services/HookInstaller.js +1 -1
  55. package/src/domain/services/HttpSyncServer.js +92 -41
  56. package/src/domain/services/IndexRebuildService.js +7 -7
  57. package/src/domain/services/IndexStalenessChecker.js +4 -3
  58. package/src/domain/services/JoinReducer.js +11 -11
  59. package/src/domain/services/LogicalTraversal.js +1 -1
  60. package/src/domain/services/MessageCodecInternal.js +1 -1
  61. package/src/domain/services/MigrationService.js +1 -1
  62. package/src/domain/services/ObserverView.js +8 -8
  63. package/src/domain/services/PatchBuilderV2.js +42 -26
  64. package/src/domain/services/ProvenanceIndex.js +1 -1
  65. package/src/domain/services/ProvenancePayload.js +1 -1
  66. package/src/domain/services/QueryBuilder.js +3 -3
  67. package/src/domain/services/StateDiff.js +14 -11
  68. package/src/domain/services/StateSerializerV5.js +2 -2
  69. package/src/domain/services/StreamingBitmapIndexBuilder.js +26 -24
  70. package/src/domain/services/SyncAuthService.js +3 -2
  71. package/src/domain/services/SyncProtocol.js +25 -11
  72. package/src/domain/services/TemporalQuery.js +9 -6
  73. package/src/domain/services/TranslationCost.js +7 -5
  74. package/src/domain/services/WormholeService.js +16 -7
  75. package/src/domain/trust/TrustCanonical.js +3 -3
  76. package/src/domain/trust/TrustEvaluator.js +18 -3
  77. package/src/domain/trust/TrustRecordService.js +30 -23
  78. package/src/domain/trust/TrustStateBuilder.js +21 -8
  79. package/src/domain/trust/canonical.js +6 -6
  80. package/src/domain/types/TickReceipt.js +1 -1
  81. package/src/domain/types/WarpErrors.js +45 -0
  82. package/src/domain/types/WarpOptions.js +29 -0
  83. package/src/domain/types/WarpPersistence.js +41 -0
  84. package/src/domain/types/WarpTypes.js +2 -2
  85. package/src/domain/types/WarpTypesV2.js +2 -2
  86. package/src/domain/utils/MinHeap.js +6 -5
  87. package/src/domain/utils/canonicalStringify.js +5 -4
  88. package/src/domain/utils/roaring.js +31 -5
  89. package/src/domain/warp/PatchSession.js +9 -18
  90. package/src/domain/warp/_wiredMethods.d.ts +199 -45
  91. package/src/domain/warp/checkpoint.methods.js +5 -1
  92. package/src/domain/warp/fork.methods.js +2 -2
  93. package/src/domain/warp/materialize.methods.js +55 -5
  94. package/src/domain/warp/materializeAdvanced.methods.js +15 -4
  95. package/src/domain/warp/patch.methods.js +54 -29
  96. package/src/domain/warp/provenance.methods.js +5 -3
  97. package/src/domain/warp/query.methods.js +6 -5
  98. package/src/domain/warp/sync.methods.js +16 -11
  99. package/src/globals.d.ts +64 -0
  100. package/src/infrastructure/adapters/BunHttpAdapter.js +14 -9
  101. package/src/infrastructure/adapters/CasSeekCacheAdapter.js +9 -4
  102. package/src/infrastructure/adapters/DenoHttpAdapter.js +5 -6
  103. package/src/infrastructure/adapters/GitGraphAdapter.js +14 -12
  104. package/src/infrastructure/adapters/NodeHttpAdapter.js +2 -2
  105. package/src/infrastructure/adapters/WebCryptoAdapter.js +2 -2
  106. package/src/visualization/layouts/converters.js +2 -2
  107. package/src/visualization/layouts/elkAdapter.js +1 -1
  108. package/src/visualization/layouts/elkLayout.js +10 -7
  109. package/src/visualization/layouts/index.js +1 -1
  110. package/src/visualization/renderers/ascii/seek.js +16 -6
  111. package/src/visualization/renderers/svg/index.js +1 -1
@@ -7,6 +7,28 @@
7
7
 
8
8
  import { formatStructuralDiff } from '../../src/visualization/renderers/ascii/seek.js';
9
9
 
10
+ // ── Payload typedefs ────────────────────────────────────────────────────────
11
+
12
+ /**
13
+ * @typedef {{ installed: boolean, foreign?: boolean, current?: boolean, version?: string }} HookStatus
14
+ * @typedef {{ repo: string, graphs: Array<{ name: string, writers?: { count: number } | null, checkpoint?: { sha: string } | null, coverage?: { sha: string } | null, cursor?: { active: boolean, tick: number, mode: string } | null }> }} InfoPayload
15
+ * @typedef {{ graph: string, stateHash?: string, nodes: Array<{ id?: string, props?: Record<string, unknown>, edges?: NodeEdges }>, _renderedAscii?: string, _renderedSvg?: string }} QueryPayload
16
+ * @typedef {{ outgoing?: Array<{ label: string, to: string }>, incoming?: Array<{ label: string, from: string }> }} NodeEdges
17
+ * @typedef {{ graph: string, from: string, to: string, found: boolean, length?: number, path?: string[] }} PathPayload
18
+ * @typedef {{ graph: string, health: { status: string }, checkpoint?: { sha: string, ageSeconds: number | null } | null, writers: { count: number, heads: Array<{ writerId: string, sha: string }> }, coverage?: { sha: string, missingWriters: string[] } | null, gc?: { totalTombstones: number, tombstoneRatio: number } | null, hook?: HookStatus | null, status?: { cachedState: string, patchesSinceCheckpoint: number, tombstoneRatio: number, writers: number } | null }} CheckPayload
19
+ * @typedef {{ graph: string, writer: string, nodeFilter?: string | null, entries: Array<{ sha: string, lamport: number, opCount: number }> }} HistoryPayload
20
+ * @typedef {{ error: { message: string } }} ErrorPayload
21
+ * @typedef {{ graphs: Array<{ graph: string, nodes?: number, edges?: number, checkpoint?: string, error?: string }> }} MaterializePayload
22
+ * @typedef {{ action: string, hookPath?: string, version?: string, backupPath?: string, name?: string }} InstallHooksPayload
23
+ * @typedef {{ graph?: string, action: string, tick?: number, maxTick?: number, ticks?: number[], nodes?: number, edges?: number, patchCount?: number, perWriter?: Record<string, unknown>, diff?: { nodes?: number, edges?: number } | null, tickReceipt?: Record<string, unknown>, structuralDiff?: import('../../src/domain/services/StateDiff.js').StateDiffResult | null, cursor?: { active: boolean, tick?: number }, message?: string, cursors?: Array<{ name: string, tick: number }>, activeTick?: number | null, name?: string, diffBaseline?: string, baselineTick?: number | null, truncated?: boolean, totalChanges?: number, shownChanges?: number }} SeekPayload
24
+ * @typedef {{ graph: string, health: string, checkedAt: string, summary: { checksRun: number, findingsTotal: number, ok: number, warn: number, fail: number, priorityActions: string[] }, findings: Array<{ status: string, id: string, message: string, fix?: string }> }} DoctorPayload
25
+ * @typedef {{ graph: string, verifiedAt: string, summary: { total: number, valid: number, partial: number, invalid: number }, chains: Array<{ writerId: string, status: string, receiptsVerified: number, since?: string, errors: Array<{ code: string, message: string }>, warnings: Array<{ code: string, message: string }> }>, trustWarning?: { message: string } }} VerifyAuditPayload
26
+ * @typedef {{ graph: string, trustVerdict: string, mode: string, trust: { source: string, evidenceSummary: { activeKeys: number, revokedKeys: number, activeBindings: number }, explanations: Array<{ trusted: boolean, writerId: string, reasonCode: string, reason: string }>, untrustedWriters: string[] } }} TrustPayload
27
+ * @typedef {{ type: string, node?: string, from?: string, to?: string, label?: string, key?: string, value?: unknown }} PatchOp
28
+ * @typedef {{ graph: string, sha: string, writer: string, lamport: number, schema?: number, ops: PatchOp[] }} PatchShowPayload
29
+ * @typedef {{ graph: string, total: number, showing: number, writerFilter?: string | null, entries: Array<{ sha: string, writer: string, lamport: number, opCount: number, nodeIds: string[] }> }} PatchListPayload
30
+ */
31
+
10
32
  // ── ANSI helpers ─────────────────────────────────────────────────────────────
11
33
 
12
34
  const ANSI_GREEN = '\x1b[32m';
@@ -26,7 +48,7 @@ function colorCachedState(state) {
26
48
  return `${ANSI_RED}${ANSI_DIM}${state}${ANSI_RESET}`;
27
49
  }
28
50
 
29
- /** @param {*} hook */
51
+ /** @param {HookStatus} hook */
30
52
  function formatHookStatusLine(hook) {
31
53
  if (!hook.installed && hook.foreign) {
32
54
  return "Hook: foreign hook present — run 'git warp install-hooks'";
@@ -42,7 +64,7 @@ function formatHookStatusLine(hook) {
42
64
 
43
65
  // ── Simple renderers ─────────────────────────────────────────────────────────
44
66
 
45
- /** @param {*} payload */
67
+ /** @param {InfoPayload} payload */
46
68
  export function renderInfo(payload) {
47
69
  const lines = [`Repo: ${payload.repo}`];
48
70
  lines.push(`Graphs: ${payload.graphs.length}`);
@@ -62,7 +84,25 @@ export function renderInfo(payload) {
62
84
  return `${lines.join('\n')}\n`;
63
85
  }
64
86
 
65
- /** @param {*} payload */
87
+ /**
88
+ * Appends edge lines for a single node to the output array.
89
+ * @param {string[]} lines
90
+ * @param {NodeEdges} edges
91
+ */
92
+ function appendNodeEdges(lines, edges) {
93
+ if (edges.outgoing && edges.outgoing.length > 0) {
94
+ for (const e of edges.outgoing) {
95
+ lines.push(` -> ${e.label} -> ${e.to}`);
96
+ }
97
+ }
98
+ if (edges.incoming && edges.incoming.length > 0) {
99
+ for (const e of edges.incoming) {
100
+ lines.push(` <- ${e.label} <- ${e.from}`);
101
+ }
102
+ }
103
+ }
104
+
105
+ /** @param {QueryPayload} payload */
66
106
  export function renderQuery(payload) {
67
107
  const lines = [
68
108
  `Graph: ${payload.graph}`,
@@ -76,21 +116,27 @@ export function renderQuery(payload) {
76
116
  if (node.props && Object.keys(node.props).length > 0) {
77
117
  lines.push(` props: ${JSON.stringify(node.props)}`);
78
118
  }
119
+ if (node.edges) {
120
+ appendNodeEdges(lines, node.edges);
121
+ }
79
122
  }
80
123
 
81
124
  return `${lines.join('\n')}\n`;
82
125
  }
83
126
 
84
- /** @param {*} payload */
127
+ /** @param {PathPayload} payload */
85
128
  export function renderPath(payload) {
86
129
  const lines = [
87
130
  `Graph: ${payload.graph}`,
88
131
  `From: ${payload.from}`,
89
132
  `To: ${payload.to}`,
90
133
  `Found: ${payload.found ? 'yes' : 'no'}`,
91
- `Length: ${payload.length}`,
92
134
  ];
93
135
 
136
+ if (payload.found) {
137
+ lines.push(`Length: ${payload.length}`);
138
+ }
139
+
94
140
  if (payload.path && payload.path.length > 0) {
95
141
  lines.push(`Path: ${payload.path.join(' -> ')}`);
96
142
  }
@@ -101,7 +147,7 @@ export function renderPath(payload) {
101
147
  /**
102
148
  * Appends checkpoint and writer lines to check output.
103
149
  * @param {string[]} lines
104
- * @param {*} payload
150
+ * @param {CheckPayload} payload
105
151
  */
106
152
  function appendCheckpointAndWriters(lines, payload) {
107
153
  if (payload.checkpoint?.sha) {
@@ -124,7 +170,7 @@ function appendCheckpointAndWriters(lines, payload) {
124
170
  /**
125
171
  * Appends coverage, gc, and hook lines to check output.
126
172
  * @param {string[]} lines
127
- * @param {*} payload
173
+ * @param {CheckPayload} payload
128
174
  */
129
175
  function appendCoverageAndExtras(lines, payload) {
130
176
  if (payload.coverage?.sha) {
@@ -146,7 +192,7 @@ function appendCoverageAndExtras(lines, payload) {
146
192
  }
147
193
  }
148
194
 
149
- /** @param {*} payload */
195
+ /** @param {CheckPayload} payload */
150
196
  export function renderCheck(payload) {
151
197
  const lines = [
152
198
  `Graph: ${payload.graph}`,
@@ -165,7 +211,7 @@ export function renderCheck(payload) {
165
211
  return `${lines.join('\n')}\n`;
166
212
  }
167
213
 
168
- /** @param {*} payload */
214
+ /** @param {HistoryPayload} payload */
169
215
  export function renderHistory(payload) {
170
216
  const lines = [
171
217
  `Graph: ${payload.graph}`,
@@ -184,12 +230,12 @@ export function renderHistory(payload) {
184
230
  return `${lines.join('\n')}\n`;
185
231
  }
186
232
 
187
- /** @param {*} payload */
233
+ /** @param {ErrorPayload} payload */
188
234
  export function renderError(payload) {
189
235
  return `Error: ${payload.error.message}\n`;
190
236
  }
191
237
 
192
- /** @param {*} payload */
238
+ /** @param {MaterializePayload} payload */
193
239
  export function renderMaterialize(payload) {
194
240
  if (payload.graphs.length === 0) {
195
241
  return 'No graphs found in repo.\n';
@@ -206,7 +252,7 @@ export function renderMaterialize(payload) {
206
252
  return `${lines.join('\n')}\n`;
207
253
  }
208
254
 
209
- /** @param {*} payload */
255
+ /** @param {InstallHooksPayload} payload */
210
256
  export function renderInstallHooks(payload) {
211
257
  if (payload.action === 'up-to-date') {
212
258
  return `Hook: already up to date (v${payload.version}) at ${payload.hookPath}\n`;
@@ -225,10 +271,10 @@ export function renderInstallHooks(payload) {
225
271
 
226
272
  /**
227
273
  * Formats a numeric delta as " (+N)" or " (-N)", or empty string for zero/non-finite.
228
- * @param {*} n
274
+ * @param {unknown} n
229
275
  * @returns {string}
230
276
  */
231
- function formatDelta(n) { // TODO(ts-cleanup): type CLI payload
277
+ function formatDelta(n) {
232
278
  if (typeof n !== 'number' || !Number.isFinite(n) || n === 0) {
233
279
  return '';
234
280
  }
@@ -238,10 +284,10 @@ function formatDelta(n) { // TODO(ts-cleanup): type CLI payload
238
284
 
239
285
  /**
240
286
  * Formats an operation summary object as a compact plain-text string.
241
- * @param {*} summary
287
+ * @param {Record<string, number> | null | undefined} summary
242
288
  * @returns {string}
243
289
  */
244
- function formatOpSummaryPlain(summary) { // TODO(ts-cleanup): type CLI payload
290
+ function formatOpSummaryPlain(summary) {
245
291
  const order = [
246
292
  ['NodeAdd', '+', 'node'],
247
293
  ['EdgeAdd', '+', 'edge'],
@@ -264,7 +310,7 @@ function formatOpSummaryPlain(summary) { // TODO(ts-cleanup): type CLI payload
264
310
  /**
265
311
  * Appends a per-writer tick receipt summary below a base line.
266
312
  * @param {string} baseLine
267
- * @param {*} payload
313
+ * @param {SeekPayload} payload
268
314
  * @returns {string}
269
315
  */
270
316
  function appendReceiptSummary(baseLine, payload) {
@@ -284,8 +330,12 @@ function appendReceiptSummary(baseLine, payload) {
284
330
  const maxWriterLen = Math.max(5, ...entries.map(([writerId]) => writerId.length));
285
331
  const receiptLines = [` Tick ${payload.tick}:`];
286
332
  for (const [writerId, entry] of entries) {
287
- const sha = typeof entry.sha === 'string' ? entry.sha.slice(0, 7) : '';
288
- const opSummary = entry.opSummary && typeof entry.opSummary === 'object' ? entry.opSummary : entry;
333
+ /** @type {Record<string, unknown>} */
334
+ const rec = /** @type {Record<string, unknown>} */ (entry);
335
+ const sha = typeof rec.sha === 'string' ? rec.sha.slice(0, 7) : '';
336
+ const opSummary = rec.opSummary && typeof rec.opSummary === 'object'
337
+ ? /** @type {Record<string, number>} */ (rec.opSummary)
338
+ : /** @type {Record<string, number>} */ (rec);
289
339
  receiptLines.push(` ${writerId.padEnd(maxWriterLen)} ${sha.padEnd(7)} ${formatOpSummaryPlain(opSummary)}`);
290
340
  }
291
341
 
@@ -294,7 +344,7 @@ function appendReceiptSummary(baseLine, payload) {
294
344
 
295
345
  /**
296
346
  * Builds human-readable state count strings from a seek payload.
297
- * @param {*} payload
347
+ * @param {SeekPayload} payload
298
348
  * @returns {{nodesStr: string, edgesStr: string, patchesStr: string}}
299
349
  */
300
350
  function buildStateStrings(payload) {
@@ -310,20 +360,20 @@ function buildStateStrings(payload) {
310
360
 
311
361
  /**
312
362
  * Renders the "tick" / "latest" / "load" seek action with receipt + structural diff.
313
- * @param {*} payload
363
+ * @param {SeekPayload} payload
314
364
  * @param {string} headerLine
315
365
  * @returns {string}
316
366
  */
317
367
  function renderSeekWithDiff(payload, headerLine) {
318
368
  const base = appendReceiptSummary(headerLine, payload);
319
- return base + formatStructuralDiff(payload);
369
+ return base + formatStructuralDiff(/** @type {import('../../src/visualization/renderers/ascii/seek.js').SeekPayload} */ (payload));
320
370
  }
321
371
 
322
372
  // ── Seek simple-action renderers ─────────────────────────────────────────────
323
373
 
324
374
  /**
325
375
  * Renders seek actions that don't involve state counts: clear-cache, list, drop, save.
326
- * @param {*} payload
376
+ * @param {SeekPayload} payload
327
377
  * @returns {string|null} Rendered string, or null if action is not simple
328
378
  */
329
379
  function renderSeekSimple(payload) {
@@ -344,11 +394,11 @@ function renderSeekSimple(payload) {
344
394
 
345
395
  /**
346
396
  * Renders the cursor list action.
347
- * @param {*} payload
397
+ * @param {SeekPayload} payload
348
398
  * @returns {string}
349
399
  */
350
400
  function renderSeekList(payload) {
351
- if (payload.cursors.length === 0) {
401
+ if (!payload.cursors || payload.cursors.length === 0) {
352
402
  return 'No saved cursors.\n';
353
403
  }
354
404
  const lines = [];
@@ -363,7 +413,7 @@ function renderSeekList(payload) {
363
413
 
364
414
  /**
365
415
  * Renders seek actions that show state: latest, load, tick, status.
366
- * @param {*} payload
416
+ * @param {SeekPayload} payload
367
417
  * @returns {string}
368
418
  */
369
419
  function renderSeekState(payload) {
@@ -396,19 +446,19 @@ function renderSeekState(payload) {
396
446
  payload,
397
447
  );
398
448
  }
399
- return `${payload.graph}: no cursor active, ${payload.ticks.length} ticks available\n`;
449
+ return `${payload.graph}: no cursor active, ${(payload.ticks ?? []).length} ticks available\n`;
400
450
  }
401
451
 
402
452
  // ── Seek main renderer ──────────────────────────────────────────────────────
403
453
 
404
- /** @param {*} payload */
454
+ /** @param {SeekPayload} payload */
405
455
  export function renderSeek(payload) {
406
456
  return renderSeekSimple(payload) ?? renderSeekState(payload);
407
457
  }
408
458
 
409
459
  // ── Doctor renderer ──────────────────────────────────────────────────────────
410
460
 
411
- /** @param {'ok'|'warn'|'fail'} status */
461
+ /** @param {string} status */
412
462
  function findingIcon(status) {
413
463
  if (status === 'ok') {
414
464
  return `${ANSI_GREEN}\u2713${ANSI_RESET}`;
@@ -419,7 +469,7 @@ function findingIcon(status) {
419
469
  return `${ANSI_RED}\u2717${ANSI_RESET}`;
420
470
  }
421
471
 
422
- /** @param {'ok'|'degraded'|'failed'} health */
472
+ /** @param {string} health */
423
473
  function colorHealth(health) {
424
474
  if (health === 'ok') {
425
475
  return `${ANSI_GREEN}${health}${ANSI_RESET}`;
@@ -430,7 +480,7 @@ function colorHealth(health) {
430
480
  return `${ANSI_RED}${health}${ANSI_RESET}`;
431
481
  }
432
482
 
433
- /** @param {*} payload */
483
+ /** @param {DoctorPayload} payload */
434
484
  export function renderDoctor(payload) {
435
485
  const lines = [
436
486
  `Graph: ${payload.graph}`,
@@ -468,7 +518,7 @@ function colorStatus(status) {
468
518
  return `${ANSI_RED}${status}${ANSI_RESET}`;
469
519
  }
470
520
 
471
- /** @param {*} payload */
521
+ /** @param {VerifyAuditPayload} payload */
472
522
  export function renderVerifyAudit(payload) {
473
523
  const lines = [
474
524
  `Graph: ${payload.graph}`,
@@ -513,7 +563,7 @@ function colorVerdict(verdict) {
513
563
  return `${ANSI_RED}${verdict}${ANSI_RESET}`;
514
564
  }
515
565
 
516
- /** @param {*} payload */
566
+ /** @param {TrustPayload} payload */
517
567
  export function renderTrust(payload) {
518
568
  const lines = [
519
569
  `Graph: ${payload.graph}`,
@@ -541,3 +591,75 @@ export function renderTrust(payload) {
541
591
 
542
592
  return `${lines.join('\n')}\n`;
543
593
  }
594
+
595
+ // ── Patch renderers ──────────────────────────────────────────────────────────
596
+
597
+ /**
598
+ * Formats a single operation line for patch show output.
599
+ * @param {PatchOp} op
600
+ * @returns {string|null}
601
+ */
602
+ function formatPatchOp(op) {
603
+ if (op.type === 'NodeAdd') {
604
+ return ` + node ${op.node}`;
605
+ }
606
+ if (op.type === 'NodeTombstone') {
607
+ return ` - node ${op.node}`;
608
+ }
609
+ if (op.type === 'EdgeAdd') {
610
+ return ` + edge ${op.from} -[${op.label}]-> ${op.to}`;
611
+ }
612
+ if (op.type === 'EdgeTombstone') {
613
+ return ` - edge ${op.from} -[${op.label}]-> ${op.to}`;
614
+ }
615
+ if (op.type === 'PropSet') {
616
+ return ` ~ ${op.node}.${op.key} = ${JSON.stringify(op.value)}`;
617
+ }
618
+ if (op.type === 'BlobValue') {
619
+ return ` + blob ${op.node}`;
620
+ }
621
+ return null;
622
+ }
623
+
624
+ /** @param {PatchShowPayload} payload */
625
+ export function renderPatchShow(payload) {
626
+ const lines = [
627
+ `Graph: ${payload.graph}`,
628
+ `SHA: ${payload.sha}`,
629
+ `Writer: ${payload.writer}`,
630
+ `Lamport: ${payload.lamport}`,
631
+ `Schema: ${payload.schema}`,
632
+ `Operations: ${payload.ops.length}`,
633
+ '',
634
+ ];
635
+
636
+ for (const op of payload.ops) {
637
+ const line = formatPatchOp(op);
638
+ if (line) {
639
+ lines.push(line);
640
+ }
641
+ }
642
+
643
+ return `${lines.join('\n')}\n`;
644
+ }
645
+
646
+ /** @param {PatchListPayload} payload */
647
+ export function renderPatchList(payload) {
648
+ const lines = [
649
+ `Graph: ${payload.graph}`,
650
+ `Patches: ${payload.showing}/${payload.total}`,
651
+ ];
652
+
653
+ if (payload.writerFilter) {
654
+ lines.push(`Writer: ${payload.writerFilter}`);
655
+ }
656
+
657
+ lines.push('');
658
+
659
+ for (const entry of payload.entries) {
660
+ const nodes = entry.nodeIds.length > 0 ? ` [${entry.nodeIds.join(', ')}]` : '';
661
+ lines.push(` ${entry.sha} L${String(entry.lamport).padStart(3)} ${entry.writer.padEnd(20)} ${entry.opCount} ops${nodes}`);
662
+ }
663
+
664
+ return `${lines.join('\n')}\n`;
665
+ }
package/index.d.ts CHANGED
@@ -566,6 +566,15 @@ export interface GitPlumbing {
566
566
  */
567
567
  export class InMemoryGraphAdapter extends GraphPersistencePort {
568
568
  constructor();
569
+
570
+ get emptyTree(): string;
571
+ commitNode(options: CreateNodeOptions): Promise<string>;
572
+ showNode(sha: string): Promise<string>;
573
+ getNodeInfo(sha: string): Promise<NodeInfo>;
574
+ logNodesStream(options: ListNodesOptions & { format: string }): Promise<AsyncIterable<Uint8Array | string>>;
575
+ logNodes(options: ListNodesOptions & { format: string }): Promise<string>;
576
+ ping(): Promise<PingResult>;
577
+ countNodes(ref: string): Promise<number>;
569
578
  }
570
579
 
571
580
  /**
@@ -1032,7 +1041,7 @@ export class SyncError extends Error {
1032
1041
  * Base error class for bitmap index operations.
1033
1042
  */
1034
1043
  export class IndexError extends Error {
1035
- readonly name: 'IndexError';
1044
+ readonly name: string;
1036
1045
  readonly code: string;
1037
1046
  readonly context: Record<string, unknown>;
1038
1047
 
@@ -1293,12 +1302,65 @@ export interface TemporalQuery {
1293
1302
  ): Promise<boolean>;
1294
1303
  }
1295
1304
 
1305
+ // ============================================================================
1306
+ // PatchV2 & PatchBuilderV2
1307
+ // ============================================================================
1308
+
1309
+ /**
1310
+ * WARP V5 patch object (schema 2 or 3).
1311
+ */
1312
+ export interface PatchV2 {
1313
+ /** Schema version (2 for node/edge ops, 3 if edge properties present) */
1314
+ schema: 2 | 3;
1315
+ /** Writer ID */
1316
+ writer: string;
1317
+ /** Lamport timestamp for ordering */
1318
+ lamport: number;
1319
+ /** Writer's observed frontier (version vector) */
1320
+ context: Record<string, number>;
1321
+ /** Ordered array of operations */
1322
+ ops: unknown[];
1323
+ /** Node/edge IDs read by this patch (provenance tracking) */
1324
+ reads?: string[];
1325
+ /** Node/edge IDs written by this patch (provenance tracking) */
1326
+ writes?: string[];
1327
+ }
1328
+
1329
+ /**
1330
+ * Fluent builder for creating WARP v5 patches with OR-Set semantics.
1331
+ *
1332
+ * Returned by WarpGraph.createPatch(). Chain mutation methods then call
1333
+ * commit() to persist the patch atomically.
1334
+ */
1335
+ export class PatchBuilderV2 {
1336
+ /** Adds a node to the graph. */
1337
+ addNode(nodeId: string): PatchBuilderV2;
1338
+ /** Removes a node from the graph. */
1339
+ removeNode(nodeId: string): PatchBuilderV2;
1340
+ /** Adds an edge between two nodes. */
1341
+ addEdge(from: string, to: string, label: string): PatchBuilderV2;
1342
+ /** Removes an edge between two nodes. */
1343
+ removeEdge(from: string, to: string, label: string): PatchBuilderV2;
1344
+ /** Sets a property on a node. */
1345
+ setProperty(nodeId: string, key: string, value: unknown): PatchBuilderV2;
1346
+ /** Sets a property on an edge. */
1347
+ setEdgeProperty(from: string, to: string, label: string, key: string, value: unknown): PatchBuilderV2;
1348
+ /** Builds the PatchV2 object without committing. */
1349
+ build(): PatchV2;
1350
+ /** Commits the patch to the graph and returns the commit SHA. */
1351
+ commit(): Promise<string>;
1352
+ /** Number of operations in this patch. */
1353
+ readonly opCount: number;
1354
+ }
1355
+
1296
1356
  // ============================================================================
1297
1357
  // Writer & PatchSession
1298
1358
  // ============================================================================
1299
1359
 
1300
1360
  /**
1301
1361
  * Fluent patch session for building and committing graph mutations.
1362
+ *
1363
+ * Created by Writer.beginPatch(). Wraps a PatchBuilderV2 with CAS protection.
1302
1364
  */
1303
1365
  export class PatchSession {
1304
1366
  /** Adds a node to the graph. */
@@ -1313,8 +1375,8 @@ export class PatchSession {
1313
1375
  setProperty(nodeId: string, key: string, value: unknown): this;
1314
1376
  /** Sets a property on an edge. */
1315
1377
  setEdgeProperty(from: string, to: string, label: string, key: string, value: unknown): this;
1316
- /** Builds the patch object without committing. */
1317
- build(): unknown;
1378
+ /** Builds the PatchV2 object without committing. */
1379
+ build(): PatchV2;
1318
1380
  /** Commits the patch with CAS protection. */
1319
1381
  commit(): Promise<string>;
1320
1382
  /** Number of operations in this patch. */
@@ -1527,21 +1589,21 @@ export default class WarpGraph {
1527
1589
  setSeekCache(cache: SeekCachePort | null): void;
1528
1590
 
1529
1591
  /**
1530
- * Creates a new patch for adding operations.
1592
+ * Creates a new PatchBuilderV2 for adding operations.
1531
1593
  */
1532
- createPatch(): Promise<PatchSession>;
1594
+ createPatch(): Promise<PatchBuilderV2>;
1533
1595
 
1534
1596
  /**
1535
1597
  * Convenience wrapper: creates a patch, runs the callback, and commits.
1536
1598
  *
1537
- * The callback receives a patch builder and may be synchronous or
1599
+ * The callback receives a PatchBuilderV2 and may be synchronous or
1538
1600
  * asynchronous. The commit happens only after the callback resolves.
1539
1601
  * If the callback throws or rejects, no commit is attempted.
1540
1602
  *
1541
1603
  * Not reentrant: calling `graph.patch()` inside a callback throws.
1542
1604
  * Use `createPatch()` directly for nested or concurrent patches.
1543
1605
  */
1544
- patch(build: (patch: PatchSession) => void | Promise<void>): Promise<string>;
1606
+ patch(build: (patch: PatchBuilderV2) => void | Promise<void>): Promise<string>;
1545
1607
 
1546
1608
  /**
1547
1609
  * Returns patches from a writer's ref chain.
@@ -1549,7 +1611,7 @@ export default class WarpGraph {
1549
1611
  getWriterPatches(
1550
1612
  writerId: string,
1551
1613
  stopAtSha?: string | null
1552
- ): Promise<Array<{ patch: unknown; sha: string }>>;
1614
+ ): Promise<Array<{ patch: PatchV2; sha: string }>>;
1553
1615
 
1554
1616
  /**
1555
1617
  * Gets all visible nodes in the materialized state.
@@ -1621,7 +1683,7 @@ export default class WarpGraph {
1621
1683
  /**
1622
1684
  * Materializes graph state from a checkpoint, applying incremental patches.
1623
1685
  */
1624
- materializeAt(checkpointSha: string): Promise<unknown>;
1686
+ materializeAt(checkpointSha: string): Promise<WarpStateV5>;
1625
1687
 
1626
1688
  /**
1627
1689
  * Logical graph traversal helpers.
@@ -1654,8 +1716,12 @@ export default class WarpGraph {
1654
1716
 
1655
1717
  /**
1656
1718
  * Materializes the current graph state from all patches.
1719
+ *
1720
+ * When `options.receipts` is true, returns `{ state, receipts }`.
1721
+ * Otherwise returns the WarpStateV5 directly.
1657
1722
  */
1658
- materialize(): Promise<unknown>;
1723
+ materialize(options: { receipts: true; ceiling?: number | null }): Promise<{ state: WarpStateV5; receipts: TickReceipt[] }>;
1724
+ materialize(options?: { receipts?: false; ceiling?: number | null }): Promise<WarpStateV5>;
1659
1725
 
1660
1726
  /**
1661
1727
  * Starts a built-in sync server for this graph.
@@ -1672,6 +1738,8 @@ export default class WarpGraph {
1672
1738
 
1673
1739
  /**
1674
1740
  * Syncs with a remote peer (HTTP URL or another WarpGraph instance).
1741
+ *
1742
+ * When `options.materialize` is true, the returned object also contains a `state` property.
1675
1743
  */
1676
1744
  syncWith(remote: string | WarpGraph, options?: {
1677
1745
  path?: string;
@@ -1688,7 +1756,9 @@ export default class WarpGraph {
1688
1756
  error?: Error;
1689
1757
  }) => void;
1690
1758
  auth?: SyncAuthClientOptions;
1691
- }): Promise<{ applied: number; attempts: number }>;
1759
+ /** Auto-materialize after sync; when true, result includes `state` */
1760
+ materialize?: boolean;
1761
+ }): Promise<{ applied: number; attempts: number; state?: WarpStateV5 }>;
1692
1762
 
1693
1763
  /**
1694
1764
  * Creates a fork of this graph at a specific point in a writer's history.
@@ -2113,17 +2183,7 @@ export function migrateV4toV5(v4State: {
2113
2183
  */
2114
2184
  export interface PatchEntry {
2115
2185
  /** The decoded patch object */
2116
- patch: {
2117
- schema: 2 | 3;
2118
- writer: string;
2119
- lamport: number;
2120
- context: Record<string, number> | Map<string, number>;
2121
- ops: unknown[];
2122
- /** Node/edge IDs read by this patch (V2 provenance) */
2123
- reads?: string[];
2124
- /** Node/edge IDs written by this patch (V2 provenance) */
2125
- writes?: string[];
2126
- };
2186
+ patch: PatchV2;
2127
2187
  /** The Git SHA of the patch commit */
2128
2188
  sha: string;
2129
2189
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@git-stunts/git-warp",
3
- "version": "11.2.1",
3
+ "version": "11.3.3",
4
4
  "description": "Deterministic WARP graph over Git: graph-native storage, traversal, and tooling.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -78,7 +78,7 @@
78
78
  "demo:down": "cd examples && docker compose down -v",
79
79
  "setup:hooks": "node scripts/setup-hooks.js",
80
80
  "prepare": "patch-package && node scripts/setup-hooks.js",
81
- "prepack": "npm run lint && npm run test:local",
81
+ "prepack": "npm run lint && npm run test:local && npm run typecheck:consumer",
82
82
  "install:git-warp": "bash scripts/install-git-warp.sh",
83
83
  "uninstall:git-warp": "bash scripts/uninstall-git-warp.sh",
84
84
  "test:node20": "docker compose -f docker-compose.test.yml --profile node20 run --build --rm test-node20",
@@ -89,6 +89,7 @@
89
89
  "typecheck": "tsc --noEmit",
90
90
  "typecheck:src": "tsc --noEmit -p tsconfig.src.json",
91
91
  "typecheck:test": "tsc --noEmit -p tsconfig.test.json",
92
+ "typecheck:consumer": "tsc --noEmit -p test/type-check/tsconfig.json",
92
93
  "typecheck:policy": "node scripts/ts-policy-check.js"
93
94
  },
94
95
  "dependencies": {
@@ -95,6 +95,9 @@ export default class WarpGraph {
95
95
  /** @type {number} */
96
96
  this._patchesSinceCheckpoint = 0;
97
97
 
98
+ /** @type {number} */
99
+ this._maxObservedLamport = 0;
100
+
98
101
  /** @type {{every: number}|null} */
99
102
  this._checkpointPolicy = checkpointPolicy || null;
100
103
 
@@ -285,7 +288,7 @@ export default class WarpGraph {
285
288
  // Initialize audit service if enabled
286
289
  if (graph._audit) {
287
290
  graph._auditService = new AuditReceiptService({
288
- persistence: /** @type {any} */ (persistence), // TODO(ts-cleanup): persistence implements full port union
291
+ persistence: /** @type {import('./types/WarpPersistence.js').CorePersistence} */ (persistence),
289
292
  graphName,
290
293
  writerId,
291
294
  codec: graph._codec,