swarm-mail 0.1.3 → 0.1.4

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 (45) hide show
  1. package/README.md +4 -0
  2. package/dist/beads/adapter.d.ts +38 -0
  3. package/dist/beads/adapter.d.ts.map +1 -0
  4. package/dist/beads/blocked-cache.d.ts +21 -0
  5. package/dist/beads/blocked-cache.d.ts.map +1 -0
  6. package/dist/beads/comments.d.ts +21 -0
  7. package/dist/beads/comments.d.ts.map +1 -0
  8. package/dist/beads/dependencies.d.ts +58 -0
  9. package/dist/beads/dependencies.d.ts.map +1 -0
  10. package/dist/beads/events.d.ts +163 -0
  11. package/dist/beads/events.d.ts.map +1 -0
  12. package/dist/beads/flush-manager.d.ts +71 -0
  13. package/dist/beads/flush-manager.d.ts.map +1 -0
  14. package/dist/beads/index.d.ts +25 -0
  15. package/dist/beads/index.d.ts.map +1 -0
  16. package/dist/beads/jsonl.d.ts +103 -0
  17. package/dist/beads/jsonl.d.ts.map +1 -0
  18. package/dist/beads/labels.d.ts +21 -0
  19. package/dist/beads/labels.d.ts.map +1 -0
  20. package/dist/beads/merge.d.ts +99 -0
  21. package/dist/beads/merge.d.ts.map +1 -0
  22. package/dist/beads/migrations.d.ts +41 -0
  23. package/dist/beads/migrations.d.ts.map +1 -0
  24. package/dist/beads/operations.d.ts +56 -0
  25. package/dist/beads/operations.d.ts.map +1 -0
  26. package/dist/beads/projections.d.ts +103 -0
  27. package/dist/beads/projections.d.ts.map +1 -0
  28. package/dist/beads/queries.d.ts +77 -0
  29. package/dist/beads/queries.d.ts.map +1 -0
  30. package/dist/beads/store.d.ts +98 -0
  31. package/dist/beads/store.d.ts.map +1 -0
  32. package/dist/beads/validation.d.ts +75 -0
  33. package/dist/beads/validation.d.ts.map +1 -0
  34. package/dist/index.d.ts +1 -0
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +1957 -39
  37. package/dist/pglite.d.ts +9 -1
  38. package/dist/pglite.d.ts.map +1 -1
  39. package/dist/streams/debug.d.ts.map +1 -1
  40. package/dist/streams/migrations.d.ts +1 -1
  41. package/dist/streams/migrations.d.ts.map +1 -1
  42. package/dist/streams/store.d.ts.map +1 -1
  43. package/dist/types/beads-adapter.d.ts +397 -0
  44. package/dist/types/beads-adapter.d.ts.map +1 -0
  45. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -14821,7 +14821,6 @@ async function debugEventsPaginated(options) {
14821
14821
  }) : batch;
14822
14822
  allEvents.push(...filtered);
14823
14823
  offset += batchSize;
14824
- console.log(`[SwarmMail] Fetched ${allEvents.length} events (batch size: ${batchSize})`);
14825
14824
  }
14826
14825
  allEvents.sort((a, b) => b.sequence - a.sequence);
14827
14826
  const limitedEvents = allEvents.slice(0, limit);
@@ -15063,7 +15062,7 @@ async function ensureVersionTable(db) {
15063
15062
  async function getCurrentVersion(db) {
15064
15063
  await ensureVersionTable(db);
15065
15064
  const result = await db.query(`SELECT MAX(version) as version FROM schema_version`);
15066
- return result.rows[0]?.version ?? 0;
15065
+ return result.rows[0]?.version ?? -1;
15067
15066
  }
15068
15067
  async function getAppliedMigrations(db) {
15069
15068
  await ensureVersionTable(db);
@@ -15141,6 +15140,123 @@ async function getPendingMigrations(db) {
15141
15140
  var migrations;
15142
15141
  var init_migrations = __esm(() => {
15143
15142
  migrations = [
15143
+ {
15144
+ version: 0,
15145
+ description: "Create core event store tables",
15146
+ up: `
15147
+ -- Events table: The source of truth (append-only)
15148
+ CREATE TABLE IF NOT EXISTS events (
15149
+ id SERIAL PRIMARY KEY,
15150
+ type TEXT NOT NULL,
15151
+ project_key TEXT NOT NULL,
15152
+ timestamp BIGINT NOT NULL,
15153
+ sequence SERIAL,
15154
+ data JSONB NOT NULL,
15155
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
15156
+ );
15157
+
15158
+ -- Index for efficient queries
15159
+ CREATE INDEX IF NOT EXISTS idx_events_project_key ON events(project_key);
15160
+ CREATE INDEX IF NOT EXISTS idx_events_type ON events(type);
15161
+ CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);
15162
+ CREATE INDEX IF NOT EXISTS idx_events_project_type ON events(project_key, type);
15163
+
15164
+ -- Agents materialized view (rebuilt from events)
15165
+ CREATE TABLE IF NOT EXISTS agents (
15166
+ id SERIAL PRIMARY KEY,
15167
+ project_key TEXT NOT NULL,
15168
+ name TEXT NOT NULL,
15169
+ program TEXT DEFAULT 'opencode',
15170
+ model TEXT DEFAULT 'unknown',
15171
+ task_description TEXT,
15172
+ registered_at BIGINT NOT NULL,
15173
+ last_active_at BIGINT NOT NULL,
15174
+ UNIQUE(project_key, name)
15175
+ );
15176
+
15177
+ CREATE INDEX IF NOT EXISTS idx_agents_project ON agents(project_key);
15178
+
15179
+ -- Messages materialized view
15180
+ CREATE TABLE IF NOT EXISTS messages (
15181
+ id SERIAL PRIMARY KEY,
15182
+ project_key TEXT NOT NULL,
15183
+ from_agent TEXT NOT NULL,
15184
+ subject TEXT NOT NULL,
15185
+ body TEXT NOT NULL,
15186
+ thread_id TEXT,
15187
+ importance TEXT DEFAULT 'normal',
15188
+ ack_required BOOLEAN DEFAULT FALSE,
15189
+ created_at BIGINT NOT NULL
15190
+ );
15191
+
15192
+ CREATE INDEX IF NOT EXISTS idx_messages_project ON messages(project_key);
15193
+ CREATE INDEX IF NOT EXISTS idx_messages_thread ON messages(thread_id);
15194
+ CREATE INDEX IF NOT EXISTS idx_messages_created ON messages(created_at DESC);
15195
+
15196
+ -- Message recipients (many-to-many)
15197
+ CREATE TABLE IF NOT EXISTS message_recipients (
15198
+ message_id INTEGER REFERENCES messages(id) ON DELETE CASCADE,
15199
+ agent_name TEXT NOT NULL,
15200
+ read_at BIGINT,
15201
+ acked_at BIGINT,
15202
+ PRIMARY KEY(message_id, agent_name)
15203
+ );
15204
+
15205
+ CREATE INDEX IF NOT EXISTS idx_recipients_agent ON message_recipients(agent_name);
15206
+
15207
+ -- File reservations materialized view
15208
+ CREATE TABLE IF NOT EXISTS reservations (
15209
+ id SERIAL PRIMARY KEY,
15210
+ project_key TEXT NOT NULL,
15211
+ agent_name TEXT NOT NULL,
15212
+ path_pattern TEXT NOT NULL,
15213
+ exclusive BOOLEAN DEFAULT TRUE,
15214
+ reason TEXT,
15215
+ created_at BIGINT NOT NULL,
15216
+ expires_at BIGINT NOT NULL,
15217
+ released_at BIGINT
15218
+ );
15219
+
15220
+ CREATE INDEX IF NOT EXISTS idx_reservations_project ON reservations(project_key);
15221
+ CREATE INDEX IF NOT EXISTS idx_reservations_agent ON reservations(agent_name);
15222
+ CREATE INDEX IF NOT EXISTS idx_reservations_expires ON reservations(expires_at);
15223
+ CREATE INDEX IF NOT EXISTS idx_reservations_active ON reservations(project_key, released_at) WHERE released_at IS NULL;
15224
+
15225
+ -- Locks table for distributed mutual exclusion (DurableLock)
15226
+ CREATE TABLE IF NOT EXISTS locks (
15227
+ resource TEXT PRIMARY KEY,
15228
+ holder TEXT NOT NULL,
15229
+ seq INTEGER NOT NULL DEFAULT 0,
15230
+ acquired_at BIGINT NOT NULL,
15231
+ expires_at BIGINT NOT NULL
15232
+ );
15233
+
15234
+ CREATE INDEX IF NOT EXISTS idx_locks_expires ON locks(expires_at);
15235
+ CREATE INDEX IF NOT EXISTS idx_locks_holder ON locks(holder);
15236
+ `,
15237
+ down: `
15238
+ DROP INDEX IF EXISTS idx_locks_holder;
15239
+ DROP INDEX IF EXISTS idx_locks_expires;
15240
+ DROP TABLE IF EXISTS locks;
15241
+ DROP INDEX IF EXISTS idx_reservations_active;
15242
+ DROP INDEX IF EXISTS idx_reservations_expires;
15243
+ DROP INDEX IF EXISTS idx_reservations_agent;
15244
+ DROP INDEX IF EXISTS idx_reservations_project;
15245
+ DROP TABLE IF EXISTS reservations;
15246
+ DROP INDEX IF EXISTS idx_recipients_agent;
15247
+ DROP TABLE IF EXISTS message_recipients;
15248
+ DROP INDEX IF EXISTS idx_messages_thread;
15249
+ DROP INDEX IF EXISTS idx_messages_project;
15250
+ DROP TABLE IF EXISTS messages;
15251
+ DROP INDEX IF EXISTS idx_agents_project;
15252
+ DROP TABLE IF EXISTS agents;
15253
+ DROP INDEX IF EXISTS idx_events_project_type;
15254
+ DROP INDEX IF EXISTS idx_events_timestamp;
15255
+ DROP INDEX IF EXISTS idx_events_type;
15256
+ DROP INDEX IF EXISTS idx_events_project_key;
15257
+ DROP TABLE IF EXISTS events;
15258
+ `
15259
+ },
15144
15260
  {
15145
15261
  version: 1,
15146
15262
  description: "Add cursors table for DurableCursor",
@@ -15227,6 +15343,41 @@ var init_migrations = __esm(() => {
15227
15343
  CREATE INDEX IF NOT EXISTS idx_swarm_contexts_bead ON swarm_contexts(bead_id);
15228
15344
  `,
15229
15345
  down: `DROP TABLE IF EXISTS swarm_contexts;`
15346
+ },
15347
+ {
15348
+ version: 5,
15349
+ description: "Add project_key and checkpointed_at to swarm_contexts, change primary key",
15350
+ up: `
15351
+ -- Add new columns
15352
+ ALTER TABLE swarm_contexts ADD COLUMN IF NOT EXISTS project_key TEXT;
15353
+ ALTER TABLE swarm_contexts ADD COLUMN IF NOT EXISTS checkpointed_at BIGINT;
15354
+ ALTER TABLE swarm_contexts ADD COLUMN IF NOT EXISTS recovered_at BIGINT;
15355
+ ALTER TABLE swarm_contexts ADD COLUMN IF NOT EXISTS recovered_from_checkpoint BIGINT;
15356
+
15357
+ -- Drop old primary key constraint on id
15358
+ ALTER TABLE swarm_contexts DROP CONSTRAINT IF EXISTS swarm_contexts_pkey;
15359
+
15360
+ -- Make id nullable since we're switching to composite key
15361
+ ALTER TABLE swarm_contexts ALTER COLUMN id DROP NOT NULL;
15362
+
15363
+ -- Create new indexes
15364
+ CREATE INDEX IF NOT EXISTS idx_swarm_contexts_project ON swarm_contexts(project_key);
15365
+ DROP INDEX IF EXISTS idx_swarm_contexts_epic;
15366
+ DROP INDEX IF EXISTS idx_swarm_contexts_bead;
15367
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_swarm_contexts_unique ON swarm_contexts(project_key, epic_id, bead_id);
15368
+ `,
15369
+ down: `
15370
+ DROP INDEX IF EXISTS idx_swarm_contexts_unique;
15371
+ DROP INDEX IF EXISTS idx_swarm_contexts_project;
15372
+ ALTER TABLE swarm_contexts DROP COLUMN IF EXISTS project_key;
15373
+ ALTER TABLE swarm_contexts DROP COLUMN IF EXISTS checkpointed_at;
15374
+ ALTER TABLE swarm_contexts DROP COLUMN IF EXISTS recovered_at;
15375
+ ALTER TABLE swarm_contexts DROP COLUMN IF EXISTS recovered_from_checkpoint;
15376
+ ALTER TABLE swarm_contexts ALTER COLUMN id SET NOT NULL;
15377
+ ALTER TABLE swarm_contexts ADD CONSTRAINT swarm_contexts_pkey PRIMARY KEY (id);
15378
+ CREATE INDEX IF NOT EXISTS idx_swarm_contexts_epic ON swarm_contexts(epic_id);
15379
+ CREATE INDEX IF NOT EXISTS idx_swarm_contexts_bead ON swarm_contexts(bead_id);
15380
+ `
15230
15381
  }
15231
15382
  ];
15232
15383
  });
@@ -15868,11 +16019,6 @@ function parseTimestamp(timestamp) {
15868
16019
  async function appendEvent(event, projectPath, dbOverride) {
15869
16020
  const db = dbOverride ?? await getDatabase(projectPath);
15870
16021
  const { type, project_key, timestamp, ...rest } = event;
15871
- console.log("[SwarmMail] Appending event", {
15872
- type,
15873
- projectKey: project_key,
15874
- timestamp
15875
- });
15876
16022
  const result = await db.query(`INSERT INTO events (type, project_key, timestamp, data)
15877
16023
  VALUES ($1, $2, $3, $4)
15878
16024
  RETURNING id, sequence`, [type, project_key, timestamp, JSON.stringify(rest)]);
@@ -15881,13 +16027,6 @@ async function appendEvent(event, projectPath, dbOverride) {
15881
16027
  throw new Error("Failed to insert event - no row returned");
15882
16028
  }
15883
16029
  const { id, sequence } = row;
15884
- console.log("[SwarmMail] Event appended", {
15885
- type,
15886
- id,
15887
- sequence,
15888
- projectKey: project_key
15889
- });
15890
- console.debug("[SwarmMail] Updating materialized views", { type, id });
15891
16030
  await updateMaterializedViews(db, { ...event, id, sequence });
15892
16031
  return { ...event, id, sequence };
15893
16032
  }
@@ -16073,7 +16212,6 @@ async function replayEventsBatched2(projectKey, onBatch, options = {}, projectPa
16073
16212
  processed += events2.length;
16074
16213
  const percent = Math.round(processed / total * 100);
16075
16214
  await onBatch(events2, { processed, total, percent });
16076
- console.log(`[SwarmMail] Replaying events: ${processed}/${total} (${percent}%)`);
16077
16215
  offset += batchSize;
16078
16216
  }
16079
16217
  return {
@@ -16153,12 +16291,6 @@ async function handleAgentRegistered(db, event) {
16153
16291
  ]);
16154
16292
  }
16155
16293
  async function handleMessageSent(db, event) {
16156
- console.log("[SwarmMail] Handling message sent event", {
16157
- from: event.from_agent,
16158
- to: event.to_agents,
16159
- subject: event.subject,
16160
- projectKey: event.project_key
16161
- });
16162
16294
  const result = await db.query(`INSERT INTO messages (project_key, from_agent, subject, body, thread_id, importance, ack_required, created_at)
16163
16295
  VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
16164
16296
  RETURNING id`, [
@@ -16182,19 +16314,9 @@ async function handleMessageSent(db, event) {
16182
16314
  await db.query(`INSERT INTO message_recipients (message_id, agent_name)
16183
16315
  VALUES ${values}
16184
16316
  ON CONFLICT DO NOTHING`, params);
16185
- console.log("[SwarmMail] Message recipients inserted", {
16186
- messageId,
16187
- recipientCount: event.to_agents.length
16188
- });
16189
16317
  }
16190
16318
  }
16191
16319
  async function handleFileReserved(db, event) {
16192
- console.log("[SwarmMail] Handling file reservation event", {
16193
- agent: event.agent_name,
16194
- paths: event.paths,
16195
- exclusive: event.exclusive,
16196
- projectKey: event.project_key
16197
- });
16198
16320
  if (event.paths.length > 0) {
16199
16321
  const values = event.paths.map((_, i) => `($1, $2, $${i + 3}, $${event.paths.length + 3}, $${event.paths.length + 4}, $${event.paths.length + 5}, $${event.paths.length + 6})`).join(", ");
16200
16322
  const params = [
@@ -16215,10 +16337,6 @@ async function handleFileReserved(db, event) {
16215
16337
  }
16216
16338
  await db.query(`INSERT INTO reservations (project_key, agent_name, path_pattern, exclusive, reason, created_at, expires_at)
16217
16339
  VALUES ${values}`, params);
16218
- console.log("[SwarmMail] File reservations inserted", {
16219
- agent: event.agent_name,
16220
- reservationCount: event.paths.length
16221
- });
16222
16340
  }
16223
16341
  }
16224
16342
  async function handleFileReleased(db, event) {
@@ -16322,10 +16440,11 @@ async function handleSwarmCheckpointed(db, event) {
16322
16440
  if (event.type !== "swarm_checkpointed")
16323
16441
  return;
16324
16442
  await db.query(`INSERT INTO swarm_contexts (
16325
- project_key, epic_id, bead_id, strategy, files, dependencies,
16326
- directives, recovery, checkpointed_at, updated_at
16327
- ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $9)
16328
- ON CONFLICT (project_key, epic_id, bead_id) DO UPDATE SET
16443
+ id, project_key, epic_id, bead_id, strategy, files, dependencies,
16444
+ directives, recovery, created_at, checkpointed_at, updated_at
16445
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $10, $10)
16446
+ ON CONFLICT (id) DO UPDATE SET
16447
+ project_key = EXCLUDED.project_key,
16329
16448
  strategy = EXCLUDED.strategy,
16330
16449
  files = EXCLUDED.files,
16331
16450
  dependencies = EXCLUDED.dependencies,
@@ -16333,6 +16452,7 @@ async function handleSwarmCheckpointed(db, event) {
16333
16452
  recovery = EXCLUDED.recovery,
16334
16453
  checkpointed_at = EXCLUDED.checkpointed_at,
16335
16454
  updated_at = EXCLUDED.updated_at`, [
16455
+ event.bead_id,
16336
16456
  event.project_key,
16337
16457
  event.epic_id,
16338
16458
  event.bead_id,
@@ -16428,6 +16548,218 @@ var init_store = __esm(() => {
16428
16548
  TIMESTAMP_SAFE_UNTIL = new Date("2286-01-01").getTime();
16429
16549
  });
16430
16550
 
16551
+ // src/beads/dependencies.ts
16552
+ var exports_dependencies = {};
16553
+ __export(exports_dependencies, {
16554
+ wouldCreateCycle: () => wouldCreateCycle,
16555
+ rebuildBeadBlockedCache: () => rebuildBeadBlockedCache,
16556
+ rebuildAllBlockedCaches: () => rebuildAllBlockedCaches,
16557
+ invalidateBlockedCache: () => invalidateBlockedCache,
16558
+ getOpenBlockers: () => getOpenBlockers
16559
+ });
16560
+ async function wouldCreateCycle(db, beadId, dependsOnId) {
16561
+ const result = await db.query(`WITH RECURSIVE paths AS (
16562
+ -- Start from the target (what we want to depend on)
16563
+ SELECT
16564
+ bead_id,
16565
+ depends_on_id,
16566
+ 1 as depth
16567
+ FROM bead_dependencies
16568
+ WHERE bead_id = $2
16569
+
16570
+ UNION
16571
+
16572
+ -- Follow dependencies transitively
16573
+ SELECT
16574
+ bd.bead_id,
16575
+ bd.depends_on_id,
16576
+ p.depth + 1
16577
+ FROM bead_dependencies bd
16578
+ JOIN paths p ON bd.bead_id = p.depends_on_id
16579
+ WHERE p.depth < $3
16580
+ )
16581
+ SELECT EXISTS(
16582
+ SELECT 1 FROM paths WHERE depends_on_id = $1
16583
+ ) as exists`, [beadId, dependsOnId, MAX_DEPENDENCY_DEPTH]);
16584
+ return result.rows[0]?.exists ?? false;
16585
+ }
16586
+ async function getOpenBlockers(db, projectKey, beadId) {
16587
+ const result = await db.query(`WITH RECURSIVE blockers AS (
16588
+ -- Direct blockers
16589
+ SELECT depends_on_id as blocker_id, 1 as depth
16590
+ FROM bead_dependencies
16591
+ WHERE bead_id = $1 AND relationship = 'blocks'
16592
+
16593
+ UNION
16594
+
16595
+ -- Transitive blockers
16596
+ SELECT bd.depends_on_id, b.depth + 1
16597
+ FROM bead_dependencies bd
16598
+ JOIN blockers b ON bd.bead_id = b.blocker_id
16599
+ WHERE bd.relationship = 'blocks' AND b.depth < $3
16600
+ )
16601
+ SELECT DISTINCT b.blocker_id
16602
+ FROM blockers b
16603
+ JOIN beads bead ON b.blocker_id = bead.id
16604
+ WHERE bead.project_key = $2 AND bead.status != 'closed' AND bead.deleted_at IS NULL`, [beadId, projectKey, MAX_DEPENDENCY_DEPTH]);
16605
+ return result.rows.map((r) => r.blocker_id);
16606
+ }
16607
+ async function rebuildBeadBlockedCache(db, projectKey, beadId) {
16608
+ const blockerIds = await getOpenBlockers(db, projectKey, beadId);
16609
+ if (blockerIds.length > 0) {
16610
+ await db.query(`INSERT INTO blocked_beads_cache (bead_id, blocker_ids, updated_at)
16611
+ VALUES ($1, $2, $3)
16612
+ ON CONFLICT (bead_id)
16613
+ DO UPDATE SET blocker_ids = $2, updated_at = $3`, [beadId, blockerIds, Date.now()]);
16614
+ } else {
16615
+ await db.query(`DELETE FROM blocked_beads_cache WHERE bead_id = $1`, [beadId]);
16616
+ }
16617
+ }
16618
+ async function rebuildAllBlockedCaches(db, projectKey) {
16619
+ const result = await db.query(`SELECT DISTINCT b.id FROM beads b
16620
+ JOIN bead_dependencies bd ON b.id = bd.bead_id
16621
+ WHERE b.project_key = $1 AND bd.relationship = 'blocks' AND b.deleted_at IS NULL`, [projectKey]);
16622
+ for (const row of result.rows) {
16623
+ await rebuildBeadBlockedCache(db, projectKey, row.id);
16624
+ }
16625
+ }
16626
+ async function invalidateBlockedCache(db, projectKey, beadId) {
16627
+ await rebuildBeadBlockedCache(db, projectKey, beadId);
16628
+ const dependents = await db.query(`SELECT bead_id FROM bead_dependencies WHERE depends_on_id = $1`, [beadId]);
16629
+ for (const row of dependents.rows) {
16630
+ await rebuildBeadBlockedCache(db, projectKey, row.bead_id);
16631
+ }
16632
+ }
16633
+ var MAX_DEPENDENCY_DEPTH = 100;
16634
+
16635
+ // src/beads/migrations.ts
16636
+ var exports_migrations2 = {};
16637
+ __export(exports_migrations2, {
16638
+ beadsMigrations: () => beadsMigrations,
16639
+ beadsMigration: () => beadsMigration
16640
+ });
16641
+ var beadsMigration, beadsMigrations;
16642
+ var init_migrations2 = __esm(() => {
16643
+ beadsMigration = {
16644
+ version: 6,
16645
+ description: "Add beads tables for issue tracking",
16646
+ up: `
16647
+ -- ========================================================================
16648
+ -- Core Beads Table
16649
+ -- ========================================================================
16650
+ CREATE TABLE IF NOT EXISTS beads (
16651
+ id TEXT PRIMARY KEY,
16652
+ project_key TEXT NOT NULL,
16653
+ type TEXT NOT NULL CHECK (type IN ('bug', 'feature', 'task', 'epic', 'chore', 'message')),
16654
+ status TEXT NOT NULL DEFAULT 'open' CHECK (status IN ('open', 'in_progress', 'blocked', 'closed', 'tombstone')),
16655
+ title TEXT NOT NULL CHECK (length(title) <= 500),
16656
+ description TEXT,
16657
+ priority INTEGER NOT NULL DEFAULT 2 CHECK (priority BETWEEN 0 AND 3),
16658
+ parent_id TEXT REFERENCES beads(id) ON DELETE SET NULL,
16659
+ assignee TEXT,
16660
+ created_at BIGINT NOT NULL,
16661
+ updated_at BIGINT NOT NULL,
16662
+ closed_at BIGINT,
16663
+ closed_reason TEXT,
16664
+ deleted_at BIGINT,
16665
+ deleted_by TEXT,
16666
+ delete_reason TEXT,
16667
+ created_by TEXT,
16668
+ CHECK ((status = 'closed') = (closed_at IS NOT NULL))
16669
+ );
16670
+
16671
+ -- Indexes for common queries
16672
+ CREATE INDEX IF NOT EXISTS idx_beads_project ON beads(project_key);
16673
+ CREATE INDEX IF NOT EXISTS idx_beads_status ON beads(status);
16674
+ CREATE INDEX IF NOT EXISTS idx_beads_type ON beads(type);
16675
+ CREATE INDEX IF NOT EXISTS idx_beads_priority ON beads(priority);
16676
+ CREATE INDEX IF NOT EXISTS idx_beads_assignee ON beads(assignee);
16677
+ CREATE INDEX IF NOT EXISTS idx_beads_parent ON beads(parent_id);
16678
+ CREATE INDEX IF NOT EXISTS idx_beads_created ON beads(created_at);
16679
+ CREATE INDEX IF NOT EXISTS idx_beads_project_status ON beads(project_key, status);
16680
+
16681
+ -- ========================================================================
16682
+ -- Dependencies Table
16683
+ -- ========================================================================
16684
+ CREATE TABLE IF NOT EXISTS bead_dependencies (
16685
+ bead_id TEXT NOT NULL REFERENCES beads(id) ON DELETE CASCADE,
16686
+ depends_on_id TEXT NOT NULL REFERENCES beads(id) ON DELETE CASCADE,
16687
+ relationship TEXT NOT NULL CHECK (relationship IN ('blocks', 'related', 'parent-child', 'discovered-from', 'replies-to', 'relates-to', 'duplicates', 'supersedes')),
16688
+ created_at BIGINT NOT NULL,
16689
+ created_by TEXT,
16690
+ PRIMARY KEY (bead_id, depends_on_id, relationship)
16691
+ );
16692
+
16693
+ CREATE INDEX IF NOT EXISTS idx_bead_deps_bead ON bead_dependencies(bead_id);
16694
+ CREATE INDEX IF NOT EXISTS idx_bead_deps_depends_on ON bead_dependencies(depends_on_id);
16695
+ CREATE INDEX IF NOT EXISTS idx_bead_deps_relationship ON bead_dependencies(relationship);
16696
+
16697
+ -- ========================================================================
16698
+ -- Labels Table
16699
+ -- ========================================================================
16700
+ CREATE TABLE IF NOT EXISTS bead_labels (
16701
+ bead_id TEXT NOT NULL REFERENCES beads(id) ON DELETE CASCADE,
16702
+ label TEXT NOT NULL,
16703
+ created_at BIGINT NOT NULL,
16704
+ PRIMARY KEY (bead_id, label)
16705
+ );
16706
+
16707
+ CREATE INDEX IF NOT EXISTS idx_bead_labels_label ON bead_labels(label);
16708
+
16709
+ -- ========================================================================
16710
+ -- Comments Table
16711
+ -- ========================================================================
16712
+ CREATE TABLE IF NOT EXISTS bead_comments (
16713
+ id SERIAL PRIMARY KEY,
16714
+ bead_id TEXT NOT NULL REFERENCES beads(id) ON DELETE CASCADE,
16715
+ author TEXT NOT NULL,
16716
+ body TEXT NOT NULL,
16717
+ parent_id INTEGER REFERENCES bead_comments(id) ON DELETE CASCADE,
16718
+ created_at BIGINT NOT NULL,
16719
+ updated_at BIGINT
16720
+ );
16721
+
16722
+ CREATE INDEX IF NOT EXISTS idx_bead_comments_bead ON bead_comments(bead_id);
16723
+ CREATE INDEX IF NOT EXISTS idx_bead_comments_author ON bead_comments(author);
16724
+ CREATE INDEX IF NOT EXISTS idx_bead_comments_created ON bead_comments(created_at);
16725
+
16726
+ -- ========================================================================
16727
+ -- Blocked Beads Cache
16728
+ -- ========================================================================
16729
+ -- Materialized view for fast blocked queries
16730
+ -- Updated by projections when dependencies change
16731
+ CREATE TABLE IF NOT EXISTS blocked_beads_cache (
16732
+ bead_id TEXT PRIMARY KEY REFERENCES beads(id) ON DELETE CASCADE,
16733
+ blocker_ids TEXT[] NOT NULL,
16734
+ updated_at BIGINT NOT NULL
16735
+ );
16736
+
16737
+ CREATE INDEX IF NOT EXISTS idx_blocked_beads_updated ON blocked_beads_cache(updated_at);
16738
+
16739
+ -- ========================================================================
16740
+ -- Dirty Beads Table
16741
+ -- ========================================================================
16742
+ -- Tracks beads that need JSONL export (incremental sync)
16743
+ CREATE TABLE IF NOT EXISTS dirty_beads (
16744
+ bead_id TEXT PRIMARY KEY REFERENCES beads(id) ON DELETE CASCADE,
16745
+ marked_at BIGINT NOT NULL
16746
+ );
16747
+
16748
+ CREATE INDEX IF NOT EXISTS idx_dirty_beads_marked ON dirty_beads(marked_at);
16749
+ `,
16750
+ down: `
16751
+ -- Drop in reverse order to handle foreign key constraints
16752
+ DROP TABLE IF EXISTS dirty_beads;
16753
+ DROP TABLE IF EXISTS blocked_beads_cache;
16754
+ DROP TABLE IF EXISTS bead_comments;
16755
+ DROP TABLE IF EXISTS bead_labels;
16756
+ DROP TABLE IF EXISTS bead_dependencies;
16757
+ DROP TABLE IF EXISTS beads;
16758
+ `
16759
+ };
16760
+ beadsMigrations = [beadsMigration];
16761
+ });
16762
+
16431
16763
  // src/adapter.ts
16432
16764
  init_store();
16433
16765
  init_projections();
@@ -16622,10 +16954,1553 @@ async function closeAllSwarmMail() {
16622
16954
 
16623
16955
  // src/index.ts
16624
16956
  init_streams();
16957
+
16958
+ // src/beads/store.ts
16959
+ init_streams();
16960
+
16961
+ // src/beads/projections.ts
16962
+ async function updateProjections(db, event) {
16963
+ switch (event.type) {
16964
+ case "bead_created":
16965
+ await handleBeadCreated(db, event);
16966
+ break;
16967
+ case "bead_updated":
16968
+ await handleBeadUpdated(db, event);
16969
+ break;
16970
+ case "bead_status_changed":
16971
+ await handleBeadStatusChanged(db, event);
16972
+ break;
16973
+ case "bead_closed":
16974
+ await handleBeadClosed(db, event);
16975
+ break;
16976
+ case "bead_reopened":
16977
+ await handleBeadReopened(db, event);
16978
+ break;
16979
+ case "bead_deleted":
16980
+ await handleBeadDeleted(db, event);
16981
+ break;
16982
+ case "bead_dependency_added":
16983
+ await handleDependencyAdded(db, event);
16984
+ break;
16985
+ case "bead_dependency_removed":
16986
+ await handleDependencyRemoved(db, event);
16987
+ break;
16988
+ case "bead_label_added":
16989
+ await handleLabelAdded(db, event);
16990
+ break;
16991
+ case "bead_label_removed":
16992
+ await handleLabelRemoved(db, event);
16993
+ break;
16994
+ case "bead_comment_added":
16995
+ await handleCommentAdded(db, event);
16996
+ break;
16997
+ case "bead_comment_updated":
16998
+ await handleCommentUpdated(db, event);
16999
+ break;
17000
+ case "bead_comment_deleted":
17001
+ await handleCommentDeleted(db, event);
17002
+ break;
17003
+ case "bead_epic_child_added":
17004
+ await handleEpicChildAdded(db, event);
17005
+ break;
17006
+ case "bead_epic_child_removed":
17007
+ await handleEpicChildRemoved(db, event);
17008
+ break;
17009
+ case "bead_assigned":
17010
+ await handleBeadAssigned(db, event);
17011
+ break;
17012
+ case "bead_work_started":
17013
+ await handleWorkStarted(db, event);
17014
+ break;
17015
+ default:
17016
+ console.warn(`[beads/projections] Unknown event type: ${event.type}`);
17017
+ }
17018
+ await markBeadDirty(db, event.project_key, event.bead_id);
17019
+ }
17020
+ async function handleBeadCreated(db, event) {
17021
+ await db.query(`INSERT INTO beads (
17022
+ id, project_key, type, status, title, description, priority,
17023
+ parent_id, assignee, created_at, updated_at, created_by
17024
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`, [
17025
+ event.bead_id,
17026
+ event.project_key,
17027
+ event.issue_type,
17028
+ "open",
17029
+ event.title,
17030
+ event.description || null,
17031
+ event.priority ?? 2,
17032
+ event.parent_id || null,
17033
+ null,
17034
+ event.timestamp,
17035
+ event.timestamp,
17036
+ event.created_by || null
17037
+ ]);
17038
+ }
17039
+ async function handleBeadUpdated(db, event) {
17040
+ const changes = event.changes;
17041
+ const updates = [];
17042
+ const params = [];
17043
+ let paramIndex = 1;
17044
+ if (changes.title) {
17045
+ updates.push(`title = $${paramIndex++}`);
17046
+ params.push(changes.title.new);
17047
+ }
17048
+ if (changes.description) {
17049
+ updates.push(`description = $${paramIndex++}`);
17050
+ params.push(changes.description.new);
17051
+ }
17052
+ if (changes.priority) {
17053
+ updates.push(`priority = $${paramIndex++}`);
17054
+ params.push(changes.priority.new);
17055
+ }
17056
+ if (changes.assignee) {
17057
+ updates.push(`assignee = $${paramIndex++}`);
17058
+ params.push(changes.assignee.new);
17059
+ }
17060
+ if (updates.length > 0) {
17061
+ updates.push(`updated_at = $${paramIndex++}`);
17062
+ params.push(event.timestamp);
17063
+ params.push(event.bead_id);
17064
+ await db.query(`UPDATE beads SET ${updates.join(", ")} WHERE id = $${paramIndex}`, params);
17065
+ }
17066
+ }
17067
+ async function handleBeadStatusChanged(db, event) {
17068
+ await db.query(`UPDATE beads SET status = $1, updated_at = $2 WHERE id = $3`, [event.to_status, event.timestamp, event.bead_id]);
17069
+ }
17070
+ async function handleBeadClosed(db, event) {
17071
+ await db.query(`UPDATE beads SET
17072
+ status = 'closed',
17073
+ closed_at = $1,
17074
+ closed_reason = $2,
17075
+ updated_at = $3
17076
+ WHERE id = $4`, [event.timestamp, event.reason, event.timestamp, event.bead_id]);
17077
+ const { invalidateBlockedCache: invalidateBlockedCache2 } = await Promise.resolve().then(() => exports_dependencies);
17078
+ await invalidateBlockedCache2(db, event.project_key, event.bead_id);
17079
+ }
17080
+ async function handleBeadReopened(db, event) {
17081
+ await db.query(`UPDATE beads SET
17082
+ status = 'open',
17083
+ closed_at = NULL,
17084
+ closed_reason = NULL,
17085
+ updated_at = $1
17086
+ WHERE id = $2`, [event.timestamp, event.bead_id]);
17087
+ }
17088
+ async function handleBeadDeleted(db, event) {
17089
+ await db.query(`UPDATE beads SET
17090
+ deleted_at = $1,
17091
+ deleted_by = $2,
17092
+ delete_reason = $3,
17093
+ updated_at = $4
17094
+ WHERE id = $5`, [event.timestamp, event.deleted_by || null, event.reason || null, event.timestamp, event.bead_id]);
17095
+ }
17096
+ async function handleDependencyAdded(db, event) {
17097
+ const dep = event.dependency;
17098
+ await db.query(`INSERT INTO bead_dependencies (bead_id, depends_on_id, relationship, created_at, created_by)
17099
+ VALUES ($1, $2, $3, $4, $5)
17100
+ ON CONFLICT (bead_id, depends_on_id, relationship) DO NOTHING`, [event.bead_id, dep.target, dep.type, event.timestamp, event.added_by || null]);
17101
+ const { invalidateBlockedCache: invalidate } = await Promise.resolve().then(() => exports_dependencies);
17102
+ await invalidate(db, event.project_key, event.bead_id);
17103
+ }
17104
+ async function handleDependencyRemoved(db, event) {
17105
+ const dep = event.dependency;
17106
+ await db.query(`DELETE FROM bead_dependencies
17107
+ WHERE bead_id = $1 AND depends_on_id = $2 AND relationship = $3`, [event.bead_id, dep.target, dep.type]);
17108
+ const { invalidateBlockedCache: invalidate } = await Promise.resolve().then(() => exports_dependencies);
17109
+ await invalidate(db, event.project_key, event.bead_id);
17110
+ }
17111
+ async function handleLabelAdded(db, event) {
17112
+ await db.query(`INSERT INTO bead_labels (bead_id, label, created_at)
17113
+ VALUES ($1, $2, $3)
17114
+ ON CONFLICT (bead_id, label) DO NOTHING`, [event.bead_id, event.label, event.timestamp]);
17115
+ }
17116
+ async function handleLabelRemoved(db, event) {
17117
+ await db.query(`DELETE FROM bead_labels WHERE bead_id = $1 AND label = $2`, [event.bead_id, event.label]);
17118
+ }
17119
+ async function handleCommentAdded(db, event) {
17120
+ await db.query(`INSERT INTO bead_comments (bead_id, author, body, parent_id, created_at)
17121
+ VALUES ($1, $2, $3, $4, $5)`, [event.bead_id, event.author, event.body, event.parent_comment_id || null, event.timestamp]);
17122
+ }
17123
+ async function handleCommentUpdated(db, event) {
17124
+ await db.query(`UPDATE bead_comments SET body = $1, updated_at = $2 WHERE id = $3`, [event.new_body, event.timestamp, event.comment_id]);
17125
+ }
17126
+ async function handleCommentDeleted(db, event) {
17127
+ await db.query(`DELETE FROM bead_comments WHERE id = $1`, [event.comment_id]);
17128
+ }
17129
+ async function handleEpicChildAdded(db, event) {
17130
+ await db.query(`UPDATE beads SET parent_id = $1, updated_at = $2 WHERE id = $3`, [event.bead_id, event.timestamp, event.child_id]);
17131
+ }
17132
+ async function handleEpicChildRemoved(db, event) {
17133
+ await db.query(`UPDATE beads SET parent_id = NULL, updated_at = $1 WHERE id = $2`, [event.timestamp, event.child_id]);
17134
+ }
17135
+ async function handleBeadAssigned(db, event) {
17136
+ await db.query(`UPDATE beads SET assignee = $1, updated_at = $2 WHERE id = $3`, [event.assignee, event.timestamp, event.bead_id]);
17137
+ }
17138
+ async function handleWorkStarted(db, event) {
17139
+ await db.query(`UPDATE beads SET status = 'in_progress', updated_at = $1 WHERE id = $2`, [event.timestamp, event.bead_id]);
17140
+ }
17141
+ async function getBead(db, projectKey, beadId) {
17142
+ const result = await db.query(`SELECT * FROM beads WHERE project_key = $1 AND id = $2 AND deleted_at IS NULL`, [projectKey, beadId]);
17143
+ return result.rows[0] ?? null;
17144
+ }
17145
+ async function queryBeads(db, projectKey, options = {}) {
17146
+ const conditions = ["project_key = $1"];
17147
+ const params = [projectKey];
17148
+ let paramIndex = 2;
17149
+ if (!options.include_deleted) {
17150
+ conditions.push("deleted_at IS NULL");
17151
+ }
17152
+ if (options.status) {
17153
+ const statuses = Array.isArray(options.status) ? options.status : [options.status];
17154
+ conditions.push(`status = ANY($${paramIndex++})`);
17155
+ params.push(statuses);
17156
+ }
17157
+ if (options.type) {
17158
+ const types2 = Array.isArray(options.type) ? options.type : [options.type];
17159
+ conditions.push(`type = ANY($${paramIndex++})`);
17160
+ params.push(types2);
17161
+ }
17162
+ if (options.parent_id) {
17163
+ conditions.push(`parent_id = $${paramIndex++}`);
17164
+ params.push(options.parent_id);
17165
+ }
17166
+ if (options.assignee) {
17167
+ conditions.push(`assignee = $${paramIndex++}`);
17168
+ params.push(options.assignee);
17169
+ }
17170
+ let query = `SELECT * FROM beads WHERE ${conditions.join(" AND ")} ORDER BY priority DESC, created_at ASC`;
17171
+ if (options.limit) {
17172
+ query += ` LIMIT $${paramIndex++}`;
17173
+ params.push(options.limit);
17174
+ }
17175
+ if (options.offset) {
17176
+ query += ` OFFSET $${paramIndex++}`;
17177
+ params.push(options.offset);
17178
+ }
17179
+ const result = await db.query(query, params);
17180
+ return result.rows;
17181
+ }
17182
+ async function getDependencies(db, projectKey, beadId) {
17183
+ const result = await db.query(`SELECT * FROM bead_dependencies WHERE bead_id = $1`, [beadId]);
17184
+ return result.rows;
17185
+ }
17186
+ async function getDependents(db, projectKey, beadId) {
17187
+ const result = await db.query(`SELECT * FROM bead_dependencies WHERE depends_on_id = $1`, [beadId]);
17188
+ return result.rows;
17189
+ }
17190
+ async function isBlocked(db, projectKey, beadId) {
17191
+ const result = await db.query(`SELECT EXISTS(SELECT 1 FROM blocked_beads_cache WHERE bead_id = $1) as exists`, [beadId]);
17192
+ return result.rows[0]?.exists ?? false;
17193
+ }
17194
+ async function getBlockers(db, projectKey, beadId) {
17195
+ const result = await db.query(`SELECT blocker_ids FROM blocked_beads_cache WHERE bead_id = $1`, [beadId]);
17196
+ return result.rows[0]?.blocker_ids ?? [];
17197
+ }
17198
+ async function getLabels(db, projectKey, beadId) {
17199
+ const result = await db.query(`SELECT label FROM bead_labels WHERE bead_id = $1 ORDER BY label`, [beadId]);
17200
+ return result.rows.map((r) => r.label);
17201
+ }
17202
+ async function getComments(db, projectKey, beadId) {
17203
+ const result = await db.query(`SELECT * FROM bead_comments WHERE bead_id = $1 ORDER BY created_at ASC`, [beadId]);
17204
+ return result.rows;
17205
+ }
17206
+ async function getNextReadyBead(db, projectKey) {
17207
+ const result = await db.query(`SELECT b.* FROM beads b
17208
+ WHERE b.project_key = $1
17209
+ AND b.status = 'open'
17210
+ AND b.deleted_at IS NULL
17211
+ AND NOT EXISTS (
17212
+ SELECT 1 FROM blocked_beads_cache bbc WHERE bbc.bead_id = b.id
17213
+ )
17214
+ ORDER BY b.priority DESC, b.created_at ASC
17215
+ LIMIT 1`, [projectKey]);
17216
+ return result.rows[0] ?? null;
17217
+ }
17218
+ async function getInProgressBeads(db, projectKey) {
17219
+ const result = await db.query(`SELECT * FROM beads
17220
+ WHERE project_key = $1 AND status = 'in_progress' AND deleted_at IS NULL
17221
+ ORDER BY priority DESC, created_at ASC`, [projectKey]);
17222
+ return result.rows;
17223
+ }
17224
+ async function getBlockedBeads(db, projectKey) {
17225
+ const result = await db.query(`SELECT b.*, bbc.blocker_ids
17226
+ FROM beads b
17227
+ JOIN blocked_beads_cache bbc ON b.id = bbc.bead_id
17228
+ WHERE b.project_key = $1 AND b.deleted_at IS NULL
17229
+ ORDER BY b.priority DESC, b.created_at ASC`, [projectKey]);
17230
+ return result.rows.map((r) => {
17231
+ const { blocker_ids, ...bead } = r;
17232
+ return { bead, blockers: blocker_ids };
17233
+ });
17234
+ }
17235
+ async function markBeadDirty(db, projectKey, beadId) {
17236
+ await db.query(`INSERT INTO dirty_beads (bead_id, marked_at)
17237
+ VALUES ($1, $2)
17238
+ ON CONFLICT (bead_id) DO UPDATE SET marked_at = $2`, [beadId, Date.now()]);
17239
+ }
17240
+ async function getDirtyBeads(db, projectKey) {
17241
+ const result = await db.query(`SELECT db.bead_id FROM dirty_beads db
17242
+ JOIN beads b ON db.bead_id = b.id
17243
+ WHERE b.project_key = $1
17244
+ ORDER BY db.marked_at ASC`, [projectKey]);
17245
+ return result.rows.map((r) => r.bead_id);
17246
+ }
17247
+ async function clearDirtyBead(db, projectKey, beadId) {
17248
+ await db.query(`DELETE FROM dirty_beads WHERE bead_id = $1`, [beadId]);
17249
+ }
17250
+ async function clearAllDirtyBeads(db, projectKey) {
17251
+ await db.query(`DELETE FROM dirty_beads WHERE bead_id IN (
17252
+ SELECT id FROM beads WHERE project_key = $1
17253
+ )`, [projectKey]);
17254
+ }
17255
+
17256
+ // src/beads/store.ts
17257
+ function parseTimestamp2(timestamp) {
17258
+ const ts = parseInt(timestamp, 10);
17259
+ if (Number.isNaN(ts)) {
17260
+ throw new Error(`[BeadsStore] Invalid timestamp: ${timestamp}`);
17261
+ }
17262
+ if (ts > Number.MAX_SAFE_INTEGER) {
17263
+ console.warn(`[BeadsStore] Timestamp ${timestamp} exceeds MAX_SAFE_INTEGER (year 2286+)`);
17264
+ }
17265
+ return ts;
17266
+ }
17267
+ async function appendBeadEvent(event, projectPath, dbOverride) {
17268
+ const db = dbOverride ?? await getDatabase(projectPath);
17269
+ const { type, project_key, timestamp, ...rest } = event;
17270
+ const result = await db.query(`INSERT INTO events (type, project_key, timestamp, data)
17271
+ VALUES ($1, $2, $3, $4)
17272
+ RETURNING id, sequence`, [type, project_key, timestamp, JSON.stringify(rest)]);
17273
+ const row = result.rows[0];
17274
+ if (!row) {
17275
+ throw new Error("[BeadsStore] Failed to insert event - no row returned");
17276
+ }
17277
+ const { id, sequence } = row;
17278
+ await updateProjections(db, { ...event, id, sequence });
17279
+ return { ...event, id, sequence };
17280
+ }
17281
+ async function readBeadEvents(options = {}, projectPath, dbOverride) {
17282
+ return withTiming("readBeadEvents", async () => {
17283
+ const db = dbOverride ?? await getDatabase(projectPath);
17284
+ const conditions = [];
17285
+ const params = [];
17286
+ let paramIndex = 1;
17287
+ conditions.push(`type LIKE 'bead_%'`);
17288
+ if (options.projectKey) {
17289
+ conditions.push(`project_key = $${paramIndex++}`);
17290
+ params.push(options.projectKey);
17291
+ }
17292
+ if (options.beadId) {
17293
+ conditions.push(`data->>'bead_id' = $${paramIndex++}`);
17294
+ params.push(options.beadId);
17295
+ }
17296
+ if (options.types && options.types.length > 0) {
17297
+ conditions.push(`type = ANY($${paramIndex++})`);
17298
+ params.push(options.types);
17299
+ }
17300
+ if (options.since !== undefined) {
17301
+ conditions.push(`timestamp >= $${paramIndex++}`);
17302
+ params.push(options.since);
17303
+ }
17304
+ if (options.until !== undefined) {
17305
+ conditions.push(`timestamp <= $${paramIndex++}`);
17306
+ params.push(options.until);
17307
+ }
17308
+ if (options.afterSequence !== undefined) {
17309
+ conditions.push(`sequence > $${paramIndex++}`);
17310
+ params.push(options.afterSequence);
17311
+ }
17312
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
17313
+ let query = `
17314
+ SELECT id, type, project_key, timestamp, sequence, data
17315
+ FROM events
17316
+ ${whereClause}
17317
+ ORDER BY sequence ASC
17318
+ `;
17319
+ if (options.limit) {
17320
+ query += ` LIMIT $${paramIndex++}`;
17321
+ params.push(options.limit);
17322
+ }
17323
+ if (options.offset) {
17324
+ query += ` OFFSET $${paramIndex++}`;
17325
+ params.push(options.offset);
17326
+ }
17327
+ const result = await db.query(query, params);
17328
+ return result.rows.map((row) => {
17329
+ const data = typeof row.data === "string" ? JSON.parse(row.data) : row.data;
17330
+ return {
17331
+ id: row.id,
17332
+ type: row.type,
17333
+ project_key: row.project_key,
17334
+ timestamp: parseTimestamp2(row.timestamp),
17335
+ sequence: row.sequence,
17336
+ ...data
17337
+ };
17338
+ });
17339
+ });
17340
+ }
17341
+ async function replayBeadEvents(options = {}, projectPath, dbOverride) {
17342
+ return withTiming("replayBeadEvents", async () => {
17343
+ const startTime = Date.now();
17344
+ const db = dbOverride ?? await getDatabase(projectPath);
17345
+ if (options.clearViews) {
17346
+ if (options.projectKey) {
17347
+ await db.query(`DELETE FROM bead_comments WHERE bead_id IN (
17348
+ SELECT id FROM beads WHERE project_key = $1
17349
+ )`, [options.projectKey]);
17350
+ await db.query(`DELETE FROM bead_labels WHERE bead_id IN (
17351
+ SELECT id FROM beads WHERE project_key = $1
17352
+ )`, [options.projectKey]);
17353
+ await db.query(`DELETE FROM bead_dependencies WHERE bead_id IN (
17354
+ SELECT id FROM beads WHERE project_key = $1
17355
+ )`, [options.projectKey]);
17356
+ await db.query(`DELETE FROM blocked_beads_cache WHERE bead_id IN (
17357
+ SELECT id FROM beads WHERE project_key = $1
17358
+ )`, [options.projectKey]);
17359
+ await db.query(`DELETE FROM dirty_beads WHERE bead_id IN (
17360
+ SELECT id FROM beads WHERE project_key = $1
17361
+ )`, [options.projectKey]);
17362
+ await db.query(`DELETE FROM beads WHERE project_key = $1`, [
17363
+ options.projectKey
17364
+ ]);
17365
+ } else {
17366
+ await db.exec(`
17367
+ DELETE FROM bead_comments;
17368
+ DELETE FROM bead_labels;
17369
+ DELETE FROM bead_dependencies;
17370
+ DELETE FROM blocked_beads_cache;
17371
+ DELETE FROM dirty_beads;
17372
+ DELETE FROM beads;
17373
+ `);
17374
+ }
17375
+ }
17376
+ const events2 = await readBeadEvents({
17377
+ projectKey: options.projectKey,
17378
+ afterSequence: options.fromSequence
17379
+ }, projectPath, dbOverride);
17380
+ for (const event of events2) {
17381
+ await updateProjections(db, event);
17382
+ }
17383
+ return {
17384
+ eventsReplayed: events2.length,
17385
+ duration: Date.now() - startTime
17386
+ };
17387
+ });
17388
+ }
17389
+
17390
+ // src/beads/adapter.ts
17391
+ function createBeadsAdapter(db, projectKey) {
17392
+ return {
17393
+ async createBead(projectKeyParam, options, projectPath) {
17394
+ const event = {
17395
+ type: "bead_created",
17396
+ project_key: projectKeyParam,
17397
+ bead_id: generateBeadId(projectKeyParam),
17398
+ timestamp: Date.now(),
17399
+ title: options.title,
17400
+ description: options.description || null,
17401
+ issue_type: options.type,
17402
+ priority: options.priority ?? 2,
17403
+ parent_id: options.parent_id || null,
17404
+ created_by: options.created_by || null,
17405
+ metadata: options.metadata || null
17406
+ };
17407
+ await appendBeadEvent(event, projectPath, db);
17408
+ if (options.assignee) {
17409
+ const assignEvent = {
17410
+ type: "bead_assigned",
17411
+ project_key: projectKeyParam,
17412
+ bead_id: event.bead_id,
17413
+ timestamp: Date.now(),
17414
+ assignee: options.assignee,
17415
+ assigned_by: options.created_by || null
17416
+ };
17417
+ await appendBeadEvent(assignEvent, projectPath, db);
17418
+ }
17419
+ const bead = await getBead(db, projectKeyParam, event.bead_id);
17420
+ if (!bead) {
17421
+ throw new Error(`[BeadsAdapter] Failed to create bead - not found after insert`);
17422
+ }
17423
+ return bead;
17424
+ },
17425
+ async getBead(projectKeyParam, beadId, projectPath) {
17426
+ return getBead(db, projectKeyParam, beadId);
17427
+ },
17428
+ async queryBeads(projectKeyParam, options, projectPath) {
17429
+ return queryBeads(db, projectKeyParam, options);
17430
+ },
17431
+ async updateBead(projectKeyParam, beadId, options, projectPath) {
17432
+ const existingBead = await getBead(db, projectKeyParam, beadId);
17433
+ if (!existingBead) {
17434
+ throw new Error(`[BeadsAdapter] Bead not found: ${beadId}`);
17435
+ }
17436
+ const changes = {};
17437
+ if (options.title && options.title !== existingBead.title) {
17438
+ changes.title = { old: existingBead.title, new: options.title };
17439
+ }
17440
+ if (options.description !== undefined && options.description !== existingBead.description) {
17441
+ changes.description = { old: existingBead.description, new: options.description };
17442
+ }
17443
+ if (options.priority !== undefined && options.priority !== existingBead.priority) {
17444
+ changes.priority = { old: existingBead.priority, new: options.priority };
17445
+ }
17446
+ if (options.assignee !== undefined && options.assignee !== existingBead.assignee) {
17447
+ changes.assignee = { old: existingBead.assignee, new: options.assignee };
17448
+ }
17449
+ if (Object.keys(changes).length === 0) {
17450
+ return existingBead;
17451
+ }
17452
+ const event = {
17453
+ type: "bead_updated",
17454
+ project_key: projectKeyParam,
17455
+ bead_id: beadId,
17456
+ timestamp: Date.now(),
17457
+ changes,
17458
+ updated_by: options.updated_by || null
17459
+ };
17460
+ await appendBeadEvent(event, projectPath, db);
17461
+ const updated = await getBead(db, projectKeyParam, beadId);
17462
+ if (!updated) {
17463
+ throw new Error(`[BeadsAdapter] Bead disappeared after update: ${beadId}`);
17464
+ }
17465
+ return updated;
17466
+ },
17467
+ async changeBeadStatus(projectKeyParam, beadId, toStatus, options, projectPath) {
17468
+ const existingBead = await getBead(db, projectKeyParam, beadId);
17469
+ if (!existingBead) {
17470
+ throw new Error(`[BeadsAdapter] Bead not found: ${beadId}`);
17471
+ }
17472
+ const event = {
17473
+ type: "bead_status_changed",
17474
+ project_key: projectKeyParam,
17475
+ bead_id: beadId,
17476
+ timestamp: Date.now(),
17477
+ from_status: existingBead.status,
17478
+ to_status: toStatus,
17479
+ reason: options?.reason || null,
17480
+ changed_by: options?.changed_by || null
17481
+ };
17482
+ await appendBeadEvent(event, projectPath, db);
17483
+ const updated = await getBead(db, projectKeyParam, beadId);
17484
+ if (!updated) {
17485
+ throw new Error(`[BeadsAdapter] Bead disappeared after status change: ${beadId}`);
17486
+ }
17487
+ return updated;
17488
+ },
17489
+ async closeBead(projectKeyParam, beadId, reason, options, projectPath) {
17490
+ const existingBead = await getBead(db, projectKeyParam, beadId);
17491
+ if (!existingBead) {
17492
+ throw new Error(`[BeadsAdapter] Bead not found: ${beadId}`);
17493
+ }
17494
+ const event = {
17495
+ type: "bead_closed",
17496
+ project_key: projectKeyParam,
17497
+ bead_id: beadId,
17498
+ timestamp: Date.now(),
17499
+ reason,
17500
+ closed_by: options?.closed_by || null,
17501
+ files_touched: options?.files_touched || null,
17502
+ duration_ms: options?.duration_ms || null
17503
+ };
17504
+ await appendBeadEvent(event, projectPath, db);
17505
+ const updated = await getBead(db, projectKeyParam, beadId);
17506
+ if (!updated) {
17507
+ throw new Error(`[BeadsAdapter] Bead disappeared after close: ${beadId}`);
17508
+ }
17509
+ return updated;
17510
+ },
17511
+ async reopenBead(projectKeyParam, beadId, options, projectPath) {
17512
+ const existingBead = await getBead(db, projectKeyParam, beadId);
17513
+ if (!existingBead) {
17514
+ throw new Error(`[BeadsAdapter] Bead not found: ${beadId}`);
17515
+ }
17516
+ const event = {
17517
+ type: "bead_reopened",
17518
+ project_key: projectKeyParam,
17519
+ bead_id: beadId,
17520
+ timestamp: Date.now(),
17521
+ reason: options?.reason || null,
17522
+ reopened_by: options?.reopened_by || null
17523
+ };
17524
+ await appendBeadEvent(event, projectPath, db);
17525
+ const updated = await getBead(db, projectKeyParam, beadId);
17526
+ if (!updated) {
17527
+ throw new Error(`[BeadsAdapter] Bead disappeared after reopen: ${beadId}`);
17528
+ }
17529
+ return updated;
17530
+ },
17531
+ async deleteBead(projectKeyParam, beadId, options, projectPath) {
17532
+ const existingBead = await getBead(db, projectKeyParam, beadId);
17533
+ if (!existingBead) {
17534
+ throw new Error(`[BeadsAdapter] Bead not found: ${beadId}`);
17535
+ }
17536
+ const event = {
17537
+ type: "bead_deleted",
17538
+ project_key: projectKeyParam,
17539
+ bead_id: beadId,
17540
+ timestamp: Date.now(),
17541
+ reason: options?.reason || null,
17542
+ deleted_by: options?.deleted_by || null
17543
+ };
17544
+ await appendBeadEvent(event, projectPath, db);
17545
+ },
17546
+ async addDependency(projectKeyParam, beadId, dependsOnId, relationship, options, projectPath) {
17547
+ const sourceBead = await getBead(db, projectKeyParam, beadId);
17548
+ if (!sourceBead) {
17549
+ throw new Error(`[BeadsAdapter] Bead not found: ${beadId}`);
17550
+ }
17551
+ const targetBead = await getBead(db, projectKeyParam, dependsOnId);
17552
+ if (!targetBead) {
17553
+ throw new Error(`[BeadsAdapter] Target bead not found: ${dependsOnId}`);
17554
+ }
17555
+ if (beadId === dependsOnId) {
17556
+ throw new Error(`[BeadsAdapter] Bead cannot depend on itself`);
17557
+ }
17558
+ const { wouldCreateCycle: wouldCreateCycle2 } = await Promise.resolve().then(() => exports_dependencies);
17559
+ const hasCycle = await wouldCreateCycle2(db, beadId, dependsOnId);
17560
+ if (hasCycle) {
17561
+ throw new Error(`[BeadsAdapter] Adding dependency would create a cycle`);
17562
+ }
17563
+ const event = {
17564
+ type: "bead_dependency_added",
17565
+ project_key: projectKeyParam,
17566
+ bead_id: beadId,
17567
+ timestamp: Date.now(),
17568
+ dependency: {
17569
+ target: dependsOnId,
17570
+ type: relationship
17571
+ },
17572
+ reason: options?.reason || null,
17573
+ added_by: options?.added_by || null
17574
+ };
17575
+ await appendBeadEvent(event, projectPath, db);
17576
+ const deps = await getDependencies(db, projectKeyParam, beadId);
17577
+ const dep = deps.find((d) => d.depends_on_id === dependsOnId && d.relationship === relationship);
17578
+ if (!dep) {
17579
+ throw new Error(`[BeadsAdapter] Dependency not found after insert`);
17580
+ }
17581
+ return dep;
17582
+ },
17583
+ async removeDependency(projectKeyParam, beadId, dependsOnId, relationship, options, projectPath) {
17584
+ const event = {
17585
+ type: "bead_dependency_removed",
17586
+ project_key: projectKeyParam,
17587
+ bead_id: beadId,
17588
+ timestamp: Date.now(),
17589
+ dependency: {
17590
+ target: dependsOnId,
17591
+ type: relationship
17592
+ },
17593
+ reason: options?.reason || null,
17594
+ removed_by: options?.removed_by || null
17595
+ };
17596
+ await appendBeadEvent(event, projectPath, db);
17597
+ },
17598
+ async getDependencies(projectKeyParam, beadId, projectPath) {
17599
+ return getDependencies(db, projectKeyParam, beadId);
17600
+ },
17601
+ async getDependents(projectKeyParam, beadId, projectPath) {
17602
+ return getDependents(db, projectKeyParam, beadId);
17603
+ },
17604
+ async isBlocked(projectKeyParam, beadId, projectPath) {
17605
+ return isBlocked(db, projectKeyParam, beadId);
17606
+ },
17607
+ async getBlockers(projectKeyParam, beadId, projectPath) {
17608
+ return getBlockers(db, projectKeyParam, beadId);
17609
+ },
17610
+ async addLabel(projectKeyParam, beadId, label, options, projectPath) {
17611
+ const event = {
17612
+ type: "bead_label_added",
17613
+ project_key: projectKeyParam,
17614
+ bead_id: beadId,
17615
+ timestamp: Date.now(),
17616
+ label,
17617
+ added_by: options?.added_by || null
17618
+ };
17619
+ await appendBeadEvent(event, projectPath, db);
17620
+ return {
17621
+ bead_id: beadId,
17622
+ label,
17623
+ created_at: event.timestamp
17624
+ };
17625
+ },
17626
+ async removeLabel(projectKeyParam, beadId, label, options, projectPath) {
17627
+ const event = {
17628
+ type: "bead_label_removed",
17629
+ project_key: projectKeyParam,
17630
+ bead_id: beadId,
17631
+ timestamp: Date.now(),
17632
+ label,
17633
+ removed_by: options?.removed_by || null
17634
+ };
17635
+ await appendBeadEvent(event, projectPath, db);
17636
+ },
17637
+ async getLabels(projectKeyParam, beadId, projectPath) {
17638
+ return getLabels(db, projectKeyParam, beadId);
17639
+ },
17640
+ async getBeadsWithLabel(projectKeyParam, label, projectPath) {
17641
+ return queryBeads(db, projectKeyParam, { labels: [label] });
17642
+ },
17643
+ async addComment(projectKeyParam, beadId, author, body, options, projectPath) {
17644
+ const event = {
17645
+ type: "bead_comment_added",
17646
+ project_key: projectKeyParam,
17647
+ bead_id: beadId,
17648
+ timestamp: Date.now(),
17649
+ author,
17650
+ body,
17651
+ parent_comment_id: options?.parent_id || null,
17652
+ metadata: options?.metadata || null
17653
+ };
17654
+ await appendBeadEvent(event, projectPath, db);
17655
+ const comments = await getComments(db, projectKeyParam, beadId);
17656
+ const comment = comments[comments.length - 1];
17657
+ if (!comment) {
17658
+ throw new Error(`[BeadsAdapter] Comment not found after insert`);
17659
+ }
17660
+ return comment;
17661
+ },
17662
+ async updateComment(projectKeyParam, commentId, newBody, updated_by, projectPath) {
17663
+ const event = {
17664
+ type: "bead_comment_updated",
17665
+ project_key: projectKeyParam,
17666
+ bead_id: "",
17667
+ timestamp: Date.now(),
17668
+ comment_id: commentId,
17669
+ new_body: newBody,
17670
+ updated_by
17671
+ };
17672
+ await appendBeadEvent(event, projectPath, db);
17673
+ return {
17674
+ id: commentId,
17675
+ bead_id: "",
17676
+ author: updated_by,
17677
+ body: newBody,
17678
+ parent_id: null,
17679
+ created_at: Date.now(),
17680
+ updated_at: event.timestamp
17681
+ };
17682
+ },
17683
+ async deleteComment(projectKeyParam, commentId, deleted_by, options, projectPath) {
17684
+ const event = {
17685
+ type: "bead_comment_deleted",
17686
+ project_key: projectKeyParam,
17687
+ bead_id: "",
17688
+ timestamp: Date.now(),
17689
+ comment_id: commentId,
17690
+ deleted_by,
17691
+ reason: options?.reason || null
17692
+ };
17693
+ await appendBeadEvent(event, projectPath, db);
17694
+ },
17695
+ async getComments(projectKeyParam, beadId, projectPath) {
17696
+ return getComments(db, projectKeyParam, beadId);
17697
+ },
17698
+ async addChildToEpic(projectKeyParam, epicId, childId, options, projectPath) {
17699
+ const event = {
17700
+ type: "bead_epic_child_added",
17701
+ project_key: projectKeyParam,
17702
+ bead_id: epicId,
17703
+ timestamp: Date.now(),
17704
+ child_id: childId,
17705
+ child_index: options?.child_index || null,
17706
+ added_by: options?.added_by || null
17707
+ };
17708
+ await appendBeadEvent(event, projectPath, db);
17709
+ },
17710
+ async removeChildFromEpic(projectKeyParam, epicId, childId, options, projectPath) {
17711
+ const event = {
17712
+ type: "bead_epic_child_removed",
17713
+ project_key: projectKeyParam,
17714
+ bead_id: epicId,
17715
+ timestamp: Date.now(),
17716
+ child_id: childId,
17717
+ reason: options?.reason || null,
17718
+ removed_by: options?.removed_by || null
17719
+ };
17720
+ await appendBeadEvent(event, projectPath, db);
17721
+ },
17722
+ async getEpicChildren(projectKeyParam, epicId, projectPath) {
17723
+ return queryBeads(db, projectKeyParam, { parent_id: epicId });
17724
+ },
17725
+ async isEpicClosureEligible(projectKeyParam, epicId, projectPath) {
17726
+ const children = await queryBeads(db, projectKeyParam, { parent_id: epicId });
17727
+ return children.every((child) => child.status === "closed");
17728
+ },
17729
+ async getNextReadyBead(projectKeyParam, projectPath) {
17730
+ return getNextReadyBead(db, projectKeyParam);
17731
+ },
17732
+ async getInProgressBeads(projectKeyParam, projectPath) {
17733
+ return getInProgressBeads(db, projectKeyParam);
17734
+ },
17735
+ async getBlockedBeads(projectKeyParam, projectPath) {
17736
+ return getBlockedBeads(db, projectKeyParam);
17737
+ },
17738
+ async markDirty(projectKeyParam, beadId, projectPath) {
17739
+ await markBeadDirty(db, projectKeyParam, beadId);
17740
+ },
17741
+ async getDirtyBeads(projectKeyParam, projectPath) {
17742
+ return getDirtyBeads(db, projectKeyParam);
17743
+ },
17744
+ async clearDirty(projectKeyParam, beadId, projectPath) {
17745
+ await clearDirtyBead(db, projectKeyParam, beadId);
17746
+ },
17747
+ async runMigrations(projectPath) {
17748
+ const { beadsMigration: beadsMigration2 } = await Promise.resolve().then(() => (init_migrations2(), exports_migrations2));
17749
+ await db.exec(`
17750
+ CREATE TABLE IF NOT EXISTS schema_version (
17751
+ version INTEGER PRIMARY KEY,
17752
+ applied_at BIGINT NOT NULL,
17753
+ description TEXT
17754
+ );
17755
+ `);
17756
+ await db.exec("BEGIN");
17757
+ try {
17758
+ await db.exec(beadsMigration2.up);
17759
+ await db.query(`INSERT INTO schema_version (version, applied_at, description) VALUES ($1, $2, $3)
17760
+ ON CONFLICT (version) DO NOTHING`, [beadsMigration2.version, Date.now(), beadsMigration2.description]);
17761
+ await db.exec("COMMIT");
17762
+ } catch (error45) {
17763
+ await db.exec("ROLLBACK");
17764
+ throw error45;
17765
+ }
17766
+ },
17767
+ async getBeadsStats(projectPath) {
17768
+ const [totalResult, openResult, inProgressResult, blockedResult, closedResult] = await Promise.all([
17769
+ db.query("SELECT COUNT(*) as count FROM beads WHERE project_key = $1", [projectKey]),
17770
+ db.query("SELECT COUNT(*) as count FROM beads WHERE project_key = $1 AND status = 'open'", [projectKey]),
17771
+ db.query("SELECT COUNT(*) as count FROM beads WHERE project_key = $1 AND status = 'in_progress'", [projectKey]),
17772
+ db.query("SELECT COUNT(*) as count FROM beads WHERE project_key = $1 AND status = 'blocked'", [projectKey]),
17773
+ db.query("SELECT COUNT(*) as count FROM beads WHERE project_key = $1 AND status = 'closed'", [projectKey])
17774
+ ]);
17775
+ const byTypeResult = await db.query("SELECT type, COUNT(*) as count FROM beads WHERE project_key = $1 GROUP BY type", [projectKey]);
17776
+ const by_type = {};
17777
+ for (const row of byTypeResult.rows) {
17778
+ by_type[row.type] = parseInt(row.count);
17779
+ }
17780
+ return {
17781
+ total_beads: parseInt(totalResult.rows[0]?.count || "0"),
17782
+ open: parseInt(openResult.rows[0]?.count || "0"),
17783
+ in_progress: parseInt(inProgressResult.rows[0]?.count || "0"),
17784
+ blocked: parseInt(blockedResult.rows[0]?.count || "0"),
17785
+ closed: parseInt(closedResult.rows[0]?.count || "0"),
17786
+ by_type
17787
+ };
17788
+ },
17789
+ async rebuildBlockedCache(projectKeyParam, projectPath) {
17790
+ const { rebuildAllBlockedCaches: rebuildAllBlockedCaches2 } = await Promise.resolve().then(() => exports_dependencies);
17791
+ await rebuildAllBlockedCaches2(db, projectKeyParam);
17792
+ },
17793
+ async getDatabase(projectPath) {
17794
+ return db;
17795
+ },
17796
+ async close(projectPath) {
17797
+ if (db.close) {
17798
+ await db.close();
17799
+ }
17800
+ },
17801
+ async closeAll() {
17802
+ if (db.close) {
17803
+ await db.close();
17804
+ }
17805
+ }
17806
+ };
17807
+ }
17808
+ function generateBeadId(projectKey) {
17809
+ const hash2 = projectKey.split("").reduce((acc, char) => (acc << 5) - acc + char.charCodeAt(0) | 0, 0).toString(36).slice(0, 6);
17810
+ const timestamp = Date.now().toString(36);
17811
+ const random = Math.random().toString(36).slice(2, 5);
17812
+ return `bd-${hash2}-${timestamp}${random}`;
17813
+ }
17814
+
17815
+ // src/beads/index.ts
17816
+ init_migrations2();
17817
+
17818
+ // src/beads/labels.ts
17819
+ async function getBeadsByLabel(db, projectKey, label) {
17820
+ const result = await db.query(`SELECT b.* FROM beads b
17821
+ JOIN bead_labels bl ON b.id = bl.bead_id
17822
+ WHERE b.project_key = $1 AND bl.label = $2 AND b.deleted_at IS NULL
17823
+ ORDER BY b.priority DESC, b.created_at ASC`, [projectKey, label]);
17824
+ return result.rows;
17825
+ }
17826
+ async function getAllLabels(db, projectKey) {
17827
+ const result = await db.query(`SELECT DISTINCT bl.label FROM bead_labels bl
17828
+ JOIN beads b ON bl.bead_id = b.id
17829
+ WHERE b.project_key = $1 AND b.deleted_at IS NULL
17830
+ ORDER BY bl.label`, [projectKey]);
17831
+ return result.rows.map((r) => r.label);
17832
+ }
17833
+ // src/beads/comments.ts
17834
+ async function getCommentById(db, commentId) {
17835
+ const result = await db.query(`SELECT * FROM bead_comments WHERE id = $1`, [commentId]);
17836
+ return result.rows[0] ?? null;
17837
+ }
17838
+ async function getCommentThread(db, rootCommentId) {
17839
+ const result = await db.query(`WITH RECURSIVE thread AS (
17840
+ -- Root comment
17841
+ SELECT * FROM bead_comments WHERE id = $1
17842
+
17843
+ UNION
17844
+
17845
+ -- Replies
17846
+ SELECT c.* FROM bead_comments c
17847
+ JOIN thread t ON c.parent_id = t.id
17848
+ )
17849
+ SELECT * FROM thread ORDER BY created_at ASC`, [rootCommentId]);
17850
+ return result.rows;
17851
+ }
17852
+ // src/beads/jsonl.ts
17853
+ import { createHash } from "node:crypto";
17854
+ function serializeToJSONL(bead) {
17855
+ return JSON.stringify(bead);
17856
+ }
17857
+ function parseJSONL(jsonl) {
17858
+ if (!jsonl || jsonl.trim() === "") {
17859
+ return [];
17860
+ }
17861
+ const lines = jsonl.split(`
17862
+ `);
17863
+ const beads = [];
17864
+ for (const line of lines) {
17865
+ const trimmed = line.trim();
17866
+ if (trimmed === "") {
17867
+ continue;
17868
+ }
17869
+ try {
17870
+ const bead = JSON.parse(trimmed);
17871
+ beads.push(bead);
17872
+ } catch (err) {
17873
+ throw new Error(`Invalid JSON in JSONL: ${err instanceof Error ? err.message : String(err)}`);
17874
+ }
17875
+ }
17876
+ return beads;
17877
+ }
17878
+ function computeContentHash(bead) {
17879
+ const canonical = JSON.stringify(bead, Object.keys(bead).sort());
17880
+ return createHash("sha256").update(canonical).digest("hex");
17881
+ }
17882
+ async function exportToJSONL(adapter, projectKey, options = {}) {
17883
+ const db = await adapter.getDatabase();
17884
+ const conditions = ["project_key = $1"];
17885
+ const params = [projectKey];
17886
+ let paramIndex = 2;
17887
+ if (!options.includeDeleted) {
17888
+ conditions.push("deleted_at IS NULL");
17889
+ }
17890
+ if (options.beadIds && options.beadIds.length > 0) {
17891
+ conditions.push(`id = ANY($${paramIndex++})`);
17892
+ params.push(options.beadIds);
17893
+ }
17894
+ const query = `
17895
+ SELECT * FROM beads
17896
+ WHERE ${conditions.join(" AND ")}
17897
+ ORDER BY id ASC
17898
+ `;
17899
+ const result = await db.query(query, params);
17900
+ const beads = result.rows;
17901
+ if (beads.length === 0) {
17902
+ return "";
17903
+ }
17904
+ const lines = [];
17905
+ for (const bead of beads) {
17906
+ const deps = await getDependencies(db, projectKey, bead.id);
17907
+ const dependencies = deps.map((d) => ({
17908
+ depends_on_id: d.depends_on_id,
17909
+ type: d.relationship
17910
+ }));
17911
+ const labels = await getLabels(db, projectKey, bead.id);
17912
+ const comments = await getComments(db, projectKey, bead.id);
17913
+ const commentExports = comments.map((c) => ({
17914
+ author: c.author,
17915
+ text: c.body
17916
+ }));
17917
+ const beadExport = {
17918
+ id: bead.id,
17919
+ title: bead.title,
17920
+ description: bead.description || undefined,
17921
+ status: bead.deleted_at ? "tombstone" : bead.status,
17922
+ priority: bead.priority,
17923
+ issue_type: bead.type,
17924
+ created_at: new Date(bead.created_at).toISOString(),
17925
+ updated_at: new Date(bead.updated_at).toISOString(),
17926
+ closed_at: bead.closed_at ? new Date(bead.closed_at).toISOString() : undefined,
17927
+ assignee: bead.assignee || undefined,
17928
+ parent_id: bead.parent_id || undefined,
17929
+ dependencies,
17930
+ labels,
17931
+ comments: commentExports
17932
+ };
17933
+ lines.push(serializeToJSONL(beadExport));
17934
+ }
17935
+ return lines.join(`
17936
+ `);
17937
+ }
17938
+ async function exportDirtyBeads(adapter, projectKey) {
17939
+ const db = await adapter.getDatabase();
17940
+ const dirtyIds = await getDirtyBeads(db, projectKey);
17941
+ if (dirtyIds.length === 0) {
17942
+ return { jsonl: "", beadIds: [] };
17943
+ }
17944
+ const jsonl = await exportToJSONL(adapter, projectKey, {
17945
+ beadIds: dirtyIds
17946
+ });
17947
+ return { jsonl, beadIds: dirtyIds };
17948
+ }
17949
+ async function importFromJSONL(adapter, projectKey, jsonl, options = {}) {
17950
+ const beads = parseJSONL(jsonl);
17951
+ const result = {
17952
+ created: 0,
17953
+ updated: 0,
17954
+ skipped: 0,
17955
+ errors: []
17956
+ };
17957
+ for (const beadExport of beads) {
17958
+ try {
17959
+ await importSingleBead(adapter, projectKey, beadExport, options, result);
17960
+ } catch (err) {
17961
+ result.errors.push({
17962
+ beadId: beadExport.id,
17963
+ error: err instanceof Error ? err.message : String(err)
17964
+ });
17965
+ }
17966
+ }
17967
+ return result;
17968
+ }
17969
+ async function importSingleBead(adapter, projectKey, beadExport, options, result) {
17970
+ const existing = await adapter.getBead(projectKey, beadExport.id);
17971
+ if (existing && options.skipExisting) {
17972
+ result.skipped++;
17973
+ return;
17974
+ }
17975
+ if (existing) {
17976
+ const existingHash = await computeBeadHash(adapter, projectKey, existing.id);
17977
+ const importHash = computeContentHash(beadExport);
17978
+ if (existingHash === importHash) {
17979
+ result.skipped++;
17980
+ return;
17981
+ }
17982
+ }
17983
+ if (options.dryRun) {
17984
+ if (existing) {
17985
+ result.updated++;
17986
+ } else {
17987
+ result.created++;
17988
+ }
17989
+ return;
17990
+ }
17991
+ if (!existing) {
17992
+ const db = await adapter.getDatabase();
17993
+ await db.query(`INSERT INTO beads (
17994
+ id, project_key, type, status, title, description, priority,
17995
+ parent_id, assignee, created_at, updated_at
17996
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`, [
17997
+ beadExport.id,
17998
+ projectKey,
17999
+ beadExport.issue_type,
18000
+ beadExport.status === "tombstone" ? "closed" : beadExport.status,
18001
+ beadExport.title,
18002
+ beadExport.description || null,
18003
+ beadExport.priority,
18004
+ beadExport.parent_id || null,
18005
+ beadExport.assignee || null,
18006
+ new Date(beadExport.created_at).getTime(),
18007
+ new Date(beadExport.updated_at).getTime()
18008
+ ]);
18009
+ if (beadExport.status === "closed" && beadExport.closed_at) {
18010
+ await db.query("UPDATE beads SET closed_at = $1 WHERE id = $2", [new Date(beadExport.closed_at).getTime(), beadExport.id]);
18011
+ }
18012
+ if (beadExport.status === "tombstone") {
18013
+ await db.query("UPDATE beads SET deleted_at = $1 WHERE id = $2", [Date.now(), beadExport.id]);
18014
+ }
18015
+ result.created++;
18016
+ } else {
18017
+ await adapter.updateBead(projectKey, beadExport.id, {
18018
+ title: beadExport.title,
18019
+ description: beadExport.description,
18020
+ priority: beadExport.priority,
18021
+ assignee: beadExport.assignee
18022
+ });
18023
+ if (existing.status !== beadExport.status) {
18024
+ if (beadExport.status === "closed") {
18025
+ await adapter.closeBead(projectKey, beadExport.id, "imported");
18026
+ } else if (beadExport.status === "in_progress") {
18027
+ const db = await adapter.getDatabase();
18028
+ await db.query("UPDATE beads SET status = $1, updated_at = $2 WHERE id = $3", ["in_progress", Date.now(), beadExport.id]);
18029
+ }
18030
+ }
18031
+ result.updated++;
18032
+ }
18033
+ await importDependencies(adapter, projectKey, beadExport);
18034
+ await importLabels(adapter, projectKey, beadExport);
18035
+ await importComments(adapter, projectKey, beadExport);
18036
+ }
18037
+ async function computeBeadHash(adapter, projectKey, beadId) {
18038
+ const db = await adapter.getDatabase();
18039
+ const beadResult = await db.query("SELECT * FROM beads WHERE project_key = $1 AND id = $2", [projectKey, beadId]);
18040
+ const bead = beadResult.rows[0];
18041
+ if (!bead) {
18042
+ throw new Error(`Bead not found: ${beadId}`);
18043
+ }
18044
+ const deps = await getDependencies(db, projectKey, beadId);
18045
+ const dependencies = deps.map((d) => ({
18046
+ depends_on_id: d.depends_on_id,
18047
+ type: d.relationship
18048
+ }));
18049
+ const labels = await getLabels(db, projectKey, beadId);
18050
+ const comments = await getComments(db, projectKey, beadId);
18051
+ const commentExports = comments.map((c) => ({
18052
+ author: c.author,
18053
+ text: c.body
18054
+ }));
18055
+ const beadExport = {
18056
+ id: bead.id,
18057
+ title: bead.title,
18058
+ description: bead.description || undefined,
18059
+ status: bead.deleted_at ? "tombstone" : bead.status,
18060
+ priority: bead.priority,
18061
+ issue_type: bead.type,
18062
+ created_at: new Date(bead.created_at).toISOString(),
18063
+ updated_at: new Date(bead.updated_at).toISOString(),
18064
+ closed_at: bead.closed_at ? new Date(bead.closed_at).toISOString() : undefined,
18065
+ assignee: bead.assignee || undefined,
18066
+ parent_id: bead.parent_id || undefined,
18067
+ dependencies,
18068
+ labels,
18069
+ comments: commentExports
18070
+ };
18071
+ return computeContentHash(beadExport);
18072
+ }
18073
+ async function importDependencies(adapter, projectKey, beadExport) {
18074
+ const db = await adapter.getDatabase();
18075
+ await db.query("DELETE FROM bead_dependencies WHERE bead_id = $1", [
18076
+ beadExport.id
18077
+ ]);
18078
+ for (const dep of beadExport.dependencies) {
18079
+ await adapter.addDependency(projectKey, beadExport.id, dep.depends_on_id, dep.type);
18080
+ }
18081
+ }
18082
+ async function importLabels(adapter, projectKey, beadExport) {
18083
+ const db = await adapter.getDatabase();
18084
+ await db.query("DELETE FROM bead_labels WHERE bead_id = $1", [
18085
+ beadExport.id
18086
+ ]);
18087
+ for (const label of beadExport.labels) {
18088
+ await adapter.addLabel(projectKey, beadExport.id, label);
18089
+ }
18090
+ }
18091
+ async function importComments(adapter, projectKey, beadExport) {
18092
+ const db = await adapter.getDatabase();
18093
+ await db.query("DELETE FROM bead_comments WHERE bead_id = $1", [
18094
+ beadExport.id
18095
+ ]);
18096
+ for (const comment of beadExport.comments) {
18097
+ await adapter.addComment(projectKey, beadExport.id, comment.author, comment.text);
18098
+ }
18099
+ }
18100
+ // src/beads/flush-manager.ts
18101
+ import { writeFile } from "node:fs/promises";
18102
+ class FlushManager {
18103
+ adapter;
18104
+ projectKey;
18105
+ outputPath;
18106
+ debounceMs;
18107
+ onFlush;
18108
+ timer = null;
18109
+ flushing = false;
18110
+ constructor(options) {
18111
+ this.adapter = options.adapter;
18112
+ this.projectKey = options.projectKey;
18113
+ this.outputPath = options.outputPath;
18114
+ this.debounceMs = options.debounceMs ?? 30000;
18115
+ this.onFlush = options.onFlush;
18116
+ }
18117
+ scheduleFlush() {
18118
+ if (this.timer) {
18119
+ clearTimeout(this.timer);
18120
+ }
18121
+ this.timer = setTimeout(() => {
18122
+ this.flush().catch((err) => {
18123
+ console.error("[FlushManager] Flush error:", err);
18124
+ });
18125
+ }, this.debounceMs);
18126
+ }
18127
+ async flush() {
18128
+ const startTime = Date.now();
18129
+ if (this.flushing) {
18130
+ return {
18131
+ beadsExported: 0,
18132
+ bytesWritten: 0,
18133
+ duration: 0
18134
+ };
18135
+ }
18136
+ this.flushing = true;
18137
+ try {
18138
+ if (this.timer) {
18139
+ clearTimeout(this.timer);
18140
+ this.timer = null;
18141
+ }
18142
+ const { jsonl, beadIds } = await exportDirtyBeads(this.adapter, this.projectKey);
18143
+ if (beadIds.length === 0) {
18144
+ return {
18145
+ beadsExported: 0,
18146
+ bytesWritten: 0,
18147
+ duration: Date.now() - startTime
18148
+ };
18149
+ }
18150
+ await writeFile(this.outputPath, jsonl, "utf-8");
18151
+ const bytesWritten = Buffer.byteLength(jsonl, "utf-8");
18152
+ const db = await this.adapter.getDatabase();
18153
+ for (const beadId of beadIds) {
18154
+ await clearDirtyBead(db, this.projectKey, beadId);
18155
+ }
18156
+ const result = {
18157
+ beadsExported: beadIds.length,
18158
+ bytesWritten,
18159
+ duration: Date.now() - startTime
18160
+ };
18161
+ if (this.onFlush) {
18162
+ this.onFlush(result);
18163
+ }
18164
+ return result;
18165
+ } finally {
18166
+ this.flushing = false;
18167
+ }
18168
+ }
18169
+ stop() {
18170
+ if (this.timer) {
18171
+ clearTimeout(this.timer);
18172
+ this.timer = null;
18173
+ }
18174
+ }
18175
+ }
18176
+ // src/beads/merge.ts
18177
+ var DEFAULT_TOMBSTONE_TTL_MS = 30 * 24 * 60 * 60 * 1000;
18178
+ var MIN_TOMBSTONE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
18179
+ var CLOCK_SKEW_GRACE_MS = 60 * 60 * 1000;
18180
+ var STATUS_TOMBSTONE = "tombstone";
18181
+ function makeKey(bead) {
18182
+ const key = {
18183
+ id: bead.id,
18184
+ createdAt: bead.created_at,
18185
+ createdBy: undefined
18186
+ };
18187
+ return JSON.stringify(key);
18188
+ }
18189
+ function isTombstone(bead) {
18190
+ return bead.status === STATUS_TOMBSTONE;
18191
+ }
18192
+ function isExpiredTombstone(bead, ttlMs = DEFAULT_TOMBSTONE_TTL_MS) {
18193
+ if (!isTombstone(bead)) {
18194
+ return false;
18195
+ }
18196
+ if (!bead.closed_at) {
18197
+ return false;
18198
+ }
18199
+ const deletedAt = new Date(bead.closed_at).getTime();
18200
+ if (Number.isNaN(deletedAt)) {
18201
+ return false;
18202
+ }
18203
+ const effectiveTtl = ttlMs + CLOCK_SKEW_GRACE_MS;
18204
+ const expirationTime = deletedAt + effectiveTtl;
18205
+ return Date.now() > expirationTime;
18206
+ }
18207
+ function isTimeAfter(t1, t2) {
18208
+ if (!t1)
18209
+ return false;
18210
+ if (!t2)
18211
+ return true;
18212
+ const time1 = new Date(t1).getTime();
18213
+ const time22 = new Date(t2).getTime();
18214
+ const err1 = Number.isNaN(time1);
18215
+ const err2 = Number.isNaN(time22);
18216
+ if (err1 && err2)
18217
+ return true;
18218
+ if (err1)
18219
+ return false;
18220
+ if (err2)
18221
+ return true;
18222
+ return time1 >= time22;
18223
+ }
18224
+ function maxTime(t1, t2) {
18225
+ if (!t1 && !t2)
18226
+ return;
18227
+ if (!t1)
18228
+ return t2;
18229
+ if (!t2)
18230
+ return t1;
18231
+ const time1 = new Date(t1).getTime();
18232
+ const time22 = new Date(t2).getTime();
18233
+ if (Number.isNaN(time1) && Number.isNaN(time22))
18234
+ return t2;
18235
+ if (Number.isNaN(time1))
18236
+ return t2;
18237
+ if (Number.isNaN(time22))
18238
+ return t1;
18239
+ return time1 > time22 ? t1 : t2;
18240
+ }
18241
+ function mergeField(base, left, right) {
18242
+ if (base === left && base !== right)
18243
+ return right;
18244
+ if (base === right && base !== left)
18245
+ return left;
18246
+ return left;
18247
+ }
18248
+ function mergeFieldByUpdatedAt(base, left, right, leftUpdatedAt, rightUpdatedAt) {
18249
+ if (base === left && base !== right)
18250
+ return right;
18251
+ if (base === right && base !== left)
18252
+ return left;
18253
+ if (left === right)
18254
+ return left;
18255
+ return isTimeAfter(leftUpdatedAt, rightUpdatedAt) ? left : right;
18256
+ }
18257
+ function mergeStatus(base, left, right) {
18258
+ if (left === STATUS_TOMBSTONE || right === STATUS_TOMBSTONE) {
18259
+ return STATUS_TOMBSTONE;
18260
+ }
18261
+ if (left === "closed" || right === "closed") {
18262
+ return "closed";
18263
+ }
18264
+ return mergeField(base ?? "open", left ?? "open", right ?? "open");
18265
+ }
18266
+ function mergePriority(base, left, right) {
18267
+ if (base === left && base !== right)
18268
+ return right;
18269
+ if (base === right && base !== left)
18270
+ return left;
18271
+ if (left === right)
18272
+ return left;
18273
+ if (left === 0 && right !== 0)
18274
+ return right;
18275
+ if (right === 0 && left !== 0)
18276
+ return left;
18277
+ return left < right ? left : right;
18278
+ }
18279
+ function mergeDependencies(left, right) {
18280
+ const seen = new Set;
18281
+ const result = [];
18282
+ for (const dep of left) {
18283
+ const key = `${dep.depends_on_id}:${dep.type}`;
18284
+ if (!seen.has(key)) {
18285
+ seen.add(key);
18286
+ result.push(dep);
18287
+ }
18288
+ }
18289
+ for (const dep of right) {
18290
+ const key = `${dep.depends_on_id}:${dep.type}`;
18291
+ if (!seen.has(key)) {
18292
+ seen.add(key);
18293
+ result.push(dep);
18294
+ }
18295
+ }
18296
+ return result;
18297
+ }
18298
+ function mergeLabels(left, right) {
18299
+ const seen = new Set;
18300
+ const result = [];
18301
+ for (const label of left) {
18302
+ if (!seen.has(label)) {
18303
+ seen.add(label);
18304
+ result.push(label);
18305
+ }
18306
+ }
18307
+ for (const label of right) {
18308
+ if (!seen.has(label)) {
18309
+ seen.add(label);
18310
+ result.push(label);
18311
+ }
18312
+ }
18313
+ return result;
18314
+ }
18315
+ function mergeComments(left, right) {
18316
+ const seen = new Set;
18317
+ const result = [];
18318
+ for (const comment of left) {
18319
+ const key = `${comment.author}:${comment.text}`;
18320
+ if (!seen.has(key)) {
18321
+ seen.add(key);
18322
+ result.push(comment);
18323
+ }
18324
+ }
18325
+ for (const comment of right) {
18326
+ const key = `${comment.author}:${comment.text}`;
18327
+ if (!seen.has(key)) {
18328
+ seen.add(key);
18329
+ result.push(comment);
18330
+ }
18331
+ }
18332
+ return result;
18333
+ }
18334
+ function mergeTombstones(left, right) {
18335
+ if (!left.closed_at && !right.closed_at)
18336
+ return left;
18337
+ if (!left.closed_at)
18338
+ return right;
18339
+ if (!right.closed_at)
18340
+ return left;
18341
+ return isTimeAfter(left.closed_at, right.closed_at) ? left : right;
18342
+ }
18343
+ function mergeIssue(base, left, right) {
18344
+ const merged = {
18345
+ id: base.id,
18346
+ title: "",
18347
+ status: "open",
18348
+ priority: 0,
18349
+ issue_type: base.issue_type,
18350
+ created_at: base.created_at,
18351
+ updated_at: "",
18352
+ dependencies: [],
18353
+ labels: [],
18354
+ comments: []
18355
+ };
18356
+ merged.title = mergeFieldByUpdatedAt(base.title, left.title, right.title, left.updated_at, right.updated_at) ?? left.title;
18357
+ merged.description = mergeFieldByUpdatedAt(base.description, left.description, right.description, left.updated_at, right.updated_at);
18358
+ merged.status = mergeStatus(base.status, left.status, right.status);
18359
+ merged.priority = mergePriority(base.priority, left.priority, right.priority);
18360
+ merged.issue_type = mergeField(base.issue_type, left.issue_type, right.issue_type);
18361
+ merged.updated_at = maxTime(left.updated_at, right.updated_at) ?? left.updated_at;
18362
+ if (merged.status === "closed") {
18363
+ merged.closed_at = maxTime(left.closed_at, right.closed_at);
18364
+ }
18365
+ merged.assignee = mergeField(base.assignee, left.assignee, right.assignee);
18366
+ merged.parent_id = mergeField(base.parent_id, left.parent_id, right.parent_id);
18367
+ merged.dependencies = mergeDependencies(left.dependencies, right.dependencies);
18368
+ merged.labels = mergeLabels(left.labels, right.labels);
18369
+ merged.comments = mergeComments(left.comments, right.comments);
18370
+ return { merged, conflict: "" };
18371
+ }
18372
+ function merge3Way(base, left, right, options = {}) {
18373
+ const ttl = options.tombstoneTtlMs ?? DEFAULT_TOMBSTONE_TTL_MS;
18374
+ const debug2 = options.debug ?? false;
18375
+ const baseMap = new Map;
18376
+ for (const bead of base) {
18377
+ baseMap.set(makeKey(bead), bead);
18378
+ }
18379
+ const leftMap = new Map;
18380
+ for (const bead of left) {
18381
+ leftMap.set(makeKey(bead), bead);
18382
+ }
18383
+ const rightMap = new Map;
18384
+ for (const bead of right) {
18385
+ rightMap.set(makeKey(bead), bead);
18386
+ }
18387
+ const allKeys = new Set;
18388
+ for (const key of baseMap.keys())
18389
+ allKeys.add(key);
18390
+ for (const key of leftMap.keys())
18391
+ allKeys.add(key);
18392
+ for (const key of rightMap.keys())
18393
+ allKeys.add(key);
18394
+ const result = [];
18395
+ const conflicts = [];
18396
+ for (const key of allKeys) {
18397
+ const baseBead = baseMap.get(key);
18398
+ const leftBead = leftMap.get(key);
18399
+ const rightBead = rightMap.get(key);
18400
+ const leftTombstone = leftBead !== undefined && isTombstone(leftBead);
18401
+ const rightTombstone = rightBead !== undefined && isTombstone(rightBead);
18402
+ if (baseBead && leftBead && rightBead) {
18403
+ if (leftTombstone && rightTombstone) {
18404
+ result.push(mergeTombstones(leftBead, rightBead));
18405
+ continue;
18406
+ }
18407
+ if (leftTombstone && !rightTombstone) {
18408
+ if (isExpiredTombstone(leftBead, ttl)) {
18409
+ result.push(rightBead);
18410
+ } else {
18411
+ result.push(leftBead);
18412
+ }
18413
+ continue;
18414
+ }
18415
+ if (rightTombstone && !leftTombstone) {
18416
+ if (isExpiredTombstone(rightBead, ttl)) {
18417
+ result.push(leftBead);
18418
+ } else {
18419
+ result.push(rightBead);
18420
+ }
18421
+ continue;
18422
+ }
18423
+ const { merged, conflict } = mergeIssue(baseBead, leftBead, rightBead);
18424
+ if (conflict) {
18425
+ conflicts.push(conflict);
18426
+ } else {
18427
+ result.push(merged);
18428
+ }
18429
+ } else if (!baseBead && leftBead && rightBead) {
18430
+ if (leftTombstone && rightTombstone) {
18431
+ result.push(mergeTombstones(leftBead, rightBead));
18432
+ continue;
18433
+ }
18434
+ if (leftTombstone && !rightTombstone) {
18435
+ if (isExpiredTombstone(leftBead, ttl)) {
18436
+ result.push(rightBead);
18437
+ } else {
18438
+ result.push(leftBead);
18439
+ }
18440
+ continue;
18441
+ }
18442
+ if (rightTombstone && !leftTombstone) {
18443
+ if (isExpiredTombstone(rightBead, ttl)) {
18444
+ result.push(leftBead);
18445
+ } else {
18446
+ result.push(rightBead);
18447
+ }
18448
+ continue;
18449
+ }
18450
+ const emptyBase = {
18451
+ id: leftBead.id,
18452
+ title: "",
18453
+ status: "open",
18454
+ priority: 0,
18455
+ issue_type: leftBead.issue_type,
18456
+ created_at: leftBead.created_at,
18457
+ updated_at: leftBead.created_at,
18458
+ dependencies: [],
18459
+ labels: [],
18460
+ comments: []
18461
+ };
18462
+ const { merged } = mergeIssue(emptyBase, leftBead, rightBead);
18463
+ result.push(merged);
18464
+ } else if (baseBead && leftBead && !rightBead) {
18465
+ if (leftTombstone) {
18466
+ result.push(leftBead);
18467
+ }
18468
+ } else if (baseBead && !leftBead && rightBead) {
18469
+ if (rightTombstone) {
18470
+ result.push(rightBead);
18471
+ }
18472
+ } else if (!baseBead && leftBead && !rightBead) {
18473
+ result.push(leftBead);
18474
+ } else if (!baseBead && !leftBead && rightBead) {
18475
+ result.push(rightBead);
18476
+ }
18477
+ }
18478
+ if (debug2) {}
18479
+ return { merged: result, conflicts };
18480
+ }
18481
+ function mergeJsonl(baseJsonl, leftJsonl, rightJsonl, options = {}) {
18482
+ const parseJSONL2 = (jsonl2) => {
18483
+ if (!jsonl2 || jsonl2.trim() === "")
18484
+ return [];
18485
+ return jsonl2.split(`
18486
+ `).filter((line) => line.trim() !== "").map((line) => JSON.parse(line));
18487
+ };
18488
+ const base = parseJSONL2(baseJsonl);
18489
+ const left = parseJSONL2(leftJsonl);
18490
+ const right = parseJSONL2(rightJsonl);
18491
+ const { merged, conflicts } = merge3Way(base, left, right, options);
18492
+ const jsonl = merged.map((bead) => JSON.stringify(bead)).join(`
18493
+ `);
18494
+ return { jsonl, conflicts };
18495
+ }
18496
+ // src/index.ts
16625
18497
  var SWARM_MAIL_VERSION = "0.1.0";
16626
18498
  export {
18499
+ wouldCreateCycle,
16627
18500
  withTiming,
16628
18501
  withTimeout,
18502
+ updateProjections,
18503
+ serializeToJSONL,
16629
18504
  sendSwarmMessage,
16630
18505
  sendMessage,
16631
18506
  sendAgentMessage,
@@ -16637,38 +18512,69 @@ export {
16637
18512
  reserveAgentFiles,
16638
18513
  replayEventsBatched2 as replayEventsBatched,
16639
18514
  replayEvents,
18515
+ replayBeadEvents,
16640
18516
  releaseSwarmFiles,
16641
18517
  releaseAgentFiles,
16642
18518
  registerAgent,
18519
+ rebuildBeadBlockedCache,
18520
+ rebuildAllBlockedCaches,
16643
18521
  readSwarmMessage,
16644
18522
  readEvents,
18523
+ readBeadEvents,
16645
18524
  readAgentMessage,
18525
+ queryBeads,
18526
+ parseJSONL,
16646
18527
  migrations,
18528
+ mergeJsonl,
18529
+ merge3Way,
18530
+ markBeadDirty,
18531
+ isTombstone,
16647
18532
  isMigrationApplied,
18533
+ isExpiredTombstone,
16648
18534
  isEventType,
16649
18535
  isDatabaseHealthy,
18536
+ isBlocked,
18537
+ invalidateBlockedCache,
16650
18538
  inspectState,
16651
18539
  initSwarmAgent,
16652
18540
  initAgent,
18541
+ importFromJSONL,
16653
18542
  getThreadMessages,
16654
18543
  getSwarmMail,
16655
18544
  getSwarmInbox,
16656
18545
  getPendingMigrations,
18546
+ getOpenBlockers,
18547
+ getNextReadyBead,
16657
18548
  getMessage,
16658
18549
  getLatestSequence,
18550
+ getLabels,
16659
18551
  getInbox,
18552
+ getInProgressBeads,
16660
18553
  getEventTimeline,
16661
18554
  getEvalStats,
16662
18555
  getEvalRecords,
18556
+ getDirtyBeads,
18557
+ getDependents,
18558
+ getDependencies,
16663
18559
  getDatabaseStats,
16664
18560
  getDatabasePath2 as getDatabasePath,
16665
18561
  getDatabase,
16666
18562
  getCurrentVersion,
18563
+ getComments,
18564
+ getCommentThread,
18565
+ getCommentById,
18566
+ getBlockers,
18567
+ getBlockedBeads,
18568
+ getBeadsByLabel,
18569
+ getBead,
16667
18570
  getAppliedMigrations,
18571
+ getAllLabels,
16668
18572
  getAgents,
16669
18573
  getAgentInbox,
16670
18574
  getAgent,
16671
18575
  getActiveReservations,
18576
+ exportToJSONL,
18577
+ exportDirtyBeads,
16672
18578
  debugReservations,
16673
18579
  debugMessage,
16674
18580
  debugEvents,
@@ -16676,15 +18582,22 @@ export {
16676
18582
  createSwarmMailAdapter,
16677
18583
  createInMemorySwarmMail,
16678
18584
  createEvent,
18585
+ createBeadsAdapter,
18586
+ computeContentHash,
16679
18587
  closeSwarmMail,
16680
18588
  closeDatabase,
16681
18589
  closeAllSwarmMail,
16682
18590
  closeAllDatabases,
18591
+ clearDirtyBead,
18592
+ clearAllDirtyBeads,
16683
18593
  checkSwarmHealth,
16684
18594
  checkHealth,
16685
18595
  checkConflicts,
18596
+ beadsMigrations,
18597
+ beadsMigration,
16686
18598
  appendEvents,
16687
18599
  appendEvent,
18600
+ appendBeadEvent,
16688
18601
  acknowledgeSwarmMessage,
16689
18602
  acknowledgeMessage,
16690
18603
  TaskStartedEventSchema,
@@ -16695,14 +18608,19 @@ export {
16695
18608
  SwarmCheckpointedEventSchema,
16696
18609
  SubtaskOutcomeEventSchema,
16697
18610
  SWARM_MAIL_VERSION,
18611
+ STATUS_TOMBSTONE,
16698
18612
  PGlite2 as PGlite,
16699
18613
  MessageSentEventSchema,
16700
18614
  MessageReadEventSchema,
16701
18615
  MessageAckedEventSchema,
18616
+ MIN_TOMBSTONE_TTL_MS,
16702
18617
  HumanFeedbackEventSchema,
18618
+ FlushManager,
16703
18619
  FileReservedEventSchema,
16704
18620
  FileReleasedEventSchema,
16705
18621
  DecompositionGeneratedEventSchema,
18622
+ DEFAULT_TOMBSTONE_TTL_MS,
18623
+ CLOCK_SKEW_GRACE_MS,
16706
18624
  BaseEventSchema,
16707
18625
  AgentRegisteredEventSchema,
16708
18626
  AgentEventSchema,