@git-stunts/git-warp 11.2.1 → 11.5.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 (114) hide show
  1. package/README.md +24 -1
  2. package/bin/cli/commands/check.js +2 -2
  3. package/bin/cli/commands/doctor/checks.js +12 -12
  4. package/bin/cli/commands/doctor/index.js +2 -2
  5. package/bin/cli/commands/doctor/types.js +1 -1
  6. package/bin/cli/commands/history.js +12 -5
  7. package/bin/cli/commands/install-hooks.js +5 -5
  8. package/bin/cli/commands/materialize.js +2 -2
  9. package/bin/cli/commands/patch.js +142 -0
  10. package/bin/cli/commands/path.js +4 -4
  11. package/bin/cli/commands/query.js +54 -13
  12. package/bin/cli/commands/registry.js +4 -0
  13. package/bin/cli/commands/seek.js +17 -11
  14. package/bin/cli/commands/tree.js +230 -0
  15. package/bin/cli/commands/trust.js +3 -3
  16. package/bin/cli/commands/verify-audit.js +8 -7
  17. package/bin/cli/commands/view.js +6 -5
  18. package/bin/cli/infrastructure.js +26 -12
  19. package/bin/cli/shared.js +2 -2
  20. package/bin/cli/types.js +19 -8
  21. package/bin/presenters/index.js +35 -9
  22. package/bin/presenters/json.js +14 -12
  23. package/bin/presenters/text.js +155 -33
  24. package/index.d.ts +118 -22
  25. package/index.js +2 -0
  26. package/package.json +5 -3
  27. package/src/domain/WarpGraph.js +4 -1
  28. package/src/domain/crdt/ORSet.js +8 -8
  29. package/src/domain/errors/EmptyMessageError.js +2 -2
  30. package/src/domain/errors/ForkError.js +1 -1
  31. package/src/domain/errors/IndexError.js +1 -1
  32. package/src/domain/errors/OperationAbortedError.js +1 -1
  33. package/src/domain/errors/QueryError.js +1 -1
  34. package/src/domain/errors/SchemaUnsupportedError.js +1 -1
  35. package/src/domain/errors/ShardCorruptionError.js +2 -2
  36. package/src/domain/errors/ShardLoadError.js +2 -2
  37. package/src/domain/errors/ShardValidationError.js +4 -4
  38. package/src/domain/errors/StorageError.js +2 -2
  39. package/src/domain/errors/SyncError.js +1 -1
  40. package/src/domain/errors/TraversalError.js +1 -1
  41. package/src/domain/errors/TrustError.js +1 -1
  42. package/src/domain/errors/WarpError.js +2 -2
  43. package/src/domain/errors/WormholeError.js +1 -1
  44. package/src/domain/services/AuditReceiptService.js +6 -6
  45. package/src/domain/services/AuditVerifierService.js +52 -38
  46. package/src/domain/services/BitmapIndexBuilder.js +3 -3
  47. package/src/domain/services/BitmapIndexReader.js +28 -19
  48. package/src/domain/services/BoundaryTransitionRecord.js +18 -17
  49. package/src/domain/services/CheckpointSerializerV5.js +17 -16
  50. package/src/domain/services/CheckpointService.js +22 -3
  51. package/src/domain/services/CommitDagTraversalService.js +13 -13
  52. package/src/domain/services/DagPathFinding.js +7 -7
  53. package/src/domain/services/DagTopology.js +1 -1
  54. package/src/domain/services/DagTraversal.js +1 -1
  55. package/src/domain/services/HealthCheckService.js +1 -1
  56. package/src/domain/services/HookInstaller.js +1 -1
  57. package/src/domain/services/HttpSyncServer.js +92 -41
  58. package/src/domain/services/IndexRebuildService.js +7 -7
  59. package/src/domain/services/IndexStalenessChecker.js +4 -3
  60. package/src/domain/services/JoinReducer.js +26 -11
  61. package/src/domain/services/KeyCodec.js +7 -0
  62. package/src/domain/services/LogicalTraversal.js +1 -1
  63. package/src/domain/services/MessageCodecInternal.js +1 -1
  64. package/src/domain/services/MigrationService.js +1 -1
  65. package/src/domain/services/ObserverView.js +8 -8
  66. package/src/domain/services/PatchBuilderV2.js +96 -30
  67. package/src/domain/services/ProvenanceIndex.js +1 -1
  68. package/src/domain/services/ProvenancePayload.js +1 -1
  69. package/src/domain/services/QueryBuilder.js +3 -3
  70. package/src/domain/services/StateDiff.js +14 -11
  71. package/src/domain/services/StateSerializerV5.js +2 -2
  72. package/src/domain/services/StreamingBitmapIndexBuilder.js +26 -24
  73. package/src/domain/services/SyncAuthService.js +3 -2
  74. package/src/domain/services/SyncProtocol.js +25 -11
  75. package/src/domain/services/TemporalQuery.js +9 -6
  76. package/src/domain/services/TranslationCost.js +7 -5
  77. package/src/domain/services/WormholeService.js +16 -7
  78. package/src/domain/trust/TrustCanonical.js +3 -3
  79. package/src/domain/trust/TrustEvaluator.js +18 -3
  80. package/src/domain/trust/TrustRecordService.js +30 -23
  81. package/src/domain/trust/TrustStateBuilder.js +21 -8
  82. package/src/domain/trust/canonical.js +6 -6
  83. package/src/domain/types/TickReceipt.js +1 -1
  84. package/src/domain/types/WarpErrors.js +45 -0
  85. package/src/domain/types/WarpOptions.js +29 -0
  86. package/src/domain/types/WarpPersistence.js +41 -0
  87. package/src/domain/types/WarpTypes.js +2 -2
  88. package/src/domain/types/WarpTypesV2.js +2 -2
  89. package/src/domain/utils/MinHeap.js +6 -5
  90. package/src/domain/utils/canonicalStringify.js +5 -4
  91. package/src/domain/utils/roaring.js +31 -5
  92. package/src/domain/warp/PatchSession.js +40 -18
  93. package/src/domain/warp/_wiredMethods.d.ts +199 -45
  94. package/src/domain/warp/checkpoint.methods.js +5 -1
  95. package/src/domain/warp/fork.methods.js +2 -2
  96. package/src/domain/warp/materialize.methods.js +55 -5
  97. package/src/domain/warp/materializeAdvanced.methods.js +15 -4
  98. package/src/domain/warp/patch.methods.js +54 -29
  99. package/src/domain/warp/provenance.methods.js +5 -3
  100. package/src/domain/warp/query.methods.js +89 -6
  101. package/src/domain/warp/sync.methods.js +16 -11
  102. package/src/globals.d.ts +64 -0
  103. package/src/infrastructure/adapters/BunHttpAdapter.js +14 -9
  104. package/src/infrastructure/adapters/CasSeekCacheAdapter.js +9 -4
  105. package/src/infrastructure/adapters/DenoHttpAdapter.js +5 -6
  106. package/src/infrastructure/adapters/GitGraphAdapter.js +18 -13
  107. package/src/infrastructure/adapters/NodeHttpAdapter.js +2 -2
  108. package/src/infrastructure/adapters/WebCryptoAdapter.js +2 -2
  109. package/src/visualization/layouts/converters.js +2 -2
  110. package/src/visualization/layouts/elkAdapter.js +1 -1
  111. package/src/visualization/layouts/elkLayout.js +10 -7
  112. package/src/visualization/layouts/index.js +1 -1
  113. package/src/visualization/renderers/ascii/seek.js +16 -6
  114. package/src/visualization/renderers/svg/index.js +1 -1
@@ -17,6 +17,7 @@ import { openGraph, readActiveCursor, writeActiveCursor, wireSeekCache } from '.
17
17
  /** @typedef {import('../types.js').WriterTickInfo} WriterTickInfo */
18
18
  /** @typedef {import('../types.js').CursorBlob} CursorBlob */
19
19
  /** @typedef {import('../types.js').SeekSpec} SeekSpec */
20
+ /** @typedef {import('../../../src/domain/services/StateDiff.js').StateDiffResult} StateDiffResult */
20
21
 
21
22
  // ============================================================================
22
23
  // Cursor I/O Helpers (seek-only)
@@ -257,18 +258,19 @@ function computeSeekStateDiff(prevCursor, next, frontierHash) {
257
258
 
258
259
  /**
259
260
  * @param {{tick: number, perWriter: Map<string, WriterTickInfo>, graph: WarpGraphInstance}} params
260
- * @returns {Promise<Record<string, {sha: string, opSummary: *}>|null>}
261
+ * @returns {Promise<Record<string, {sha: string, opSummary: unknown}>|null>}
261
262
  */
262
263
  async function buildTickReceipt({ tick, perWriter, graph }) {
263
264
  if (!Number.isInteger(tick) || tick <= 0) {
264
265
  return null;
265
266
  }
266
267
 
267
- /** @type {Record<string, {sha: string, opSummary: *}>} */
268
+ /** @type {Record<string, {sha: string, opSummary: unknown}>} */
268
269
  const receipt = {};
269
270
 
270
271
  for (const [writerId, info] of perWriter) {
271
- const sha = /** @type {*} */ (info?.tickShas)?.[tick]; // TODO(ts-cleanup): type CLI payload
272
+ const tickShas = /** @type {Record<number, string> | undefined} */ (info?.tickShas);
273
+ const sha = tickShas?.[tick];
272
274
  if (!sha) {
273
275
  continue;
274
276
  }
@@ -283,7 +285,7 @@ async function buildTickReceipt({ tick, perWriter, graph }) {
283
285
 
284
286
  /**
285
287
  * @param {{graph: WarpGraphInstance, prevTick: number|null, currentTick: number, diffLimit: number}} params
286
- * @returns {Promise<{structuralDiff: *, diffBaseline: string, baselineTick: number|null, truncated: boolean, totalChanges: number, shownChanges: number}>}
288
+ * @returns {Promise<{structuralDiff: unknown, diffBaseline: string, baselineTick: number|null, truncated: boolean, totalChanges: number, shownChanges: number}>}
287
289
  */
288
290
  async function computeStructuralDiff({ graph, prevTick, currentTick, diffLimit }) {
289
291
  let beforeState = null;
@@ -303,18 +305,22 @@ async function computeStructuralDiff({ graph, prevTick, currentTick, diffLimit }
303
305
  }
304
306
 
305
307
  await graph.materialize({ ceiling: currentTick });
306
- const afterState = /** @type {*} */ (await graph.getStateSnapshot()); // TODO(ts-cleanup): narrow WarpStateV5
308
+ const afterState = await graph.getStateSnapshot();
309
+ if (!afterState) {
310
+ const empty = { nodes: { added: [], removed: [] }, edges: { added: [], removed: [] }, props: { set: [], removed: [] } };
311
+ return applyDiffLimit(empty, diffBaseline, baselineTick, diffLimit);
312
+ }
307
313
  const diff = diffStates(beforeState, afterState);
308
314
 
309
315
  return applyDiffLimit(diff, diffBaseline, baselineTick, diffLimit);
310
316
  }
311
317
 
312
318
  /**
313
- * @param {*} diff
319
+ * @param {StateDiffResult} diff
314
320
  * @param {string} diffBaseline
315
321
  * @param {number|null} baselineTick
316
322
  * @param {number} diffLimit
317
- * @returns {{structuralDiff: *, diffBaseline: string, baselineTick: number|null, truncated: boolean, totalChanges: number, shownChanges: number}}
323
+ * @returns {{structuralDiff: StateDiffResult, diffBaseline: string, baselineTick: number|null, truncated: boolean, totalChanges: number, shownChanges: number}}
318
324
  */
319
325
  function applyDiffLimit(diff, diffBaseline, baselineTick, diffLimit) {
320
326
  const totalChanges =
@@ -327,7 +333,7 @@ function applyDiffLimit(diff, diffBaseline, baselineTick, diffLimit) {
327
333
  }
328
334
 
329
335
  let remaining = diffLimit;
330
- const cap = (/** @type {any[]} */ arr) => {
336
+ const cap = (/** @type {unknown[]} */ arr) => {
331
337
  const take = Math.min(arr.length, remaining);
332
338
  remaining -= take;
333
339
  return arr.slice(0, take);
@@ -340,7 +346,7 @@ function applyDiffLimit(diff, diffBaseline, baselineTick, diffLimit) {
340
346
  };
341
347
 
342
348
  const shownChanges = diffLimit - remaining;
343
- return { structuralDiff: capped, diffBaseline, baselineTick, truncated: true, totalChanges, shownChanges };
349
+ return { structuralDiff: /** @type {StateDiffResult} */ (capped), diffBaseline, baselineTick, truncated: true, totalChanges, shownChanges };
344
350
  }
345
351
 
346
352
  // ============================================================================
@@ -349,7 +355,7 @@ function applyDiffLimit(diff, diffBaseline, baselineTick, diffLimit) {
349
355
 
350
356
  /**
351
357
  * @param {{graph: WarpGraphInstance, graphName: string, persistence: Persistence, activeCursor: CursorBlob|null, ticks: number[], maxTick: number, perWriter: Map<string, WriterTickInfo>, frontierHash: string}} params
352
- * @returns {Promise<{payload: *, exitCode: number}>}
358
+ * @returns {Promise<{payload: unknown, exitCode: number}>}
353
359
  */
354
360
  async function handleSeekStatus({ graph, graphName, persistence, activeCursor, ticks, maxTick, perWriter, frontierHash }) {
355
361
  if (activeCursor) {
@@ -411,7 +417,7 @@ async function handleSeekStatus({ graph, graphName, persistence, activeCursor, t
411
417
  /**
412
418
  * Handles the `git warp seek` command across all sub-actions.
413
419
  * @param {{options: CliOptions, args: string[]}} params
414
- * @returns {Promise<{payload: *, exitCode: number}>}
420
+ * @returns {Promise<{payload: unknown, exitCode: number}>}
415
421
  */
416
422
  export default async function handleSeek({ options, args }) {
417
423
  const seekSpec = parseSeekArgs(args);
@@ -0,0 +1,230 @@
1
+ import { EXIT_CODES, usageError, parseCommandArgs } from '../infrastructure.js';
2
+ import { openGraph, applyCursorCeiling, emitCursorWarning } from '../shared.js';
3
+ import { z } from 'zod';
4
+
5
+ /** @typedef {import('../types.js').CliOptions} CliOptions */
6
+
7
+ const TREE_OPTIONS = {
8
+ edge: { type: 'string' },
9
+ prop: { type: 'string', multiple: true },
10
+ 'max-depth': { type: 'string' },
11
+ };
12
+
13
+ const treeSchema = z.object({
14
+ edge: z.string().optional(),
15
+ prop: z.union([z.string(), z.array(z.string())]).optional(),
16
+ 'max-depth': z.coerce.number().int().nonnegative().optional(),
17
+ }).strict().transform((val) => ({
18
+ edgeLabel: val.edge ?? null,
19
+ props: Array.isArray(val.prop) ? val.prop : val.prop ? [val.prop] : [],
20
+ maxDepth: val['max-depth'],
21
+ }));
22
+
23
+ /**
24
+ * Builds a parent-to-children adjacency map from edges.
25
+ * @param {Array<{from: string, to: string, label?: string}>} edges
26
+ * @param {string|null} labelFilter
27
+ * @returns {Map<string, Array<{id: string, label: string}>>}
28
+ */
29
+ function buildChildMap(edges, labelFilter) {
30
+ /** @type {Map<string, Array<{id: string, label: string}>>} */
31
+ const children = new Map();
32
+ /** @type {Set<string>} */
33
+ const hasParent = new Set();
34
+
35
+ for (const edge of edges) {
36
+ if (labelFilter && edge.label !== labelFilter) {
37
+ continue;
38
+ }
39
+ if (!children.has(edge.from)) {
40
+ children.set(edge.from, []);
41
+ }
42
+ const fromChildren = children.get(edge.from);
43
+ if (fromChildren) {
44
+ fromChildren.push({ id: edge.to, label: edge.label || '' });
45
+ }
46
+ hasParent.add(edge.to);
47
+ }
48
+
49
+ return children;
50
+ }
51
+
52
+ /**
53
+ * Finds root nodes (nodes with outgoing edges but no incoming edges in the filtered set).
54
+ * @param {string[]} nodeIds
55
+ * @param {Array<{from: string, to: string, label?: string}>} edges
56
+ * @param {string|null} labelFilter
57
+ * @returns {string[]}
58
+ */
59
+ function findRoots(nodeIds, edges, labelFilter) {
60
+ const hasParent = new Set();
61
+ const hasChild = new Set();
62
+
63
+ for (const edge of edges) {
64
+ if (labelFilter && edge.label !== labelFilter) {
65
+ continue;
66
+ }
67
+ hasParent.add(edge.to);
68
+ hasChild.add(edge.from);
69
+ }
70
+
71
+ // Roots: nodes that have children but no parents in the filtered edge set
72
+ const roots = nodeIds.filter((id) => !hasParent.has(id) && hasChild.has(id));
73
+ if (roots.length > 0) {
74
+ return roots.sort();
75
+ }
76
+
77
+ // Fallback: nodes with no incoming edges at all
78
+ return nodeIds.filter((id) => !hasParent.has(id)).sort();
79
+ }
80
+
81
+ /**
82
+ * Formats annotation string for a node based on requested props.
83
+ * @param {Record<string, unknown>} nodeProps
84
+ * @param {string[]} propKeys
85
+ * @returns {string}
86
+ */
87
+ function formatAnnotation(nodeProps, propKeys) {
88
+ if (propKeys.length === 0 || !nodeProps) {
89
+ return '';
90
+ }
91
+ const parts = [];
92
+ for (const key of propKeys) {
93
+ if (Object.prototype.hasOwnProperty.call(nodeProps, key)) {
94
+ parts.push(`${key}: ${nodeProps[key]}`);
95
+ }
96
+ }
97
+ return parts.length > 0 ? ` [${parts.join(', ')}]` : '';
98
+ }
99
+
100
+ /**
101
+ * Renders a tree structure as lines with box-drawing characters.
102
+ * @param {object} params
103
+ * @param {string} params.nodeId
104
+ * @param {Map<string, Array<{id: string, label: string}>>} params.childMap
105
+ * @param {Map<string, Record<string, unknown>>} params.propsMap
106
+ * @param {string[]} params.propKeys
107
+ * @param {string} params.prefix
108
+ * @param {boolean} params.isLast
109
+ * @param {Set<string>} params.visited
110
+ * @param {number} params.depth
111
+ * @param {number|undefined} params.maxDepth
112
+ * @param {string[]} params.lines
113
+ */
114
+ function renderTreeNode({ nodeId, childMap, propsMap, propKeys, prefix, isLast, visited, depth, maxDepth, lines }) {
115
+ const connector = depth === 0 ? '' : (isLast ? '\u2514\u2500\u2500 ' : '\u251C\u2500\u2500 ');
116
+ const annotation = formatAnnotation(propsMap.get(nodeId) || {}, propKeys);
117
+ lines.push(`${prefix}${connector}${nodeId}${annotation}`);
118
+
119
+ if (visited.has(nodeId)) {
120
+ lines.push(`${prefix}${isLast ? ' ' : '\u2502 '} (cycle)`);
121
+ return;
122
+ }
123
+ visited.add(nodeId);
124
+
125
+ if (maxDepth !== undefined && depth >= maxDepth) {
126
+ const kids = childMap.get(nodeId);
127
+ if (kids && kids.length > 0) {
128
+ lines.push(`${prefix}${isLast ? ' ' : '\u2502 '} ... (${kids.length} children)`);
129
+ }
130
+ return;
131
+ }
132
+
133
+ const kids = childMap.get(nodeId) || [];
134
+ const childPrefix = depth === 0 ? '' : `${prefix}${isLast ? ' ' : '\u2502 '}`;
135
+ for (let i = 0; i < kids.length; i++) {
136
+ renderTreeNode({
137
+ nodeId: kids[i].id,
138
+ childMap,
139
+ propsMap,
140
+ propKeys,
141
+ prefix: childPrefix,
142
+ isLast: i === kids.length - 1,
143
+ visited,
144
+ depth: depth + 1,
145
+ maxDepth,
146
+ lines,
147
+ });
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Handles the `tree` command: ASCII tree output from graph edges.
153
+ * @param {{options: CliOptions, args: string[]}} params
154
+ * @returns {Promise<{payload: unknown, exitCode: number}>}
155
+ */
156
+ export default async function handleTree({ options, args }) {
157
+ const { values, positionals } = parseCommandArgs(
158
+ args, TREE_OPTIONS, treeSchema, { allowPositionals: true },
159
+ );
160
+ const { graph, graphName, persistence } = await openGraph(options);
161
+ const cursorInfo = await applyCursorCeiling(graph, persistence, graphName);
162
+ emitCursorWarning(cursorInfo, null);
163
+
164
+ const queryResult = await graph.query().run();
165
+ const edges = await graph.getEdges();
166
+ const rootArg = positionals[0] || null;
167
+
168
+ const nodeIds = queryResult.nodes.map((/** @type {{id: string}} */ n) => n.id);
169
+ const propsMap = new Map(queryResult.nodes.map((/** @type {{id: string, props?: Record<string, unknown>}} */ n) => [n.id, n.props || {}]));
170
+ const childMap = buildChildMap(edges, values.edgeLabel);
171
+
172
+ const roots = rootArg ? [rootArg] : findRoots(nodeIds, edges, values.edgeLabel);
173
+
174
+ if (rootArg && !nodeIds.includes(rootArg)) {
175
+ throw usageError(`Node not found: ${rootArg}`);
176
+ }
177
+
178
+ /** @type {string[]} */
179
+ const lines = [];
180
+ for (const root of roots) {
181
+ renderTreeNode({
182
+ nodeId: root,
183
+ childMap,
184
+ propsMap,
185
+ propKeys: values.props,
186
+ prefix: '',
187
+ isLast: true,
188
+ visited: new Set(),
189
+ depth: 0,
190
+ maxDepth: values.maxDepth,
191
+ lines,
192
+ });
193
+ }
194
+
195
+ // Collect orphans (nodes not reachable from any root)
196
+ const reachable = new Set();
197
+ collectReachable(roots, childMap, reachable);
198
+ const orphans = nodeIds.filter((/** @type {string} */ id) => !reachable.has(id));
199
+
200
+ const payload = {
201
+ graph: graphName,
202
+ roots,
203
+ tree: lines.join('\n'),
204
+ orphanCount: orphans.length,
205
+ orphans: orphans.length > 0 ? orphans : undefined,
206
+ };
207
+
208
+ return { payload, exitCode: EXIT_CODES.OK };
209
+ }
210
+
211
+ /**
212
+ * Collects all reachable node IDs via DFS.
213
+ * @param {string[]} roots
214
+ * @param {Map<string, Array<{id: string, label: string}>>} childMap
215
+ * @param {Set<string>} reachable
216
+ */
217
+ function collectReachable(roots, childMap, reachable) {
218
+ const stack = [...roots];
219
+ while (stack.length > 0) {
220
+ const id = /** @type {string} */ (stack.pop());
221
+ if (reachable.has(id)) {
222
+ continue;
223
+ }
224
+ reachable.add(id);
225
+ const kids = childMap.get(id) || [];
226
+ for (const kid of kids) {
227
+ stack.push(kid.id);
228
+ }
229
+ }
230
+ }
@@ -49,7 +49,7 @@ function resolveTrustPin(cliPin) {
49
49
 
50
50
  /**
51
51
  * Discovers all writer IDs from the writers prefix refs.
52
- * @param {*} persistence
52
+ * @param {import('../types.js').Persistence} persistence
53
53
  * @param {string} graphName
54
54
  * @returns {Promise<string[]>}
55
55
  */
@@ -96,7 +96,7 @@ function buildNotConfiguredResult(graphName) {
96
96
 
97
97
  /**
98
98
  * @param {{options: CliOptions, args: string[]}} params
99
- * @returns {Promise<{payload: *, exitCode: number}>}
99
+ * @returns {Promise<{payload: unknown, exitCode: number}>}
100
100
  */
101
101
  export default async function handleTrust({ options, args }) {
102
102
  const { mode, trustPin } = parseTrustArgs(args);
@@ -104,7 +104,7 @@ export default async function handleTrust({ options, args }) {
104
104
  const graphName = await resolveGraphName(persistence, options.graph);
105
105
 
106
106
  const recordService = new TrustRecordService({
107
- persistence: /** @type {*} TODO(ts-cleanup) */ (persistence),
107
+ persistence: /** @type {import('../../../src/domain/types/WarpPersistence.js').CorePersistence} */ (/** @type {unknown} */ (persistence)),
108
108
  codec: defaultCodec,
109
109
  });
110
110
 
@@ -46,20 +46,20 @@ export function parseVerifyAuditArgs(args) {
46
46
 
47
47
  /**
48
48
  * @param {{options: CliOptions, args: string[]}} params
49
- * @returns {Promise<{payload: *, exitCode: number}>}
49
+ * @returns {Promise<{payload: unknown, exitCode: number}>}
50
50
  */
51
51
  export default async function handleVerifyAudit({ options, args }) {
52
52
  const { since, writerFilter, trustMode, trustPin } = parseVerifyAuditArgs(args);
53
53
  const { persistence } = await createPersistence(options.repo);
54
54
  const graphName = await resolveGraphName(persistence, options.graph);
55
55
  const verifier = new AuditVerifierService({
56
- persistence: /** @type {*} */ (persistence), // TODO(ts-cleanup): narrow port type
56
+ persistence: /** @type {import('../../../src/domain/types/WarpPersistence.js').CorePersistence} */ (/** @type {unknown} */ (persistence)),
57
57
  codec: defaultCodec,
58
58
  });
59
59
 
60
60
  const trustWarning = detectTrustWarning();
61
61
 
62
- /** @type {*} */ // TODO(ts-cleanup): type verify-audit payload
62
+ /** @type {Record<string, unknown>} */
63
63
  let payload;
64
64
  if (writerFilter !== undefined) {
65
65
  const chain = await verifier.verifyChain(graphName, writerFilter, { since });
@@ -88,7 +88,7 @@ export default async function handleVerifyAudit({ options, args }) {
88
88
  mode: trustMode,
89
89
  });
90
90
  payload.trustAssessment = trustAssessment;
91
- } catch (/** @type {*} */ err) { // TODO(ts-cleanup): type catch
91
+ } catch (err) {
92
92
  if (trustMode === 'enforce') {
93
93
  throw err;
94
94
  }
@@ -96,14 +96,15 @@ export default async function handleVerifyAudit({ options, args }) {
96
96
  trustSchemaVersion: 1,
97
97
  mode: 'signed_evidence_v1',
98
98
  trustVerdict: 'error',
99
- error: err?.message ?? 'Trust evaluation failed',
99
+ error: err instanceof Error ? err.message : 'Trust evaluation failed',
100
100
  };
101
101
  }
102
102
  }
103
103
 
104
- const hasInvalid = payload.summary.invalid > 0;
104
+ const { summary, trustAssessment } = /** @type {{summary: {invalid: number}, trustAssessment?: {trustVerdict?: string}}} */ (payload);
105
+ const hasInvalid = summary.invalid > 0;
105
106
  const trustFailed = trustMode === 'enforce' &&
106
- payload.trustAssessment?.trustVerdict === 'fail';
107
+ trustAssessment?.trustVerdict === 'fail';
107
108
  return {
108
109
  payload,
109
110
  exitCode: trustFailed ? EXIT_CODES.TRUST_FAIL
@@ -11,7 +11,7 @@ const VIEW_OPTIONS = {
11
11
 
12
12
  /**
13
13
  * @param {{options: CliOptions, args: string[]}} params
14
- * @returns {Promise<{payload: *, exitCode: number}>}
14
+ * @returns {Promise<{payload: unknown, exitCode: number}>}
15
15
  */
16
16
  export default async function handleView({ options, args }) {
17
17
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
@@ -29,10 +29,11 @@ export default async function handleView({ options, args }) {
29
29
  graph: options.graph || 'default',
30
30
  mode: viewMode,
31
31
  });
32
- } catch (/** @type {*} */ err) { // TODO(ts-cleanup): type error
33
- const isMissing = err.code === 'ERR_MODULE_NOT_FOUND' || (err.message && err.message.includes('Cannot find module'));
34
- const isTui = err.specifier?.includes('git-warp-tui') ||
35
- /cannot find (?:package|module) ['"]@git-stunts\/git-warp-tui/i.test(err.message);
32
+ } catch (err) {
33
+ const errObj = /** @type {{code?: string, message?: string, specifier?: string}} */ (typeof err === 'object' && err !== null ? err : {});
34
+ const isMissing = errObj.code === 'ERR_MODULE_NOT_FOUND' || (errObj.message && errObj.message.includes('Cannot find module'));
35
+ const isTui = errObj.specifier?.includes('git-warp-tui') ||
36
+ /cannot find (?:package|module) ['"]@git-stunts\/git-warp-tui/i.test(errObj.message || '');
36
37
  if (isMissing && isTui) {
37
38
  throw usageError(
38
39
  'Interactive TUI requires @git-stunts/git-warp-tui.\n' +
@@ -11,6 +11,8 @@ export const EXIT_CODES = {
11
11
  INTERNAL: 3,
12
12
  /** Trust policy denial (enforce mode). */
13
13
  TRUST_FAIL: 4,
14
+ /** Valid result but negative (e.g. no path found). Follows grep convention. */
15
+ NO_MATCH: 1,
14
16
  };
15
17
 
16
18
  /**
@@ -22,9 +24,7 @@ export function getEnvVar(name) {
22
24
  if (typeof process !== 'undefined' && process.env) {
23
25
  return process.env[name];
24
26
  }
25
- // @ts-expect-error — Deno global is only present in Deno runtime
26
27
  if (typeof Deno !== 'undefined') {
27
- // @ts-expect-error — Deno global is only present in Deno runtime
28
28
  // eslint-disable-next-line no-undef
29
29
  try { return Deno.env.get(name); } catch { return undefined; }
30
30
  }
@@ -45,6 +45,8 @@ Commands:
45
45
  trust Evaluate writer trust from signed evidence
46
46
  materialize Materialize and checkpoint all graphs
47
47
  seek Time-travel: step through graph history by Lamport tick
48
+ patch Decode and inspect raw patches
49
+ tree ASCII tree traversal from root nodes
48
50
  view Interactive TUI graph browser (requires @git-stunts/git-warp-tui)
49
51
  install-hooks Install post-merge git hook
50
52
 
@@ -99,6 +101,18 @@ Seek options:
99
101
  --drop <name> Delete a saved cursor
100
102
  --diff Show structural diff (added/removed nodes, edges, props)
101
103
  --diff-limit <N> Max diff entries (default 2000)
104
+
105
+ Patch options:
106
+ show <sha> Decode and display a single patch as JSON
107
+ list List all patches sorted by Lamport clock
108
+ --writer <id> Filter by writer (list only)
109
+ --limit <n> Max entries to show (list only)
110
+
111
+ Tree options:
112
+ [rootNode] Root node id (auto-detected if omitted)
113
+ --edge <label> Follow only this edge label
114
+ --prop <key> Annotate nodes with this property (repeatable)
115
+ --max-depth <n> Maximum traversal depth
102
116
  `;
103
117
 
104
118
  /**
@@ -130,7 +144,7 @@ export function notFoundError(message) {
130
144
  return new CliError(message, { code: 'E_NOT_FOUND', exitCode: EXIT_CODES.NOT_FOUND });
131
145
  }
132
146
 
133
- export const KNOWN_COMMANDS = ['info', 'query', 'path', 'history', 'check', 'doctor', 'materialize', 'seek', 'verify-audit', 'trust', 'install-hooks', 'view'];
147
+ export const KNOWN_COMMANDS = ['info', 'query', 'path', 'history', 'check', 'doctor', 'materialize', 'seek', 'verify-audit', 'trust', 'patch', 'tree', 'install-hooks', 'view'];
134
148
 
135
149
  const BASE_OPTIONS = {
136
150
  repo: { type: 'string', short: 'r' },
@@ -272,17 +286,17 @@ export function parseArgs(argv) {
272
286
  const { baseArgs, command, commandArgs } = extractBaseArgs(argv);
273
287
  const processed = preprocessView(baseArgs);
274
288
 
275
- /** @type {*} */ // TODO(ts-cleanup): type parseArgs return
289
+ /** @type {{ values: Record<string, string|boolean|string[]|boolean[]|undefined>, positionals: string[] }} */
276
290
  let parsed;
277
291
  try {
278
292
  parsed = nodeParseArgs({
279
293
  args: processed,
280
- options: /** @type {*} */ (BASE_OPTIONS), // TODO(ts-cleanup): type parseArgs config
294
+ options: /** @type {import('node:util').ParseArgsConfig['options']} */ (BASE_OPTIONS),
281
295
  strict: true,
282
296
  allowPositionals: false,
283
297
  });
284
- } catch (/** @type {*} */ err) { // TODO(ts-cleanup): type parseArgs error
285
- throw usageError(err.message);
298
+ } catch (err) {
299
+ throw usageError(err instanceof Error ? err.message : String(err));
286
300
  }
287
301
 
288
302
  const { values } = parsed;
@@ -312,22 +326,22 @@ export function parseArgs(argv) {
312
326
  * @returns {{values: *, positionals: string[]}}
313
327
  */
314
328
  export function parseCommandArgs(args, config, schema, { allowPositionals = false } = {}) {
315
- /** @type {*} */ // TODO(ts-cleanup): type parseArgs return
329
+ /** @type {{ values: Record<string, string|boolean|string[]|boolean[]|undefined>, positionals: string[] }} */
316
330
  let parsed;
317
331
  try {
318
332
  parsed = nodeParseArgs({
319
333
  args,
320
- options: /** @type {*} */ (config), // TODO(ts-cleanup): type parseArgs config
334
+ options: /** @type {import('node:util').ParseArgsConfig['options']} */ (config),
321
335
  strict: true,
322
336
  allowPositionals,
323
337
  });
324
- } catch (/** @type {*} */ err) { // TODO(ts-cleanup): type parseArgs error
325
- throw usageError(err.message);
338
+ } catch (err) {
339
+ throw usageError(err instanceof Error ? err.message : String(err));
326
340
  }
327
341
 
328
342
  const result = schema.safeParse(parsed.values);
329
343
  if (!result.success) {
330
- const msg = result.error.issues.map((/** @type {*} */ issue) => issue.message).join('; '); // TODO(ts-cleanup): type Zod issue
344
+ const msg = result.error.issues.map((/** @type {{message: string}} */ issue) => issue.message).join('; ');
331
345
  throw usageError(msg);
332
346
  }
333
347
 
package/bin/cli/shared.js CHANGED
@@ -92,7 +92,7 @@ export async function openGraph(options) {
92
92
  throw notFoundError(`Graph not found: ${options.graph}`);
93
93
  }
94
94
  }
95
- const graph = /** @type {WarpGraphInstance} */ (/** @type {*} */ (await WarpGraph.open({ // TODO(ts-cleanup): narrow port type
95
+ const graph = /** @type {WarpGraphInstance} */ (/** @type {unknown} */ (await WarpGraph.open({
96
96
  persistence,
97
97
  graphName,
98
98
  writerId: options.writer,
@@ -183,7 +183,7 @@ export function createHookInstaller() {
183
183
  const templateDir = path.resolve(__dirname, '..', '..', 'scripts', 'hooks');
184
184
  const { version } = JSON.parse(fs.readFileSync(path.resolve(__dirname, '..', '..', 'package.json'), 'utf8'));
185
185
  return new HookInstaller({
186
- fs: /** @type {*} */ (fs), // TODO(ts-cleanup): narrow port type
186
+ fs: /** @type {import('../../src/domain/services/HookInstaller.js').FsAdapter} */ (/** @type {unknown} */ (fs)),
187
187
  execGitConfig: execGitConfigValue,
188
188
  version,
189
189
  templateDir,
package/bin/cli/types.js CHANGED
@@ -10,7 +10,7 @@
10
10
  * @property {(sha: string) => Promise<boolean>} nodeExists
11
11
  * @property {(sha: string, coverageSha: string) => Promise<boolean>} isAncestor
12
12
  * @property {() => Promise<{ok: boolean}>} ping
13
- * @property {*} plumbing
13
+ * @property {unknown} plumbing
14
14
  */
15
15
 
16
16
  /**
@@ -19,18 +19,19 @@
19
19
  * @property {() => Promise<Array<{id: string}>>} getNodes
20
20
  * @property {() => Promise<Array<{from: string, to: string, label?: string}>>} getEdges
21
21
  * @property {() => Promise<string|null>} createCheckpoint
22
- * @property {() => *} query
22
+ * @property {() => QueryBuilderLike} query
23
23
  * @property {{ shortestPath: Function }} traverse
24
- * @property {(writerId: string) => Promise<Array<{patch: any, sha: string}>>} getWriterPatches
25
- * @property {() => Promise<{frontier: Record<string, any>}>} status
26
- * @property {() => Promise<Map<string, any>>} getFrontier
24
+ * @property {(writerId: string) => Promise<Array<{patch: {schema?: number, lamport: number, ops?: Array<{type: string, node?: string, from?: string, to?: string}>}, sha: string}>>} getWriterPatches
25
+ * @property {() => Promise<{frontier: Record<string, string>}>} status
26
+ * @property {() => Promise<string[]>} discoverWriters
27
+ * @property {() => Promise<Map<string, string>>} getFrontier
27
28
  * @property {() => {totalTombstones: number, tombstoneRatio: number}} getGCMetrics
28
29
  * @property {() => Promise<number>} getPropertyCount
29
30
  * @property {() => Promise<import('../../src/domain/services/JoinReducer.js').WarpStateV5 | null>} getStateSnapshot
30
31
  * @property {() => Promise<{ticks: number[], maxTick: number, perWriter: Map<string, WriterTickInfo>}>} discoverTicks
31
- * @property {(sha: string) => Promise<{ops?: any[]}>} loadPatchBySha
32
- * @property {(cache: any) => void} setSeekCache
33
- * @property {*} seekCache
32
+ * @property {(sha: string) => Promise<{ops?: Array<{type: string, node?: string, from?: string, to?: string}>}>} loadPatchBySha
33
+ * @property {(cache: import('../../src/ports/SeekCachePort.js').default) => void} setSeekCache
34
+ * @property {{clear: () => Promise<void>} | null} seekCache
34
35
  * @property {number} [_seekCeiling]
35
36
  * @property {boolean} [_provenanceDegraded]
36
37
  */
@@ -82,4 +83,14 @@
82
83
  * @property {number} diffLimit
83
84
  */
84
85
 
86
+ /**
87
+ * @typedef {Object} QueryBuilderLike
88
+ * @property {(label?: string) => QueryBuilderLike} outgoing
89
+ * @property {(label?: string) => QueryBuilderLike} incoming
90
+ * @property {(fn: Function) => QueryBuilderLike} where
91
+ * @property {(pattern: string) => QueryBuilderLike} match
92
+ * @property {(fields: string[]) => QueryBuilderLike} select
93
+ * @property {() => Promise<{nodes: Array<{id: string, props?: Record<string, unknown>}>, stateHash?: string}>} run
94
+ */
95
+
85
96
  export {};