@meterbility/collector 0.3.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.
package/LICENSE ADDED
@@ -0,0 +1,33 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Meterbility authors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ----------------------------------------------------------------------
24
+
25
+ Note: This MIT license covers everything in this repository EXCEPT the
26
+ contents of the /ee directory (when present), which are licensed under the
27
+ Elastic License 2.0 (ELv2) — see /ee/LICENSE.
28
+
29
+ The /ee directory is reserved for Enterprise Edition modules: multi-tenant
30
+ fleet orchestration, SSO/RBAC, audit logs, and long-retention features.
31
+
32
+ Nothing under /ee today; the directory is created as a forward-compatible
33
+ marker so the licensing boundary is visible before any /ee code lands.
package/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # @meterbility/collector
2
+
3
+ Part of [Meterbility](https://github.com/HoneycombHairDevelopers/Meterbility) — the debugger for AI agents. Capture every run, inspect every decision, pause and inject live, fork from any step.
4
+
5
+ The SQLite data plane: schema, queries, content-addressed blob store, baseline trees, and replay primitives.
6
+
7
+ ```bash
8
+ npm install @meterbility/collector
9
+ ```
10
+
11
+ See the [Meterbility documentation](https://github.com/HoneycombHairDevelopers/Meterbility#readme) for the full guide. MIT licensed.
@@ -0,0 +1,497 @@
1
+ import Database from 'better-sqlite3';
2
+ import { StepStatus, Step, TokenUsage, FileChange, BaselineTree, Run, AnnotationKind, AnnotationVerdict, Annotation, ForkEdit, ProbeState, Project, WorkingTree, ManifestEntry, IgnoreMatcher } from '@meterbility/shared';
3
+ export { BaselineTree } from '@meterbility/shared';
4
+
5
+ /**
6
+ * Two-stage heuristic for "is this buffer safe to send through the
7
+ * redaction pipeline?". Used to gate `redactBuffer` so PNG / .woff2 /
8
+ * lockfile / `.pyc` bytes don't get round-tripped through
9
+ * `String#replace` and shredded by U+FFFD substitution.
10
+ *
11
+ * Stage 1 — Full-buffer NUL scan. UTF-8 source code — even with CJK,
12
+ * emoji, every weird code point — never contains 0x00. A NUL byte
13
+ * anywhere is strong evidence of binary content. For files whose
14
+ * binary signature is at the start (PNG, ELF, .pyc, .woff2) this
15
+ * bails at byte 7-12 and is effectively O(1). Removing the previous
16
+ * 8KB cap closes the rare-but-real "binary file with all-non-NUL
17
+ * first 8KB" gap (gzip headers, some compressed formats can hit it).
18
+ *
19
+ * Stage 2 — Round-trip-length check. Even with no NUL, a buffer can
20
+ * still be invalid UTF-8 (stray continuation bytes, truncated multi-
21
+ * byte sequences). The redaction pipeline calls
22
+ * `Buffer.toString('utf-8')` which silently replaces invalid bytes
23
+ * with U+FFFD (3 encoded bytes per replacement). If the re-encoded
24
+ * length differs from the input, the round trip is lossy and would
25
+ * corrupt the caller's bytes. Classify as binary so the bytes survive.
26
+ *
27
+ * Both stages are O(n). Cost is dominated by the redaction pipeline
28
+ * itself (which also runs `toString` on the buffer), so this adds at
29
+ * most one extra full pass to the redact path. Callers who already
30
+ * know their bytes are binary can still pass `skipRedact: true` to
31
+ * skip both stages entirely.
32
+ */
33
+ declare function isProbablyText(buf: Buffer): boolean;
34
+ /**
35
+ * Content-addressed blob store.
36
+ *
37
+ * Layout: $METERBILITY_HOME/blobs/<aa>/<bb>/<sha256>
38
+ *
39
+ * Two-level sharding (256 × 256) keeps any single directory under a few
40
+ * thousand files even on heavy users. Writes are write-once: if the file
41
+ * exists, we trust it (SHA collision is the universe's problem, not ours).
42
+ *
43
+ * Every text write passes through the redaction pass and emits a
44
+ * `redaction_log` row when a rule fires so the user can audit what was
45
+ * scrubbed. **Binary writes skip redaction entirely** — `redactBuffer`
46
+ * goes via `String#replace`, which would shred any non-UTF-8 bytes. The
47
+ * `isProbablyText` heuristic gates this automatically; callers who
48
+ * already know the bytes are binary (e.g. v0.3 file capture) can also
49
+ * pass `skipRedact: true` to be explicit.
50
+ */
51
+ declare class BlobStore {
52
+ private db;
53
+ constructor(db: Database.Database);
54
+ putString(content: string, opts?: {
55
+ skipRedact?: boolean;
56
+ }): Promise<string>;
57
+ putJson(value: unknown, opts?: {
58
+ skipRedact?: boolean;
59
+ }): Promise<string>;
60
+ /**
61
+ * Hash without persisting — useful when you want a snapshot id before
62
+ * deciding whether to write.
63
+ */
64
+ hashJson(value: unknown): string;
65
+ putBuffer(buf: Buffer, opts?: {
66
+ skipRedact?: boolean;
67
+ }): Promise<string>;
68
+ getString(hash: string): Promise<string>;
69
+ getJson<T = unknown>(hash: string): Promise<T>;
70
+ getBuffer(hash: string): Promise<Buffer>;
71
+ tryGetString(hash: string): Promise<string | undefined>;
72
+ /**
73
+ * Binary-safe variant of `tryGetString`. Used by /api/blob/:hash/render
74
+ * which has to look at raw bytes for MIME sniffing before deciding
75
+ * whether to render text or serve as image/octet-stream.
76
+ */
77
+ tryGetBuffer(hash: string): Promise<Buffer | undefined>;
78
+ /**
79
+ * Write pre-computed HTML (or other text) under a caller-chosen
80
+ * synthetic key, bypassing the content-addressed hash. Used by the
81
+ * blob_render cache to store rendered HTML under a key derived
82
+ * from `sha(blob_hash + lang + RENDER_VERSION)` rather than the
83
+ * content's own hash — this way two different renders of the same
84
+ * source code (e.g. lang=auto vs lang=typescript) cache separately
85
+ * but predictably.
86
+ *
87
+ * Skips redaction (renders are derived from already-stored blobs
88
+ * that went through redaction at put time).
89
+ */
90
+ putWithKey(content: string, key: string): Promise<void>;
91
+ rootDir(): string;
92
+ }
93
+
94
+ /**
95
+ * One-stop entry into the local data plane. `open()` creates the
96
+ * `$METERBILITY_HOME` tree if needed, applies the schema, and exposes both the
97
+ * raw SQLite handle (for queries.ts) and the blob store.
98
+ */
99
+ declare class Store {
100
+ readonly db: Database.Database;
101
+ readonly blobs: BlobStore;
102
+ private constructor();
103
+ static open(opts?: {
104
+ path?: string;
105
+ }): Store;
106
+ static openAsync(opts?: {
107
+ path?: string;
108
+ }): Promise<Store>;
109
+ close(): void;
110
+ }
111
+
112
+ /**
113
+ * Schema — flat tables that map directly onto SPEC §6 entities. JSON
114
+ * columns hold the structured-but-not-indexed parts (action, outcome,
115
+ * tags). Foreign keys are enforced; large content lives in the blob store
116
+ * referenced by SHA256.
117
+ *
118
+ * Migration policy (locked in by SPEC v0.2 §17 and re-affirmed by v0.3
119
+ * §3.3): **additive only**. No renames, no drops, no CHECK-constraint
120
+ * tightening — SQLite can't ALTER a CHECK without a table rebuild, so we
121
+ * declare full enum coverage up front even for values future milestones
122
+ * write. v0.3 only writes `derived_from='tool_call'` and three of the
123
+ * five ops, but the constraint allows all of them so v0.4's file watcher
124
+ * and v0.5's normalizer don't need a migration.
125
+ *
126
+ * Version history:
127
+ * v1 → v2 — split 5m vs 1h cache write tokens on `steps`
128
+ * v2 → v3 — settings table for the web UI Settings page
129
+ * v3 → v4 — file_change, baseline_tree, runs.baseline_tree_id,
130
+ * runs.probe_state (Track A file capture + Track B Live Probe)
131
+ */
132
+ declare const SCHEMA_VERSION = 5;
133
+ declare function ensureSchema(db: Database.Database): void;
134
+
135
+ declare function upsertProjectByCwd(store: Store, cwd: string, name?: string): Project;
136
+ declare function upsertAgent(store: Store, projectId: string, name: string): {
137
+ agent_id: string;
138
+ project_id: string;
139
+ name: string;
140
+ created_at: string;
141
+ };
142
+ declare function insertRun(store: Store, run: Run): void;
143
+ declare function updateRunTotals(store: Store, runId: string): void;
144
+ declare function setRunStatus(store: Store, runId: string, status: StepStatus, endedAt?: string): void;
145
+ declare function insertStep(store: Store, step: Step): void;
146
+ declare function recordContextSnapshot(store: Store, snapshotId: string, blobRef: string, componentCount: number): void;
147
+ /** Translate a logical snapshot_id (hash of components) into the blob_ref
148
+ * under which the serialized snapshot is stored. Falls back to using the
149
+ * snapshot_id itself if no mapping is recorded — handy for round-tripped
150
+ * traces where the two hashes happen to coincide. */
151
+ declare function resolveSnapshotBlobRef(store: Store, snapshotId: string): string;
152
+ declare function getRun(store: Store, runId: string): Run | undefined;
153
+ interface ListRunsOpts {
154
+ limit?: number;
155
+ projectId?: string;
156
+ agentId?: string;
157
+ status?: StepStatus;
158
+ containsTool?: string;
159
+ }
160
+ declare function listRuns(store: Store, opts?: ListRunsOpts): Run[];
161
+ declare function listSteps(store: Store, runId: string): Step[];
162
+ declare function getStep(store: Store, stepId: string): Step | undefined;
163
+ declare function getStepBySequence(store: Store, runId: string, sequence: number): Step | undefined;
164
+ declare function insertFork(store: Store, args: {
165
+ originRunId: string;
166
+ originStepId: string;
167
+ forkRunId: string;
168
+ edit: ForkEdit;
169
+ }): string;
170
+ declare function listForks(store: Store, originRunId: string): Array<{
171
+ fork_id: string;
172
+ origin_run_id: string;
173
+ origin_step_id: string;
174
+ fork_run_id: string;
175
+ edit_type: string;
176
+ created_at: string;
177
+ }>;
178
+ declare function insertAnnotation(store: Store, args: {
179
+ targetKind: "step" | "run";
180
+ targetId: string;
181
+ author: string;
182
+ /**
183
+ * Annotation discriminator (v0.3 schema v5). Defaults to 'comment'
184
+ * — human-authored notes via the Annotate CLI / web UI keep the
185
+ * back-compat shape. System-emitted events pass their own kind
186
+ * (`probe_pause`, `probe_edit`, `capture_skipped`).
187
+ */
188
+ kind?: AnnotationKind;
189
+ verdict?: AnnotationVerdict;
190
+ note?: string;
191
+ }): Annotation;
192
+ declare function listAnnotations(store: Store, targetKind: "step" | "run", targetId: string): Annotation[];
193
+ declare function getIngestOffset(store: Store, source_runtime: string, source_path: string): number;
194
+ declare function setIngestOffset(store: Store, source_runtime: string, source_path: string, offset: number): void;
195
+ declare function getRunBySessionId(store: Store, sourceSessionId: string): Run | undefined;
196
+ declare function aggregateTokens(steps: Step[]): TokenUsage;
197
+ /**
198
+ * Thrown by `insertFileChange` when a candidate row violates the
199
+ * biconditional contract documented on `FileChange` in
200
+ * `packages/shared/src/types.ts:242-244`:
201
+ *
202
+ * - `before_blob_ref` is null iff op === "create" OR partial_diff
203
+ * - `after_blob_ref` is null iff op === "delete" OR partial_diff
204
+ * - op === "rename" requires `old_path`
205
+ *
206
+ * `chmod` is the documented exception — it's a mode-only operation
207
+ * and is exempt from both blob_ref biconditionals. The replay layer
208
+ * (`applyFileChange`) treats chmod blob refs as a no-op regardless,
209
+ * so accepting either shape is safe.
210
+ *
211
+ * Hard-fail by design: an adapter that produces an invariant-violating
212
+ * row would otherwise ship the malformation silently into the DB and
213
+ * surface as a downstream rendering or replay bug. Better to throw at
214
+ * the boundary.
215
+ */
216
+ declare class FileChangeInvariantError extends Error {
217
+ constructor(message: string);
218
+ }
219
+ /**
220
+ * Verify a candidate FileChange satisfies the contract on `FileChange`
221
+ * in `packages/shared/src/types.ts`. Throws `FileChangeInvariantError`
222
+ * with a clear message on the first violation. Pure; no side effects.
223
+ *
224
+ * Exported for direct testing.
225
+ */
226
+ declare function assertFileChangeInvariants(fc: {
227
+ op: FileChange["op"];
228
+ partial_diff: boolean;
229
+ before_blob_ref?: string;
230
+ after_blob_ref?: string;
231
+ old_path?: string;
232
+ }): void;
233
+ /**
234
+ * Insert a FileChange row. ID auto-generated if not provided.
235
+ *
236
+ * Idempotency: the schema's `UNIQUE(step_id, sequence)` constraint
237
+ * means writing the same logical FileChange twice will fail loudly.
238
+ * Adapters should compute deterministic `(step_id, sequence)` so a
239
+ * re-ingest of the same JSONL session doesn't duplicate rows.
240
+ *
241
+ * `created_at` defaults to "now" if omitted — adapters usually want
242
+ * this since they don't have a per-FileChange wall-clock from the
243
+ * source.
244
+ *
245
+ * Validation: every row passes through `assertFileChangeInvariants`
246
+ * before the INSERT. Adapter bugs that would otherwise ship malformed
247
+ * rows surface here as a thrown `FileChangeInvariantError` rather
248
+ * than as a silent downstream replay bug.
249
+ */
250
+ declare function insertFileChange(store: Store, fc: Omit<FileChange, "file_change_id" | "created_at"> & {
251
+ file_change_id?: string;
252
+ created_at?: string;
253
+ }): FileChange;
254
+ interface ListFileChangesOpts {
255
+ runId?: string;
256
+ stepId?: string;
257
+ path?: string;
258
+ /**
259
+ * Used by the replay algorithm: include only FileChanges for steps
260
+ * with sequence < this value. Implemented via a join on `steps`. If
261
+ * `runId` is also given, the join is scoped to that run.
262
+ */
263
+ maxStepSeqExclusive?: number;
264
+ }
265
+ /**
266
+ * Sort order is `(step.sequence ASC, file_change.sequence ASC)` which
267
+ * matches the replay algorithm's contract (v0.3 §3.6): later steps win
268
+ * over earlier ones, and within a step, the intra-step `sequence` field
269
+ * preserves atomic-batch ordering (MultiEdit fans out N rows in order).
270
+ */
271
+ declare function listFileChanges(store: Store, opts?: ListFileChangesOpts): FileChange[];
272
+ declare function getFileChange(store: Store, fileChangeId: string): FileChange | undefined;
273
+ declare function insertBaselineTree(store: Store, bt: Omit<BaselineTree, "baseline_tree_id" | "captured_at"> & {
274
+ baseline_tree_id?: string;
275
+ captured_at?: string;
276
+ }): BaselineTree;
277
+ declare function getBaselineTree(store: Store, baselineTreeId: string): BaselineTree | undefined;
278
+ /**
279
+ * Find an existing baseline_tree row by its manifest blob ref — useful
280
+ * during baseline capture so two runs against the same git HEAD don't
281
+ * create two rows pointing at the same content. v0.3 §3.5 calls this
282
+ * out as the dominant dedup win.
283
+ */
284
+ declare function findBaselineByManifest(store: Store, projectId: string, manifestBlobRef: string): BaselineTree | undefined;
285
+ /**
286
+ * Attach a baseline tree to a run. Idempotent: passing the same id
287
+ * twice is a no-op. Passing a different id overwrites (the assumption
288
+ * being that the adapter re-walked the cwd and produced a new
289
+ * manifest — rare but legal).
290
+ */
291
+ declare function setRunBaselineTree(store: Store, runId: string, baselineTreeId: string): void;
292
+ /**
293
+ * Set the Live Probe state on a run. Pass `null` to clear. v0.3 §4 —
294
+ * only meaningful for source_runtime in (sdk-ts, sdk-py); callers are
295
+ * responsible for that guard.
296
+ */
297
+ declare function setRunProbeState(store: Store, runId: string, state: ProbeState | null): void;
298
+
299
+ /**
300
+ * Tiny key-value store on top of the `settings` SQLite table. Used by
301
+ * the web UI's Settings page so users can configure Slack webhooks,
302
+ * watched tools, default fork model, etc. without re-typing per
303
+ * session.
304
+ *
305
+ * Secrets warning: values are stored in plaintext in `~/.meterbility/meterbility.db`.
306
+ * The Settings UI surfaces this clearly. For Keychain-backed storage,
307
+ * see SPEC-DESKTOP.md (the desktop app's job, not the web UI's).
308
+ */
309
+ type SettingKey = "slack.webhook" | "slack.default_events" | "live.watch_tools" | "live.stall_seconds" | "fork.default_model" | "fork.default_max_iterations" | "anthropic.api_key" | "postgres.url" | "export.include_file_blobs" | "web.bind_token" | "capture.files.enabled" | "capture.files.max_partial_bytes" | "capture.files.max_skip_bytes";
310
+ declare function getSetting(store: Store, key: SettingKey): string | undefined;
311
+ declare function setSetting(store: Store, key: SettingKey, value: string): void;
312
+ declare function deleteSetting(store: Store, key: SettingKey): void;
313
+ declare function listSettings(store: Store): Array<{
314
+ key: string;
315
+ value: string;
316
+ updated_at: string;
317
+ }>;
318
+ /**
319
+ * Resolve a setting that may also live in an environment variable.
320
+ * Env var wins when both are present — matches CLI semantics where
321
+ * env vars are the authoritative source.
322
+ */
323
+ declare function resolveSetting(store: Store, key: SettingKey, envVar: string): string | undefined;
324
+ declare function isSecret(key: string): boolean;
325
+ /** Mask a secret value for display: `sk-ant-xxx…last4`. */
326
+ declare function maskSecret(value: string): string;
327
+
328
+ /**
329
+ * Serialize a list of manifest entries into the on-disk format. Entries
330
+ * are sorted by path (bytewise on the UTF-8 representation, matching
331
+ * git's convention — deterministic across locales).
332
+ *
333
+ * Throws on illegal paths: NUL (0x00) and newline (0x0A) bytes would
334
+ * break the record separators. POSIX disallows both in filenames; if
335
+ * the agent somehow produced one we refuse rather than silently
336
+ * corrupt the manifest.
337
+ */
338
+ declare function serializeManifest(entries: ManifestEntry[]): Buffer;
339
+ /**
340
+ * Inverse of `serializeManifest`. Tolerates an absent trailing newline
341
+ * (empty manifests serialize to an empty buffer; we return [] for both
342
+ * `Buffer.alloc(0)` and `Buffer.from([])`).
343
+ */
344
+ declare function parseManifest(buf: Buffer): ManifestEntry[];
345
+ /**
346
+ * Load a baseline tree's manifest from the blob store and parse it
347
+ * into a WorkingTree (the seed state the replay layers FileChanges on
348
+ * top of). Returns an empty tree if the manifest blob can't be read
349
+ * — better than throwing because a run with a missing baseline can
350
+ * still be partially reconstructed from FileChanges, and the UI can
351
+ * surface a warning instead of a hard failure.
352
+ */
353
+ declare function loadBaselineTree(store: Store, baseline: BaselineTree): Promise<WorkingTree>;
354
+ interface WorkingTreeAtOpts {
355
+ /**
356
+ * Apply only FileChanges from steps with `sequence < this value`.
357
+ * Default: undefined → include every step in the run (final state).
358
+ * Use 0 to get the baseline (pre-step-0) state.
359
+ */
360
+ stepSeq?: number;
361
+ }
362
+ /**
363
+ * Compute the working tree at a chosen point in a run.
364
+ *
365
+ * Implements the algorithm in v0.3 §3.6:
366
+ * 1. Load the run's baseline tree (the seed).
367
+ * 2. Apply every FileChange with `step.sequence < stepSeq` in order
368
+ * (step.sequence ASC, file_change.sequence ASC).
369
+ * 3. Return the resulting (path → {blob_ref, mode}) map.
370
+ *
371
+ * Complexity: O(touched_paths). The `idx_fc_run_path` and the
372
+ * `listFileChanges` join keep this bounded by the number of files
373
+ * modified in the run, never by the total files in the repo.
374
+ *
375
+ * Returns an empty tree if:
376
+ * - The run doesn't exist.
377
+ * - The run has no `baseline_tree_id` AND no FileChanges (typical for
378
+ * non-coding runs).
379
+ *
380
+ * Returns a baseline-only tree if the run has a baseline but no
381
+ * FileChanges before `stepSeq` — useful for "what did the agent see
382
+ * walking in?" queries.
383
+ */
384
+ declare function workingTreeAt(store: Store, runId: string, opts?: WorkingTreeAtOpts): Promise<WorkingTree>;
385
+ /**
386
+ * Apply a single FileChange to a WorkingTree in place. Pulled out so
387
+ * the v0.5 working-tree-scrubber UI can re-use it for incremental
388
+ * updates without re-running the whole replay each frame.
389
+ *
390
+ * `chmod` updates mode bits only — the spec's algorithm sketch passes
391
+ * for blob-ref purposes (content unchanged), but we DO update `mode`
392
+ * when present because mode is also part of the working-tree contract.
393
+ * If `mode_after` is unset, the operation is a true no-op.
394
+ *
395
+ * `partial_diff` FileChanges (typically Bash steps in v0.3) carry no
396
+ * blob refs. They're applied as a no-op for tree reconstruction — the
397
+ * Files UI surfaces the existence of the change separately so the user
398
+ * knows to enable the v0.4 watcher for full fidelity.
399
+ */
400
+ declare function applyFileChange(tree: WorkingTree, fc: {
401
+ op: string;
402
+ path: string;
403
+ old_path?: string;
404
+ after_blob_ref?: string;
405
+ mode_after?: number;
406
+ partial_diff?: boolean;
407
+ }): void;
408
+
409
+ interface CaptureBaselineResult {
410
+ baseline_tree_id: string;
411
+ /** Manifest blob ref, exposed for diagnostics + tests. */
412
+ manifest_blob_ref: string;
413
+ /** Number of files that made it into the manifest. */
414
+ file_count: number;
415
+ /** Number of files skipped (oversize, unreadable, ignored). */
416
+ skipped: number;
417
+ /** True when this run dedup'd onto an existing baseline_tree row. */
418
+ reused_existing: boolean;
419
+ git_head?: string;
420
+ git_dirty: boolean;
421
+ }
422
+ interface CaptureBaselineOptions {
423
+ /**
424
+ * Pre-built ignore matcher. If omitted, the function loads
425
+ * `<cwd>/.meterbilityignore` + `<cwd>/.gitignore` on top of the
426
+ * shipped defaults.
427
+ */
428
+ matcher?: IgnoreMatcher;
429
+ /** Override the size cap (mostly for tests). */
430
+ maxFileBytes?: number;
431
+ }
432
+ /**
433
+ * Capture the baseline tree for a project's cwd. Returns `undefined` if
434
+ * the cwd doesn't exist or is empty — the adapter treats that as
435
+ * "non-coding run, no baseline needed" and continues without one.
436
+ */
437
+ declare function captureBaseline(store: Store, projectId: string, cwd: string, opts?: CaptureBaselineOptions): Promise<CaptureBaselineResult | undefined>;
438
+
439
+ /**
440
+ * File-size capture policy. Single source of truth for the 5MB / 50MB
441
+ * thresholds per SPEC-V0_3 §11.1. Every adapter that captures files
442
+ * MUST funnel through this helper before calling `insertFileChange`
443
+ * so the policy is uniform — a future adapter (Codex, Cursor,
444
+ * file-watcher) gets the gate for free.
445
+ *
446
+ * Policy (per SPEC-V0_3 §11.1):
447
+ *
448
+ * ≤ max_partial_bytes (default 5 MB): full capture, both blobs kept,
449
+ * partial_diff = false.
450
+ * > max_partial_bytes AND ≤ max_skip_bytes: drop the LARGER side's
451
+ * buffer (keep the smaller for delta context), partial_diff = true.
452
+ * > max_skip_bytes: drop both buffers, redacted = true, emit a stub
453
+ * FileChange row that records the path + op + original sizes so
454
+ * the user knows something happened.
455
+ *
456
+ * Behaviour is pure (no I/O, no settings read inside the helper). The
457
+ * adapter resolves settings once via `getSetting()` and passes the
458
+ * resolved numbers in — per A7 "live-read per FileChange," settings
459
+ * are read at the call site so a mid-run toggle takes effect on the
460
+ * very next FileChange.
461
+ */
462
+ declare const DEFAULT_MAX_PARTIAL_BYTES = 5000000;
463
+ declare const DEFAULT_MAX_SKIP_BYTES = 50000000;
464
+ interface FileSizePolicySettings {
465
+ /** Files at or under this size are captured in full. */
466
+ max_partial_bytes?: number;
467
+ /** Files at or under this size are partial-captured (larger blob dropped). */
468
+ max_skip_bytes?: number;
469
+ }
470
+ interface FileSizePolicyInput {
471
+ beforeBuf?: Buffer;
472
+ afterBuf?: Buffer;
473
+ settings?: FileSizePolicySettings;
474
+ }
475
+ interface FileSizePolicyResult {
476
+ /** The (possibly nulled) buffer to persist for the before-blob. */
477
+ beforeBuf?: Buffer;
478
+ /** The (possibly nulled) buffer to persist for the after-blob. */
479
+ afterBuf?: Buffer;
480
+ /** True when at least one buffer was dropped due to max_partial_bytes. */
481
+ partial_diff: boolean;
482
+ /** True when both buffers were dropped due to max_skip_bytes. */
483
+ redacted: boolean;
484
+ /** Original (pre-policy) size of before, regardless of whether kept. */
485
+ size_before?: number;
486
+ /** Original (pre-policy) size of after, regardless of whether kept. */
487
+ size_after?: number;
488
+ }
489
+ /**
490
+ * Apply the size-cap policy to the given buffers. Pure function — no
491
+ * I/O, no SQLite, no side effects. Throws on misconfiguration
492
+ * (max_skip ≤ max_partial) so a bad settings value surfaces loudly
493
+ * at ingest rather than silently producing skewed captures.
494
+ */
495
+ declare function enforceFileSizePolicy(input: FileSizePolicyInput): FileSizePolicyResult;
496
+
497
+ export { BlobStore, type CaptureBaselineOptions, type CaptureBaselineResult, DEFAULT_MAX_PARTIAL_BYTES, DEFAULT_MAX_SKIP_BYTES, FileChangeInvariantError, type FileSizePolicyInput, type FileSizePolicyResult, type FileSizePolicySettings, type ListFileChangesOpts, type ListRunsOpts, SCHEMA_VERSION, type SettingKey, Store, type WorkingTreeAtOpts, aggregateTokens, applyFileChange, assertFileChangeInvariants, captureBaseline, deleteSetting, enforceFileSizePolicy, ensureSchema, findBaselineByManifest, getBaselineTree, getFileChange, getIngestOffset, getRun, getRunBySessionId, getSetting, getStep, getStepBySequence, insertAnnotation, insertBaselineTree, insertFileChange, insertFork, insertRun, insertStep, isProbablyText, isSecret, listAnnotations, listFileChanges, listForks, listRuns, listSettings, listSteps, loadBaselineTree, maskSecret, parseManifest, recordContextSnapshot, resolveSetting, resolveSnapshotBlobRef, serializeManifest, setIngestOffset, setRunBaselineTree, setRunProbeState, setRunStatus, setSetting, updateRunTotals, upsertAgent, upsertProjectByCwd, workingTreeAt };