trellis 3.1.30 → 3.1.33

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 (53) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +1 -1
  3. package/dist/cli/index.d.ts +1 -0
  4. package/dist/cli/index.d.ts.map +1 -1
  5. package/dist/cli/index.js +320 -24
  6. package/dist/cli/lane.d.ts +6 -0
  7. package/dist/cli/lane.d.ts.map +1 -0
  8. package/dist/cms/client.d.ts +1 -1
  9. package/dist/cms/client.d.ts.map +1 -1
  10. package/dist/cms/index.js +0 -16
  11. package/dist/cms/types.d.ts +1 -1
  12. package/dist/cms/types.d.ts.map +1 -1
  13. package/dist/context/manager.d.ts +1 -1
  14. package/dist/context/manager.d.ts.map +1 -1
  15. package/dist/context/types.d.ts +1 -1
  16. package/dist/context/types.d.ts.map +1 -1
  17. package/dist/decisions/index.js +2 -2
  18. package/dist/engine.d.ts +86 -2
  19. package/dist/engine.d.ts.map +1 -1
  20. package/dist/{index-65z0xfjw.js → index-4cdr7x2x.js} +2 -2
  21. package/dist/{index-hy73j9z8.js → index-nq520y6k.js} +7 -3
  22. package/dist/{index-v9b4hqa7.js → index-nzb3am4f.js} +4 -1
  23. package/dist/{index-ncckgtrk.js → index-rv1p47gp.js} +1000 -114
  24. package/dist/{index-4gknc19b.js → index-w5wccer3.js} +1 -1
  25. package/dist/{index-a2a394zz.js → index-xhcp6xrn.js} +503 -41
  26. package/dist/index.js +39 -7
  27. package/dist/mcp/server.d.ts.map +1 -1
  28. package/dist/plugins/agent-memory/graph-context-manager.d.ts +5 -0
  29. package/dist/plugins/agent-memory/graph-context-manager.d.ts.map +1 -1
  30. package/dist/{remote-manager-n71bmcy1.js → remote-manager-xvbg4230.js} +4 -4
  31. package/dist/vcs/branch.d.ts +6 -0
  32. package/dist/vcs/branch.d.ts.map +1 -1
  33. package/dist/vcs/decompose.d.ts.map +1 -1
  34. package/dist/vcs/engine-context.d.ts +9 -2
  35. package/dist/vcs/engine-context.d.ts.map +1 -1
  36. package/dist/vcs/index.d.ts +2 -0
  37. package/dist/vcs/index.d.ts.map +1 -1
  38. package/dist/vcs/index.js +36 -4
  39. package/dist/vcs/issue.d.ts +2 -0
  40. package/dist/vcs/issue.d.ts.map +1 -1
  41. package/dist/vcs/lane-materialize.d.ts +45 -0
  42. package/dist/vcs/lane-materialize.d.ts.map +1 -0
  43. package/dist/vcs/lane-promote.d.ts +56 -0
  44. package/dist/vcs/lane-promote.d.ts.map +1 -0
  45. package/dist/vcs/lane.d.ts +55 -0
  46. package/dist/vcs/lane.d.ts.map +1 -0
  47. package/dist/vcs/op-log.d.ts +29 -0
  48. package/dist/vcs/op-log.d.ts.map +1 -0
  49. package/dist/vcs/types.d.ts +15 -1
  50. package/dist/vcs/types.d.ts.map +1 -1
  51. package/dist/watcher/ingestion.d.ts +1 -0
  52. package/dist/watcher/ingestion.d.ts.map +1 -1
  53. package/package.json +1 -1
@@ -1,6 +1,8 @@
1
1
  // @bun
2
2
  import {
3
3
  BlobStore,
4
+ JsonOpLog,
5
+ LaneOpLog,
4
6
  addCriterion,
5
7
  assignIssue,
6
8
  blockIssue,
@@ -10,12 +12,14 @@ import {
10
12
  createBranch,
11
13
  createCheckpoint,
12
14
  createIssue,
15
+ createLaneMeta,
13
16
  createMilestone,
14
17
  decompose,
15
18
  deleteBranch,
16
19
  diffFileStates,
17
20
  diffOpRange,
18
21
  getActiveIssues,
22
+ getBranchHeadOpHash,
19
23
  getIssue,
20
24
  init_blob_store,
21
25
  init_branch,
@@ -23,39 +27,50 @@ import {
23
27
  init_decompose,
24
28
  init_diff,
25
29
  init_issue,
30
+ init_lane,
26
31
  init_merge,
27
32
  init_milestone,
33
+ init_op_log,
34
+ laneDir,
28
35
  listBranches,
29
36
  listCheckpoints,
30
37
  listIssues,
38
+ listLaneMetas,
31
39
  listMilestones,
32
40
  loadBranchState,
41
+ loadLaneMeta,
33
42
  pauseIssue,
34
43
  reopenIssue,
44
+ resolveLaneHeadFromJournal,
35
45
  resumeIssue,
36
46
  runCriteria,
37
47
  saveBranchState,
48
+ saveLaneMeta,
38
49
  setCriterionStatus,
50
+ shouldAdvanceBranchHead,
39
51
  startIssue,
40
52
  switchBranch,
41
53
  threeWayMerge,
54
+ threeWayTextMerge,
42
55
  triageIssue,
43
56
  unblockIssue,
44
- updateIssue
45
- } from "./index-a2a394zz.js";
57
+ updateIssue,
58
+ updateLaneHead
59
+ } from "./index-xhcp6xrn.js";
46
60
  import {
47
61
  getDecision,
48
62
  getDecisionChain,
49
63
  init_decisions,
50
64
  queryDecisions,
51
65
  recordDecision
52
- } from "./index-65z0xfjw.js";
66
+ } from "./index-4cdr7x2x.js";
53
67
  import {
54
68
  DEFAULT_CONFIG,
55
69
  createVcsOp,
56
70
  init_ops,
57
- init_types
58
- } from "./index-v9b4hqa7.js";
71
+ init_types,
72
+ isVcsOpKind
73
+ } from "./index-nzb3am4f.js";
59
74
  import {
60
75
  EAVStore,
61
76
  init_eav_store
@@ -308,6 +323,9 @@ class Ingestion {
308
323
  getLastOpHash() {
309
324
  return this.lastOpHash;
310
325
  }
326
+ setLastOpHash(hash) {
327
+ this.lastOpHash = hash;
328
+ }
311
329
  }
312
330
  var EXT_LANGUAGE;
313
331
  var init_ingestion = __esm(() => {
@@ -5333,109 +5351,443 @@ var init_semantic = __esm(() => {
5333
5351
  init_semantic_merge();
5334
5352
  });
5335
5353
 
5336
- // src/engine.ts
5337
- import {
5338
- existsSync as existsSync4,
5339
- mkdirSync as mkdirSync3,
5340
- readFileSync as readFileSync4,
5341
- writeFileSync as writeFileSync3,
5342
- copyFileSync
5343
- } from "fs";
5344
- import { readFile as readFile2 } from "fs/promises";
5345
- import { join as join5, dirname } from "path";
5346
-
5347
- class JsonOpLog {
5348
- ops = [];
5349
- filePath;
5350
- constructor(filePath) {
5351
- this.filePath = filePath;
5354
+ // src/vcs/lane-promote.ts
5355
+ function replayOpIntoStore(store, op) {
5356
+ const d = decompose(op);
5357
+ if (d.deleteFacts.length > 0)
5358
+ store.deleteFacts(d.deleteFacts);
5359
+ if (d.deleteLinks.length > 0)
5360
+ store.deleteLinks(d.deleteLinks);
5361
+ if (d.addFacts.length > 0)
5362
+ store.addFacts(d.addFacts);
5363
+ if (d.addLinks.length > 0)
5364
+ store.addLinks(d.addLinks);
5365
+ }
5366
+ function resolveBranchHeadFromOps(ops, branchName) {
5367
+ for (let i = ops.length - 1;i >= 0; i--) {
5368
+ const op = ops[i];
5369
+ if (op.kind === "vcs:branchAdvance" && op.vcs?.branchName === branchName && op.vcs.targetOpHash) {
5370
+ return op.vcs.targetOpHash;
5371
+ }
5372
+ }
5373
+ return ops[ops.length - 1]?.hash;
5374
+ }
5375
+ function buildStoreUpTo(ops, atOpHash) {
5376
+ const store = new EAVStore;
5377
+ for (const op of ops) {
5378
+ replayOpIntoStore(store, op);
5379
+ if (atOpHash && op.hash === atOpHash)
5380
+ break;
5352
5381
  }
5353
- load() {
5354
- if (existsSync4(this.filePath)) {
5355
- const raw = readFileSync4(this.filePath, "utf-8");
5356
- try {
5357
- this.ops = JSON.parse(raw);
5358
- } catch (err) {
5359
- const backupPath = this.filePath + ".bak";
5360
- if (existsSync4(backupPath)) {
5361
- const backupRaw = readFileSync4(backupPath, "utf-8");
5362
- this.ops = JSON.parse(backupRaw);
5363
- writeFileSync3(this.filePath, backupRaw);
5364
- } else {
5365
- throw new Error(`Corrupted ops.json and no backup found. Run \`trellis repair\` to attempt recovery.`);
5366
- }
5382
+ return store;
5383
+ }
5384
+ function getFactValue(store, entity, attribute) {
5385
+ const facts = store.getFactsByEntity(entity).filter((f) => f.a === attribute);
5386
+ return facts[facts.length - 1]?.v;
5387
+ }
5388
+ function entityAttributes(store, entity) {
5389
+ const map = new Map;
5390
+ for (const fact of store.getFactsByEntity(entity)) {
5391
+ map.set(fact.a, fact.v);
5392
+ }
5393
+ return map;
5394
+ }
5395
+ function collectTouchedAttributes(op) {
5396
+ const d = decompose(op);
5397
+ const map = new Map;
5398
+ const touch = (fact) => {
5399
+ if (!map.has(fact.e))
5400
+ map.set(fact.e, new Set);
5401
+ map.get(fact.e).add(fact.a);
5402
+ };
5403
+ for (const f of d.addFacts)
5404
+ touch(f);
5405
+ for (const f of d.deleteFacts)
5406
+ touch(f);
5407
+ return map;
5408
+ }
5409
+ function collectTouchedEntities(op) {
5410
+ const d = decompose(op);
5411
+ const entities = new Set;
5412
+ for (const f of [...d.addFacts, ...d.deleteFacts])
5413
+ entities.add(f.e);
5414
+ for (const l of [...d.addLinks, ...d.deleteLinks]) {
5415
+ entities.add(l.e1);
5416
+ entities.add(l.e2);
5417
+ }
5418
+ return entities;
5419
+ }
5420
+ function atomsEqual(a, b) {
5421
+ if (a === b)
5422
+ return true;
5423
+ if (a === undefined || b === undefined)
5424
+ return false;
5425
+ return String(a) === String(b);
5426
+ }
5427
+ function buildLaneFileState(laneOps, throughIndex) {
5428
+ const slice = laneOps.slice(0, throughIndex + 1);
5429
+ return buildFileStateAtOp(slice);
5430
+ }
5431
+ function filePathsFromOp(op) {
5432
+ const paths = [];
5433
+ if (op.vcs?.filePath)
5434
+ paths.push(op.vcs.filePath);
5435
+ if (op.vcs?.oldFilePath)
5436
+ paths.push(op.vcs.oldFilePath);
5437
+ return paths;
5438
+ }
5439
+ function detectEntityConflicts(op, baseStore, headStore) {
5440
+ const conflicts = [];
5441
+ const d = decompose(op);
5442
+ const touchedEntities = collectTouchedEntities(op);
5443
+ const laneAttrsByEntity = collectTouchedAttributes(op);
5444
+ for (const fact of d.addFacts) {
5445
+ const headValue = getFactValue(headStore, fact.e, fact.a);
5446
+ const baseValue = getFactValue(baseStore, fact.e, fact.a);
5447
+ if (atomsEqual(headValue, fact.v))
5448
+ continue;
5449
+ if (!atomsEqual(headValue, baseValue) && headValue !== undefined) {
5450
+ conflicts.push({
5451
+ class: "hard",
5452
+ laneOpHash: op.hash,
5453
+ entityId: fact.e,
5454
+ attribute: fact.a,
5455
+ integrationValue: headValue,
5456
+ laneValue: fact.v,
5457
+ suggestion: "manual",
5458
+ message: `Integration changed ${fact.e}.${fact.a} since fork`
5459
+ });
5460
+ }
5461
+ }
5462
+ for (const entityId of touchedEntities) {
5463
+ const laneAttrs = laneAttrsByEntity.get(entityId) ?? new Set;
5464
+ if (laneAttrs.size === 0)
5465
+ continue;
5466
+ const baseAttrs = entityAttributes(baseStore, entityId);
5467
+ const headAttrs = entityAttributes(headStore, entityId);
5468
+ for (const [attribute, headValue] of headAttrs) {
5469
+ if (laneAttrs.has(attribute))
5470
+ continue;
5471
+ const baseValue = baseAttrs.get(attribute);
5472
+ if (!atomsEqual(headValue, baseValue)) {
5473
+ conflicts.push({
5474
+ class: "soft",
5475
+ laneOpHash: op.hash,
5476
+ entityId,
5477
+ attribute,
5478
+ integrationValue: headValue,
5479
+ suggestion: "manual",
5480
+ message: `Integration updated ${entityId}.${attribute} while lane touched the same entity`
5481
+ });
5367
5482
  }
5368
5483
  }
5369
5484
  }
5370
- append(op) {
5371
- this.ops.push(op);
5372
- this.flush();
5485
+ return conflicts;
5486
+ }
5487
+ function blobText(blobStore, hash) {
5488
+ if (!hash || !blobStore)
5489
+ return;
5490
+ const content = blobStore.get(hash);
5491
+ if (!content)
5492
+ return;
5493
+ const text = content.toString("utf-8");
5494
+ return text.endsWith(`
5495
+ `) ? text.slice(0, -1) : text;
5496
+ }
5497
+ function mergeLaneFile(path, base, ours, theirs, blobStore) {
5498
+ const b = base.get(path);
5499
+ const o = ours.get(path);
5500
+ const t = theirs.get(path);
5501
+ const baseHash = b && !b.deleted ? b.contentHash : undefined;
5502
+ const oursHash = o && !o.deleted ? o.contentHash : undefined;
5503
+ const theirsHash = t && !t.deleted ? t.contentHash : undefined;
5504
+ if (oursHash === theirsHash)
5505
+ return { clean: true };
5506
+ if (theirsHash === baseHash && oursHash !== baseHash)
5507
+ return { clean: true };
5508
+ if (oursHash === baseHash && theirsHash !== baseHash) {
5509
+ const theirsContent2 = blobText(blobStore, theirsHash);
5510
+ return { clean: true, merged: theirsContent2 };
5511
+ }
5512
+ const baseContent = blobText(blobStore, baseHash) ?? "";
5513
+ const oursContent = blobText(blobStore, oursHash);
5514
+ const theirsContent = blobText(blobStore, theirsHash);
5515
+ if (oursContent === undefined || theirsContent === undefined) {
5516
+ return { clean: false, conflictKind: "modify-modify" };
5517
+ }
5518
+ const textResult = threeWayTextMerge(baseContent, oursContent, theirsContent);
5519
+ if (textResult.clean) {
5520
+ return { clean: true, merged: textResult.merged };
5521
+ }
5522
+ return { clean: false, conflictKind: "modify-modify" };
5523
+ }
5524
+ function detectFileConflict(op, integrationOps, laneOps, laneOpIndex, baseOpHash, snapshotHead, blobStore) {
5525
+ const paths = filePathsFromOp(op);
5526
+ if (paths.length === 0)
5527
+ return {};
5528
+ const base = buildFileStateAtOp(integrationOps, baseOpHash);
5529
+ const ours = buildFileStateAtOp(integrationOps, snapshotHead);
5530
+ const theirs = buildLaneFileState(laneOps, laneOpIndex);
5531
+ for (const path of paths) {
5532
+ const baseState = base.get(path);
5533
+ const headState = ours.get(path);
5534
+ const laneState = theirs.get(path);
5535
+ const baseHash = baseState && !baseState.deleted ? baseState.contentHash : undefined;
5536
+ const headHash = headState && !headState.deleted ? headState.contentHash : undefined;
5537
+ const laneHash = laneState && !laneState.deleted ? laneState.contentHash : undefined;
5538
+ if (headHash === laneHash)
5539
+ continue;
5540
+ if (headHash === baseHash && laneHash !== baseHash)
5541
+ continue;
5542
+ if (laneHash === baseHash && headHash !== baseHash)
5543
+ continue;
5544
+ const mergeResult = mergeLaneFile(path, base, ours, theirs, blobStore);
5545
+ if (mergeResult.clean) {
5546
+ if (mergeResult.merged !== undefined) {
5547
+ return { mergedContent: mergeResult.merged };
5548
+ }
5549
+ continue;
5550
+ }
5551
+ return {
5552
+ conflict: {
5553
+ class: "file",
5554
+ laneOpHash: op.hash,
5555
+ filePath: path,
5556
+ suggestion: "manual",
5557
+ message: `File conflict on ${path} (${mergeResult.conflictKind ?? "modify-modify"})`
5558
+ }
5559
+ };
5373
5560
  }
5374
- readAll() {
5375
- return [...this.ops];
5561
+ return {};
5562
+ }
5563
+ function isBlockingConflict(conflict) {
5564
+ return conflict.class === "soft" || conflict.class === "hard" || conflict.class === "file";
5565
+ }
5566
+ async function planLanePromote(params) {
5567
+ const {
5568
+ laneId,
5569
+ meta,
5570
+ targetBranch,
5571
+ snapshotHead,
5572
+ integrationOps,
5573
+ laneOps,
5574
+ parentLaneOps,
5575
+ blobStore
5576
+ } = params;
5577
+ const baseStore = buildStoreUpTo(integrationOps, meta.baseOpHash);
5578
+ const headStore = buildStoreUpTo(integrationOps, snapshotHead);
5579
+ const fileLaneOps = meta.forkKind === "child" && parentLaneOps?.length ? [...parentLaneOps, ...laneOps] : laneOps;
5580
+ const childOpOffset = parentLaneOps?.length ?? 0;
5581
+ const opsToReplay = [];
5582
+ const conflicts = [];
5583
+ let safeOpCount = 0;
5584
+ for (let i = 0;i < laneOps.length; i++) {
5585
+ const op = laneOps[i];
5586
+ if (SKIP_PROMOTE_KINDS.has(op.kind))
5587
+ continue;
5588
+ if (FILE_OP_KINDS2.has(op.kind)) {
5589
+ const fileResult = detectFileConflict(op, integrationOps, fileLaneOps, childOpOffset + i, meta.baseOpHash, snapshotHead, blobStore);
5590
+ if (fileResult.conflict) {
5591
+ conflicts.push(fileResult.conflict);
5592
+ continue;
5593
+ }
5594
+ opsToReplay.push({
5595
+ sourceOp: op,
5596
+ mergedContent: fileResult.mergedContent
5597
+ });
5598
+ safeOpCount++;
5599
+ continue;
5600
+ }
5601
+ const decomposed = decompose(op);
5602
+ if (decomposed.addFacts.length > 0 && decomposed.deleteFacts.length === 0 && decomposed.addFacts.every((fact) => atomsEqual(getFactValue(headStore, fact.e, fact.a), fact.v))) {
5603
+ continue;
5604
+ }
5605
+ const entityConflicts = detectEntityConflicts(op, baseStore, headStore);
5606
+ const blockingEntity = entityConflicts.filter(isBlockingConflict);
5607
+ conflicts.push(...entityConflicts);
5608
+ if (blockingEntity.length > 0)
5609
+ continue;
5610
+ opsToReplay.push({ sourceOp: op });
5611
+ safeOpCount++;
5612
+ replayOpIntoStore(headStore, op);
5376
5613
  }
5377
- getLastOp() {
5378
- return this.ops.length > 0 ? this.ops[this.ops.length - 1] : undefined;
5614
+ const blockingConflicts = conflicts.filter(isBlockingConflict);
5615
+ return {
5616
+ laneId,
5617
+ targetBranch,
5618
+ snapshotHead,
5619
+ baseOpHash: meta.baseOpHash,
5620
+ opsToReplay,
5621
+ conflicts,
5622
+ blockingConflicts,
5623
+ safeOpCount,
5624
+ canPromote: blockingConflicts.length === 0 && opsToReplay.length > 0
5625
+ };
5626
+ }
5627
+ async function rechainOpForIntegration(op, previousHash) {
5628
+ if (!isVcsOpKindSafe(op.kind)) {
5629
+ throw new Error(`Cannot rechain op kind '${op.kind}' for integration replay`);
5379
5630
  }
5380
- count() {
5381
- return this.ops.length;
5631
+ return createVcsOp(op.kind, {
5632
+ agentId: op.agentId,
5633
+ previousHash,
5634
+ vcs: { ...op.vcs }
5635
+ });
5636
+ }
5637
+ function isVcsOpKindSafe(kind) {
5638
+ return kind.startsWith("vcs:");
5639
+ }
5640
+ function formatPromoteExplain(plan) {
5641
+ const lines = [
5642
+ `Lane promote plan: ${plan.laneId}`,
5643
+ ` Target branch: ${plan.targetBranch}`,
5644
+ ` Fork base: ${plan.baseOpHash}`,
5645
+ ` Snapshot head: ${plan.snapshotHead}`,
5646
+ ` Ops to replay: ${plan.opsToReplay.length}`,
5647
+ ` Safe ops: ${plan.safeOpCount}`
5648
+ ];
5649
+ if (plan.blockingConflicts.length === 0) {
5650
+ lines.push("", plan.canPromote ? "\u2713 Ready to promote" : "No ops to replay");
5651
+ return lines.join(`
5652
+ `);
5382
5653
  }
5383
- flush() {
5384
- const dir = dirname(this.filePath);
5385
- if (!existsSync4(dir))
5386
- mkdirSync3(dir, { recursive: true });
5387
- if (existsSync4(this.filePath)) {
5388
- const backupPath = this.filePath + ".bak";
5389
- try {
5390
- copyFileSync(this.filePath, backupPath);
5391
- } catch {}
5654
+ lines.push("", `Blocking conflicts (${plan.blockingConflicts.length}):`);
5655
+ for (const c of plan.blockingConflicts) {
5656
+ const where = c.filePath ?? (c.entityId && c.attribute ? `${c.entityId}.${c.attribute}` : c.entityId);
5657
+ lines.push(` [${c.class}] ${where ?? c.laneOpHash.slice(0, 24)}`);
5658
+ if (c.message)
5659
+ lines.push(` ${c.message}`);
5660
+ if (c.integrationValue !== undefined) {
5661
+ lines.push(` integration: ${String(c.integrationValue)}`);
5392
5662
  }
5393
- writeFileSync3(this.filePath, JSON.stringify(this.ops, null, 2));
5394
- }
5395
- static repair(filePath) {
5396
- if (!existsSync4(filePath)) {
5397
- return { recovered: 0, lost: 0 };
5663
+ if (c.laneValue !== undefined) {
5664
+ lines.push(` lane: ${String(c.laneValue)}`);
5398
5665
  }
5399
- const raw = readFileSync4(filePath, "utf-8");
5400
- try {
5401
- const ops = JSON.parse(raw);
5402
- return { recovered: ops.length, lost: 0 };
5403
- } catch {}
5404
- const lastHash = raw.lastIndexOf('"hash": "trellis:op:');
5405
- if (lastHash === -1) {
5406
- const bakPath = filePath + ".bak";
5407
- if (existsSync4(bakPath)) {
5408
- const bakRaw = readFileSync4(bakPath, "utf-8");
5409
- try {
5410
- const ops = JSON.parse(bakRaw);
5411
- writeFileSync3(filePath, bakRaw);
5412
- return { recovered: ops.length, lost: 0 };
5413
- } catch {}
5666
+ }
5667
+ return lines.join(`
5668
+ `);
5669
+ }
5670
+ var SKIP_PROMOTE_KINDS, FILE_OP_KINDS2;
5671
+ var init_lane_promote = __esm(() => {
5672
+ init_eav_store();
5673
+ init_decompose();
5674
+ init_diff();
5675
+ init_merge();
5676
+ init_ops();
5677
+ SKIP_PROMOTE_KINDS = new Set([
5678
+ "vcs:branchAdvance",
5679
+ "vcs:laneCreate",
5680
+ "vcs:laneDrop",
5681
+ "vcs:lanePromoteStart",
5682
+ "vcs:lanePromoteComplete",
5683
+ "vcs:lanePromoteAbort"
5684
+ ]);
5685
+ FILE_OP_KINDS2 = new Set([
5686
+ "vcs:fileAdd",
5687
+ "vcs:fileModify",
5688
+ "vcs:fileDelete",
5689
+ "vcs:fileRename"
5690
+ ]);
5691
+ });
5692
+
5693
+ // src/vcs/lane-materialize.ts
5694
+ function emptyMaterializationStats() {
5695
+ return {
5696
+ integrationOpsReplayed: 0,
5697
+ laneOpsReplayed: 0,
5698
+ integrationCacheHit: false
5699
+ };
5700
+ }
5701
+ function replayOpIntoStore2(store, op) {
5702
+ const d = decompose(op);
5703
+ if (d.deleteFacts.length > 0)
5704
+ store.deleteFacts(d.deleteFacts);
5705
+ if (d.deleteLinks.length > 0)
5706
+ store.deleteLinks(d.deleteLinks);
5707
+ if (d.addFacts.length > 0)
5708
+ store.addFacts(d.addFacts);
5709
+ if (d.addLinks.length > 0)
5710
+ store.addLinks(d.addLinks);
5711
+ }
5712
+ function cloneStore(source) {
5713
+ const clone = new EAVStore;
5714
+ clone.restore(source.snapshot());
5715
+ return clone;
5716
+ }
5717
+ function materializeIntegrationOps(ops, cache, tailHash) {
5718
+ if (cache && cache.tailHash === tailHash) {
5719
+ return {
5720
+ store: cache.store,
5721
+ cache,
5722
+ stats: {
5723
+ integrationOpsReplayed: 0,
5724
+ laneOpsReplayed: 0,
5725
+ integrationCacheHit: true,
5726
+ integrationTailHash: tailHash
5414
5727
  }
5415
- writeFileSync3(filePath, "[]");
5416
- return { recovered: 0, lost: -1 };
5417
- }
5418
- const endOfLine = raw.indexOf(`
5419
- `, lastHash);
5420
- const closingBrace = raw.indexOf(" }", endOfLine);
5421
- if (closingBrace === -1) {
5422
- writeFileSync3(filePath, "[]");
5423
- return { recovered: 0, lost: -1 };
5424
- }
5425
- const fixed = raw.slice(0, closingBrace + 3) + `
5426
- ]`;
5427
- try {
5428
- const ops = JSON.parse(fixed);
5429
- writeFileSync3(filePath + ".corrupted", raw);
5430
- writeFileSync3(filePath, fixed);
5431
- return { recovered: ops.length, lost: 0 };
5432
- } catch {
5433
- writeFileSync3(filePath + ".corrupted", raw);
5434
- writeFileSync3(filePath, "[]");
5435
- return { recovered: 0, lost: -1 };
5728
+ };
5729
+ }
5730
+ const store = new EAVStore;
5731
+ for (const op of ops) {
5732
+ replayOpIntoStore2(store, op);
5733
+ }
5734
+ return {
5735
+ store,
5736
+ cache: { tailHash, store },
5737
+ stats: {
5738
+ integrationOpsReplayed: ops.length,
5739
+ laneOpsReplayed: 0,
5740
+ integrationCacheHit: false,
5741
+ integrationTailHash: tailHash
5436
5742
  }
5743
+ };
5744
+ }
5745
+ function overlayLaneOps(integrationStore, laneOps) {
5746
+ const store = cloneStore(integrationStore);
5747
+ for (const op of laneOps) {
5748
+ replayOpIntoStore2(store, op);
5437
5749
  }
5750
+ return { store, laneOpsReplayed: laneOps.length };
5438
5751
  }
5752
+ function materializeChildForkEntry(integrationOps, baseOpHash, parentLaneOps, childLaneOps) {
5753
+ const store = new EAVStore;
5754
+ let integrationReplayed = 0;
5755
+ for (const op of integrationOps) {
5756
+ replayOpIntoStore2(store, op);
5757
+ integrationReplayed++;
5758
+ if (op.hash === baseOpHash)
5759
+ break;
5760
+ }
5761
+ for (const op of parentLaneOps) {
5762
+ replayOpIntoStore2(store, op);
5763
+ }
5764
+ for (const op of childLaneOps) {
5765
+ replayOpIntoStore2(store, op);
5766
+ }
5767
+ return {
5768
+ store,
5769
+ stats: {
5770
+ integrationOpsReplayed: integrationReplayed,
5771
+ laneOpsReplayed: parentLaneOps.length + childLaneOps.length,
5772
+ integrationCacheHit: false,
5773
+ integrationTailHash: integrationOps[integrationOps.length - 1]?.hash
5774
+ }
5775
+ };
5776
+ }
5777
+ var init_lane_materialize = __esm(() => {
5778
+ init_eav_store();
5779
+ init_decompose();
5780
+ });
5781
+
5782
+ // src/engine.ts
5783
+ import {
5784
+ existsSync as existsSync4,
5785
+ mkdirSync as mkdirSync3,
5786
+ readFileSync as readFileSync4,
5787
+ writeFileSync as writeFileSync3
5788
+ } from "fs";
5789
+ import { readFile as readFile2 } from "fs/promises";
5790
+ import { join as join5 } from "path";
5439
5791
  function parseIgnoreFile(filePath) {
5440
5792
  if (!existsSync4(filePath))
5441
5793
  return [];
@@ -5489,6 +5841,10 @@ class TrellisVcsEngine {
5489
5841
  checkpointThreshold = 100;
5490
5842
  _pendingAutoCheckpoint = false;
5491
5843
  _blobStore = null;
5844
+ activeLaneId;
5845
+ activeLaneLog = null;
5846
+ integrationCache = null;
5847
+ materializationStats = emptyMaterializationStats();
5492
5848
  constructor(opts) {
5493
5849
  const gitignorePatterns = readIgnorePatterns(opts.rootPath);
5494
5850
  const mergedIgnore = [
@@ -5533,7 +5889,7 @@ class TrellisVcsEngine {
5533
5889
  branchName: this.config.defaultBranch
5534
5890
  }
5535
5891
  });
5536
- this.applyOp(branchOp);
5892
+ await this.applyOp(branchOp);
5537
5893
  const scanner = new FileWatcher({
5538
5894
  rootPath: this.config.rootPath,
5539
5895
  ignorePatterns: [...this.config.ignorePatterns, ".trellis"],
@@ -5577,7 +5933,7 @@ class TrellisVcsEngine {
5577
5933
  size: event.size
5578
5934
  }
5579
5935
  });
5580
- this.applyOp(op);
5936
+ await this.applyOp(op);
5581
5937
  opsCreated++;
5582
5938
  const scannedFiles = opsCreated - 1;
5583
5939
  if (scannedFiles % 25 === 0 || scannedFiles === events.length) {
@@ -5626,16 +5982,19 @@ class TrellisVcsEngine {
5626
5982
  this.config.defaultBranch = persisted.defaultBranch;
5627
5983
  }
5628
5984
  this.loadCurrentBranch();
5629
- const ops = this.opLog.readAll();
5630
- for (const op of ops) {
5631
- this.replayOp(op);
5632
- }
5633
- return { opsReplayed: ops.length };
5985
+ const integrationOps = this.opLog.readAll();
5986
+ const laneOps = this.activeLaneLog ? this.activeLaneLog.readAll() : undefined;
5987
+ const activeMeta = this.activeLaneId ? this.getLaneMeta(this.activeLaneId) : undefined;
5988
+ this.refreshMaterializedStore(integrationOps, laneOps, activeMeta);
5989
+ const laneReplayed = this.materializationStats.laneOpsReplayed;
5990
+ return {
5991
+ opsReplayed: this.materializationStats.integrationOpsReplayed + laneReplayed
5992
+ };
5634
5993
  }
5635
5994
  watch() {
5636
5995
  this.ingestion = new Ingestion({
5637
5996
  agentId: this.agentId,
5638
- lastOpHash: this.opLog.getLastOp()?.hash,
5997
+ lastOpHash: this.getActiveJournal().getLastOp()?.hash,
5639
5998
  onOp: (op) => this.applyOp(op)
5640
5999
  });
5641
6000
  this.watcher = new FileWatcher({
@@ -5740,7 +6099,11 @@ class TrellisVcsEngine {
5740
6099
  switchBranch(name) {
5741
6100
  switchBranch(this._ctx(), name);
5742
6101
  this.currentBranch = name;
5743
- saveBranchState(this.config.rootPath, { currentBranch: name });
6102
+ const state = loadBranchState(this.config.rootPath);
6103
+ saveBranchState(this.config.rootPath, {
6104
+ ...state,
6105
+ currentBranch: name
6106
+ });
5744
6107
  }
5745
6108
  listBranches() {
5746
6109
  return listBranches(this._ctx(), this.currentBranch);
@@ -5753,6 +6116,344 @@ class TrellisVcsEngine {
5753
6116
  getCurrentBranch() {
5754
6117
  return this.currentBranch;
5755
6118
  }
6119
+ getBranchHeadOpHash(branchName = this.currentBranch) {
6120
+ return getBranchHeadOpHash(this._ctx(), branchName);
6121
+ }
6122
+ getActiveLaneId() {
6123
+ return this.activeLaneId;
6124
+ }
6125
+ getMaterializationStats() {
6126
+ return { ...this.materializationStats };
6127
+ }
6128
+ listLanes() {
6129
+ return listLaneMetas(this.trellisDir());
6130
+ }
6131
+ getIntegrationOpCount() {
6132
+ return this.opLog.count();
6133
+ }
6134
+ getLaneOpCount(laneId) {
6135
+ const log = new LaneOpLog(laneDir(this.trellisDir(), laneId));
6136
+ log.load();
6137
+ return log.count();
6138
+ }
6139
+ getLaneMeta(laneId) {
6140
+ return loadLaneMeta(this.trellisDir(), laneId);
6141
+ }
6142
+ findLaneForIssue(issueId) {
6143
+ const normalized = issueId.startsWith("issue:") ? issueId : `issue:${issueId}`;
6144
+ return this.listLanes().find((lane) => lane.issueId === normalized && lane.status === "active");
6145
+ }
6146
+ async syncEnvLaneFromEnv() {
6147
+ const laneId = process.env.TRELLIS_LANE_ID?.trim();
6148
+ if (!laneId)
6149
+ return;
6150
+ if (this.activeLaneId === laneId)
6151
+ return;
6152
+ if (this.activeLaneId) {
6153
+ throw new Error(`TRELLIS_LANE_ID=${laneId} conflicts with active lane '${this.activeLaneId}'`);
6154
+ }
6155
+ await this.enterLane(laneId);
6156
+ }
6157
+ summarizeLane(laneId) {
6158
+ const meta = this.getLaneMeta(laneId);
6159
+ if (!meta) {
6160
+ throw new Error(`Lane not found: ${laneId}`);
6161
+ }
6162
+ const log = new LaneOpLog(laneDir(this.trellisDir(), laneId));
6163
+ log.load();
6164
+ const ops = log.readAll();
6165
+ const filePaths = [
6166
+ ...new Set(ops.map((op) => op.vcs?.filePath ?? op.vcs?.oldFilePath).filter((p) => Boolean(p)))
6167
+ ];
6168
+ return {
6169
+ meta,
6170
+ ops,
6171
+ filePaths,
6172
+ integrationHead: this.getBranchHeadOpHash(meta.targetBranch)
6173
+ };
6174
+ }
6175
+ async createLane(opts) {
6176
+ if (this.activeLaneId) {
6177
+ throw new Error(`Cannot create a lane while inside lane '${this.activeLaneId}' \u2014 leave first`);
6178
+ }
6179
+ const baseBranch = opts?.fromBranch ?? this.currentBranch;
6180
+ const baseOpHash = getBranchHeadOpHash(this._ctx(), baseBranch) ?? this.opLog.getLastOp()?.hash;
6181
+ if (!baseOpHash) {
6182
+ throw new Error(`No integration head on branch '${baseBranch}' to fork lane from`);
6183
+ }
6184
+ const meta = createLaneMeta(this.trellisDir(), {
6185
+ baseBranch,
6186
+ baseOpHash,
6187
+ targetBranch: opts?.targetBranch ?? baseBranch,
6188
+ agentId: this.agentId,
6189
+ issueId: opts?.issueId,
6190
+ sessionId: opts?.sessionId,
6191
+ worktreePath: opts?.worktreePath
6192
+ });
6193
+ const op = await createVcsOp("vcs:laneCreate", {
6194
+ agentId: this.agentId,
6195
+ previousHash: this.opLog.getLastOp()?.hash,
6196
+ vcs: {
6197
+ laneId: meta.id,
6198
+ baseBranch: meta.baseBranch,
6199
+ baseOpHash: meta.baseOpHash,
6200
+ targetBranch: meta.targetBranch,
6201
+ issueId: meta.issueId
6202
+ }
6203
+ });
6204
+ await this.applyOp(op);
6205
+ return meta;
6206
+ }
6207
+ async forkLane(parentLaneId, opts) {
6208
+ if (this.activeLaneId) {
6209
+ throw new Error(`Cannot fork a lane while inside lane '${this.activeLaneId}' \u2014 leave first`);
6210
+ }
6211
+ const parent = loadLaneMeta(this.trellisDir(), parentLaneId);
6212
+ if (!parent) {
6213
+ throw new Error(`Lane not found: ${parentLaneId}`);
6214
+ }
6215
+ if (parent.status !== "active") {
6216
+ throw new Error(`Lane '${parentLaneId}' is ${parent.status} \u2014 cannot fork`);
6217
+ }
6218
+ const forkKind = opts?.forkKind ?? "sibling";
6219
+ const forkedAt = new Date().toISOString();
6220
+ const parentLog = new LaneOpLog(laneDir(this.trellisDir(), parentLaneId));
6221
+ parentLog.load();
6222
+ const parentLaneOps = parentLog.readAll();
6223
+ const parentHead = resolveLaneHeadFromJournal(parent, parentLaneOps);
6224
+ if (forkKind === "child") {
6225
+ const meta2 = createLaneMeta(this.trellisDir(), {
6226
+ baseBranch: parent.baseBranch,
6227
+ baseOpHash: parent.baseOpHash,
6228
+ targetBranch: parent.targetBranch,
6229
+ agentId: this.agentId,
6230
+ issueId: opts?.issueId ?? parent.issueId,
6231
+ sessionId: opts?.sessionId,
6232
+ worktreePath: opts?.worktreePath,
6233
+ parentLaneId: parent.id,
6234
+ forkKind: "child",
6235
+ forkedAt,
6236
+ virtualBaseOpHash: parentHead
6237
+ });
6238
+ const op2 = await createVcsOp("vcs:laneCreate", {
6239
+ agentId: this.agentId,
6240
+ previousHash: this.opLog.getLastOp()?.hash,
6241
+ vcs: {
6242
+ laneId: meta2.id,
6243
+ baseBranch: meta2.baseBranch,
6244
+ baseOpHash: meta2.baseOpHash,
6245
+ targetBranch: meta2.targetBranch,
6246
+ issueId: meta2.issueId,
6247
+ sessionId: meta2.sessionId,
6248
+ parentLaneId: parent.id,
6249
+ forkKind: "child",
6250
+ virtualBaseOpHash: parentHead
6251
+ }
6252
+ });
6253
+ await this.applyOp(op2);
6254
+ return meta2;
6255
+ }
6256
+ const meta = createLaneMeta(this.trellisDir(), {
6257
+ baseBranch: parent.baseBranch,
6258
+ baseOpHash: parent.baseOpHash,
6259
+ targetBranch: parent.targetBranch,
6260
+ agentId: this.agentId,
6261
+ issueId: opts?.issueId ?? parent.issueId,
6262
+ sessionId: opts?.sessionId,
6263
+ worktreePath: opts?.worktreePath,
6264
+ parentLaneId: parent.id,
6265
+ forkKind: "sibling",
6266
+ forkedAt
6267
+ });
6268
+ const op = await createVcsOp("vcs:laneCreate", {
6269
+ agentId: this.agentId,
6270
+ previousHash: this.opLog.getLastOp()?.hash,
6271
+ vcs: {
6272
+ laneId: meta.id,
6273
+ baseBranch: meta.baseBranch,
6274
+ baseOpHash: meta.baseOpHash,
6275
+ targetBranch: meta.targetBranch,
6276
+ issueId: meta.issueId,
6277
+ sessionId: meta.sessionId,
6278
+ parentLaneId: parent.id,
6279
+ forkKind: "sibling"
6280
+ }
6281
+ });
6282
+ await this.applyOp(op);
6283
+ return meta;
6284
+ }
6285
+ async enterLane(laneId) {
6286
+ if (this.activeLaneId) {
6287
+ throw new Error(`Already in lane '${this.activeLaneId}' \u2014 leave before entering another`);
6288
+ }
6289
+ const meta = loadLaneMeta(this.trellisDir(), laneId);
6290
+ if (!meta) {
6291
+ throw new Error(`Lane not found: ${laneId}`);
6292
+ }
6293
+ if (meta.status !== "active") {
6294
+ throw new Error(`Lane '${laneId}' is ${meta.status} \u2014 cannot enter`);
6295
+ }
6296
+ this.activeLaneId = laneId;
6297
+ this.activeLaneLog = new LaneOpLog(laneDir(this.trellisDir(), laneId));
6298
+ this.activeLaneLog.load();
6299
+ this.refreshMaterializedStore(this.opLog.readAll(), this.activeLaneLog.readAll(), meta);
6300
+ saveBranchState(this.config.rootPath, {
6301
+ currentBranch: this.currentBranch,
6302
+ activeLaneId: laneId
6303
+ });
6304
+ this.syncIngestionLastOpHash();
6305
+ return meta;
6306
+ }
6307
+ async leaveLane() {
6308
+ if (!this.activeLaneId)
6309
+ return;
6310
+ this.activeLaneId = undefined;
6311
+ this.activeLaneLog = null;
6312
+ saveBranchState(this.config.rootPath, {
6313
+ currentBranch: this.currentBranch
6314
+ });
6315
+ this.restoreIntegrationOnlyStore();
6316
+ this.syncIngestionLastOpHash();
6317
+ }
6318
+ async dropLane(laneId) {
6319
+ if (this.activeLaneId === laneId) {
6320
+ await this.leaveLane();
6321
+ }
6322
+ const meta = loadLaneMeta(this.trellisDir(), laneId);
6323
+ if (!meta) {
6324
+ throw new Error(`Lane not found: ${laneId}`);
6325
+ }
6326
+ if (meta.status === "dropped")
6327
+ return;
6328
+ meta.status = "dropped";
6329
+ meta.updatedAt = new Date().toISOString();
6330
+ saveLaneMeta(this.trellisDir(), meta);
6331
+ const op = await createVcsOp("vcs:laneDrop", {
6332
+ agentId: this.agentId,
6333
+ previousHash: this.opLog.getLastOp()?.hash,
6334
+ vcs: {
6335
+ laneId: meta.id,
6336
+ laneStatus: "dropped"
6337
+ }
6338
+ });
6339
+ await this.applyOp(op);
6340
+ }
6341
+ async promoteLane(laneId, opts) {
6342
+ const meta = this.getLaneMeta(laneId);
6343
+ if (!meta) {
6344
+ throw new Error(`Lane not found: ${laneId}`);
6345
+ }
6346
+ if (meta.status !== "active") {
6347
+ throw new Error(`Lane '${laneId}' is ${meta.status} \u2014 cannot promote`);
6348
+ }
6349
+ if (this.activeLaneId === laneId) {
6350
+ await this.leaveLane();
6351
+ } else if (this.activeLaneId) {
6352
+ throw new Error(`Cannot promote while inside lane '${this.activeLaneId}' \u2014 leave first`);
6353
+ }
6354
+ const targetBranch = opts?.toBranch ?? meta.targetBranch;
6355
+ const integrationOps = this.opLog.readAll();
6356
+ const snapshotHead = resolveBranchHeadFromOps(integrationOps, targetBranch) ?? getBranchHeadOpHash(this._ctx(), targetBranch);
6357
+ if (!snapshotHead) {
6358
+ throw new Error(`No head on branch '${targetBranch}' to promote onto`);
6359
+ }
6360
+ const laneLog = new LaneOpLog(laneDir(this.trellisDir(), laneId));
6361
+ laneLog.load();
6362
+ const laneOps = laneLog.readAll();
6363
+ let parentLaneOps;
6364
+ if (meta.forkKind === "child" && meta.parentLaneId) {
6365
+ parentLaneOps = this.loadLaneJournalOps(meta.parentLaneId);
6366
+ }
6367
+ const plan = await planLanePromote({
6368
+ laneId,
6369
+ meta,
6370
+ targetBranch,
6371
+ snapshotHead,
6372
+ integrationOps: this.opLog.readAll(),
6373
+ laneOps,
6374
+ parentLaneOps,
6375
+ blobStore: this._blobStore
6376
+ });
6377
+ if (opts?.dryRun || !plan.canPromote) {
6378
+ return { ...plan, promoted: false };
6379
+ }
6380
+ meta.status = "promoting";
6381
+ meta.updatedAt = new Date().toISOString();
6382
+ saveLaneMeta(this.trellisDir(), meta);
6383
+ const startOp = await createVcsOp("vcs:lanePromoteStart", {
6384
+ agentId: this.agentId,
6385
+ previousHash: this.opLog.getLastOp()?.hash,
6386
+ vcs: {
6387
+ laneId,
6388
+ targetBranch,
6389
+ baseOpHash: meta.baseOpHash
6390
+ }
6391
+ });
6392
+ await this.applyOp(startOp, { skipBranchAdvance: true });
6393
+ const currentHead = resolveBranchHeadFromOps(this.opLog.readAll(), targetBranch) ?? getBranchHeadOpHash(this._ctx(), targetBranch);
6394
+ if (currentHead !== snapshotHead) {
6395
+ meta.status = "active";
6396
+ meta.updatedAt = new Date().toISOString();
6397
+ saveLaneMeta(this.trellisDir(), meta);
6398
+ const abortOp = await createVcsOp("vcs:lanePromoteAbort", {
6399
+ agentId: this.agentId,
6400
+ previousHash: this.opLog.getLastOp()?.hash,
6401
+ vcs: { laneId }
6402
+ });
6403
+ await this.applyOp(abortOp, { skipBranchAdvance: true });
6404
+ throw new Error(`Integration head moved during promote \u2014 retry after integration settles`);
6405
+ }
6406
+ let previousHash = this.opLog.getLastOp()?.hash;
6407
+ let lastReplayedHash;
6408
+ let opsAppended = 0;
6409
+ for (const action of plan.opsToReplay) {
6410
+ let opToApply;
6411
+ if (action.mergedContent !== undefined && action.sourceOp.vcs?.filePath) {
6412
+ const contentHash = await this._blobStore.put(Buffer.from(action.mergedContent, "utf-8"));
6413
+ opToApply = await createVcsOp("vcs:fileModify", {
6414
+ agentId: action.sourceOp.agentId,
6415
+ previousHash,
6416
+ vcs: {
6417
+ filePath: action.sourceOp.vcs.filePath,
6418
+ contentHash,
6419
+ laneId: action.sourceOp.vcs.laneId ?? laneId
6420
+ }
6421
+ });
6422
+ } else {
6423
+ opToApply = await rechainOpForIntegration(action.sourceOp, previousHash);
6424
+ }
6425
+ await this.applyOp(opToApply, { skipBranchAdvance: true });
6426
+ previousHash = opToApply.hash;
6427
+ lastReplayedHash = opToApply.hash;
6428
+ opsAppended++;
6429
+ }
6430
+ if (lastReplayedHash) {
6431
+ await this.appendBranchAdvance(lastReplayedHash);
6432
+ }
6433
+ const completeOp = await createVcsOp("vcs:lanePromoteComplete", {
6434
+ agentId: this.agentId,
6435
+ previousHash: this.opLog.getLastOp()?.hash,
6436
+ vcs: {
6437
+ laneId,
6438
+ targetBranch,
6439
+ laneStatus: "promoted"
6440
+ }
6441
+ });
6442
+ await this.applyOp(completeOp, { skipBranchAdvance: true });
6443
+ meta.status = "promoted";
6444
+ meta.headOpHash = lastReplayedHash ?? meta.headOpHash;
6445
+ meta.updatedAt = new Date().toISOString();
6446
+ saveLaneMeta(this.trellisDir(), meta);
6447
+ this.invalidateIntegrationCache();
6448
+ this.refreshMaterializedStore(this.opLog.readAll());
6449
+ this.syncIngestionLastOpHash();
6450
+ return {
6451
+ ...plan,
6452
+ promoted: true,
6453
+ integrationOpsAppended: opsAppended + 2,
6454
+ completeOpHash: completeOp.hash
6455
+ };
6456
+ }
5756
6457
  async createMilestone(message, opts) {
5757
6458
  const op = await createMilestone(this._ctx(), message, opts);
5758
6459
  await this.flushAutoCheckpoint();
@@ -5885,7 +6586,10 @@ class TrellisVcsEngine {
5885
6586
  await this.flushAutoCheckpoint();
5886
6587
  return op;
5887
6588
  }
5888
- async startIssue(id) {
6589
+ async startIssue(id, opts) {
6590
+ if (this.activeLaneId) {
6591
+ await this.leaveLane();
6592
+ }
5889
6593
  const issue = getIssue(this._ctx(), id);
5890
6594
  if (!issue)
5891
6595
  throw new Error(`Issue ${id} not found.`);
@@ -5894,16 +6598,27 @@ class TrellisVcsEngine {
5894
6598
  await this.createBranch(branchName);
5895
6599
  const op = await startIssue(this._ctx(), id, branchName);
5896
6600
  this.switchBranch(branchName);
6601
+ if (opts?.lane !== false) {
6602
+ const issueKey = id.startsWith("issue:") ? id : `issue:${id}`;
6603
+ let lane = this.findLaneForIssue(issueKey);
6604
+ if (!lane) {
6605
+ lane = await this.createLane({ issueId: issueKey });
6606
+ }
6607
+ await this.enterLane(lane.id);
6608
+ }
5897
6609
  await this.flushAutoCheckpoint();
5898
6610
  return op;
5899
6611
  }
5900
6612
  async pauseIssue(id, note) {
6613
+ if (this.activeLaneId) {
6614
+ await this.leaveLane();
6615
+ }
5901
6616
  const op = await pauseIssue(this._ctx(), id, note);
5902
6617
  this.switchBranch(this.config.defaultBranch);
5903
6618
  await this.flushAutoCheckpoint();
5904
6619
  return op;
5905
6620
  }
5906
- async resumeIssue(id) {
6621
+ async resumeIssue(id, opts) {
5907
6622
  const issue = getIssue(this._ctx(), id);
5908
6623
  if (!issue)
5909
6624
  throw new Error(`Issue ${id} not found.`);
@@ -5911,10 +6626,20 @@ class TrellisVcsEngine {
5911
6626
  throw new Error(`Issue ${id} has no tracked branch.`);
5912
6627
  const op = await resumeIssue(this._ctx(), id);
5913
6628
  this.switchBranch(issue.branchName);
6629
+ if (opts?.lane !== false) {
6630
+ const issueKey = id.startsWith("issue:") ? id : `issue:${id}`;
6631
+ const lane = this.findLaneForIssue(issueKey);
6632
+ if (lane) {
6633
+ await this.enterLane(lane.id);
6634
+ }
6635
+ }
5914
6636
  await this.flushAutoCheckpoint();
5915
6637
  return op;
5916
6638
  }
5917
6639
  async closeIssue(id, opts) {
6640
+ if (this.activeLaneId) {
6641
+ await this.leaveLane();
6642
+ }
5918
6643
  const result = await closeIssue(this._ctx(), id, opts);
5919
6644
  if (result.op) {
5920
6645
  await this.flushAutoCheckpoint();
@@ -5989,13 +6714,106 @@ class TrellisVcsEngine {
5989
6714
  return {
5990
6715
  store: this.store,
5991
6716
  agentId: this.agentId,
5992
- readAllOps: () => this.opLog.readAll(),
5993
- getLastOp: () => this.opLog.getLastOp(),
5994
- applyOp: (op) => this.applyOp(op)
6717
+ readAllOps: () => this.getActiveJournal().readAll(),
6718
+ getLastOp: () => this.getActiveJournal().getLastOp(),
6719
+ applyOp: (op, opts) => this.applyOp(op, opts)
5995
6720
  };
5996
6721
  }
5997
- applyOp(op) {
5998
- const decomposed = decompose(op);
6722
+ trellisDir() {
6723
+ return join5(this.config.rootPath, ".trellis");
6724
+ }
6725
+ getActiveJournal() {
6726
+ if (this.activeLaneId && this.activeLaneLog) {
6727
+ return this.activeLaneLog;
6728
+ }
6729
+ return this.opLog;
6730
+ }
6731
+ invalidateIntegrationCache() {
6732
+ this.integrationCache = null;
6733
+ }
6734
+ loadLaneJournalOps(laneId) {
6735
+ const log = new LaneOpLog(laneDir(this.trellisDir(), laneId));
6736
+ log.load();
6737
+ return log.readAll();
6738
+ }
6739
+ refreshMaterializedStore(integrationOps, laneOps, meta) {
6740
+ const laneMeta = meta ?? (this.activeLaneId ? this.getLaneMeta(this.activeLaneId) : undefined);
6741
+ if (laneMeta?.forkKind === "child" && laneMeta.parentLaneId) {
6742
+ const parentLaneOps = this.loadLaneJournalOps(laneMeta.parentLaneId);
6743
+ const { store: store2, stats: stats2 } = materializeChildForkEntry(integrationOps, laneMeta.baseOpHash, parentLaneOps, laneOps ?? []);
6744
+ this.store = store2;
6745
+ this.materializationStats = stats2;
6746
+ return;
6747
+ }
6748
+ const tailHash = integrationOps[integrationOps.length - 1]?.hash;
6749
+ const { store, cache, stats } = materializeIntegrationOps(integrationOps, this.integrationCache, tailHash);
6750
+ this.integrationCache = cache;
6751
+ if (laneOps !== undefined) {
6752
+ const overlay = overlayLaneOps(store, laneOps);
6753
+ this.store = overlay.store;
6754
+ this.materializationStats = {
6755
+ ...stats,
6756
+ laneOpsReplayed: overlay.laneOpsReplayed
6757
+ };
6758
+ return;
6759
+ }
6760
+ this.store = store;
6761
+ this.materializationStats = stats;
6762
+ }
6763
+ restoreIntegrationOnlyStore() {
6764
+ const integrationOps = this.opLog.readAll();
6765
+ const tailHash = integrationOps[integrationOps.length - 1]?.hash;
6766
+ const { store, cache, stats } = materializeIntegrationOps(integrationOps, this.integrationCache, tailHash);
6767
+ this.integrationCache = cache;
6768
+ this.store = store;
6769
+ this.materializationStats = {
6770
+ ...stats,
6771
+ laneOpsReplayed: 0
6772
+ };
6773
+ }
6774
+ rebuildStore(ops) {
6775
+ this.store = new EAVStore;
6776
+ for (const op of ops) {
6777
+ this.replayOp(op);
6778
+ }
6779
+ }
6780
+ syncIngestionLastOpHash() {
6781
+ if (this.ingestion) {
6782
+ this.ingestion.setLastOpHash(this.getActiveJournal().getLastOp()?.hash);
6783
+ }
6784
+ }
6785
+ stampLaneId(op) {
6786
+ if (!this.activeLaneId)
6787
+ return;
6788
+ op.vcs = { ...op.vcs, laneId: this.activeLaneId };
6789
+ }
6790
+ requireActiveLaneLog() {
6791
+ if (!this.activeLaneId || !this.activeLaneLog) {
6792
+ throw new Error("No active lane journal");
6793
+ }
6794
+ return this.activeLaneLog;
6795
+ }
6796
+ isIssueIntegrationOp(kind) {
6797
+ return ISSUE_INTEGRATION_KINDS.has(kind);
6798
+ }
6799
+ async applyOp(op, opts) {
6800
+ const inLane = Boolean(this.activeLaneId);
6801
+ const forceIntegration = Boolean(opts?.allowIntegrationWrite) || inLane && this.isIssueIntegrationOp(op.kind);
6802
+ let opToApply = op;
6803
+ if (inLane && forceIntegration) {
6804
+ const intLast = this.opLog.getLastOp();
6805
+ if (intLast?.hash !== op.previousHash && isVcsOpKind(op.kind)) {
6806
+ opToApply = await createVcsOp(op.kind, {
6807
+ agentId: op.agentId,
6808
+ previousHash: intLast?.hash,
6809
+ vcs: op.vcs ?? {}
6810
+ });
6811
+ }
6812
+ }
6813
+ if (inLane && !forceIntegration) {
6814
+ this.stampLaneId(opToApply);
6815
+ }
6816
+ const decomposed = decompose(opToApply);
5999
6817
  if (decomposed.deleteFacts.length > 0) {
6000
6818
  this.store.deleteFacts(decomposed.deleteFacts);
6001
6819
  }
@@ -6008,13 +6826,55 @@ class TrellisVcsEngine {
6008
6826
  if (decomposed.addLinks.length > 0) {
6009
6827
  this.store.addLinks(decomposed.addLinks);
6010
6828
  }
6011
- this.opLog.append(op);
6012
- if (op.kind !== "vcs:checkpointCreate" && this.checkpointThreshold > 0) {
6829
+ if (inLane && !forceIntegration) {
6830
+ const laneLog = this.requireActiveLaneLog();
6831
+ laneLog.append(opToApply);
6832
+ updateLaneHead(this.trellisDir(), this.activeLaneId, opToApply.hash);
6833
+ if (opToApply.kind !== "vcs:checkpointCreate" && this.checkpointThreshold > 0) {
6834
+ this.checkpointOpCount++;
6835
+ if (this.checkpointOpCount >= this.checkpointThreshold) {
6836
+ this._pendingAutoCheckpoint = true;
6837
+ }
6838
+ }
6839
+ return;
6840
+ }
6841
+ this.opLog.append(opToApply);
6842
+ if (inLane && forceIntegration) {
6843
+ const meta = this.getLaneMeta(this.activeLaneId);
6844
+ this.refreshMaterializedStore(this.opLog.readAll(), this.activeLaneLog.readAll(), meta);
6845
+ } else if (!inLane) {
6846
+ if (!this.integrationCache) {
6847
+ this.integrationCache = {
6848
+ tailHash: opToApply.hash,
6849
+ store: this.store
6850
+ };
6851
+ } else {
6852
+ this.integrationCache.tailHash = opToApply.hash;
6853
+ }
6854
+ }
6855
+ if (opToApply.kind !== "vcs:checkpointCreate" && this.checkpointThreshold > 0) {
6013
6856
  this.checkpointOpCount++;
6014
6857
  if (this.checkpointOpCount >= this.checkpointThreshold) {
6015
6858
  this._pendingAutoCheckpoint = true;
6016
6859
  }
6017
6860
  }
6861
+ if (!opts?.skipBranchAdvance && shouldAdvanceBranchHead(opToApply.kind)) {
6862
+ await this.appendBranchAdvance(opToApply.hash);
6863
+ }
6864
+ }
6865
+ async appendBranchAdvance(targetOpHash) {
6866
+ const advanceOp = await createVcsOp("vcs:branchAdvance", {
6867
+ agentId: this.agentId,
6868
+ previousHash: this.opLog.getLastOp()?.hash,
6869
+ vcs: {
6870
+ branchName: this.currentBranch,
6871
+ targetOpHash
6872
+ }
6873
+ });
6874
+ await this.applyOp(advanceOp, {
6875
+ skipBranchAdvance: true,
6876
+ allowIntegrationWrite: true
6877
+ });
6018
6878
  }
6019
6879
  async flushAutoCheckpoint() {
6020
6880
  if (this._pendingAutoCheckpoint) {
@@ -6025,6 +6885,17 @@ class TrellisVcsEngine {
6025
6885
  loadCurrentBranch() {
6026
6886
  const state = loadBranchState(this.config.rootPath);
6027
6887
  this.currentBranch = state.currentBranch;
6888
+ this.activeLaneId = state.activeLaneId;
6889
+ this.activeLaneLog = null;
6890
+ if (this.activeLaneId) {
6891
+ const meta = loadLaneMeta(this.trellisDir(), this.activeLaneId);
6892
+ if (meta && meta.status === "active") {
6893
+ this.activeLaneLog = new LaneOpLog(laneDir(this.trellisDir(), this.activeLaneId));
6894
+ this.activeLaneLog.load();
6895
+ } else {
6896
+ this.activeLaneId = undefined;
6897
+ }
6898
+ }
6028
6899
  }
6029
6900
  replayOp(op) {
6030
6901
  const decomposed = decompose(op);
@@ -6042,7 +6913,7 @@ class TrellisVcsEngine {
6042
6913
  }
6043
6914
  }
6044
6915
  }
6045
- var TRELLIS_GITIGNORE_ENTRY = ".trellis/";
6916
+ var TRELLIS_GITIGNORE_ENTRY = ".trellis/", ISSUE_INTEGRATION_KINDS;
6046
6917
  var init_engine = __esm(() => {
6047
6918
  init_eav_store();
6048
6919
  init_fs_watcher();
@@ -6063,6 +6934,21 @@ var init_engine = __esm(() => {
6063
6934
  init_infer();
6064
6935
  init_profile();
6065
6936
  init_write();
6937
+ init_op_log();
6938
+ init_lane();
6939
+ init_lane_promote();
6940
+ init_lane_materialize();
6941
+ ISSUE_INTEGRATION_KINDS = new Set([
6942
+ "vcs:issueCreate",
6943
+ "vcs:issueStart",
6944
+ "vcs:issuePause",
6945
+ "vcs:issueResume",
6946
+ "vcs:issueClose",
6947
+ "vcs:issueReopen",
6948
+ "vcs:criterionUpdate",
6949
+ "vcs:issueBlock",
6950
+ "vcs:issueUnblock"
6951
+ ]);
6066
6952
  });
6067
6953
 
6068
- export { FileWatcher, init_fs_watcher, Ingestion, init_ingestion, inferProjectContext, init_infer, loadProfile, saveProfile, hasProfile, promptForProfile, updateProfile, init_profile, writeAgentScaffold, writeIdeScaffold, init_write, TrellisVcsEngine, init_engine };
6954
+ export { FileWatcher, init_fs_watcher, Ingestion, init_ingestion, inferProjectContext, init_infer, loadProfile, saveProfile, hasProfile, promptForProfile, updateProfile, init_profile, writeAgentScaffold, writeIdeScaffold, init_write, formatPromoteExplain, init_lane_promote, TrellisVcsEngine, init_engine };