@git-stunts/git-warp 10.7.0 → 11.2.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 (71) hide show
  1. package/README.md +53 -32
  2. package/SECURITY.md +64 -0
  3. package/bin/cli/commands/check.js +168 -0
  4. package/bin/cli/commands/doctor/checks.js +422 -0
  5. package/bin/cli/commands/doctor/codes.js +46 -0
  6. package/bin/cli/commands/doctor/index.js +239 -0
  7. package/bin/cli/commands/doctor/types.js +89 -0
  8. package/bin/cli/commands/history.js +73 -0
  9. package/bin/cli/commands/info.js +139 -0
  10. package/bin/cli/commands/install-hooks.js +128 -0
  11. package/bin/cli/commands/materialize.js +99 -0
  12. package/bin/cli/commands/path.js +88 -0
  13. package/bin/cli/commands/query.js +194 -0
  14. package/bin/cli/commands/registry.js +28 -0
  15. package/bin/cli/commands/seek.js +592 -0
  16. package/bin/cli/commands/trust.js +154 -0
  17. package/bin/cli/commands/verify-audit.js +113 -0
  18. package/bin/cli/commands/view.js +45 -0
  19. package/bin/cli/infrastructure.js +336 -0
  20. package/bin/cli/schemas.js +177 -0
  21. package/bin/cli/shared.js +244 -0
  22. package/bin/cli/types.js +85 -0
  23. package/bin/presenters/index.js +214 -0
  24. package/bin/presenters/json.js +66 -0
  25. package/bin/presenters/text.js +543 -0
  26. package/bin/warp-graph.js +19 -2824
  27. package/index.d.ts +32 -2
  28. package/index.js +2 -0
  29. package/package.json +9 -7
  30. package/src/domain/WarpGraph.js +106 -3252
  31. package/src/domain/errors/QueryError.js +2 -2
  32. package/src/domain/errors/TrustError.js +29 -0
  33. package/src/domain/errors/index.js +1 -0
  34. package/src/domain/services/AuditMessageCodec.js +137 -0
  35. package/src/domain/services/AuditReceiptService.js +471 -0
  36. package/src/domain/services/AuditVerifierService.js +693 -0
  37. package/src/domain/services/HttpSyncServer.js +36 -22
  38. package/src/domain/services/MessageCodecInternal.js +3 -0
  39. package/src/domain/services/MessageSchemaDetector.js +2 -2
  40. package/src/domain/services/SyncAuthService.js +69 -3
  41. package/src/domain/services/WarpMessageCodec.js +4 -1
  42. package/src/domain/trust/TrustCanonical.js +42 -0
  43. package/src/domain/trust/TrustCrypto.js +111 -0
  44. package/src/domain/trust/TrustEvaluator.js +180 -0
  45. package/src/domain/trust/TrustRecordService.js +274 -0
  46. package/src/domain/trust/TrustStateBuilder.js +209 -0
  47. package/src/domain/trust/canonical.js +68 -0
  48. package/src/domain/trust/reasonCodes.js +64 -0
  49. package/src/domain/trust/schemas.js +160 -0
  50. package/src/domain/trust/verdict.js +42 -0
  51. package/src/domain/types/git-cas.d.ts +20 -0
  52. package/src/domain/utils/RefLayout.js +59 -0
  53. package/src/domain/warp/PatchSession.js +18 -0
  54. package/src/domain/warp/Writer.js +18 -3
  55. package/src/domain/warp/_internal.js +26 -0
  56. package/src/domain/warp/_wire.js +58 -0
  57. package/src/domain/warp/_wiredMethods.d.ts +100 -0
  58. package/src/domain/warp/checkpoint.methods.js +397 -0
  59. package/src/domain/warp/fork.methods.js +323 -0
  60. package/src/domain/warp/materialize.methods.js +188 -0
  61. package/src/domain/warp/materializeAdvanced.methods.js +339 -0
  62. package/src/domain/warp/patch.methods.js +529 -0
  63. package/src/domain/warp/provenance.methods.js +284 -0
  64. package/src/domain/warp/query.methods.js +279 -0
  65. package/src/domain/warp/subscribe.methods.js +272 -0
  66. package/src/domain/warp/sync.methods.js +549 -0
  67. package/src/infrastructure/adapters/GitGraphAdapter.js +67 -1
  68. package/src/infrastructure/adapters/InMemoryGraphAdapter.js +36 -0
  69. package/src/ports/CommitPort.js +10 -0
  70. package/src/ports/RefPort.js +17 -0
  71. package/src/hooks/post-merge.sh +0 -60
@@ -0,0 +1,177 @@
1
+ import { z } from 'zod';
2
+
3
+ // ============================================================================
4
+ // History
5
+ // ============================================================================
6
+
7
+ export const historySchema = z.object({
8
+ node: z.string().optional(),
9
+ }).strict();
10
+
11
+ // ============================================================================
12
+ // Install-hooks
13
+ // ============================================================================
14
+
15
+ export const installHooksSchema = z.object({
16
+ force: z.boolean().default(false),
17
+ }).strict();
18
+
19
+ // ============================================================================
20
+ // Verify-audit
21
+ // ============================================================================
22
+
23
+ export const verifyAuditSchema = z.object({
24
+ since: z.string().min(1, 'Missing value for --since').optional(),
25
+ writer: z.string().min(1, 'Missing value for --writer').optional(),
26
+ 'trust-mode': z.enum(['warn', 'enforce']).optional(),
27
+ 'trust-pin': z.string().min(1, 'Missing value for --trust-pin').optional(),
28
+ }).strict();
29
+
30
+ // ============================================================================
31
+ // Path
32
+ // ============================================================================
33
+
34
+ export const pathSchema = z.object({
35
+ from: z.string().optional(),
36
+ to: z.string().optional(),
37
+ dir: z.enum(['out', 'in', 'both']).optional(),
38
+ label: z.union([z.string(), z.array(z.string())]).optional(),
39
+ 'max-depth': z.coerce.number().int().nonnegative().optional(),
40
+ }).strict().transform((val) => ({
41
+ from: val.from ?? null,
42
+ to: val.to ?? null,
43
+ dir: val.dir,
44
+ labels: Array.isArray(val.label) ? val.label : val.label ? [val.label] : [],
45
+ maxDepth: val['max-depth'],
46
+ }));
47
+
48
+ // ============================================================================
49
+ // Query
50
+ // ============================================================================
51
+
52
+ export const querySchema = z.object({
53
+ match: z.string().optional(),
54
+ 'where-prop': z.union([z.string(), z.array(z.string())]).optional(),
55
+ select: z.string().optional(),
56
+ }).strict().transform((val) => ({
57
+ match: val.match ?? null,
58
+ whereProp: Array.isArray(val['where-prop']) ? val['where-prop'] : val['where-prop'] ? [val['where-prop']] : [],
59
+ select: val.select,
60
+ }));
61
+
62
+ // ============================================================================
63
+ // View
64
+ // ============================================================================
65
+
66
+ export const viewSchema = z.object({
67
+ list: z.boolean().default(false),
68
+ log: z.boolean().default(false),
69
+ }).strict();
70
+
71
+ // ============================================================================
72
+ // Trust
73
+ // ============================================================================
74
+
75
+ export const trustSchema = z.object({
76
+ mode: z.enum(['warn', 'enforce']).optional(),
77
+ 'trust-pin': z.string().min(1, 'Missing value for --trust-pin').optional(),
78
+ }).strict().transform((val) => ({
79
+ mode: val.mode ?? null,
80
+ trustPin: val['trust-pin'] ?? null,
81
+ }));
82
+
83
+ // ============================================================================
84
+ // Doctor
85
+ // ============================================================================
86
+
87
+ export const doctorSchema = z.object({
88
+ strict: z.boolean().default(false),
89
+ }).strict();
90
+
91
+ // ============================================================================
92
+ // Seek
93
+ // ============================================================================
94
+
95
+ export const seekSchema = z.object({
96
+ tick: z.string().optional(),
97
+ latest: z.boolean().default(false),
98
+ save: z.string().min(1, 'Missing value for --save').optional(),
99
+ load: z.string().min(1, 'Missing value for --load').optional(),
100
+ list: z.boolean().default(false),
101
+ drop: z.string().min(1, 'Missing value for --drop').optional(),
102
+ 'clear-cache': z.boolean().default(false),
103
+ 'no-persistent-cache': z.boolean().default(false),
104
+ diff: z.boolean().default(false),
105
+ 'diff-limit': z.coerce.number().int({ message: '--diff-limit must be a positive integer' }).positive({ message: '--diff-limit must be a positive integer' }).default(2000),
106
+ }).strict().superRefine((val, ctx) => {
107
+ // Count mutually exclusive action flags
108
+ const actions = [
109
+ val.tick !== undefined,
110
+ val.latest,
111
+ val.save !== undefined,
112
+ val.load !== undefined,
113
+ val.list,
114
+ val.drop !== undefined,
115
+ val['clear-cache'],
116
+ ].filter(Boolean);
117
+
118
+ if (actions.length > 1) {
119
+ ctx.addIssue({
120
+ code: z.ZodIssueCode.custom,
121
+ message: 'Only one seek action flag allowed at a time (--tick, --latest, --save, --load, --list, --drop, --clear-cache)',
122
+ });
123
+ }
124
+
125
+ // --diff only with tick/latest/load
126
+ const DIFF_ACTIONS = val.tick !== undefined || val.latest || val.load !== undefined;
127
+ if (val.diff && !DIFF_ACTIONS) {
128
+ ctx.addIssue({
129
+ code: z.ZodIssueCode.custom,
130
+ message: '--diff cannot be used without --tick, --latest, or --load',
131
+ });
132
+ }
133
+
134
+ // --diff-limit requires --diff
135
+ if (val['diff-limit'] !== 2000 && !val.diff) {
136
+ ctx.addIssue({
137
+ code: z.ZodIssueCode.custom,
138
+ message: '--diff-limit requires --diff',
139
+ });
140
+ }
141
+ }).transform((val) => {
142
+ /** @type {string} */
143
+ let action = 'status';
144
+ /** @type {string|null} */
145
+ let tickValue = null;
146
+ /** @type {string|null} */
147
+ let name = null;
148
+
149
+ if (val.tick !== undefined) {
150
+ action = 'tick';
151
+ tickValue = val.tick;
152
+ } else if (val.latest) {
153
+ action = 'latest';
154
+ } else if (val.save !== undefined) {
155
+ action = 'save';
156
+ name = val.save;
157
+ } else if (val.load !== undefined) {
158
+ action = 'load';
159
+ name = val.load;
160
+ } else if (val.list) {
161
+ action = 'list';
162
+ } else if (val.drop !== undefined) {
163
+ action = 'drop';
164
+ name = val.drop;
165
+ } else if (val['clear-cache']) {
166
+ action = 'clear-cache';
167
+ }
168
+
169
+ return {
170
+ action,
171
+ tickValue,
172
+ name,
173
+ noPersistentCache: val['no-persistent-cache'],
174
+ diff: val.diff,
175
+ diffLimit: val['diff-limit'],
176
+ };
177
+ });
@@ -0,0 +1,244 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import process from 'node:process';
4
+ import readline from 'node:readline';
5
+ import { execFileSync } from 'node:child_process';
6
+ // @ts-expect-error — no type declarations for @git-stunts/plumbing
7
+ import GitPlumbing, { ShellRunnerFactory } from '@git-stunts/plumbing';
8
+ import WarpGraph from '../../src/domain/WarpGraph.js';
9
+ import GitGraphAdapter from '../../src/infrastructure/adapters/GitGraphAdapter.js';
10
+ import WebCryptoAdapter from '../../src/infrastructure/adapters/WebCryptoAdapter.js';
11
+ import {
12
+ REF_PREFIX,
13
+ buildCursorActiveRef,
14
+ } from '../../src/domain/utils/RefLayout.js';
15
+ import CasSeekCacheAdapter from '../../src/infrastructure/adapters/CasSeekCacheAdapter.js';
16
+ import { HookInstaller } from '../../src/domain/services/HookInstaller.js';
17
+ import { parseCursorBlob } from '../../src/domain/utils/parseCursorBlob.js';
18
+ import { usageError, notFoundError } from './infrastructure.js';
19
+
20
+ /** @typedef {import('./types.js').Persistence} Persistence */
21
+ /** @typedef {import('./types.js').WarpGraphInstance} WarpGraphInstance */
22
+ /** @typedef {import('./types.js').CursorBlob} CursorBlob */
23
+ /** @typedef {import('./types.js').CliOptions} CliOptions */
24
+ /** @typedef {import('./types.js').SeekSpec} SeekSpec */
25
+
26
+ /** @param {string} repoPath @returns {Promise<{persistence: Persistence}>} */
27
+ export async function createPersistence(repoPath) {
28
+ const runner = ShellRunnerFactory.create();
29
+ const plumbing = new GitPlumbing({ cwd: repoPath, runner });
30
+ const persistence = new GitGraphAdapter({ plumbing });
31
+ const ping = await persistence.ping();
32
+ if (!ping.ok) {
33
+ throw usageError(`Repository not accessible: ${repoPath}`);
34
+ }
35
+ return { persistence };
36
+ }
37
+
38
+ /** @param {Persistence} persistence @returns {Promise<string[]>} */
39
+ export async function listGraphNames(persistence) {
40
+ if (typeof persistence.listRefs !== 'function') {
41
+ return [];
42
+ }
43
+ const refs = await persistence.listRefs(REF_PREFIX);
44
+ const prefix = `${REF_PREFIX}/`;
45
+ const names = new Set();
46
+
47
+ for (const ref of refs) {
48
+ if (!ref.startsWith(prefix)) {
49
+ continue;
50
+ }
51
+ const rest = ref.slice(prefix.length);
52
+ const [graphName] = rest.split('/');
53
+ if (graphName) {
54
+ names.add(graphName);
55
+ }
56
+ }
57
+
58
+ return [...names].sort();
59
+ }
60
+
61
+ /**
62
+ * @param {Persistence} persistence
63
+ * @param {string|null} explicitGraph
64
+ * @returns {Promise<string>}
65
+ */
66
+ export async function resolveGraphName(persistence, explicitGraph) {
67
+ if (explicitGraph) {
68
+ return explicitGraph;
69
+ }
70
+ const graphNames = await listGraphNames(persistence);
71
+ if (graphNames.length === 1) {
72
+ return graphNames[0];
73
+ }
74
+ if (graphNames.length === 0) {
75
+ throw notFoundError('No graphs found in repo; specify --graph');
76
+ }
77
+ throw usageError('Multiple graphs found; specify --graph');
78
+ }
79
+
80
+ /**
81
+ * Opens a WarpGraph for the given CLI options.
82
+ * @param {CliOptions} options - Parsed CLI options
83
+ * @returns {Promise<{graph: WarpGraphInstance, graphName: string, persistence: Persistence}>}
84
+ * @throws {import('./infrastructure.js').CliError} If the specified graph is not found
85
+ */
86
+ export async function openGraph(options) {
87
+ const { persistence } = await createPersistence(options.repo);
88
+ const graphName = await resolveGraphName(persistence, options.graph);
89
+ if (options.graph) {
90
+ const graphNames = await listGraphNames(persistence);
91
+ if (!graphNames.includes(options.graph)) {
92
+ throw notFoundError(`Graph not found: ${options.graph}`);
93
+ }
94
+ }
95
+ const graph = /** @type {WarpGraphInstance} */ (/** @type {*} */ (await WarpGraph.open({ // TODO(ts-cleanup): narrow port type
96
+ persistence,
97
+ graphName,
98
+ writerId: options.writer,
99
+ crypto: new WebCryptoAdapter(),
100
+ })));
101
+ return { graph, graphName, persistence };
102
+ }
103
+
104
+ /**
105
+ * Reads the active cursor and sets `_seekCeiling` on the graph instance
106
+ * so that subsequent materialize calls respect the time-travel boundary.
107
+ *
108
+ * @param {WarpGraphInstance} graph - WarpGraph instance
109
+ * @param {Persistence} persistence - GraphPersistencePort adapter
110
+ * @param {string} graphName - Name of the WARP graph
111
+ * @returns {Promise<{active: boolean, tick: number|null, maxTick: number|null}>}
112
+ */
113
+ export async function applyCursorCeiling(graph, persistence, graphName) {
114
+ const cursor = await readActiveCursor(persistence, graphName);
115
+ if (cursor) {
116
+ graph._seekCeiling = cursor.tick;
117
+ return { active: true, tick: cursor.tick, maxTick: null };
118
+ }
119
+ return { active: false, tick: null, maxTick: null };
120
+ }
121
+
122
+ /**
123
+ * Prints a seek cursor warning banner to stderr when a cursor is active.
124
+ *
125
+ * @param {{active: boolean, tick: number|null, maxTick: number|null}} cursorInfo
126
+ * @param {number|null} maxTick
127
+ * @returns {void}
128
+ */
129
+ export function emitCursorWarning(cursorInfo, maxTick) {
130
+ if (cursorInfo.active) {
131
+ const maxLabel = maxTick !== null && maxTick !== undefined ? ` of ${maxTick}` : '';
132
+ process.stderr.write(`\u26A0 seek active (tick ${cursorInfo.tick}${maxLabel}) \u2014 run "git warp seek --latest" to return to present\n`);
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Reads the active seek cursor for a graph from Git ref storage.
138
+ *
139
+ * @param {Persistence} persistence - GraphPersistencePort adapter
140
+ * @param {string} graphName - Name of the WARP graph
141
+ * @returns {Promise<CursorBlob|null>}
142
+ */
143
+ export async function readActiveCursor(persistence, graphName) {
144
+ const ref = buildCursorActiveRef(graphName);
145
+ const oid = await persistence.readRef(ref);
146
+ if (!oid) {
147
+ return null;
148
+ }
149
+ const buf = await persistence.readBlob(oid);
150
+ return parseCursorBlob(buf, 'active cursor');
151
+ }
152
+
153
+ /**
154
+ * Writes (creates or overwrites) the active seek cursor for a graph.
155
+ *
156
+ * @param {Persistence} persistence - GraphPersistencePort adapter
157
+ * @param {string} graphName - Name of the WARP graph
158
+ * @param {CursorBlob} cursor - Cursor state to persist
159
+ * @returns {Promise<void>}
160
+ */
161
+ export async function writeActiveCursor(persistence, graphName, cursor) {
162
+ const ref = buildCursorActiveRef(graphName);
163
+ const json = JSON.stringify(cursor);
164
+ const oid = await persistence.writeBlob(Buffer.from(json, 'utf8'));
165
+ await persistence.updateRef(ref, oid);
166
+ }
167
+
168
+ /**
169
+ * @param {Persistence} persistence
170
+ * @param {string|null} checkpointSha
171
+ */
172
+ export async function readCheckpointDate(persistence, checkpointSha) {
173
+ if (!checkpointSha) {
174
+ return null;
175
+ }
176
+ const info = await persistence.getNodeInfo(checkpointSha);
177
+ return info.date || null;
178
+ }
179
+
180
+ export function createHookInstaller() {
181
+ const __filename = new URL(import.meta.url).pathname;
182
+ const __dirname = path.dirname(__filename);
183
+ const templateDir = path.resolve(__dirname, '..', '..', 'scripts', 'hooks');
184
+ const { version } = JSON.parse(fs.readFileSync(path.resolve(__dirname, '..', '..', 'package.json'), 'utf8'));
185
+ return new HookInstaller({
186
+ fs: /** @type {*} */ (fs), // TODO(ts-cleanup): narrow port type
187
+ execGitConfig: execGitConfigValue,
188
+ version,
189
+ templateDir,
190
+ path,
191
+ });
192
+ }
193
+
194
+ /**
195
+ * @param {string} repoPath
196
+ * @param {string} key
197
+ * @returns {string|null}
198
+ */
199
+ export function execGitConfigValue(repoPath, key) {
200
+ try {
201
+ if (key === '--git-dir') {
202
+ return execFileSync('git', ['-C', repoPath, 'rev-parse', '--git-dir'], {
203
+ encoding: 'utf8',
204
+ }).trim();
205
+ }
206
+ return execFileSync('git', ['-C', repoPath, 'config', key], {
207
+ encoding: 'utf8',
208
+ }).trim();
209
+ } catch {
210
+ return null;
211
+ }
212
+ }
213
+
214
+ export function isInteractive() {
215
+ return Boolean(process.stderr.isTTY);
216
+ }
217
+
218
+ /** @param {string} question @returns {Promise<string>} */
219
+ export function promptUser(question) {
220
+ const rl = readline.createInterface({
221
+ input: process.stdin,
222
+ output: process.stderr,
223
+ });
224
+ return new Promise((resolve) => {
225
+ rl.question(question, (answer) => {
226
+ rl.close();
227
+ resolve(answer.trim());
228
+ });
229
+ });
230
+ }
231
+
232
+ /**
233
+ * @param {{graph: WarpGraphInstance, persistence: Persistence, graphName: string, seekSpec: SeekSpec}} params
234
+ */
235
+ export function wireSeekCache({ graph, persistence, graphName, seekSpec }) {
236
+ if (seekSpec.noPersistentCache) {
237
+ return;
238
+ }
239
+ graph.setSeekCache(new CasSeekCacheAdapter({
240
+ persistence,
241
+ plumbing: persistence.plumbing,
242
+ graphName,
243
+ }));
244
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * @typedef {Object} Persistence
3
+ * @property {(prefix: string) => Promise<string[]>} listRefs
4
+ * @property {(ref: string) => Promise<string|null>} readRef
5
+ * @property {(ref: string, oid: string) => Promise<void>} updateRef
6
+ * @property {(ref: string) => Promise<void>} deleteRef
7
+ * @property {(oid: string) => Promise<Buffer>} readBlob
8
+ * @property {(buf: Buffer) => Promise<string>} writeBlob
9
+ * @property {(sha: string) => Promise<{date?: string|null}>} getNodeInfo
10
+ * @property {(sha: string) => Promise<boolean>} nodeExists
11
+ * @property {(sha: string, coverageSha: string) => Promise<boolean>} isAncestor
12
+ * @property {() => Promise<{ok: boolean}>} ping
13
+ * @property {*} plumbing
14
+ */
15
+
16
+ /**
17
+ * @typedef {Object} WarpGraphInstance
18
+ * @property {(opts?: {ceiling?: number}) => Promise<void>} materialize
19
+ * @property {() => Promise<Array<{id: string}>>} getNodes
20
+ * @property {() => Promise<Array<{from: string, to: string, label?: string}>>} getEdges
21
+ * @property {() => Promise<string|null>} createCheckpoint
22
+ * @property {() => *} query
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
27
+ * @property {() => {totalTombstones: number, tombstoneRatio: number}} getGCMetrics
28
+ * @property {() => Promise<number>} getPropertyCount
29
+ * @property {() => Promise<import('../../src/domain/services/JoinReducer.js').WarpStateV5 | null>} getStateSnapshot
30
+ * @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
34
+ * @property {number} [_seekCeiling]
35
+ * @property {boolean} [_provenanceDegraded]
36
+ */
37
+
38
+ /**
39
+ * @typedef {Object} WriterTickInfo
40
+ * @property {number[]} ticks
41
+ * @property {string|null} tipSha
42
+ * @property {Record<number, string>} [tickShas]
43
+ */
44
+
45
+ /**
46
+ * @typedef {Object} CursorBlob
47
+ * @property {number} tick
48
+ * @property {string} [mode]
49
+ * @property {number} [nodes]
50
+ * @property {number} [edges]
51
+ * @property {string} [frontierHash]
52
+ */
53
+
54
+ /**
55
+ * @typedef {Object} CliOptions
56
+ * @property {string} repo
57
+ * @property {boolean} json
58
+ * @property {boolean} ndjson
59
+ * @property {string|null} view
60
+ * @property {string|null} graph
61
+ * @property {string} writer
62
+ * @property {boolean} help
63
+ */
64
+
65
+ /**
66
+ * @typedef {Object} GraphInfoResult
67
+ * @property {string} name
68
+ * @property {{count: number, ids?: string[]}} writers
69
+ * @property {{ref: string, sha: string|null, date?: string|null}} [checkpoint]
70
+ * @property {{ref: string, sha: string|null}} [coverage]
71
+ * @property {Record<string, number>} [writerPatches]
72
+ * @property {{active: boolean, tick?: number, mode?: string}} [cursor]
73
+ */
74
+
75
+ /**
76
+ * @typedef {Object} SeekSpec
77
+ * @property {string} action
78
+ * @property {string|null} tickValue
79
+ * @property {string|null} name
80
+ * @property {boolean} noPersistentCache
81
+ * @property {boolean} diff
82
+ * @property {number} diffLimit
83
+ */
84
+
85
+ export {};