@ibgib/vcs-gib 0.0.21 → 0.0.26

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 (93) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/VCS_CLI_USER_GUIDE.md +94 -89
  3. package/dist/AUTO-GENERATED-version.d.mts +1 -1
  4. package/dist/AUTO-GENERATED-version.mjs +1 -1
  5. package/dist/bootstrap/node-bootstrap.mjs +1 -1
  6. package/dist/cli/cli-arg-parser.d.mts +1 -0
  7. package/dist/cli/cli-arg-parser.d.mts.map +1 -1
  8. package/dist/cli/cli-arg-parser.mjs +5 -1
  9. package/dist/cli/cli-arg-parser.mjs.map +1 -1
  10. package/dist/cli/cli-constants.d.mts +6 -0
  11. package/dist/cli/cli-constants.d.mts.map +1 -1
  12. package/dist/cli/cli-constants.mjs +12 -0
  13. package/dist/cli/cli-constants.mjs.map +1 -1
  14. package/dist/cli/commands/add-cmd.d.mts.map +1 -1
  15. package/dist/cli/commands/add-cmd.mjs +5 -2
  16. package/dist/cli/commands/add-cmd.mjs.map +1 -1
  17. package/dist/cli/commands/commit-cmd.d.mts +5 -4
  18. package/dist/cli/commands/commit-cmd.d.mts.map +1 -1
  19. package/dist/cli/commands/commit-cmd.mjs +149 -33
  20. package/dist/cli/commands/commit-cmd.mjs.map +1 -1
  21. package/dist/cli/commands/generate-comment-cmd.d.mts +19 -0
  22. package/dist/cli/commands/generate-comment-cmd.d.mts.map +1 -0
  23. package/dist/cli/commands/generate-comment-cmd.mjs +140 -0
  24. package/dist/cli/commands/generate-comment-cmd.mjs.map +1 -0
  25. package/dist/cli/commands/log-cmd.d.mts +12 -3
  26. package/dist/cli/commands/log-cmd.d.mts.map +1 -1
  27. package/dist/cli/commands/log-cmd.mjs +236 -38
  28. package/dist/cli/commands/log-cmd.mjs.map +1 -1
  29. package/dist/cli/commands/merge-cmd.d.mts +21 -8
  30. package/dist/cli/commands/merge-cmd.d.mts.map +1 -1
  31. package/dist/cli/commands/merge-cmd.mjs +138 -70
  32. package/dist/cli/commands/merge-cmd.mjs.map +1 -1
  33. package/dist/cli/commands/status-cmd.d.mts +5 -6
  34. package/dist/cli/commands/status-cmd.d.mts.map +1 -1
  35. package/dist/cli/commands/status-cmd.mjs +50 -205
  36. package/dist/cli/commands/status-cmd.mjs.map +1 -1
  37. package/dist/cli/vcs-cli-main.d.mts.map +1 -1
  38. package/dist/cli/vcs-cli-main.mjs +27 -6
  39. package/dist/cli/vcs-cli-main.mjs.map +1 -1
  40. package/dist/engine/index/index-helper.d.mts.map +1 -1
  41. package/dist/engine/index/index-helper.mjs +3 -0
  42. package/dist/engine/index/index-helper.mjs.map +1 -1
  43. package/dist/engine/item/item-helper.d.mts +31 -1
  44. package/dist/engine/item/item-helper.d.mts.map +1 -1
  45. package/dist/engine/item/item-helper.mjs +44 -2
  46. package/dist/engine/item/item-helper.mjs.map +1 -1
  47. package/dist/engine/merge/b2tfs-3way-graft-strategy.d.mts +74 -0
  48. package/dist/engine/merge/b2tfs-3way-graft-strategy.d.mts.map +1 -0
  49. package/dist/engine/merge/{b2tfs-3way-merge-strategy.mjs → b2tfs-3way-graft-strategy.mjs} +133 -304
  50. package/dist/engine/merge/b2tfs-3way-graft-strategy.mjs.map +1 -0
  51. package/dist/engine/merge/{b2tfs-3way-merge-types.d.mts → b2tfs-3way-graft-types.d.mts} +2 -9
  52. package/dist/engine/merge/b2tfs-3way-graft-types.d.mts.map +1 -0
  53. package/dist/engine/merge/b2tfs-3way-graft-types.mjs +2 -0
  54. package/dist/engine/merge/b2tfs-3way-graft-types.mjs.map +1 -0
  55. package/dist/engine/status/status-helper.d.mts +89 -0
  56. package/dist/engine/status/status-helper.d.mts.map +1 -0
  57. package/dist/engine/status/status-helper.mjs +285 -0
  58. package/dist/engine/status/status-helper.mjs.map +1 -0
  59. package/dist/test/test-helpers.d.mts +51 -0
  60. package/dist/test/test-helpers.d.mts.map +1 -0
  61. package/dist/test/test-helpers.mjs +135 -0
  62. package/dist/test/test-helpers.mjs.map +1 -0
  63. package/package.json +4 -4
  64. package/src/AUTO-GENERATED-version.mts +1 -1
  65. package/src/bootstrap/node-bootstrap.mts +1 -1
  66. package/src/cli/cli-arg-parser.mts +5 -0
  67. package/src/cli/cli-constants.mts +13 -0
  68. package/src/cli/commands/add-cmd.mts +5 -2
  69. package/src/cli/commands/branch-checkout-cmd.respec.log +8 -101
  70. package/src/cli/commands/branch-checkout-cmd.respec.mts +11 -10
  71. package/src/cli/commands/commit-cmd.mts +165 -42
  72. package/src/cli/commands/commit-log-cmd.respec.mts +216 -3
  73. package/src/cli/commands/generate-comment-cmd.mts +171 -0
  74. package/src/cli/commands/log-cmd.mts +288 -41
  75. package/src/cli/commands/merge-cmd.mts +154 -76
  76. package/src/cli/commands/status-cmd.mts +57 -256
  77. package/src/cli/vcs-cli-main.mts +28 -7
  78. package/src/engine/index/index-helper.mts +3 -0
  79. package/src/engine/item/item-helper.mts +76 -3
  80. package/src/engine/merge/{b2tfs-3way-merge-strategy.mts → b2tfs-3way-graft-strategy.mts} +152 -374
  81. package/src/engine/merge/{b2tfs-3way-merge-strategy.respec.mts → b2tfs-3way-graft-strategy.respec.mts} +27 -122
  82. package/src/engine/merge/{b2tfs-3way-merge-types.mts → b2tfs-3way-graft-types.mts} +34 -42
  83. package/src/engine/merge/b2tfs-sync-comments.respec.mts +399 -0
  84. package/src/engine/status/status-helper.mts +380 -0
  85. package/src/test/test-helpers.mts +168 -0
  86. package/tsconfig.test.tsbuildinfo +1 -1
  87. package/tsconfig.tsbuildinfo +1 -1
  88. package/dist/engine/merge/b2tfs-3way-merge-strategy.d.mts +0 -48
  89. package/dist/engine/merge/b2tfs-3way-merge-strategy.d.mts.map +0 -1
  90. package/dist/engine/merge/b2tfs-3way-merge-strategy.mjs.map +0 -1
  91. package/dist/engine/merge/b2tfs-3way-merge-types.d.mts.map +0 -1
  92. package/dist/engine/merge/b2tfs-3way-merge-types.mjs +0 -2
  93. package/dist/engine/merge/b2tfs-3way-merge-types.mjs.map +0 -1
@@ -0,0 +1,399 @@
1
+ /**
2
+ * @module b2tfs-sync-comments.respec.node
3
+ *
4
+ * Direct in-memory sync & dependency graph verification for B2tFS items and comments.
5
+ */
6
+
7
+ import path from 'node:path';
8
+ import fs from 'node:fs/promises';
9
+
10
+ import {
11
+ respecfully, ifWe, ifWeMight, iReckon, firstOfEach
12
+ } from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
13
+
14
+ import { clone, hash, getUUID } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
15
+ import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
16
+ import { getGib } from '@ibgib/ts-gib/dist/V1/transforms/transform-helper.mjs';
17
+ import { SyncSagaCoordinator } from '@ibgib/core-gib/dist/sync/sync-saga-coordinator.mjs';
18
+ import { SyncPeerInnerspace_V1 } from '@ibgib/core-gib/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs';
19
+ import { SYNC_PEER_INNERSPACE_DEFAULT_DATA_V1 } from '@ibgib/core-gib/dist/sync/sync-peer/sync-peer-innerspace/sync-peer-innerspace-constants.mjs';
20
+ import { SyncConflictStrategy } from '@ibgib/core-gib/dist/sync/sync-constants.mjs';
21
+ import { GraftPreference } from '@ibgib/core-gib/dist/sync/graft-info/graft-info-types.mjs';
22
+ import { GraftStrategyService } from '@ibgib/core-gib/dist/sync/graft-info/graft-strategy-service.mjs';
23
+ import { getDependencyGraph } from '@ibgib/core-gib/dist/common/other/graph-helper.mjs';
24
+ import { createCommentIbGib } from '@ibgib/core-gib/dist/common/comment/comment-helper.mjs';
25
+ import { getBinIb } from '@ibgib/core-gib/dist/common/other/ibgib-helper.mjs';
26
+ import { BinIbGib_V1 } from '@ibgib/core-gib/dist/common/bin/bin-types.mjs';
27
+ import { putInSpace, persistTransformResult, registerNewIbGib, getFromSpace, getLatestAddrs } from '@ibgib/core-gib/dist/witness/space/space-helper.mjs';
28
+
29
+ import { bootstrapVcsMetaspace } from '../../bootstrap/node-bootstrap.mjs';
30
+ import { getOrCreateBranchSpace } from '../branch/branch-space-helper.mjs';
31
+ import { createB2tFSItemIbGib, evolveB2tFSItemIbGib } from '../item/item-helper.mjs';
32
+ import { B2tFSItemIbGib_V1, BINARY_REL8N_NAME } from '../item/item-types.mjs';
33
+ import { B2tFS3WayGraftStrategy } from './b2tfs-3way-graft-strategy.mjs';
34
+
35
+ const lc = `[apps/vcs-gib/src/engine/merge/b2tfs-sync-comments.respec.node.mts]`;
36
+ const graftStrategySvc = GraftStrategyService.instance;
37
+
38
+ async function createFileWithBinAndComment({
39
+ name,
40
+ content,
41
+ commentText,
42
+ space,
43
+ }: {
44
+ name: string;
45
+ content: string;
46
+ commentText?: string;
47
+ space: any;
48
+ }): Promise<{ fileItem: B2tFSItemIbGib_V1; commentAddr?: string }> {
49
+ const fileHash = await hash({ s: content });
50
+ const fileExt = name.includes('.') ? name.split('.').pop() || 'txt' : 'txt';
51
+ const binIb = getBinIb({ binHash: fileHash, binExt: fileExt, binEncoding: 'utf8' });
52
+ const binIbGib: BinIbGib_V1 = {
53
+ ib: binIb,
54
+ data: Buffer.from(content, 'utf8'),
55
+ rel8ns: {},
56
+ };
57
+ binIbGib.gib = await getGib({ ibGib: binIbGib, hasTjp: false });
58
+ await putInSpace({ space, ibGib: binIbGib });
59
+ await registerNewIbGib({ space, ibGib: binIbGib });
60
+ const binAddr = getIbGibAddr({ ibGib: binIbGib });
61
+
62
+ let commentAddr: string | undefined;
63
+ const rel8nsToAdd: any = {
64
+ [BINARY_REL8N_NAME]: [binAddr],
65
+ };
66
+
67
+ if (commentText) {
68
+ const commentRes = await createCommentIbGib({
69
+ text: commentText,
70
+ });
71
+ await persistTransformResult({ resTransform: commentRes, space });
72
+ await registerNewIbGib({ ibGib: commentRes.newIbGib, space });
73
+ commentAddr = getIbGibAddr({ ibGib: commentRes.newIbGib });
74
+ rel8nsToAdd.comment = [commentAddr];
75
+ }
76
+
77
+ const itemRes = await createB2tFSItemIbGib({
78
+ fsType: 'file',
79
+ name,
80
+ rel8ns: rel8nsToAdd,
81
+ });
82
+ await persistTransformResult({ resTransform: itemRes, space });
83
+ await registerNewIbGib({ ibGib: itemRes.newIbGib, space });
84
+
85
+ return { fileItem: itemRes.newIbGib as B2tFSItemIbGib_V1, commentAddr };
86
+ }
87
+
88
+ async function evolveFileWithBinAndComment({
89
+ existingItem,
90
+ newContent,
91
+ commentText,
92
+ space,
93
+ }: {
94
+ existingItem: B2tFSItemIbGib_V1;
95
+ newContent: string;
96
+ commentText?: string;
97
+ space: any;
98
+ }): Promise<{ evolvedItem: B2tFSItemIbGib_V1; commentAddr?: string }> {
99
+ const fileHash = await hash({ s: newContent });
100
+ const fileExt = existingItem.data?.fileExt || (existingItem.data?.name?.includes('.') ? existingItem.data.name.split('.').pop() || 'txt' : 'txt');
101
+ const binIb = getBinIb({ binHash: fileHash, binExt: fileExt, binEncoding: 'utf8' });
102
+ const binIbGib: BinIbGib_V1 = {
103
+ ib: binIb,
104
+ data: Buffer.from(newContent, 'utf8'),
105
+ rel8ns: {},
106
+ };
107
+ binIbGib.gib = await getGib({ ibGib: binIbGib, hasTjp: false });
108
+ await putInSpace({ space, ibGib: binIbGib });
109
+ await registerNewIbGib({ space, ibGib: binIbGib });
110
+ const binAddr = getIbGibAddr({ ibGib: binIbGib });
111
+
112
+ let commentAddr: string | undefined;
113
+ const rel8nsToAdd: any = {
114
+ [BINARY_REL8N_NAME]: [binAddr],
115
+ };
116
+
117
+ if (commentText) {
118
+ const commentRes = await createCommentIbGib({
119
+ text: commentText,
120
+ });
121
+ await persistTransformResult({ resTransform: commentRes, space });
122
+ await registerNewIbGib({ ibGib: commentRes.newIbGib, space });
123
+ commentAddr = getIbGibAddr({ ibGib: commentRes.newIbGib });
124
+ rel8nsToAdd.comment = [commentAddr];
125
+ }
126
+
127
+ const evolvedRes = await evolveB2tFSItemIbGib({
128
+ existingItem,
129
+ rel8nsToUpdate: rel8nsToAdd,
130
+ });
131
+ await persistTransformResult({ resTransform: evolvedRes, space });
132
+ await registerNewIbGib({ ibGib: evolvedRes.newIbGib, space });
133
+
134
+ return { evolvedItem: evolvedRes.newIbGib as B2tFSItemIbGib_V1, commentAddr };
135
+ }
136
+
137
+ await respecfully(lc, `B2tFS Item & Comment Sync Graph Integrity`, async () => {
138
+
139
+ firstOfEach(lc, async () => {
140
+ graftStrategySvc.reset();
141
+ graftStrategySvc.registerStrategy(new B2tFS3WayGraftStrategy());
142
+ });
143
+
144
+ await ifWe(lc, `transfers single b2tfs item and its attached comment from Space A to Space B`, async () => {
145
+ const testDir = path.join(process.cwd(), '.test_sync_comments_1');
146
+ const vcsFolderName = '.vcsgib_test_1';
147
+ try {
148
+ const { metaspace, vcsPath } = await bootstrapVcsMetaspace({
149
+ repoPath: testDir,
150
+ vcsFolderName,
151
+ });
152
+ const { space: spaceA } = await getOrCreateBranchSpace({ metaspace, vcsPath, branchName: 'branch_a' });
153
+ const { space: spaceB } = await getOrCreateBranchSpace({ metaspace, vcsPath, branchName: 'branch_b' });
154
+
155
+ // 1. Create file item with comment in Space A
156
+ const { fileItem } = await createFileWithBinAndComment({
157
+ name: 'app.ts',
158
+ content: 'const x = 1;',
159
+ commentText: 'Initial commit comment in Space A',
160
+ space: spaceA,
161
+ });
162
+
163
+ // 2. Sync A -> B
164
+ const peer = new SyncPeerInnerspace_V1(clone(SYNC_PEER_INNERSPACE_DEFAULT_DATA_V1));
165
+ await peer.initialized;
166
+ await peer.initializeOpts({
167
+ localMetaspace: metaspace,
168
+ localSpace: spaceA,
169
+ receiverSpace: spaceB,
170
+ receiverCoordinator: new SyncSagaCoordinator(),
171
+ receiverMetaspace: metaspace,
172
+ });
173
+
174
+ const senderCoordinator = new SyncSagaCoordinator();
175
+ const syncSaga = await senderCoordinator.sync({
176
+ peer,
177
+ localSpace: spaceA,
178
+ metaspace,
179
+ domainIbGibs: [fileItem],
180
+ conflictStrategy: SyncConflictStrategy.optimisticWithLCS,
181
+ });
182
+ await syncSaga.done;
183
+
184
+ // 3. Verify getDependencyGraph in Space B alone without metaspace
185
+ const fileAddr = getIbGibAddr({ ibGib: fileItem });
186
+ const resB = await getFromSpace({ addr: fileAddr, space: spaceB });
187
+ const fileInB = resB.ibGibs?.at(0);
188
+ iReckon(lc, fileInB).isGonnaBeTruthy();
189
+
190
+ const depGraphInB = await getDependencyGraph({
191
+ ibGib: fileInB,
192
+ space: spaceB,
193
+ });
194
+
195
+ const graphValues = Object.values(depGraphInB);
196
+ const commentInGraph = graphValues.find(x => (x.data as any)?.text === 'Initial commit comment in Space A');
197
+ iReckon(lc, commentInGraph).isGonnaBeTruthy();
198
+ } finally {
199
+ await fs.rm(testDir, { recursive: true, force: true });
200
+ }
201
+ });
202
+
203
+ await ifWe(lc, `syncs accreted comments bidirectionally between Space A and Space B`, async () => {
204
+ const testDir = path.join(process.cwd(), '.test_sync_comments_2');
205
+ const vcsFolderName = '.vcsgib_test_2';
206
+ try {
207
+ const { metaspace, vcsPath } = await bootstrapVcsMetaspace({
208
+ repoPath: testDir,
209
+ vcsFolderName,
210
+ });
211
+ const { space: spaceA } = await getOrCreateBranchSpace({ metaspace, vcsPath, branchName: 'branch_a' });
212
+ const { space: spaceB } = await getOrCreateBranchSpace({ metaspace, vcsPath, branchName: 'branch_b' });
213
+
214
+ // 1. Create file with comment 1 in Space A
215
+ const { fileItem: fileItemA1 } = await createFileWithBinAndComment({
216
+ name: 'app.ts',
217
+ content: 'const x = 1;',
218
+ commentText: 'Comment 1 from Space A',
219
+ space: spaceA,
220
+ });
221
+
222
+ // 2. Sync A -> B
223
+ const peerA2B = new SyncPeerInnerspace_V1(clone(SYNC_PEER_INNERSPACE_DEFAULT_DATA_V1));
224
+ await peerA2B.initialized;
225
+ await peerA2B.initializeOpts({
226
+ localMetaspace: metaspace,
227
+ localSpace: spaceA,
228
+ receiverSpace: spaceB,
229
+ receiverCoordinator: new SyncSagaCoordinator(),
230
+ receiverMetaspace: metaspace,
231
+ });
232
+
233
+ const senderA = new SyncSagaCoordinator();
234
+ const sagaA = await senderA.sync({
235
+ peer: peerA2B,
236
+ localSpace: spaceA,
237
+ metaspace,
238
+ domainIbGibs: [fileItemA1],
239
+ conflictStrategy: SyncConflictStrategy.optimisticWithLCS,
240
+ });
241
+ await sagaA.done;
242
+
243
+ // 3. Evolve file in Space B with comment 2
244
+ const fileAddrA1 = getIbGibAddr({ ibGib: fileItemA1 });
245
+ const fileInB1 = (await getFromSpace({ addr: fileAddrA1, space: spaceB })).ibGibs?.at(0) as B2tFSItemIbGib_V1;
246
+ const { evolvedItem: fileInB2 } = await evolveFileWithBinAndComment({
247
+ existingItem: fileInB1,
248
+ newContent: 'const x = 2;',
249
+ commentText: 'Comment 2 from Space B',
250
+ space: spaceB,
251
+ });
252
+
253
+ // 4. Sync B -> A
254
+ const peerB2A = new SyncPeerInnerspace_V1(clone(SYNC_PEER_INNERSPACE_DEFAULT_DATA_V1));
255
+ await peerB2A.initialized;
256
+ await peerB2A.initializeOpts({
257
+ localMetaspace: metaspace,
258
+ localSpace: spaceB,
259
+ receiverSpace: spaceA,
260
+ receiverCoordinator: new SyncSagaCoordinator(),
261
+ receiverMetaspace: metaspace,
262
+ });
263
+
264
+ const senderB = new SyncSagaCoordinator();
265
+ const sagaB = await senderB.sync({
266
+ peer: peerB2A,
267
+ localSpace: spaceB,
268
+ metaspace,
269
+ domainIbGibs: [fileInB2],
270
+ conflictStrategy: SyncConflictStrategy.optimisticWithLCS,
271
+ });
272
+ await sagaB.done;
273
+
274
+ // 5. Verify Space A has full graph containing both comments
275
+ const fileAddrB2 = getIbGibAddr({ ibGib: fileInB2 });
276
+ const fileInA2 = (await getFromSpace({ addr: fileAddrB2, space: spaceA })).ibGibs?.at(0);
277
+ iReckon(lc, fileInA2).isGonnaBeTruthy();
278
+
279
+ const depGraphInA = await getDependencyGraph({
280
+ ibGib: fileInA2,
281
+ space: spaceA,
282
+ });
283
+
284
+ const graphValuesA = Object.values(depGraphInA);
285
+ const comment1 = graphValuesA.find(x => (x.data as any)?.text === 'Comment 1 from Space A');
286
+ const comment2 = graphValuesA.find(x => (x.data as any)?.text === 'Comment 2 from Space B');
287
+ iReckon(lc, comment1).isGonnaBeTruthy();
288
+ iReckon(lc, comment2).isGonnaBeTruthy();
289
+ } finally {
290
+ await fs.rm(testDir, { recursive: true, force: true });
291
+ }
292
+ });
293
+
294
+ await ifWe(lc, `preserves and transfers all comments in full 3-way graft merge`, async () => {
295
+ const testDir = path.join(process.cwd(), '.test_sync_comments_3');
296
+ const vcsFolderName = '.vcsgib_test_3';
297
+ try {
298
+ const { metaspace, vcsPath } = await bootstrapVcsMetaspace({
299
+ repoPath: testDir,
300
+ vcsFolderName,
301
+ });
302
+ const { space: spaceA } = await getOrCreateBranchSpace({ metaspace, vcsPath, branchName: 'branch_a' });
303
+ const { space: spaceB } = await getOrCreateBranchSpace({ metaspace, vcsPath, branchName: 'branch_b' });
304
+
305
+ // 1. Initial base file in Space A
306
+ const { fileItem: lcaFile } = await createFileWithBinAndComment({
307
+ name: 'app.ts',
308
+ content: 'const a = 1;\nconst b = 2;\n',
309
+ commentText: 'Base commit comment',
310
+ space: spaceA,
311
+ });
312
+
313
+ // 2. Sync base file from A -> B so both spaces share the LCA
314
+ const peerA2B = new SyncPeerInnerspace_V1(clone(SYNC_PEER_INNERSPACE_DEFAULT_DATA_V1));
315
+ await peerA2B.initialized;
316
+ await peerA2B.initializeOpts({
317
+ localMetaspace: metaspace,
318
+ localSpace: spaceA,
319
+ receiverSpace: spaceB,
320
+ receiverCoordinator: new SyncSagaCoordinator(),
321
+ receiverMetaspace: metaspace,
322
+ });
323
+ const senderA = new SyncSagaCoordinator();
324
+ const initSaga = await senderA.sync({
325
+ peer: peerA2B,
326
+ localSpace: spaceA,
327
+ metaspace,
328
+ domainIbGibs: [lcaFile],
329
+ conflictStrategy: SyncConflictStrategy.optimisticWithLCS,
330
+ });
331
+ await initSaga.done;
332
+
333
+ // 3. Branch A: evolve file in Space A with comment A
334
+ const { evolvedItem: fileA } = await evolveFileWithBinAndComment({
335
+ existingItem: lcaFile,
336
+ newContent: 'const a = 100;\nconst b = 2;\n',
337
+ commentText: 'Branch A comment (updated a to 100)',
338
+ space: spaceA,
339
+ });
340
+
341
+ // 4. Branch B: evolve file in Space B with comment B
342
+ const lcaInB = (await getFromSpace({ addr: getIbGibAddr({ ibGib: lcaFile }), space: spaceB })).ibGibs?.at(0) as B2tFSItemIbGib_V1;
343
+ const { evolvedItem: fileB } = await evolveFileWithBinAndComment({
344
+ existingItem: lcaInB,
345
+ newContent: 'const a = 1;\nconst b = 200;\n',
346
+ commentText: 'Branch B comment (updated b to 200)',
347
+ space: spaceB,
348
+ });
349
+
350
+ // 5. Merge Branch A into Branch B (Space A -> Space B) with 3-way graft
351
+ const peerMerge = new SyncPeerInnerspace_V1(clone(SYNC_PEER_INNERSPACE_DEFAULT_DATA_V1));
352
+ await peerMerge.initialized;
353
+ await peerMerge.initializeOpts({
354
+ localMetaspace: metaspace,
355
+ localSpace: spaceA,
356
+ receiverSpace: spaceB,
357
+ receiverCoordinator: new SyncSagaCoordinator(),
358
+ receiverMetaspace: metaspace,
359
+ });
360
+ const mergeCoordinator = new SyncSagaCoordinator();
361
+ const mergeSaga = await mergeCoordinator.sync({
362
+ peer: peerMerge,
363
+ localSpace: spaceA,
364
+ metaspace,
365
+ domainIbGibs: [fileA],
366
+ conflictStrategy: SyncConflictStrategy.optimisticWithLCS,
367
+ graftPreference: GraftPreference.receiver,
368
+ });
369
+ await mergeSaga.done;
370
+
371
+ // 6. Inspect grafted tip in Space B
372
+ const tjpAddr = fileB.rel8ns?.tjp?.at(0) || getIbGibAddr({ ibGib: fileB });
373
+ const resLatestInB = await getLatestAddrs({ addrs: [tjpAddr], space: spaceB });
374
+ const latestAddrInB = (resLatestInB?.data as any)?.latestAddrsMap?.[tjpAddr];
375
+ iReckon(lc, latestAddrInB).isGonnaBeTruthy();
376
+
377
+ const resGraftedInB = await getFromSpace({ addr: latestAddrInB, space: spaceB });
378
+ const graftedTipInB = resGraftedInB.ibGibs?.at(0);
379
+ iReckon(lc, graftedTipInB).isGonnaBeTruthy();
380
+
381
+ // 7. Verify getDependencyGraph strictly inside spaceB
382
+ const depGraphB = await getDependencyGraph({
383
+ ibGib: graftedTipInB,
384
+ space: spaceB,
385
+ });
386
+
387
+ const graphValuesB = Object.values(depGraphB);
388
+ const commentBase = graphValuesB.find(x => (x.data as any)?.text === 'Base commit comment');
389
+ const commentA = graphValuesB.find(x => (x.data as any)?.text === 'Branch A comment (updated a to 100)');
390
+ const commentB = graphValuesB.find(x => (x.data as any)?.text === 'Branch B comment (updated b to 200)');
391
+
392
+ iReckon(lc, commentBase).isGonnaBeTruthy();
393
+ iReckon(lc, commentA).isGonnaBeTruthy();
394
+ iReckon(lc, commentB).isGonnaBeTruthy();
395
+ } finally {
396
+ await fs.rm(testDir, { recursive: true, force: true });
397
+ }
398
+ });
399
+ });