@git-stunts/git-warp 12.2.1 → 12.4.1

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 (121) hide show
  1. package/README.md +5 -5
  2. package/bin/cli/commands/info.js +1 -5
  3. package/bin/cli/infrastructure.js +6 -9
  4. package/bin/cli/shared.js +8 -0
  5. package/bin/presenters/text.js +10 -3
  6. package/bin/warp-graph.js +6 -6
  7. package/package.json +1 -1
  8. package/src/domain/WarpGraph.js +5 -35
  9. package/src/domain/crdt/ORSet.js +3 -0
  10. package/src/domain/crdt/VersionVector.js +1 -1
  11. package/src/domain/entities/GraphNode.js +1 -6
  12. package/src/domain/errors/ForkError.js +1 -1
  13. package/src/domain/errors/IndexError.js +1 -1
  14. package/src/domain/errors/OperationAbortedError.js +1 -1
  15. package/src/domain/errors/PatchError.js +1 -1
  16. package/src/domain/errors/PersistenceError.js +45 -0
  17. package/src/domain/errors/QueryError.js +1 -1
  18. package/src/domain/errors/SchemaUnsupportedError.js +1 -1
  19. package/src/domain/errors/SyncError.js +1 -1
  20. package/src/domain/errors/TraversalError.js +1 -1
  21. package/src/domain/errors/TrustError.js +1 -1
  22. package/src/domain/errors/WormholeError.js +1 -1
  23. package/src/domain/errors/index.js +1 -0
  24. package/src/domain/services/AdjacencyNeighborProvider.js +1 -4
  25. package/src/domain/services/AnchorMessageCodec.js +1 -3
  26. package/src/domain/services/AuditMessageCodec.js +1 -5
  27. package/src/domain/services/AuditReceiptService.js +4 -18
  28. package/src/domain/services/AuditVerifierService.js +3 -7
  29. package/src/domain/services/BitmapIndexBuilder.js +6 -12
  30. package/src/domain/services/BitmapIndexReader.js +7 -20
  31. package/src/domain/services/BitmapNeighborProvider.js +1 -3
  32. package/src/domain/services/BoundaryTransitionRecord.js +7 -23
  33. package/src/domain/services/CheckpointMessageCodec.js +6 -6
  34. package/src/domain/services/CheckpointSerializerV5.js +8 -12
  35. package/src/domain/services/CheckpointService.js +28 -40
  36. package/src/domain/services/CommitDagTraversalService.js +1 -3
  37. package/src/domain/services/DagPathFinding.js +9 -59
  38. package/src/domain/services/DagTopology.js +4 -16
  39. package/src/domain/services/DagTraversal.js +7 -31
  40. package/src/domain/services/Frontier.js +4 -6
  41. package/src/domain/services/GitLogParser.js +1 -2
  42. package/src/domain/services/GraphTraversal.js +14 -114
  43. package/src/domain/services/HealthCheckService.js +3 -9
  44. package/src/domain/services/HookInstaller.js +2 -8
  45. package/src/domain/services/HttpSyncServer.js +24 -25
  46. package/src/domain/services/IncrementalIndexUpdater.js +4 -6
  47. package/src/domain/services/IndexRebuildService.js +6 -52
  48. package/src/domain/services/IndexStalenessChecker.js +2 -3
  49. package/src/domain/services/JoinReducer.js +200 -100
  50. package/src/domain/services/KeyCodec.js +48 -0
  51. package/src/domain/services/LogicalBitmapIndexBuilder.js +1 -2
  52. package/src/domain/services/LogicalIndexBuildService.js +2 -6
  53. package/src/domain/services/LogicalIndexReader.js +1 -2
  54. package/src/domain/services/LogicalTraversal.js +13 -64
  55. package/src/domain/services/MaterializedViewService.js +5 -19
  56. package/src/domain/services/MessageSchemaDetector.js +35 -5
  57. package/src/domain/services/MigrationService.js +1 -4
  58. package/src/domain/services/ObserverView.js +1 -7
  59. package/src/domain/services/OpNormalizer.js +79 -0
  60. package/src/domain/services/PatchBuilderV2.js +67 -38
  61. package/src/domain/services/PatchMessageCodec.js +1 -6
  62. package/src/domain/services/PropertyIndexBuilder.js +1 -2
  63. package/src/domain/services/PropertyIndexReader.js +1 -4
  64. package/src/domain/services/ProvenanceIndex.js +5 -7
  65. package/src/domain/services/ProvenancePayload.js +1 -1
  66. package/src/domain/services/QueryBuilder.js +3 -16
  67. package/src/domain/services/StateDiff.js +3 -9
  68. package/src/domain/services/StateSerializerV5.js +10 -10
  69. package/src/domain/services/StreamingBitmapIndexBuilder.js +13 -41
  70. package/src/domain/services/SyncAuthService.js +8 -32
  71. package/src/domain/services/SyncController.js +5 -25
  72. package/src/domain/services/SyncProtocol.js +10 -13
  73. package/src/domain/services/SyncTrustGate.js +4 -9
  74. package/src/domain/services/TemporalQuery.js +9 -27
  75. package/src/domain/services/TranslationCost.js +2 -8
  76. package/src/domain/services/WarpMessageCodec.js +2 -0
  77. package/src/domain/services/WarpStateIndexBuilder.js +2 -4
  78. package/src/domain/services/WormholeService.js +9 -25
  79. package/src/domain/trust/TrustCrypto.js +9 -10
  80. package/src/domain/trust/TrustEvaluator.js +1 -8
  81. package/src/domain/trust/TrustRecordService.js +5 -10
  82. package/src/domain/types/TickReceipt.js +9 -11
  83. package/src/domain/types/WarpTypes.js +1 -5
  84. package/src/domain/types/WarpTypesV2.js +78 -13
  85. package/src/domain/utils/CachedValue.js +1 -4
  86. package/src/domain/utils/MinHeap.js +3 -3
  87. package/src/domain/utils/RefLayout.js +26 -0
  88. package/src/domain/utils/WriterId.js +2 -7
  89. package/src/domain/utils/canonicalCbor.js +1 -1
  90. package/src/domain/utils/defaultClock.js +1 -0
  91. package/src/domain/utils/defaultCodec.js +1 -1
  92. package/src/domain/utils/parseCursorBlob.js +4 -4
  93. package/src/domain/warp/PatchSession.js +3 -8
  94. package/src/domain/warp/Writer.js +9 -12
  95. package/src/domain/warp/_wire.js +2 -2
  96. package/src/domain/warp/_wiredMethods.d.ts +5 -7
  97. package/src/domain/warp/checkpoint.methods.js +1 -1
  98. package/src/domain/warp/fork.methods.js +2 -6
  99. package/src/domain/warp/materializeAdvanced.methods.js +3 -3
  100. package/src/domain/warp/patch.methods.js +8 -8
  101. package/src/domain/warp/provenance.methods.js +5 -5
  102. package/src/domain/warp/query.methods.js +9 -18
  103. package/src/domain/warp/subscribe.methods.js +2 -8
  104. package/src/globals.d.ts +7 -0
  105. package/src/infrastructure/adapters/BunHttpAdapter.js +14 -18
  106. package/src/infrastructure/adapters/ConsoleLogger.js +2 -9
  107. package/src/infrastructure/adapters/DenoHttpAdapter.js +15 -15
  108. package/src/infrastructure/adapters/GitGraphAdapter.js +234 -58
  109. package/src/infrastructure/adapters/InMemoryGraphAdapter.js +9 -2
  110. package/src/infrastructure/adapters/NodeHttpAdapter.js +14 -14
  111. package/src/infrastructure/adapters/WebCryptoAdapter.js +1 -2
  112. package/src/ports/BlobPort.js +2 -2
  113. package/src/ports/HttpServerPort.js +24 -2
  114. package/src/ports/RefPort.js +2 -1
  115. package/src/visualization/renderers/ascii/box.js +1 -1
  116. package/src/visualization/renderers/ascii/check.js +1 -5
  117. package/src/visualization/renderers/ascii/history.js +1 -6
  118. package/src/visualization/renderers/ascii/path.js +4 -22
  119. package/src/visualization/renderers/ascii/progress.js +1 -4
  120. package/src/visualization/renderers/ascii/seek.js +1 -5
  121. package/src/visualization/renderers/ascii/table.js +1 -3
@@ -49,9 +49,8 @@ export function getWriters(frontier) {
49
49
  * Serializes frontier to canonical CBOR bytes.
50
50
  * Keys are sorted for determinism.
51
51
  * @param {Frontier} frontier
52
- * @param {Object} [options]
53
- * @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for serialization
54
- * @returns {Buffer|Uint8Array}
52
+ * @param {{ codec?: import('../../ports/CodecPort.js').default }} [options]
53
+ * @returns {Uint8Array}
55
54
  */
56
55
  export function serializeFrontier(frontier, { codec } = /** @type {{codec?: import('../../ports/CodecPort.js').default}} */ ({})) {
57
56
  const c = codec || defaultCodec;
@@ -67,9 +66,8 @@ export function serializeFrontier(frontier, { codec } = /** @type {{codec?: impo
67
66
 
68
67
  /**
69
68
  * Deserializes frontier from CBOR bytes.
70
- * @param {Buffer} buffer
71
- * @param {Object} [options]
72
- * @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for deserialization
69
+ * @param {Uint8Array} buffer
70
+ * @param {{ codec?: import('../../ports/CodecPort.js').default }} [options]
73
71
  * @returns {Frontier}
74
72
  */
75
73
  export function deserializeFrontier(buffer, { codec } = /** @type {{codec?: import('../../ports/CodecPort.js').default}} */ ({})) {
@@ -88,8 +88,7 @@ export default class GitLogParser {
88
88
  *
89
89
  * @param {AsyncIterable<Buffer|Uint8Array|string>} stream - The git log output stream.
90
90
  * May yield Buffer, Uint8Array, or string chunks.
91
- * @param {Object} [options] - Parse options
92
- * @param {AbortSignal} [options.signal] - Optional abort signal for cancellation
91
+ * @param {{ signal?: AbortSignal }} [options] - Parse options
93
92
  * @yields {GraphNode} Parsed graph nodes. Invalid records are silently skipped.
94
93
  * @throws {OperationAbortedError} If signal is aborted during parsing
95
94
  *
@@ -92,11 +92,7 @@ const lexTieBreaker = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
92
92
  /**
93
93
  * Distinguishes true topological cycles from maxNodes truncation.
94
94
  *
95
- * @param {Object} params
96
- * @param {number} params.sortedLength
97
- * @param {number} params.discoveredSize
98
- * @param {number} params.maxNodes
99
- * @param {boolean} params.readyRemaining
95
+ * @param {{ sortedLength: number, discoveredSize: number, maxNodes: number, readyRemaining: boolean }} params
100
96
  * @returns {boolean}
101
97
  */
102
98
  function computeTopoHasCycle({
@@ -110,10 +106,7 @@ function computeTopoHasCycle({
110
106
 
111
107
  export default class GraphTraversal {
112
108
  /**
113
- * @param {Object} params
114
- * @param {NeighborProviderPort} params.provider
115
- * @param {import('../../ports/LoggerPort.js').default} [params.logger]
116
- * @param {number} [params.neighborCacheSize]
109
+ * @param {{ provider: NeighborProviderPort, logger?: import('../../ports/LoggerPort.js').default, neighborCacheSize?: number }} params
117
110
  */
118
111
  constructor({ provider, logger = nullLogger, neighborCacheSize = 256 }) {
119
112
  this._provider = provider;
@@ -187,14 +180,7 @@ export default class GraphTraversal {
187
180
  *
188
181
  * Deterministic: nodes at equal depth are visited in lexicographic nodeId order.
189
182
  *
190
- * @param {Object} params
191
- * @param {string} params.start
192
- * @param {Direction} [params.direction]
193
- * @param {NeighborOptions} [params.options]
194
- * @param {number} [params.maxNodes]
195
- * @param {number} [params.maxDepth]
196
- * @param {AbortSignal} [params.signal]
197
- * @param {TraversalHooks} [params.hooks]
183
+ * @param {{ start: string, direction?: Direction, options?: NeighborOptions, maxNodes?: number, maxDepth?: number, signal?: AbortSignal, hooks?: TraversalHooks }} params
198
184
  * @returns {Promise<{nodes: string[], stats: TraversalStats}>}
199
185
  */
200
186
  async bfs({
@@ -254,14 +240,7 @@ export default class GraphTraversal {
254
240
  *
255
241
  * Deterministic: leftmost-first via reverse-push of sorted neighbors.
256
242
  *
257
- * @param {Object} params
258
- * @param {string} params.start
259
- * @param {Direction} [params.direction]
260
- * @param {NeighborOptions} [params.options]
261
- * @param {number} [params.maxNodes]
262
- * @param {number} [params.maxDepth]
263
- * @param {AbortSignal} [params.signal]
264
- * @param {TraversalHooks} [params.hooks]
243
+ * @param {{ start: string, direction?: Direction, options?: NeighborOptions, maxNodes?: number, maxDepth?: number, signal?: AbortSignal, hooks?: TraversalHooks }} params
265
244
  * @returns {Promise<{nodes: string[], stats: TraversalStats}>}
266
245
  */
267
246
  async dfs({
@@ -311,14 +290,7 @@ export default class GraphTraversal {
311
290
  /**
312
291
  * Unweighted shortest path (BFS-based).
313
292
  *
314
- * @param {Object} params
315
- * @param {string} params.start
316
- * @param {string} params.goal
317
- * @param {Direction} [params.direction]
318
- * @param {NeighborOptions} [params.options]
319
- * @param {number} [params.maxNodes]
320
- * @param {number} [params.maxDepth]
321
- * @param {AbortSignal} [params.signal]
293
+ * @param {{ start: string, goal: string, direction?: Direction, options?: NeighborOptions, maxNodes?: number, maxDepth?: number, signal?: AbortSignal }} params
322
294
  * @returns {Promise<{found: boolean, path: string[], length: number, stats: TraversalStats}>}
323
295
  */
324
296
  async shortestPath({
@@ -374,14 +346,7 @@ export default class GraphTraversal {
374
346
  /**
375
347
  * Reachability check — BFS with early termination.
376
348
  *
377
- * @param {Object} params
378
- * @param {string} params.start
379
- * @param {string} params.goal
380
- * @param {Direction} [params.direction]
381
- * @param {NeighborOptions} [params.options]
382
- * @param {number} [params.maxNodes]
383
- * @param {number} [params.maxDepth]
384
- * @param {AbortSignal} [params.signal]
349
+ * @param {{ start: string, goal: string, direction?: Direction, options?: NeighborOptions, maxNodes?: number, maxDepth?: number, signal?: AbortSignal }} params
385
350
  * @returns {Promise<{reachable: boolean, stats: TraversalStats}>}
386
351
  */
387
352
  async isReachable({
@@ -432,15 +397,7 @@ export default class GraphTraversal {
432
397
  * Tie-breaking: equal-priority by lexicographic nodeId. Equal-cost
433
398
  * predecessor update: when altCost === bestCost && candidatePredecessor < currentPredecessor.
434
399
  *
435
- * @param {Object} params
436
- * @param {string} params.start
437
- * @param {string} params.goal
438
- * @param {Direction} [params.direction]
439
- * @param {NeighborOptions} [params.options]
440
- * @param {(from: string, to: string, label: string) => number | Promise<number>} [params.weightFn]
441
- * @param {(nodeId: string) => number | Promise<number>} [params.nodeWeightFn]
442
- * @param {number} [params.maxNodes]
443
- * @param {AbortSignal} [params.signal]
400
+ * @param {{ start: string, goal: string, direction?: Direction, options?: NeighborOptions, weightFn?: (from: string, to: string, label: string) => number | Promise<number>, nodeWeightFn?: (nodeId: string) => number | Promise<number>, maxNodes?: number, signal?: AbortSignal }} params
444
401
  * @returns {Promise<{path: string[], totalCost: number, stats: TraversalStats}>}
445
402
  * @throws {TraversalError} code 'NO_PATH' if unreachable
446
403
  * @throws {TraversalError} code 'E_WEIGHT_FN_CONFLICT' if both weightFn and nodeWeightFn provided
@@ -501,16 +458,7 @@ export default class GraphTraversal {
501
458
  /**
502
459
  * A* search with heuristic guidance.
503
460
  *
504
- * @param {Object} params
505
- * @param {string} params.start
506
- * @param {string} params.goal
507
- * @param {Direction} [params.direction]
508
- * @param {NeighborOptions} [params.options]
509
- * @param {(from: string, to: string, label: string) => number | Promise<number>} [params.weightFn]
510
- * @param {(nodeId: string) => number | Promise<number>} [params.nodeWeightFn]
511
- * @param {(nodeId: string, goalId: string) => number} [params.heuristicFn]
512
- * @param {number} [params.maxNodes]
513
- * @param {AbortSignal} [params.signal]
461
+ * @param {{ start: string, goal: string, direction?: Direction, options?: NeighborOptions, weightFn?: (from: string, to: string, label: string) => number | Promise<number>, nodeWeightFn?: (nodeId: string) => number | Promise<number>, heuristicFn?: (nodeId: string, goalId: string) => number, maxNodes?: number, signal?: AbortSignal }} params
514
462
  * @returns {Promise<{path: string[], totalCost: number, nodesExplored: number, stats: TraversalStats}>}
515
463
  * @throws {TraversalError} code 'NO_PATH' if unreachable
516
464
  * @throws {TraversalError} code 'E_WEIGHT_FN_CONFLICT' if both weightFn and nodeWeightFn provided
@@ -583,16 +531,7 @@ export default class GraphTraversal {
583
531
  * bidirectional algorithm — forward always means outgoing, backward always
584
532
  * means incoming.
585
533
  *
586
- * @param {Object} params
587
- * @param {string} params.start
588
- * @param {string} params.goal
589
- * @param {NeighborOptions} [params.options]
590
- * @param {(from: string, to: string, label: string) => number | Promise<number>} [params.weightFn]
591
- * @param {(nodeId: string) => number | Promise<number>} [params.nodeWeightFn]
592
- * @param {(nodeId: string, goalId: string) => number} [params.forwardHeuristic]
593
- * @param {(nodeId: string, goalId: string) => number} [params.backwardHeuristic]
594
- * @param {number} [params.maxNodes]
595
- * @param {AbortSignal} [params.signal]
534
+ * @param {{ start: string, goal: string, options?: NeighborOptions, weightFn?: (from: string, to: string, label: string) => number | Promise<number>, nodeWeightFn?: (nodeId: string) => number | Promise<number>, forwardHeuristic?: (nodeId: string, goalId: string) => number, backwardHeuristic?: (nodeId: string, goalId: string) => number, maxNodes?: number, signal?: AbortSignal }} params
596
535
  * @returns {Promise<{path: string[], totalCost: number, nodesExplored: number, stats: TraversalStats}>}
597
536
  * @throws {TraversalError} code 'NO_PATH' if unreachable
598
537
  * @throws {TraversalError} code 'E_WEIGHT_FN_CONFLICT' if both weightFn and nodeWeightFn provided
@@ -674,21 +613,7 @@ export default class GraphTraversal {
674
613
  /**
675
614
  * Expand one node in bidirectional A*.
676
615
  * @private
677
- * @param {Object} p
678
- * @param {MinHeap<string>} p.heap
679
- * @param {Set<string>} p.visited
680
- * @param {Map<string, number>} p.gScore
681
- * @param {Map<string, string>} p.predMap
682
- * @param {Set<string>} p.otherVisited
683
- * @param {Map<string, number>} p.otherG
684
- * @param {(from: string, to: string, label: string) => number | Promise<number>} p.weightFn
685
- * @param {(nodeId: string, goalId: string) => number} p.heuristicFn
686
- * @param {string} p.target
687
- * @param {Direction} p.directionForNeighbors
688
- * @param {NeighborOptions} [p.options]
689
- * @param {number} p.mu
690
- * @param {string|null} p.meeting
691
- * @param {RunStats} p.rs
616
+ * @param {{ heap: MinHeap<string>, visited: Set<string>, gScore: Map<string, number>, predMap: Map<string, string>, otherVisited: Set<string>, otherG: Map<string, number>, weightFn: (from: string, to: string, label: string) => number | Promise<number>, heuristicFn: (nodeId: string, goalId: string) => number, target: string, directionForNeighbors: Direction, options?: NeighborOptions, mu: number, meeting: string|null, rs: RunStats }} p
692
617
  * @returns {Promise<{explored: number, mu: number, meeting: string|null}>}
693
618
  */
694
619
  async _biAStarExpand({
@@ -749,12 +674,7 @@ export default class GraphTraversal {
749
674
  /**
750
675
  * Connected component — delegates to BFS with direction 'both'.
751
676
  *
752
- * @param {Object} params
753
- * @param {string} params.start
754
- * @param {NeighborOptions} [params.options]
755
- * @param {number} [params.maxNodes]
756
- * @param {number} [params.maxDepth]
757
- * @param {AbortSignal} [params.signal]
677
+ * @param {{ start: string, options?: NeighborOptions, maxNodes?: number, maxDepth?: number, signal?: AbortSignal }} params
758
678
  * @returns {Promise<{nodes: string[], stats: TraversalStats}>}
759
679
  */
760
680
  async connectedComponent({ start, options, maxNodes, maxDepth, signal }) {
@@ -766,14 +686,7 @@ export default class GraphTraversal {
766
686
  *
767
687
  * Deterministic: zero-indegree nodes dequeued in lexicographic nodeId order.
768
688
  *
769
- * @param {Object} params
770
- * @param {string | string[]} params.start - One or more start nodes
771
- * @param {Direction} [params.direction]
772
- * @param {NeighborOptions} [params.options]
773
- * @param {number} [params.maxNodes]
774
- * @param {boolean} [params.throwOnCycle]
775
- * @param {AbortSignal} [params.signal]
776
- * @param {boolean} [params._returnAdjList] - Private: return neighbor edge map alongside sorted (for internal reuse)
689
+ * @param {{ start: string | string[], direction?: Direction, options?: NeighborOptions, maxNodes?: number, throwOnCycle?: boolean, signal?: AbortSignal, _returnAdjList?: boolean }} params
777
690
  * @returns {Promise<{sorted: string[], hasCycle: boolean, stats: TraversalStats, _neighborEdgeMap?: Map<string, NeighborEdge[]>}>}
778
691
  * @throws {TraversalError} code 'ERR_GRAPH_HAS_CYCLES' if throwOnCycle is true and cycle found
779
692
  */
@@ -914,12 +827,7 @@ export default class GraphTraversal {
914
827
  * `[A, B, C]`, then B and C may appear in the result because A's BFS
915
828
  * reaches them and their own BFS includes themselves at depth 0.
916
829
  *
917
- * @param {Object} params
918
- * @param {string[]} params.nodes - Nodes to find common ancestors of
919
- * @param {NeighborOptions} [params.options]
920
- * @param {number} [params.maxDepth]
921
- * @param {number} [params.maxResults]
922
- * @param {AbortSignal} [params.signal]
830
+ * @param {{ nodes: string[], options?: NeighborOptions, maxDepth?: number, maxResults?: number, signal?: AbortSignal }} params
923
831
  * @returns {Promise<{ancestors: string[], stats: TraversalStats}>}
924
832
  */
925
833
  async commonAncestors({
@@ -981,15 +889,7 @@ export default class GraphTraversal {
981
889
  *
982
890
  * Only valid on DAGs. Throws ERR_GRAPH_HAS_CYCLES if graph has cycles.
983
891
  *
984
- * @param {Object} params
985
- * @param {string} params.start
986
- * @param {string} params.goal
987
- * @param {Direction} [params.direction]
988
- * @param {NeighborOptions} [params.options]
989
- * @param {(from: string, to: string, label: string) => number | Promise<number>} [params.weightFn]
990
- * @param {(nodeId: string) => number | Promise<number>} [params.nodeWeightFn]
991
- * @param {number} [params.maxNodes]
992
- * @param {AbortSignal} [params.signal]
892
+ * @param {{ start: string, goal: string, direction?: Direction, options?: NeighborOptions, weightFn?: (from: string, to: string, label: string) => number | Promise<number>, nodeWeightFn?: (nodeId: string) => number | Promise<number>, maxNodes?: number, signal?: AbortSignal }} params
993
893
  * @returns {Promise<{path: string[], totalCost: number, stats: TraversalStats}>}
994
894
  * @throws {TraversalError} code 'ERR_GRAPH_HAS_CYCLES' if graph has cycles
995
895
  * @throws {TraversalError} code 'NO_PATH' if unreachable
@@ -45,11 +45,7 @@ export const HealthStatus = {
45
45
  export default class HealthCheckService {
46
46
  /**
47
47
  * Creates a HealthCheckService instance.
48
- * @param {Object} options
49
- * @param {import('../../ports/GraphPersistencePort.js').default & import('../../ports/CommitPort.js').default} options.persistence - Persistence port for repository checks
50
- * @param {import('../../ports/ClockPort.js').default} options.clock - Clock port for timing operations
51
- * @param {number} [options.cacheTtlMs=5000] - How long to cache health results in milliseconds
52
- * @param {import('../../ports/LoggerPort.js').default} [options.logger] - Logger for structured logging
48
+ * @param {{ persistence: import('../../ports/GraphPersistencePort.js').default & import('../../ports/CommitPort.js').default, clock: import('../../ports/ClockPort.js').default, cacheTtlMs?: number, logger?: import('../../ports/LoggerPort.js').default }} options
53
49
  */
54
50
  constructor({ persistence, clock, cacheTtlMs = DEFAULT_CACHE_TTL_MS, logger = nullLogger }) {
55
51
  this._persistence = persistence;
@@ -116,9 +112,7 @@ export default class HealthCheckService {
116
112
  *
117
113
  * @typedef {Object} HealthResult
118
114
  * @property {'healthy'|'degraded'|'unhealthy'} status - Overall health status
119
- * @property {Object} components - Component health breakdown
120
- * @property {RepositoryHealth} components.repository - Repository health
121
- * @property {IndexHealth} components.index - Index health
115
+ * @property {{ repository: RepositoryHealth, index: IndexHealth }} components - Component health breakdown
122
116
  * @property {string} [cachedAt] - ISO timestamp if result is cached
123
117
  *
124
118
  * @typedef {Object} RepositoryHealth
@@ -154,7 +148,7 @@ export default class HealthCheckService {
154
148
  /**
155
149
  * Computes health by checking all components.
156
150
  * This is called by CachedValue when the cache is stale.
157
- * @returns {Promise<Object>}
151
+ * @returns {Promise<{status: 'healthy'|'degraded'|'unhealthy', components: {repository: RepositoryHealth, index: IndexHealth}}>}
158
152
  * @private
159
153
  */
160
154
  async _computeHealth() {
@@ -73,12 +73,7 @@ export class HookInstaller {
73
73
  /**
74
74
  * Creates a new HookInstaller.
75
75
  *
76
- * @param {Object} deps - Injected dependencies
77
- * @param {FsAdapter} deps.fs - Filesystem adapter with methods: readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync
78
- * @param {(repoPath: string, key: string) => string|null} deps.execGitConfig - Function to read git config values
79
- * @param {string} deps.version - Package version
80
- * @param {string} deps.templateDir - Directory containing hook templates
81
- * @param {PathUtils} deps.path - Path utilities (join and resolve)
76
+ * @param {{ fs: FsAdapter, execGitConfig: (repoPath: string, key: string) => string|null, version: string, templateDir: string, path: PathUtils }} deps - Injected dependencies
82
77
  */
83
78
  constructor({ fs, execGitConfig, version, templateDir, path }) {
84
79
  /** @type {FsAdapter} */
@@ -125,8 +120,7 @@ export class HookInstaller {
125
120
  * Installs the post-merge hook.
126
121
  *
127
122
  * @param {string} repoPath - Path to git repo
128
- * @param {Object} opts - Install options
129
- * @param {'install'|'upgrade'|'append'|'replace'} opts.strategy - Installation strategy
123
+ * @param {{ strategy: 'install'|'upgrade'|'append'|'replace' }} opts - Install options
130
124
  * @returns {{ action: string, hookPath: string, version: string, backupPath?: string }}
131
125
  * @throws {Error} If the strategy is unknown
132
126
  */
@@ -92,7 +92,7 @@ function canonicalStringify(value) {
92
92
  *
93
93
  * @param {number} status - HTTP status code
94
94
  * @param {string} message - Error message
95
- * @returns {{ status: number, headers: Object, body: string }}
95
+ * @returns {{ status: number, headers: Record<string, string>, body: string }}
96
96
  * @private
97
97
  */
98
98
  function errorResponse(status, message) {
@@ -107,7 +107,7 @@ function errorResponse(status, message) {
107
107
  * Builds a JSON success response with canonical key ordering.
108
108
  *
109
109
  * @param {unknown} data - Response payload
110
- * @returns {{ status: number, headers: Object, body: string }}
110
+ * @returns {{ status: number, headers: Record<string, string>, body: string }}
111
111
  * @private
112
112
  */
113
113
  function jsonResponse(data) {
@@ -125,7 +125,7 @@ function jsonResponse(data) {
125
125
  * content type is present but not application/json, otherwise null.
126
126
  *
127
127
  * @param {{ [x: string]: string }} headers - Request headers
128
- * @returns {{ status: number, headers: Object, body: string }|null}
128
+ * @returns {{ status: number, headers: Record<string, string>, body: string }|null}
129
129
  * @private
130
130
  */
131
131
  function checkContentType(headers) {
@@ -143,7 +143,7 @@ function checkContentType(headers) {
143
143
  * @param {{ method: string, url: string, headers: { [x: string]: string } }} request
144
144
  * @param {string} expectedPath
145
145
  * @param {string} defaultHost
146
- * @returns {{ status: number, headers: Object, body: string }|null}
146
+ * @returns {{ status: number, headers: Record<string, string>, body: string }|null}
147
147
  * @private
148
148
  */
149
149
  function validateRoute(request, expectedPath, defaultHost) {
@@ -168,9 +168,9 @@ function validateRoute(request, expectedPath, defaultHost) {
168
168
  /**
169
169
  * Checks if the request body exceeds the maximum allowed size.
170
170
  *
171
- * @param {Buffer|undefined} body
171
+ * @param {Buffer | Uint8Array | undefined} body
172
172
  * @param {number} maxBytes
173
- * @returns {{ status: number, headers: Object, body: string }|null} Error response or null if within limits
173
+ * @returns {{ status: number, headers: Record<string, string>, body: string }|null} Error response or null if within limits
174
174
  * @private
175
175
  */
176
176
  function checkBodySize(body, maxBytes) {
@@ -184,12 +184,12 @@ function checkBodySize(body, maxBytes) {
184
184
  * Parses and validates the request body as a sync request.
185
185
  * Uses Zod-based SyncPayloadSchema for shape + resource limit validation.
186
186
  *
187
- * @param {Buffer|undefined} body
188
- * @returns {{ error: { status: number, headers: Object, body: string }, parsed: null } | { error: null, parsed: import('./SyncProtocol.js').SyncRequest }}
187
+ * @param {Buffer | Uint8Array | undefined} body
188
+ * @returns {{ error: { status: number, headers: Record<string, string>, body: string }, parsed: null } | { error: null, parsed: import('./SyncProtocol.js').SyncRequest }}
189
189
  * @private
190
190
  */
191
191
  function parseBody(body) {
192
- const bodyStr = body ? body.toString('utf-8') : '';
192
+ const bodyStr = body ? new TextDecoder().decode(body) : '';
193
193
 
194
194
  let parsed;
195
195
  try {
@@ -223,14 +223,7 @@ function initAuth(auth, allowedWriters) {
223
223
 
224
224
  export default class HttpSyncServer {
225
225
  /**
226
- * @param {Object} options
227
- * @param {import('../../ports/HttpServerPort.js').default} options.httpPort - HTTP server port abstraction
228
- * @param {{ processSyncRequest: Function }} options.graph - WarpGraph instance (must expose processSyncRequest)
229
- * @param {string} [options.path='/sync'] - URL path to handle sync requests on
230
- * @param {string} [options.host='127.0.0.1'] - Host to bind
231
- * @param {number} [options.maxRequestBytes=4194304] - Maximum request body size in bytes
232
- * @param {{ keys: Record<string, string>, mode?: 'enforce'|'log-only', crypto?: import('../../ports/CryptoPort.js').default, logger?: import('../../ports/LoggerPort.js').default, wallClockMs?: () => number }} [options.auth] - Auth configuration
233
- * @param {string[]} [options.allowedWriters] - Optional whitelist of allowed writer IDs
226
+ * @param {{ httpPort: import('../../ports/HttpServerPort.js').default, graph: { processSyncRequest: (req: import('./SyncProtocol.js').SyncRequest) => Promise<unknown> }, path?: string, host?: string, maxRequestBytes?: number, auth?: { keys: Record<string, string>, mode?: 'enforce'|'log-only', crypto?: import('../../ports/CryptoPort.js').default, logger?: import('../../ports/LoggerPort.js').default, wallClockMs?: () => number }, allowedWriters?: string[] }} options
234
227
  */
235
228
  constructor(options) {
236
229
  /** @type {z.infer<typeof optionsSchema>} */
@@ -263,9 +256,9 @@ export default class HttpSyncServer {
263
256
  * In log-only mode both checks record metrics/logs but always return
264
257
  * null so the request proceeds.
265
258
  *
266
- * @param {{ method: string, url: string, headers: { [x: string]: string }, body: Buffer|undefined }} request
259
+ * @param {{ method: string, url: string, headers: Record<string, string>, body: Buffer | Uint8Array | undefined }} request
267
260
  * @param {Record<string, unknown>} parsed - Parsed sync request body
268
- * @returns {Promise<{ status: number, headers: Object, body: string }|null>}
261
+ * @returns {Promise<{ status: number, headers: Record<string, string>, body: string }|null>}
269
262
  * @private
270
263
  */
271
264
  async _authorize(request, parsed) {
@@ -299,9 +292,15 @@ export default class HttpSyncServer {
299
292
  return null;
300
293
  }
301
294
 
302
- /** @param {{ method: string, url: string, headers: Object, body: Buffer|undefined }} request */
295
+ /**
296
+ * Handles an incoming HTTP request through the sync pipeline.
297
+ *
298
+ * @param {import('../../ports/HttpServerPort.js').HttpRequest} request
299
+ * @returns {Promise<import('../../ports/HttpServerPort.js').HttpResponse>}
300
+ * @private
301
+ */
303
302
  async _handleRequest(request) {
304
- /** @type {{ method: string, url: string, headers: Record<string, string>, body: Buffer|undefined }} */
303
+ /** @type {{ method: string, url: string, headers: Record<string, string>, body: Buffer | Uint8Array | undefined }} */
305
304
  const req = { ...request, headers: /** @type {Record<string, string>} */ (request.headers) };
306
305
  const contentTypeError = checkContentType(req.headers);
307
306
  if (contentTypeError) {
@@ -348,14 +347,14 @@ export default class HttpSyncServer {
348
347
  throw new Error('listen() requires a numeric port');
349
348
  }
350
349
 
351
- /** @type {{ listen: Function, close: Function, address: Function }} */
352
350
  const server = this._httpPort.createServer(
353
- (/** @type {{ method: string, url: string, headers: Object, body: Buffer|undefined }} */ request) => this._handleRequest(request),
351
+ (/** @type {import('../../ports/HttpServerPort.js').HttpRequest} */ request) =>
352
+ this._handleRequest(request),
354
353
  );
355
354
  this._server = server;
356
355
 
357
356
  await /** @type {Promise<void>} */ (new Promise((resolve, reject) => {
358
- server.listen(port, this._host, (/** @type {Error|null} */ err) => {
357
+ server.listen(port, this._host, (err) => {
359
358
  if (err) {
360
359
  reject(err);
361
360
  } else {
@@ -372,7 +371,7 @@ export default class HttpSyncServer {
372
371
  url,
373
372
  close: () =>
374
373
  /** @type {Promise<void>} */ (new Promise((resolve, reject) => {
375
- server.close((/** @type {Error|null} */ err) => {
374
+ server.close((err) => {
376
375
  if (err) {
377
376
  reject(err);
378
377
  } else {
@@ -42,8 +42,9 @@ const MAX_LOCAL_ID = 1 << 24;
42
42
 
43
43
  export default class IncrementalIndexUpdater {
44
44
  /**
45
- * @param {Object} [options]
46
- * @param {import('../../ports/CodecPort.js').default} [options.codec]
45
+ * Create an incremental index updater.
46
+ *
47
+ * @param {{ codec?: import('../../ports/CodecPort.js').default }} [options]
47
48
  */
48
49
  constructor({ codec } = {}) {
49
50
  this._codec = codec || defaultCodec;
@@ -61,10 +62,7 @@ export default class IncrementalIndexUpdater {
61
62
  /**
62
63
  * Computes only the dirty shards from a PatchDiff.
63
64
  *
64
- * @param {Object} params
65
- * @param {import('../types/PatchDiff.js').PatchDiff} params.diff
66
- * @param {import('./JoinReducer.js').WarpStateV5} params.state
67
- * @param {(path: string) => Uint8Array|undefined} params.loadShard
65
+ * @param {{ diff: import('../types/PatchDiff.js').PatchDiff, state: import('./JoinReducer.js').WarpStateV5, loadShard: (path: string) => Uint8Array|undefined }} params
68
66
  * @returns {Record<string, Uint8Array>} dirty shard buffers (path -> Uint8Array)
69
67
  */
70
68
  computeDirtyShards({ diff, state, loadShard }) {
@@ -39,14 +39,7 @@ export default class IndexRebuildService {
39
39
  /**
40
40
  * Creates an IndexRebuildService instance.
41
41
  *
42
- * @param {Object} options - Configuration options
43
- * @param {{ iterateNodes: (opts: { ref: string, limit: number }) => AsyncIterable<{ sha: string, parents: string[] }> }} options.graphService - Graph service providing node iteration.
44
- * @param {import('../../ports/IndexStoragePort.js').default} options.storage - Storage adapter
45
- * for persisting index blobs and trees. Typically GitGraphAdapter.
46
- * @param {import('../../ports/LoggerPort.js').default} [options.logger] - Logger for
47
- * structured logging. Defaults to null logger (no logging).
48
- * @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for serialization
49
- * @param {import('../../ports/CryptoPort.js').default} [options.crypto] - Crypto adapter for checksums
42
+ * @param {{ graphService: { iterateNodes: (opts: { ref: string, limit: number }) => AsyncIterable<{ sha: string, parents: string[] }> }, storage: import('../../ports/IndexStoragePort.js').default, logger?: import('../../ports/LoggerPort.js').default, codec?: import('../../ports/CodecPort.js').default, crypto?: import('../../ports/CryptoPort.js').default }} options - Configuration options
50
43
  * @throws {Error} If graphService is not provided
51
44
  * @throws {Error} If storage adapter is not provided
52
45
  */
@@ -87,19 +80,7 @@ export default class IndexRebuildService {
87
80
  * - `shards_rev_XX.json`: Reverse edge bitmaps (parent lookups)
88
81
  *
89
82
  * @param {string} ref - Git ref to start traversal from (e.g., 'HEAD', branch name, SHA)
90
- * @param {Object} [options] - Rebuild options
91
- * @param {number} [options.limit=10000000] - Maximum nodes to process (1 to 10,000,000)
92
- * @param {number} [options.maxMemoryBytes] - Enable streaming mode with this memory threshold.
93
- * When bitmap memory exceeds this value, data is flushed to storage.
94
- * Recommended: 50-100MB for most systems. Minimum: 1MB.
95
- * @param {Function} [options.onFlush] - Callback invoked on each flush (streaming mode only).
96
- * Receives { flushedBytes, totalFlushedBytes, flushCount }.
97
- * @param {Function} [options.onProgress] - Callback invoked periodically during processing.
98
- * Receives { processedNodes, currentMemoryBytes }.
99
- * @param {AbortSignal} [options.signal] - Optional AbortSignal for cancellation support.
100
- * When aborted, throws OperationAbortedError at the next loop boundary.
101
- * @param {Map<string, string>} [options.frontier] - Frontier to persist alongside the rebuilt index.
102
- * Maps writer IDs to their tip SHAs; stored in the index tree for staleness detection.
83
+ * @param {{ limit?: number, maxMemoryBytes?: number, onFlush?: (stats: {flushedBytes: number, totalFlushedBytes: number, flushCount: number}) => void, onProgress?: (stats: {processedNodes: number, currentMemoryBytes: number | null}) => void, signal?: AbortSignal, frontier?: Map<string, string> }} [options] - Rebuild options
103
84
  * @returns {Promise<string>} OID of the created tree containing the index
104
85
  * @throws {Error} If maxMemoryBytes is specified but not positive
105
86
  * @throws {OperationAbortedError} If the signal is aborted during rebuild
@@ -174,13 +155,7 @@ export default class IndexRebuildService {
174
155
  * data structures, plus temporary overhead during serialization.
175
156
  *
176
157
  * @param {string} ref - Git ref to traverse from
177
- * @param {Object} options - Options
178
- * @param {number} options.limit - Maximum nodes to process
179
- * @param {Function} [options.onProgress] - Progress callback invoked every 10,000 nodes.
180
- * Receives `{ processedNodes: number, currentMemoryBytes: null }`.
181
- * @param {AbortSignal} [options.signal] - Abort signal for cancellation. Checked every
182
- * 10,000 nodes to balance responsiveness with performance.
183
- * @param {Map<string, string>} [options.frontier] - Frontier to persist with the index
158
+ * @param {{ limit: number, onProgress?: (stats: {processedNodes: number, currentMemoryBytes: number | null}) => void, signal?: AbortSignal, frontier?: Map<string, string> }} options - Options
184
159
  * @returns {Promise<string>} Tree OID of the persisted index
185
160
  * @throws {OperationAbortedError} If the signal is aborted during iteration
186
161
  * @throws {Error} If node iteration fails (e.g., invalid ref, Git error)
@@ -229,17 +204,7 @@ export default class IndexRebuildService {
229
204
  * - You can tolerate longer rebuild times for lower memory usage
230
205
  *
231
206
  * @param {string} ref - Git ref to traverse from
232
- * @param {Object} options - Options
233
- * @param {number} options.limit - Maximum nodes to process
234
- * @param {number} options.maxMemoryBytes - Memory threshold in bytes. When estimated
235
- * bitmap memory exceeds this, a flush is triggered.
236
- * @param {Function} [options.onFlush] - Flush callback invoked after each flush.
237
- * Receives `{ flushedBytes, totalFlushedBytes, flushCount }`.
238
- * @param {Function} [options.onProgress] - Progress callback invoked every 10,000 nodes.
239
- * Receives `{ processedNodes, currentMemoryBytes }`.
240
- * @param {AbortSignal} [options.signal] - Abort signal for cancellation. Checked every
241
- * 10,000 nodes during iteration and at finalization.
242
- * @param {Map<string, string>} [options.frontier] - Frontier to persist with the index
207
+ * @param {{ limit: number, maxMemoryBytes: number, onFlush?: (stats: {flushedBytes: number, totalFlushedBytes: number, flushCount: number}) => void, onProgress?: (stats: {processedNodes: number, currentMemoryBytes: number}) => void, signal?: AbortSignal, frontier?: Map<string, string> }} options - Options
243
208
  * @returns {Promise<string>} Tree OID of the persisted index
244
209
  * @throws {OperationAbortedError} If the signal is aborted during iteration or finalization
245
210
  * @throws {Error} If node iteration fails (e.g., invalid ref, Git error)
@@ -291,8 +256,7 @@ export default class IndexRebuildService {
291
256
  * - `100644 blob <oid>\tfrontier.json` (if frontier provided)
292
257
  *
293
258
  * @param {BitmapIndexBuilder} builder - The builder containing index data
294
- * @param {Object} [options] - Persistence options
295
- * @param {Map<string, string>} [options.frontier] - Frontier to include in the tree
259
+ * @param {{ frontier?: Map<string, string> }} [options] - Persistence options
296
260
  * @returns {Promise<string>} OID of the created tree
297
261
  * @throws {Error} If storage.writeBlob() fails for any shard
298
262
  * @throws {Error} If storage.writeTree() fails
@@ -329,17 +293,7 @@ export default class IndexRebuildService {
329
293
  * incomplete or incorrect query results.
330
294
  *
331
295
  * @param {string} treeOid - OID of the index tree (from rebuild() or a saved ref)
332
- * @param {Object} [options] - Load options
333
- * @param {boolean} [options.strict=true] - Enable strict integrity verification (fail-closed).
334
- * When true, throws on any shard validation or corruption errors.
335
- * When false, attempts graceful degradation.
336
- * @param {Map<string, string>} [options.currentFrontier] - Frontier to compare for staleness.
337
- * Maps writer IDs to their current tip SHAs. When provided, triggers a staleness
338
- * check against the frontier stored in the index.
339
- * @param {boolean} [options.autoRebuild=false] - Auto-rebuild when a stale index is detected.
340
- * Requires `rebuildRef` to be set.
341
- * @param {string} [options.rebuildRef] - Git ref to rebuild from when `autoRebuild` is true.
342
- * Required if `autoRebuild` is true.
296
+ * @param {{ strict?: boolean, currentFrontier?: Map<string, string>, autoRebuild?: boolean, rebuildRef?: string }} [options] - Load options
343
297
  * @returns {Promise<BitmapIndexReader>} Configured reader ready for O(1) queries.
344
298
  * The reader lazily loads shards on demand; initial load is O(1).
345
299
  * @throws {Error} If treeOid is invalid or tree cannot be read from storage
@@ -22,8 +22,7 @@ function validateEnvelope(envelope, label) {
22
22
  *
23
23
  * @param {Record<string, string>} shardOids - Map of path → blob OID from readTreeOids
24
24
  * @param {import('../../ports/IndexStoragePort.js').default & import('../../ports/BlobPort.js').default} storage - Storage adapter
25
- * @param {Object} [options]
26
- * @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for deserialization
25
+ * @param {{ codec?: import('../../ports/CodecPort.js').default }} [options]
27
26
  * @returns {Promise<Map<string, string>|null>} Frontier map, or null if not present (legacy index)
28
27
  */
29
28
  export async function loadIndexFrontier(shardOids, storage, { codec } = {}) {
@@ -39,7 +38,7 @@ export async function loadIndexFrontier(shardOids, storage, { codec } = {}) {
39
38
  const jsonOid = shardOids['frontier.json'];
40
39
  if (jsonOid) {
41
40
  const buffer = await storage.readBlob(jsonOid);
42
- const envelope = /** @type {{ frontier: Record<string, string> }} */ (JSON.parse(buffer.toString('utf-8')));
41
+ const envelope = /** @type {{ frontier: Record<string, string> }} */ (JSON.parse(new TextDecoder().decode(buffer)));
43
42
  validateEnvelope(envelope, 'frontier.json');
44
43
  return new Map(Object.entries(envelope.frontier));
45
44
  }