@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,171 @@
1
+ /**
2
+ * @module generate-comment-cmd
3
+ *
4
+ * Command handler for `vcs generate-comment` (synonyms: `gen-comment`, `gen-msg`, `gcomm`, `gmsg`).
5
+ * Scans repository status or target paths and creates `.ib.<file>.md` or `.ib.<topic>.md` companion metadata files.
6
+ */
7
+
8
+ import path from 'node:path';
9
+ import fs from 'node:fs/promises';
10
+
11
+ import { extractErrorMsg } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
12
+ import { bootstrapVcsMetaspace } from '../../bootstrap/node-bootstrap.mjs';
13
+ import { GLOBAL_LOG_A_LOT } from '../../vcs-gib-constants.mjs';
14
+ import { getRepoStatus } from '../../engine/status/status-helper.mjs';
15
+
16
+ const lcFile = `[generate-comment-cmd]`;
17
+ const logalot = GLOBAL_LOG_A_LOT;
18
+
19
+ export interface GenerateCommentCmdOpts {
20
+ targetDir?: string;
21
+ vcsFolderName?: string;
22
+ paths?: string[];
23
+ folderTopic?: string;
24
+ quiet?: boolean;
25
+ }
26
+
27
+ export interface GenerateCommentCmdResult {
28
+ createdFiles: string[];
29
+ skippedFiles: string[];
30
+ }
31
+
32
+ export async function executeGenerateCommentCmd({
33
+ targetDir = process.cwd(),
34
+ vcsFolderName = '.vcsgib',
35
+ paths = [],
36
+ folderTopic = 'notes',
37
+ quiet = false,
38
+ }: GenerateCommentCmdOpts = {}): Promise<GenerateCommentCmdResult> {
39
+ const lc = `${lcFile}[${executeGenerateCommentCmd.name}]`;
40
+
41
+ try {
42
+ if (logalot) {
43
+ console.log(`${lc} starting executeGenerateCommentCmd (I: 8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e)`);
44
+ }
45
+
46
+ const absTargetDir = path.resolve(targetDir);
47
+
48
+ const { metaspace, localSpace, vcsPath, repoRoot } = await bootstrapVcsMetaspace({
49
+ repoPath: absTargetDir,
50
+ vcsFolderName,
51
+ });
52
+
53
+ // Verify repo exists
54
+ try {
55
+ await fs.access(vcsPath);
56
+ } catch {
57
+ throw new Error(`Not an ibgib repository (directory ${vcsPath} does not exist) (E: 9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f)`);
58
+ }
59
+
60
+ const statusRes = await getRepoStatus({
61
+ metaspace,
62
+ localSpace,
63
+ vcsPath,
64
+ repoRoot,
65
+ vcsFolderName,
66
+ });
67
+
68
+ const activeWorktreeDir = statusRes.activeWorktreeDir;
69
+ const targetRelPaths: string[] = [];
70
+
71
+ if (paths.length > 0) {
72
+ for (const p of paths) {
73
+ const absP = path.resolve(activeWorktreeDir, p);
74
+ const relP = path.relative(activeWorktreeDir, absP).replace(/\\/g, '/');
75
+ if (relP && !targetRelPaths.includes(relP)) {
76
+ targetRelPaths.push(relP);
77
+ }
78
+ }
79
+ } else {
80
+ // Auto-detect from staged and modified items
81
+ for (const s of statusRes.staged) {
82
+ if (!targetRelPaths.includes(s.path)) {
83
+ targetRelPaths.push(s.path);
84
+ }
85
+ }
86
+ for (const m of statusRes.modified) {
87
+ if (!targetRelPaths.includes(m.path)) {
88
+ targetRelPaths.push(m.path);
89
+ }
90
+ }
91
+ for (const u of statusRes.untracked) {
92
+ if (!targetRelPaths.includes(u.path)) {
93
+ targetRelPaths.push(u.path);
94
+ }
95
+ }
96
+ }
97
+
98
+ const createdFiles: string[] = [];
99
+ const skippedFiles: string[] = [];
100
+
101
+ for (const relP of targetRelPaths) {
102
+ const absP = path.join(activeWorktreeDir, relP);
103
+
104
+ let isDirectory = false;
105
+ try {
106
+ const stat = await fs.stat(absP);
107
+ isDirectory = stat.isDirectory();
108
+ } catch {
109
+ // If path doesn't exist yet on disk, guess based on extension
110
+ isDirectory = !path.extname(relP);
111
+ }
112
+
113
+ let commentFileName: string;
114
+ let commentDirPath: string;
115
+
116
+ if (isDirectory) {
117
+ commentDirPath = absP;
118
+ commentFileName = `.ib.${folderTopic}.md`;
119
+ } else {
120
+ commentDirPath = path.dirname(absP);
121
+ const baseName = path.basename(absP);
122
+ commentFileName = `.ib.${baseName}.md`;
123
+ }
124
+
125
+ const commentFullPath = path.join(commentDirPath, commentFileName);
126
+ const commentRelPath = path.relative(activeWorktreeDir, commentFullPath).replace(/\\/g, '/');
127
+
128
+ try {
129
+ await fs.access(commentFullPath);
130
+ // Already exists
131
+ skippedFiles.push(commentRelPath);
132
+ } catch {
133
+ // Does not exist -> create boilerplate template
134
+ const headerTarget = relP ? `\`${relP}\`` : 'root';
135
+ const boilerplate = `# ${headerTarget}\n\n(Enter contemporaneous notes/comments here)\n`;
136
+ await fs.mkdir(commentDirPath, { recursive: true });
137
+ await fs.writeFile(commentFullPath, boilerplate, 'utf8');
138
+ createdFiles.push(commentRelPath);
139
+ }
140
+ }
141
+
142
+ if (!quiet) {
143
+ if (createdFiles.length > 0) {
144
+ console.log(`Generated ${createdFiles.length} comment file(s):`);
145
+ for (const f of createdFiles) {
146
+ console.log(` created: ${f}`);
147
+ }
148
+ }
149
+ if (skippedFiles.length > 0) {
150
+ for (const f of skippedFiles) {
151
+ console.log(` already exists: ${f}`);
152
+ }
153
+ }
154
+ if (createdFiles.length === 0 && skippedFiles.length === 0) {
155
+ console.log(`No modified or staged files found to generate comments for.`);
156
+ }
157
+ }
158
+
159
+ return {
160
+ createdFiles,
161
+ skippedFiles,
162
+ };
163
+ } catch (error) {
164
+ console.error(`${lc} ${extractErrorMsg(error)} (E: 0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a)`);
165
+ throw error;
166
+ } finally {
167
+ if (logalot) {
168
+ console.log(`${lc} complete. (I: 1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b)`);
169
+ }
170
+ }
171
+ }
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Command handler for `vcs log`.
5
5
  * Reads active branch timeline from B2tFSIndexIbGib_V1 in localSpace,
6
- * walks branch timeline history backward, and displays formatted commit history.
6
+ * walks branch or item timeline history backward, and displays formatted commit and comment history.
7
7
  */
8
8
 
9
9
  import path from 'node:path';
@@ -11,12 +11,12 @@ import fs from 'node:fs/promises';
11
11
 
12
12
  import { extractErrorMsg } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
13
13
  import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
14
- import { getIbGibsFromCache_fallbackToSpaces } from '@ibgib/core-gib/dist/common/other/ibgib-helper.mjs';
15
14
  import { CommentIbGib_V1 } from '@ibgib/core-gib/dist/common/comment/comment-types.mjs';
16
15
 
17
16
  import { bootstrapVcsMetaspace } from '../../bootstrap/node-bootstrap.mjs';
18
17
  import { getOrCreateBranchSpace } from '../../engine/branch/branch-space-helper.mjs';
19
18
  import { getIbGibWithMetaspaceFallback } from '../../engine/item/item-helper.mjs';
19
+ import { B2tFSItemIbGib_V1, B2TFS_CHILD_ITEM_REL8N_NAME } from '../../engine/item/item-types.mjs';
20
20
  import { B2tFSBranchIbGib_V1 } from '../../engine/branch/branch-types.mjs';
21
21
  import { getB2tFSIndexIbGib, extractBranchNameFromAddr } from '../../engine/index/index-helper.mjs';
22
22
  import { GLOBAL_LOG_A_LOT } from '../../vcs-gib-constants.mjs';
@@ -25,29 +25,36 @@ const lcFile = `[log-cmd]`;
25
25
  const logalot = GLOBAL_LOG_A_LOT;
26
26
 
27
27
  export interface VcsLogItem {
28
- branchAddr: string;
28
+ branchAddr?: string;
29
29
  commitAddr: string;
30
30
  commentAddr?: string;
31
31
  message: string;
32
32
  timestamp: string;
33
33
  author: string;
34
34
  rootItemAddr?: string;
35
+ committedPaths?: string[];
36
+ itemComments?: Array<{ path: string; name?: string; message: string }>;
35
37
  }
36
38
 
37
39
  export interface LogCmdOpts {
38
40
  targetDir?: string;
39
41
  vcsFolderName?: string;
42
+ filePath?: string;
43
+ allComments?: boolean;
40
44
  quiet?: boolean;
41
45
  }
42
46
 
43
47
  export interface LogCmdResult {
44
48
  branchName: string;
49
+ targetPath?: string;
45
50
  commits: VcsLogItem[];
46
51
  }
47
52
 
48
53
  export async function executeLogCmd({
49
54
  targetDir = process.cwd(),
50
55
  vcsFolderName = '.vcsgib',
56
+ filePath,
57
+ allComments = false,
51
58
  quiet = false,
52
59
  }: LogCmdOpts = {}): Promise<LogCmdResult> {
53
60
  const lc = `${lcFile}[${executeLogCmd.name}]`;
@@ -87,45 +94,122 @@ export async function executeLogCmd({
87
94
  });
88
95
 
89
96
  // 2. Fetch active branch node address matching active branchName
90
- const branchAddrs = [
91
- ...(indexIbGib.rel8ns?.branch || []),
92
- ...(indexIbGib.rel8ns?.branch_tjp || []),
93
- ];
97
+ const branchAddrs = indexIbGib.rel8ns?.branch || indexIbGib.rel8ns?.branch_tjp || [];
94
98
 
95
99
  let currBranchAddr: string | undefined;
96
-
97
- // Match branchName against branchAddrs by space-qualified address or raw atom ib string
98
- for (const addr of branchAddrs) {
99
- const parsedName = extractBranchNameFromAddr(addr);
100
- if (parsedName === branchName) {
101
- currBranchAddr = addr;
102
- break;
103
- }
104
- }
105
-
106
- // Fallback: fetch branch nodes via getIbGibWithMetaspaceFallback to match b.data?.name === branchName
107
- if (!currBranchAddr) {
108
- for (const addr of branchAddrs) {
100
+ if (branchAddrs.length > 0) {
101
+ let branchIbGibs: B2tFSBranchIbGib_V1[] = [];
102
+ for (const bAddr of branchAddrs) {
109
103
  const bIbGib = await getIbGibWithMetaspaceFallback({
110
- addr,
104
+ addr: bAddr,
111
105
  space: activeSpace,
112
106
  metaspace,
113
- }) as B2tFSBranchIbGib_V1;
114
- if (bIbGib?.data?.name === branchName) {
115
- currBranchAddr = addr;
116
- break;
107
+ });
108
+ if (bIbGib) {
109
+ branchIbGibs.push(bIbGib as B2tFSBranchIbGib_V1);
117
110
  }
118
111
  }
112
+
113
+ const matchingBranches = branchIbGibs.filter(b => b?.data?.name === branchName);
114
+ const activeBranchIbGib = matchingBranches.sort((a, b) => (b.data?.n || 0) - (a.data?.n || 0))[0]
115
+ || branchIbGibs.at(-1);
116
+
117
+ if (activeBranchIbGib) {
118
+ currBranchAddr = getIbGibAddr({ ibGib: activeBranchIbGib });
119
+ }
119
120
  }
120
121
 
121
- // Ultimate fallback if no match found
122
- if (!currBranchAddr) {
123
- currBranchAddr = branchAddrs.at(0);
122
+ const commits: VcsLogItem[] = [];
123
+
124
+ // #region File-Specific Log Mode (vcs log <filePath>)
125
+ if (filePath) {
126
+ const normalizedRelPath = path.relative(repoRoot, path.resolve(absTargetDir, filePath)).replace(/\\/g, '/');
127
+
128
+ // Find current root item
129
+ const branchIbGib = currBranchAddr
130
+ ? await getIbGibWithMetaspaceFallback({ addr: currBranchAddr, space: activeSpace, metaspace }) as B2tFSBranchIbGib_V1
131
+ : undefined;
132
+ const rootItemAddr = branchIbGib?.rel8ns?.b2tFS_item?.at(-1) || branchIbGib?.rel8ns?.b2tfs_item?.at(-1);
133
+
134
+ if (!rootItemAddr) {
135
+ throw new Error(`No root item found on branch ${branchName} (E: 4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b)`);
136
+ }
137
+
138
+ const targetItem = await logCmd_findItemByPath({
139
+ rootItemAddr,
140
+ targetRelPath: normalizedRelPath,
141
+ space: activeSpace,
142
+ metaspace,
143
+ });
144
+
145
+ if (!targetItem) {
146
+ throw new Error(`Path '${normalizedRelPath}' not found in active branch snapshot (E: 5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c)`);
147
+ }
148
+
149
+ // Walk target item timeline history backward
150
+ const visitedItemAddrs = new Set<string>();
151
+ let currItem: B2tFSItemIbGib_V1 | undefined = targetItem;
152
+
153
+ while (currItem) {
154
+ const itemAddr = getIbGibAddr({ ibGib: currItem });
155
+ if (visitedItemAddrs.has(itemAddr)) { break; }
156
+ visitedItemAddrs.add(itemAddr);
157
+
158
+ const commentAddrs = currItem.rel8ns?.comment || [];
159
+ for (const cAddr of commentAddrs) {
160
+ const commentIbGib = await getIbGibWithMetaspaceFallback({
161
+ addr: cAddr,
162
+ space: activeSpace,
163
+ metaspace,
164
+ }) as CommentIbGib_V1;
165
+
166
+ if (commentIbGib) {
167
+ commits.push({
168
+ commitAddr: itemAddr,
169
+ commentAddr: cAddr,
170
+ message: commentIbGib.data?.text || '',
171
+ timestamp: commentIbGib.data?.textTimestamp || commentIbGib.data?.timestamp || '',
172
+ author: 'vcs-user',
173
+ });
174
+ }
175
+ }
176
+
177
+ const pastAddr = currItem.rel8ns?.past?.at(0) || currItem.rel8ns?.ancestor?.at(0);
178
+ if (pastAddr) {
179
+ currItem = await getIbGibWithMetaspaceFallback({
180
+ addr: pastAddr,
181
+ space: activeSpace,
182
+ metaspace,
183
+ }) as B2tFSItemIbGib_V1;
184
+ } else {
185
+ currItem = undefined;
186
+ }
187
+ }
188
+
189
+ if (!quiet) {
190
+ console.log(`File: ${normalizedRelPath} (branch: ${branchName}) (I: 6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d)`);
191
+ if (commits.length === 0) {
192
+ console.log('No comments found for this file.');
193
+ } else {
194
+ for (const commit of commits) {
195
+ console.log(`\nitem ${commit.commitAddr}`);
196
+ if (commit.timestamp) { console.log(`Date: ${commit.timestamp}`); }
197
+ console.log(`\n ${commit.message}\n`);
198
+ }
199
+ }
200
+ }
201
+
202
+ return {
203
+ branchName,
204
+ targetPath: normalizedRelPath,
205
+ commits,
206
+ };
124
207
  }
208
+ // #endregion File-Specific Log Mode
209
+
210
+ // #region Standard Branch Log Mode
125
211
  const visitedAddrs = new Set<string>();
126
- const commits: VcsLogItem[] = [];
127
212
 
128
- // 3. Walk timeline history backward
129
213
  while (currBranchAddr && !visitedAddrs.has(currBranchAddr)) {
130
214
  visitedAddrs.add(currBranchAddr);
131
215
 
@@ -139,28 +223,68 @@ export async function executeLogCmd({
139
223
  break;
140
224
  }
141
225
 
142
- const commentAddr = branchIbGib.rel8ns?.comment?.at(0);
143
- const rootItemAddr = branchIbGib.rel8ns?.b2tfs_item?.at(0);
226
+ const rootItemAddr = branchIbGib.rel8ns?.b2tFS_item?.at(-1) || branchIbGib.rel8ns?.b2tfs_item?.at(-1);
227
+
228
+ let rootItem: B2tFSItemIbGib_V1 | undefined;
229
+ if (rootItemAddr) {
230
+ rootItem = await getIbGibWithMetaspaceFallback({
231
+ addr: rootItemAddr,
232
+ space: activeSpace,
233
+ metaspace,
234
+ }) as B2tFSItemIbGib_V1;
235
+ }
236
+
237
+ let commentAddrs = [
238
+ ...(branchIbGib.rel8ns?.comment || []),
239
+ ...(rootItem?.rel8ns?.comment || []),
240
+ ];
241
+ // Deduplicate
242
+ commentAddrs = Array.from(new Set(commentAddrs));
243
+
244
+ let message = '';
245
+ let timestamp = branchIbGib.data?.committedAt || '';
246
+ // If branch has a direct comment, use that first, otherwise use the last added comment on root item
247
+ let primaryCommentAddr = branchIbGib.rel8ns?.comment?.at(-1) || rootItem?.rel8ns?.comment?.at(-1) || commentAddrs.at(0);
144
248
 
145
- if (commentAddr) {
249
+ if (primaryCommentAddr) {
146
250
  const commentIbGib = await getIbGibWithMetaspaceFallback({
147
- addr: commentAddr,
251
+ addr: primaryCommentAddr,
148
252
  space: activeSpace,
149
253
  metaspace,
150
254
  }) as CommentIbGib_V1;
151
255
 
152
- const message = commentIbGib?.data?.text || '';
153
- const timestamp = commentIbGib?.data?.textTimestamp || commentIbGib?.data?.timestamp || branchIbGib.data?.committedAt || '';
154
- const author = 'vcs-user';
256
+ if (commentIbGib?.data?.text) {
257
+ message = commentIbGib.data.text;
258
+ }
259
+ if (commentIbGib?.data?.textTimestamp || commentIbGib?.data?.timestamp) {
260
+ timestamp = commentIbGib.data.textTimestamp || commentIbGib.data.timestamp || timestamp;
261
+ }
262
+ }
263
+
264
+ const committedPaths = branchIbGib.data?.committedPaths || [];
265
+ const itemComments: Array<{ path: string; name?: string; message: string }> = [];
266
+
267
+ if (allComments && rootItem) {
268
+ await logCmd_collectAllItemComments({
269
+ item: rootItem,
270
+ space: activeSpace,
271
+ metaspace,
272
+ currentRelPath: '',
273
+ outComments: itemComments,
274
+ });
275
+ }
155
276
 
277
+ if (message || primaryCommentAddr || (committedPaths && committedPaths.length > 0)) {
156
278
  commits.push({
157
279
  branchAddr: currBranchAddr,
158
- commitAddr: commentAddr,
159
- commentAddr,
160
- message,
280
+ commitAddr: currBranchAddr,
281
+ commentAddr: primaryCommentAddr,
282
+ message: message || '(no commit message)',
161
283
  timestamp,
162
- author,
284
+ author: 'vcs-user',
163
285
  rootItemAddr,
286
+ committedPaths,
287
+ itemComments: itemComments.length > 0 ? itemComments : undefined,
164
288
  });
165
289
  }
166
290
 
@@ -178,6 +302,16 @@ export async function executeLogCmd({
178
302
  console.log(`Author: ${commit.author}`);
179
303
  console.log(`Date: ${commit.timestamp}`);
180
304
  console.log(`\n ${commit.message}\n`);
305
+ if (commit.committedPaths && commit.committedPaths.length > 0) {
306
+ console.log(`Changed: ${commit.committedPaths.join(', ')}`);
307
+ }
308
+ if (commit.itemComments && commit.itemComments.length > 0) {
309
+ console.log('Item Comments:');
310
+ for (const ic of commit.itemComments) {
311
+ const label = ic.name ? `${ic.path} (${ic.name})` : ic.path;
312
+ console.log(` * [${label}]: ${ic.message}`);
313
+ }
314
+ }
181
315
  }
182
316
  }
183
317
  }
@@ -186,6 +320,7 @@ export async function executeLogCmd({
186
320
  branchName,
187
321
  commits,
188
322
  };
323
+ // #endregion Standard Branch Log Mode
189
324
  } catch (error) {
190
325
  console.error(`${lc} ${extractErrorMsg(error)} (E: 5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c)`);
191
326
  throw error;
@@ -195,3 +330,115 @@ export async function executeLogCmd({
195
330
  }
196
331
  }
197
332
  }
333
+
334
+ async function logCmd_findItemByPath({
335
+ rootItemAddr,
336
+ targetRelPath,
337
+ space,
338
+ metaspace,
339
+ }: {
340
+ rootItemAddr: string;
341
+ targetRelPath: string;
342
+ space: any;
343
+ metaspace?: any;
344
+ }): Promise<B2tFSItemIbGib_V1 | undefined> {
345
+ const rootItem = await getIbGibWithMetaspaceFallback({
346
+ addr: rootItemAddr,
347
+ space,
348
+ metaspace,
349
+ }) as B2tFSItemIbGib_V1;
350
+
351
+ if (!rootItem) { return undefined; }
352
+ if (!targetRelPath || targetRelPath === '.' || targetRelPath === '') {
353
+ return rootItem;
354
+ }
355
+
356
+ const segments = targetRelPath.split('/');
357
+ let currentItem: B2tFSItemIbGib_V1 = rootItem;
358
+
359
+ for (const segment of segments) {
360
+ if (currentItem.data?.fsType !== 'folder') {
361
+ return undefined;
362
+ }
363
+
364
+ const childAddrs = currentItem.rel8ns?.[B2TFS_CHILD_ITEM_REL8N_NAME] || [];
365
+ let nextItem: B2tFSItemIbGib_V1 | undefined;
366
+
367
+ for (const cAddr of childAddrs) {
368
+ const child = await getIbGibWithMetaspaceFallback({
369
+ addr: cAddr,
370
+ space,
371
+ metaspace,
372
+ }) as B2tFSItemIbGib_V1;
373
+
374
+ if (child && child.data?.name === segment) {
375
+ nextItem = child;
376
+ break;
377
+ }
378
+ }
379
+
380
+ if (!nextItem) {
381
+ return undefined;
382
+ }
383
+ currentItem = nextItem;
384
+ }
385
+
386
+ return currentItem;
387
+ }
388
+
389
+ async function logCmd_collectAllItemComments({
390
+ item,
391
+ space,
392
+ metaspace,
393
+ currentRelPath,
394
+ outComments,
395
+ }: {
396
+ item: B2tFSItemIbGib_V1;
397
+ space: any;
398
+ metaspace?: any;
399
+ currentRelPath: string;
400
+ outComments: Array<{ path: string; name?: string; message: string }>;
401
+ }): Promise<void> {
402
+ if (!item || !item.data) { return; }
403
+
404
+ const itemComments = item.rel8ns?.comment || [];
405
+ for (const cAddr of itemComments) {
406
+ const commentIbGib = await getIbGibWithMetaspaceFallback({
407
+ addr: cAddr,
408
+ space,
409
+ metaspace,
410
+ }) as CommentIbGib_V1;
411
+
412
+ if (commentIbGib && commentIbGib.data?.text) {
413
+ outComments.push({
414
+ path: currentRelPath || '(root)',
415
+ name: (commentIbGib.data as any)?.name,
416
+ message: commentIbGib.data.text,
417
+ });
418
+ }
419
+ }
420
+
421
+ if (item.data.fsType === 'folder') {
422
+ const childAddrs = item.rel8ns?.[B2TFS_CHILD_ITEM_REL8N_NAME] || [];
423
+ for (const childAddr of childAddrs) {
424
+ const childItem = await getIbGibWithMetaspaceFallback({
425
+ addr: childAddr,
426
+ space,
427
+ metaspace,
428
+ }) as B2tFSItemIbGib_V1;
429
+
430
+ if (childItem && childItem.data) {
431
+ const childRel = currentRelPath
432
+ ? `${currentRelPath}/${childItem.data.name}`
433
+ : childItem.data.name;
434
+ await logCmd_collectAllItemComments({
435
+ item: childItem,
436
+ space,
437
+ metaspace,
438
+ currentRelPath: childRel,
439
+ outComments,
440
+ });
441
+ }
442
+ }
443
+ }
444
+ }