@ibgib/vcs-gib 0.0.6

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 (202) hide show
  1. package/README.md +94 -0
  2. package/VCS_CLI_USER_GUIDE.md +198 -0
  3. package/dist/AUTO-GENERATED-version.d.mts +6 -0
  4. package/dist/AUTO-GENERATED-version.d.mts.map +1 -0
  5. package/dist/AUTO-GENERATED-version.mjs +6 -0
  6. package/dist/AUTO-GENERATED-version.mjs.map +1 -0
  7. package/dist/assumptions.respec.d.mts +2 -0
  8. package/dist/assumptions.respec.d.mts.map +1 -0
  9. package/dist/assumptions.respec.mjs +41 -0
  10. package/dist/assumptions.respec.mjs.map +1 -0
  11. package/dist/bootstrap/node-bootstrap.d.mts +35 -0
  12. package/dist/bootstrap/node-bootstrap.d.mts.map +1 -0
  13. package/dist/bootstrap/node-bootstrap.mjs +165 -0
  14. package/dist/bootstrap/node-bootstrap.mjs.map +1 -0
  15. package/dist/bootstrap/node-bootstrap.respec.node.d.mts +7 -0
  16. package/dist/bootstrap/node-bootstrap.respec.node.d.mts.map +1 -0
  17. package/dist/bootstrap/node-bootstrap.respec.node.mjs +28 -0
  18. package/dist/bootstrap/node-bootstrap.respec.node.mjs.map +1 -0
  19. package/dist/cli/cli-arg-parser.d.mts +27 -0
  20. package/dist/cli/cli-arg-parser.d.mts.map +1 -0
  21. package/dist/cli/cli-arg-parser.mjs +96 -0
  22. package/dist/cli/cli-arg-parser.mjs.map +1 -0
  23. package/dist/cli/cli-arg-parser.respec.node.d.mts +7 -0
  24. package/dist/cli/cli-arg-parser.respec.node.d.mts.map +1 -0
  25. package/dist/cli/cli-arg-parser.respec.node.mjs +35 -0
  26. package/dist/cli/cli-arg-parser.respec.node.mjs.map +1 -0
  27. package/dist/cli/cli-constants.d.mts +26 -0
  28. package/dist/cli/cli-constants.d.mts.map +1 -0
  29. package/dist/cli/cli-constants.mjs +67 -0
  30. package/dist/cli/cli-constants.mjs.map +1 -0
  31. package/dist/cli/cli-e2e.respec.node.d.mts +8 -0
  32. package/dist/cli/cli-e2e.respec.node.d.mts.map +1 -0
  33. package/dist/cli/cli-e2e.respec.node.mjs +263 -0
  34. package/dist/cli/cli-e2e.respec.node.mjs.map +1 -0
  35. package/dist/cli/commands/add-cmd.d.mts +20 -0
  36. package/dist/cli/commands/add-cmd.d.mts.map +1 -0
  37. package/dist/cli/commands/add-cmd.mjs +333 -0
  38. package/dist/cli/commands/add-cmd.mjs.map +1 -0
  39. package/dist/cli/commands/branch-checkout-cmd.respec.node.d.mts +7 -0
  40. package/dist/cli/commands/branch-checkout-cmd.respec.node.d.mts.map +1 -0
  41. package/dist/cli/commands/branch-checkout-cmd.respec.node.mjs +288 -0
  42. package/dist/cli/commands/branch-checkout-cmd.respec.node.mjs.map +1 -0
  43. package/dist/cli/commands/branch-cmd.d.mts +25 -0
  44. package/dist/cli/commands/branch-cmd.d.mts.map +1 -0
  45. package/dist/cli/commands/branch-cmd.mjs +187 -0
  46. package/dist/cli/commands/branch-cmd.mjs.map +1 -0
  47. package/dist/cli/commands/checkout-cmd.d.mts +21 -0
  48. package/dist/cli/commands/checkout-cmd.d.mts.map +1 -0
  49. package/dist/cli/commands/checkout-cmd.mjs +154 -0
  50. package/dist/cli/commands/checkout-cmd.mjs.map +1 -0
  51. package/dist/cli/commands/commit-cmd.d.mts +23 -0
  52. package/dist/cli/commands/commit-cmd.d.mts.map +1 -0
  53. package/dist/cli/commands/commit-cmd.mjs +326 -0
  54. package/dist/cli/commands/commit-cmd.mjs.map +1 -0
  55. package/dist/cli/commands/commit-log-cmd.respec.node.d.mts +7 -0
  56. package/dist/cli/commands/commit-log-cmd.respec.node.d.mts.map +1 -0
  57. package/dist/cli/commands/commit-log-cmd.respec.node.mjs +251 -0
  58. package/dist/cli/commands/commit-log-cmd.respec.node.mjs.map +1 -0
  59. package/dist/cli/commands/init-cmd.d.mts +20 -0
  60. package/dist/cli/commands/init-cmd.d.mts.map +1 -0
  61. package/dist/cli/commands/init-cmd.mjs +119 -0
  62. package/dist/cli/commands/init-cmd.mjs.map +1 -0
  63. package/dist/cli/commands/init-cmd.respec.node.d.mts +7 -0
  64. package/dist/cli/commands/init-cmd.respec.node.d.mts.map +1 -0
  65. package/dist/cli/commands/init-cmd.respec.node.mjs +40 -0
  66. package/dist/cli/commands/init-cmd.respec.node.mjs.map +1 -0
  67. package/dist/cli/commands/log-cmd.d.mts +27 -0
  68. package/dist/cli/commands/log-cmd.d.mts.map +1 -0
  69. package/dist/cli/commands/log-cmd.mjs +146 -0
  70. package/dist/cli/commands/log-cmd.mjs.map +1 -0
  71. package/dist/cli/commands/status-add-cmd.respec.node.d.mts +7 -0
  72. package/dist/cli/commands/status-add-cmd.respec.node.d.mts.map +1 -0
  73. package/dist/cli/commands/status-add-cmd.respec.node.mjs +215 -0
  74. package/dist/cli/commands/status-add-cmd.respec.node.mjs.map +1 -0
  75. package/dist/cli/commands/status-cmd.d.mts +25 -0
  76. package/dist/cli/commands/status-cmd.d.mts.map +1 -0
  77. package/dist/cli/commands/status-cmd.mjs +286 -0
  78. package/dist/cli/commands/status-cmd.mjs.map +1 -0
  79. package/dist/cli/vcs-cli-main.d.mts +14 -0
  80. package/dist/cli/vcs-cli-main.d.mts.map +1 -0
  81. package/dist/cli/vcs-cli-main.mjs +136 -0
  82. package/dist/cli/vcs-cli-main.mjs.map +1 -0
  83. package/dist/engine/branch/branch-graph-helper.d.mts +19 -0
  84. package/dist/engine/branch/branch-graph-helper.d.mts.map +1 -0
  85. package/dist/engine/branch/branch-graph-helper.mjs +49 -0
  86. package/dist/engine/branch/branch-graph-helper.mjs.map +1 -0
  87. package/dist/engine/branch/branch-helper.d.mts +26 -0
  88. package/dist/engine/branch/branch-helper.d.mts.map +1 -0
  89. package/dist/engine/branch/branch-helper.mjs +57 -0
  90. package/dist/engine/branch/branch-helper.mjs.map +1 -0
  91. package/dist/engine/branch/branch-space-helper.d.mts +22 -0
  92. package/dist/engine/branch/branch-space-helper.d.mts.map +1 -0
  93. package/dist/engine/branch/branch-space-helper.mjs +77 -0
  94. package/dist/engine/branch/branch-space-helper.mjs.map +1 -0
  95. package/dist/engine/branch/branch-space.respec.node.d.mts +8 -0
  96. package/dist/engine/branch/branch-space.respec.node.d.mts.map +1 -0
  97. package/dist/engine/branch/branch-space.respec.node.mjs +138 -0
  98. package/dist/engine/branch/branch-space.respec.node.mjs.map +1 -0
  99. package/dist/engine/branch/branch-types.d.mts +26 -0
  100. package/dist/engine/branch/branch-types.d.mts.map +1 -0
  101. package/dist/engine/branch/branch-types.mjs +7 -0
  102. package/dist/engine/branch/branch-types.mjs.map +1 -0
  103. package/dist/engine/diff/line-diff-helper.d.mts +76 -0
  104. package/dist/engine/diff/line-diff-helper.d.mts.map +1 -0
  105. package/dist/engine/diff/line-diff-helper.mjs +144 -0
  106. package/dist/engine/diff/line-diff-helper.mjs.map +1 -0
  107. package/dist/engine/diff/line-diff-helper.respec.node.d.mts +7 -0
  108. package/dist/engine/diff/line-diff-helper.respec.node.d.mts.map +1 -0
  109. package/dist/engine/diff/line-diff-helper.respec.node.mjs +66 -0
  110. package/dist/engine/diff/line-diff-helper.respec.node.mjs.map +1 -0
  111. package/dist/engine/filter/glob-helper.d.mts +51 -0
  112. package/dist/engine/filter/glob-helper.d.mts.map +1 -0
  113. package/dist/engine/filter/glob-helper.mjs +132 -0
  114. package/dist/engine/filter/glob-helper.mjs.map +1 -0
  115. package/dist/engine/filter/glob-helper.respec.node.d.mts +7 -0
  116. package/dist/engine/filter/glob-helper.respec.node.d.mts.map +1 -0
  117. package/dist/engine/filter/glob-helper.respec.node.mjs +78 -0
  118. package/dist/engine/filter/glob-helper.respec.node.mjs.map +1 -0
  119. package/dist/engine/index/index-helper.d.mts +78 -0
  120. package/dist/engine/index/index-helper.d.mts.map +1 -0
  121. package/dist/engine/index/index-helper.mjs +289 -0
  122. package/dist/engine/index/index-helper.mjs.map +1 -0
  123. package/dist/engine/index/index-types.d.mts +63 -0
  124. package/dist/engine/index/index-types.d.mts.map +1 -0
  125. package/dist/engine/index/index-types.mjs +7 -0
  126. package/dist/engine/index/index-types.mjs.map +1 -0
  127. package/dist/engine/item/item-atom-helper.d.mts +20 -0
  128. package/dist/engine/item/item-atom-helper.d.mts.map +1 -0
  129. package/dist/engine/item/item-atom-helper.mjs +30 -0
  130. package/dist/engine/item/item-atom-helper.mjs.map +1 -0
  131. package/dist/engine/item/item-helper.d.mts +37 -0
  132. package/dist/engine/item/item-helper.d.mts.map +1 -0
  133. package/dist/engine/item/item-helper.mjs +194 -0
  134. package/dist/engine/item/item-helper.mjs.map +1 -0
  135. package/dist/engine/item/item-types.d.mts +127 -0
  136. package/dist/engine/item/item-types.d.mts.map +1 -0
  137. package/dist/engine/item/item-types.mjs +16 -0
  138. package/dist/engine/item/item-types.mjs.map +1 -0
  139. package/dist/index.d.mts +7 -0
  140. package/dist/index.d.mts.map +1 -0
  141. package/dist/index.mjs +7 -0
  142. package/dist/index.mjs.map +1 -0
  143. package/dist/respec-gib.node.d.mts +7 -0
  144. package/dist/respec-gib.node.d.mts.map +1 -0
  145. package/dist/respec-gib.node.mjs +11 -0
  146. package/dist/respec-gib.node.mjs.map +1 -0
  147. package/dist/vcs-gib-constants.d.mts +7 -0
  148. package/dist/vcs-gib-constants.d.mts.map +1 -0
  149. package/dist/vcs-gib-constants.mjs +7 -0
  150. package/dist/vcs-gib-constants.mjs.map +1 -0
  151. package/dist/vcs-gib.respec.node.d.mts +8 -0
  152. package/dist/vcs-gib.respec.node.d.mts.map +1 -0
  153. package/dist/vcs-gib.respec.node.mjs +82 -0
  154. package/dist/vcs-gib.respec.node.mjs.map +1 -0
  155. package/generate-version-file.js +20 -0
  156. package/package.json +28 -0
  157. package/src/AUTO-GENERATED-version.mts +5 -0
  158. package/src/assumptions.respec.mts +50 -0
  159. package/src/bootstrap/node-bootstrap.mts +198 -0
  160. package/src/bootstrap/node-bootstrap.respec.node.mts +35 -0
  161. package/src/cli/cli-arg-parser.mts +119 -0
  162. package/src/cli/cli-arg-parser.respec.node.mts +45 -0
  163. package/src/cli/cli-constants.mts +77 -0
  164. package/src/cli/cli-e2e.respec.node.mts +318 -0
  165. package/src/cli/commands/add-cmd.mts +415 -0
  166. package/src/cli/commands/branch-checkout-cmd.respec.node.mts +322 -0
  167. package/src/cli/commands/branch-cmd.mts +243 -0
  168. package/src/cli/commands/checkout-cmd.mts +202 -0
  169. package/src/cli/commands/commit-cmd.mts +421 -0
  170. package/src/cli/commands/commit-log-cmd.respec.node.mts +276 -0
  171. package/src/cli/commands/init-cmd.mts +152 -0
  172. package/src/cli/commands/init-cmd.respec.node.mts +49 -0
  173. package/src/cli/commands/log-cmd.mts +197 -0
  174. package/src/cli/commands/status-add-cmd.respec.node.mts +246 -0
  175. package/src/cli/commands/status-cmd.mts +366 -0
  176. package/src/cli/vcs-cli-main.mts +153 -0
  177. package/src/engine/branch/branch-graph-helper.mts +69 -0
  178. package/src/engine/branch/branch-helper.mts +79 -0
  179. package/src/engine/branch/branch-space-helper.mts +104 -0
  180. package/src/engine/branch/branch-space.respec.node.mts +158 -0
  181. package/src/engine/branch/branch-types.mts +29 -0
  182. package/src/engine/diff/line-diff-helper.mts +210 -0
  183. package/src/engine/diff/line-diff-helper.respec.node.mts +84 -0
  184. package/src/engine/filter/glob-helper.mts +165 -0
  185. package/src/engine/filter/glob-helper.respec.node.mts +94 -0
  186. package/src/engine/index/index-helper.mts +378 -0
  187. package/src/engine/index/index-types.mts +66 -0
  188. package/src/engine/item/item-atom-helper.mts +30 -0
  189. package/src/engine/item/item-helper.mts +239 -0
  190. package/src/engine/item/item-types.mts +136 -0
  191. package/src/index.mts +7 -0
  192. package/src/respec-gib.node.mts +12 -0
  193. package/src/vcs-gib-constants.mts +7 -0
  194. package/src/vcs-gib.respec.node.mts +96 -0
  195. package/test_runs/001-hello-node/20260728_090100_0001-flow-core-main.md +39 -0
  196. package/test_runs/001-hello-node/20260728_090200_0003-flow-branching-worktree.md +54 -0
  197. package/test_runs/001-hello-node/20260728_090300_0002-flow-selective-add.md +45 -0
  198. package/test_runs/001-hello-node/20260728_142100_0007-flow-edge-cases.md +44 -0
  199. package/test_runs/001-hello-node/20260728_142400_0005-flow-hardcoded-ignores.md +36 -0
  200. package/tools/pack-for-test.mjs +73 -0
  201. package/tsconfig.json +18 -0
  202. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,210 @@
1
+ /**
2
+ * @module line-diff-helper
3
+ *
4
+ * Native, zero-dependency Myers / LCS line-diffing engine for text files.
5
+ *
6
+ * Computes precise line-by-line diff chunks between text files without external libraries.
7
+ */
8
+
9
+ import { GLOBAL_LOG_A_LOT } from '../../vcs-gib-constants.mjs';
10
+
11
+ const lcFile = `[line-diff-helper]`;
12
+ const logalot = GLOBAL_LOG_A_LOT;
13
+
14
+ export interface LineDiffChunk {
15
+ /**
16
+ * Line index in old text where the chunk starts (0-indexed)
17
+ */
18
+ old_i: number;
19
+ /**
20
+ * Deleted lines from old text
21
+ */
22
+ oldLines: string[];
23
+ /**
24
+ * Inserted lines in new text
25
+ */
26
+ newLines: string[];
27
+ }
28
+
29
+ export interface LineDiffResult {
30
+ /**
31
+ * Array of line diff chunks
32
+ */
33
+ chunks: LineDiffChunk[];
34
+ /**
35
+ * Set if total lines exceed max limit and force flag was false
36
+ */
37
+ tooBig?: boolean;
38
+ /**
39
+ * Set if binary data was detected and force flag was false
40
+ */
41
+ treatedAsBinary?: boolean;
42
+ }
43
+
44
+ export interface LineDiffOpts {
45
+ /**
46
+ * Old file data buffer (optional if new file addition)
47
+ */
48
+ oldData?: Uint8Array;
49
+ /**
50
+ * New file data buffer
51
+ */
52
+ newData: Uint8Array;
53
+ /**
54
+ * File encoding (defaults to 'utf8')
55
+ */
56
+ fileEncoding?: string;
57
+ /**
58
+ * Max line count limit before requiring force flag (defaults to 10000)
59
+ */
60
+ maxLines?: number;
61
+ /**
62
+ * Force diff computation even if file is large or binary
63
+ */
64
+ force?: boolean;
65
+ }
66
+
67
+ /**
68
+ * Basic binary detection check.
69
+ * Checks for null bytes (0x00) within the first 1024 bytes.
70
+ */
71
+ export function isBinaryBuffer(data: Uint8Array): boolean {
72
+ const sampleSize = Math.min(data.length, 1024);
73
+ for (let i = 0; i < sampleSize; i++) {
74
+ if (data[i] === 0) { return true; }
75
+ }
76
+ return false;
77
+ }
78
+
79
+ /**
80
+ * Decodes Uint8Array buffer into array of text lines.
81
+ */
82
+ export function decodeToLines(data?: Uint8Array, encoding: string = 'utf8'): string[] {
83
+ if (!data || data.length === 0) { return []; }
84
+ try {
85
+ const textDecoder = new TextDecoder(encoding || 'utf8', { fatal: false });
86
+ const text = textDecoder.decode(data);
87
+ return text.split(/\r?\n/);
88
+ } catch {
89
+ return [];
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Computes Longest Common Subsequence (LCS) edit script between two arrays of lines.
95
+ * Returns array of LineDiffChunk structures.
96
+ */
97
+ export function computeLineLcsDiff(oldLines: string[], newLines: string[]): LineDiffChunk[] {
98
+ const M = oldLines.length;
99
+ const N = newLines.length;
100
+
101
+ // Fast path: identical lines
102
+ if (M === N && oldLines.every((line, idx) => line === newLines[idx])) {
103
+ return [];
104
+ }
105
+
106
+ // Build LCS DP matrix
107
+ const dp: number[][] = Array.from({ length: M + 1 }, () => new Array(N + 1).fill(0));
108
+
109
+ for (let i = 1; i <= M; i++) {
110
+ for (let j = 1; j <= N; j++) {
111
+ if (oldLines[i - 1] === newLines[j - 1]) {
112
+ dp[i][j] = dp[i - 1][j - 1] + 1;
113
+ } else {
114
+ dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
115
+ }
116
+ }
117
+ }
118
+
119
+ // Backtrack to build edit script
120
+ let i = M;
121
+ let j = N;
122
+ const rawEdits: Array<{ type: 'delete' | 'insert' | 'equal', oldIdx?: number, line: string }> = [];
123
+
124
+ while (i > 0 || j > 0) {
125
+ if (i > 0 && j > 0 && oldLines[i - 1] === newLines[j - 1]) {
126
+ rawEdits.push({ type: 'equal', oldIdx: i - 1, line: oldLines[i - 1] });
127
+ i--;
128
+ j--;
129
+ } else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
130
+ rawEdits.push({ type: 'insert', line: newLines[j - 1] });
131
+ j--;
132
+ } else if (i > 0 && (j === 0 || dp[i][j - 1] < dp[i - 1][j])) {
133
+ rawEdits.push({ type: 'delete', oldIdx: i - 1, line: oldLines[i - 1] });
134
+ i--;
135
+ }
136
+ }
137
+
138
+ rawEdits.reverse();
139
+
140
+ // Group edit operations into chunks
141
+ const chunks: LineDiffChunk[] = [];
142
+ let currentChunk: LineDiffChunk | null = null;
143
+ let oldLineCursor = 0;
144
+
145
+ for (const edit of rawEdits) {
146
+ if (edit.type === 'equal') {
147
+ if (currentChunk) {
148
+ chunks.push(currentChunk);
149
+ currentChunk = null;
150
+ }
151
+ oldLineCursor++;
152
+ } else {
153
+ if (!currentChunk) {
154
+ currentChunk = {
155
+ old_i: oldLineCursor,
156
+ oldLines: [],
157
+ newLines: [],
158
+ };
159
+ }
160
+
161
+ if (edit.type === 'delete') {
162
+ currentChunk.oldLines.push(edit.line);
163
+ oldLineCursor++;
164
+ } else if (edit.type === 'insert') {
165
+ currentChunk.newLines.push(edit.line);
166
+ }
167
+ }
168
+ }
169
+
170
+ if (currentChunk) {
171
+ chunks.push(currentChunk);
172
+ }
173
+
174
+ return chunks;
175
+ }
176
+
177
+ /**
178
+ * Main line diff helper function for text files.
179
+ */
180
+ export function getLineDiffChunks(opts: LineDiffOpts): LineDiffResult {
181
+ const lc = `${lcFile}[${getLineDiffChunks.name}]`;
182
+ const {
183
+ oldData,
184
+ newData,
185
+ fileEncoding = 'utf8',
186
+ maxLines = 10000,
187
+ force = false,
188
+ } = opts;
189
+
190
+ // Check for binary data
191
+ const oldIsBin = oldData ? isBinaryBuffer(oldData) : false;
192
+ const newIsBin = isBinaryBuffer(newData);
193
+
194
+ if ((oldIsBin || newIsBin) && !force) {
195
+ if (logalot) { console.log(`${lc} Binary data detected, skipping line diff.`); }
196
+ return { chunks: [], treatedAsBinary: true };
197
+ }
198
+
199
+ const oldLines = decodeToLines(oldData, fileEncoding);
200
+ const newLines = decodeToLines(newData, fileEncoding);
201
+
202
+ // Check max lines threshold
203
+ if ((oldLines.length > maxLines || newLines.length > maxLines) && !force) {
204
+ if (logalot) { console.log(`${lc} Line count exceeds limit (${maxLines}), skipping line diff.`); }
205
+ return { chunks: [], tooBig: true };
206
+ }
207
+
208
+ const chunks = computeLineLcsDiff(oldLines, newLines);
209
+ return { chunks };
210
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * @module line-diff-helper.respec
3
+ *
4
+ * Unit tests for native zero-dependency Myers / LCS line-diffing engine.
5
+ */
6
+
7
+ import {
8
+ respecfully, ifWe, iReckon
9
+ } from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
10
+
11
+ import {
12
+ isBinaryBuffer, decodeToLines, computeLineLcsDiff, getLineDiffChunks
13
+ } from './line-diff-helper.mjs';
14
+
15
+ const lc = `[apps/vcs-gib/src/engine/diff/line-diff-helper.respec.node.mts]`;
16
+ const encoder = new TextEncoder();
17
+
18
+ await respecfully(lc, `Native Line Diff Engine (Zero-Dependency)`, async () => {
19
+
20
+ await respecfully(lc, `isBinaryBuffer`, async () => {
21
+ await ifWe(lc, `detects text buffer as non-binary`, async () => {
22
+ const buf = encoder.encode('Hello World\nLine 2');
23
+ iReckon(lc, isBinaryBuffer(buf)).isGonnaBeFalse();
24
+ });
25
+
26
+ await ifWe(lc, `detects null byte buffer as binary`, async () => {
27
+ const buf = new Uint8Array([0x48, 0x65, 0x00, 0x6c, 0x6f]);
28
+ iReckon(lc, isBinaryBuffer(buf)).isGonnaBeTrue();
29
+ });
30
+ });
31
+
32
+ await respecfully(lc, `computeLineLcsDiff`, async () => {
33
+ await ifWe(lc, `returns empty chunks for identical lines`, async () => {
34
+ const lines = ['line 1', 'line 2', 'line 3'];
35
+ const chunks = computeLineLcsDiff(lines, lines);
36
+ iReckon(lc, chunks.length).willEqual(0);
37
+ });
38
+
39
+ await ifWe(lc, `computes single line insertion chunk`, async () => {
40
+ const oldLines = ['line 1', 'line 3'];
41
+ const newLines = ['line 1', 'line 2', 'line 3'];
42
+ const chunks = computeLineLcsDiff(oldLines, newLines);
43
+
44
+ iReckon(lc, chunks.length).willEqual(1);
45
+ iReckon(lc, chunks[0].old_i).willEqual(1);
46
+ iReckon(lc, chunks[0].oldLines.length).willEqual(0);
47
+ iReckon(lc, chunks[0].newLines.length).willEqual(1);
48
+ iReckon(lc, chunks[0].newLines[0]).willEqual('line 2');
49
+ });
50
+
51
+ await ifWe(lc, `computes line replacement chunk`, async () => {
52
+ const oldLines = ['const x = 1;', 'const y = 2;'];
53
+ const newLines = ['const x = 1;', 'const y = 20;'];
54
+ const chunks = computeLineLcsDiff(oldLines, newLines);
55
+
56
+ iReckon(lc, chunks.length).willEqual(1);
57
+ iReckon(lc, chunks[0].old_i).willEqual(1);
58
+ iReckon(lc, chunks[0].oldLines[0]).willEqual('const y = 2;');
59
+ iReckon(lc, chunks[0].newLines[0]).willEqual('const y = 20;');
60
+ });
61
+ });
62
+
63
+ await respecfully(lc, `getLineDiffChunks`, async () => {
64
+ await ifWe(lc, `computes line diff for text buffers`, async () => {
65
+ const oldData = encoder.encode('alpha\nbeta\ngamma');
66
+ const newData = encoder.encode('alpha\nbeta_updated\ngamma');
67
+ const result = getLineDiffChunks({ oldData, newData });
68
+
69
+ iReckon(lc, result.treatedAsBinary).isGonnaBeUndefined();
70
+ iReckon(lc, result.chunks.length).willEqual(1);
71
+ iReckon(lc, result.chunks[0].oldLines[0]).willEqual('beta');
72
+ iReckon(lc, result.chunks[0].newLines[0]).willEqual('beta_updated');
73
+ });
74
+
75
+ await ifWe(lc, `flags binary data without force`, async () => {
76
+ const oldData = new Uint8Array([0x00, 0x01, 0x02]);
77
+ const newData = encoder.encode('hello');
78
+ const result = getLineDiffChunks({ oldData, newData });
79
+
80
+ iReckon(lc, result.treatedAsBinary).isGonnaBeTrue();
81
+ iReckon(lc, result.chunks.length).willEqual(0);
82
+ });
83
+ });
84
+ });
@@ -0,0 +1,165 @@
1
+ /**
2
+ * @module glob-helper
3
+ *
4
+ * Native, zero-dependency glob pattern matching engine for .ibgibignore and path filtering.
5
+ *
6
+ * Supports:
7
+ * - Basic wildcards: '*' (single path segment), '**' (recursive path matching), '?' (single char)
8
+ * - Negation/Inclusion: '!' prefix (negable matching, last match wins)
9
+ * - Directory matching: trailing '/' (matches directory paths only)
10
+ * - JS RegExp fallback: 'regexp js <pattern>' prefix
11
+ * - Ignore file parsing: stripping '#' comments and blank lines
12
+ * - Hardcoded reserved repository paths: '.vcsgib', 'b', 'ibgib', 'node_modules', '.git'
13
+ */
14
+
15
+ import { GLOBAL_LOG_A_LOT } from '../../vcs-gib-constants.mjs';
16
+
17
+ const lcFile = `[glob-helper]`;
18
+ const logalot = GLOBAL_LOG_A_LOT;
19
+
20
+ /**
21
+ * Hardcoded internal repository paths that are ALWAYS ignored during workspace scans
22
+ * regardless of user .ibgibignore settings.
23
+ */
24
+ export const HARDCODED_IGNORED_PATHS = ['.vcsgib', 'b', 'ibgib', 'node_modules', '.git'];
25
+
26
+ /**
27
+ * Converts a glob pattern string into a JavaScript RegExp.
28
+ */
29
+ export function globToRegExp(glob: string): RegExp {
30
+ const raw = glob.replace(/\\/g, '/');
31
+ let p = raw
32
+ .replace(/\./g, '\\.')
33
+ .replace(/\?/g, '[^/]')
34
+ .replace(/\/\*\*\//g, '(?:/|/.+/)')
35
+ .replace(/\/\*\*/g, '(?:/.*)?')
36
+ .replace(/\*\*\//g, '(?:.*/)?')
37
+ .replace(/\*/g, '[^/]*');
38
+
39
+ if (!raw.endsWith('/')) {
40
+ p += '(?:/.*)?';
41
+ }
42
+
43
+ return new RegExp(`^${p}$`);
44
+ }
45
+
46
+ /**
47
+ * Parses raw .ibgibignore file text into an array of active pattern strings.
48
+ * Strips blank lines and '#' comments.
49
+ */
50
+ export function parseFilterFileContent(content: string): string[] {
51
+ if (!content) { return []; }
52
+
53
+ return content
54
+ .split(/\r?\n/)
55
+ .map(line => line.trim())
56
+ .filter(line => line.length > 0 && !line.startsWith('#'));
57
+ }
58
+
59
+ /**
60
+ * Options passed to path filtering check.
61
+ */
62
+ export interface IsPathFilteredOpts {
63
+ /**
64
+ * Relative path from repository root (e.g. 'src/utils/helper.mts' or 'b/feature-auth')
65
+ */
66
+ relativePath: string;
67
+ /**
68
+ * Whether the target path is a directory
69
+ */
70
+ isDirectory?: boolean;
71
+ /**
72
+ * Active filter patterns (e.g. from .ibgibignore)
73
+ */
74
+ filterPatterns?: string[];
75
+ }
76
+
77
+ /**
78
+ * Determines whether a relative path is filtered (ignored) based on active pattern rules.
79
+ * Reserved repository paths ('.vcsgib', 'b', 'ibgib') are ALWAYS ignored.
80
+ * Last matching rule wins for user rules (standard gitignore semantics).
81
+ */
82
+ export function isPathFiltered({
83
+ relativePath,
84
+ isDirectory = false,
85
+ filterPatterns = [],
86
+ }: IsPathFilteredOpts): boolean {
87
+ const lc = `${lcFile}[${isPathFiltered.name}]`;
88
+
89
+ if (!relativePath) { return false; }
90
+
91
+ // Normalize path separators to forward slashes
92
+ const normalizedPath = relativePath.replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/$/, '');
93
+ const pathBasename = normalizedPath.split('/')[0] || normalizedPath;
94
+
95
+ // 0. Hardcoded reserved repository paths (ALWAYS ignored)
96
+ if (HARDCODED_IGNORED_PATHS.includes(pathBasename) || HARDCODED_IGNORED_PATHS.includes(normalizedPath)) {
97
+ return true;
98
+ }
99
+
100
+ if (filterPatterns.length === 0) { return false; }
101
+
102
+ let isIgnored = false;
103
+
104
+ for (const rawPattern of filterPatterns) {
105
+ if (!rawPattern || rawPattern.trim() === '') { continue; }
106
+ const pattern = rawPattern.trim();
107
+
108
+ // 1. JS RegExp fallback
109
+ if (pattern.startsWith('regexp js ')) {
110
+ const regexText = pattern.substring('regexp js '.length);
111
+ try {
112
+ const rx = new RegExp(regexText);
113
+ if (rx.test(normalizedPath) || rx.test(pathBasename)) {
114
+ isIgnored = true;
115
+ }
116
+ } catch (err) {
117
+ console.error(`${lc} Error evaluating regex pattern (${pattern}):`, err);
118
+ }
119
+ continue;
120
+ }
121
+
122
+ // 2. Negation / Inclusion rule ('!')
123
+ let isNegated = false;
124
+ let p = pattern;
125
+ if (p.startsWith('!')) {
126
+ isNegated = true;
127
+ p = p.substring(1).trim();
128
+ } else if (p.startsWith('include ')) {
129
+ isNegated = true;
130
+ p = p.substring('include '.length).trim();
131
+ }
132
+
133
+ // 3. Directory-only pattern check
134
+ const directoryOnly = p.endsWith('/');
135
+ if (directoryOnly && !isDirectory) {
136
+ continue; // Skip directory-only rule for non-directories
137
+ }
138
+
139
+ const cleanPattern = directoryOnly ? p.slice(0, -1) : p;
140
+
141
+ // 4. Exact basename or path match check
142
+ let matches = false;
143
+
144
+ if (cleanPattern === pathBasename || normalizedPath === cleanPattern) {
145
+ matches = true;
146
+ } else if (cleanPattern.includes('*') || cleanPattern.includes('?')) {
147
+ const rx = globToRegExp(cleanPattern);
148
+ matches = rx.test(normalizedPath) || rx.test(pathBasename);
149
+ } else {
150
+ // Substring directory segment match (e.g., 'node_modules' matches 'a/b/node_modules/c')
151
+ const segments = normalizedPath.split('/');
152
+ matches = segments.includes(cleanPattern) || normalizedPath.endsWith('/' + cleanPattern);
153
+ }
154
+
155
+ if (matches) {
156
+ isIgnored = !isNegated;
157
+ }
158
+ }
159
+
160
+ if (logalot) {
161
+ console.log(`${lc} relativePath: ${relativePath}, isIgnored: ${isIgnored}`);
162
+ }
163
+
164
+ return isIgnored;
165
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * @module glob-helper.respec
3
+ *
4
+ * Unit tests for native zero-dependency glob pattern matching engine.
5
+ */
6
+
7
+ import {
8
+ respecfully, ifWe, iReckon
9
+ } from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
10
+
11
+ import {
12
+ globToRegExp, parseFilterFileContent, isPathFiltered
13
+ } from './glob-helper.mjs';
14
+
15
+ const lc = `[apps/vcs-gib/src/engine/filter/glob-helper.respec.node.mts]`;
16
+
17
+ await respecfully(lc, `Native Glob Engine (Zero-Dependency)`, async () => {
18
+
19
+ await respecfully(lc, `parseFilterFileContent`, async () => {
20
+ await ifWe(lc, `parses filter file and strips comments & blank lines`, async () => {
21
+ const raw = `
22
+ # This is a comment
23
+ node_modules/
24
+ *.log
25
+
26
+ # Another comment
27
+ !important.log
28
+ `;
29
+ const patterns = parseFilterFileContent(raw);
30
+ iReckon(lc, patterns.length).willEqual(3);
31
+ iReckon(lc, patterns[0]).willEqual('node_modules/');
32
+ iReckon(lc, patterns[1]).willEqual('*.log');
33
+ iReckon(lc, patterns[2]).willEqual('!important.log');
34
+ });
35
+ });
36
+
37
+ await respecfully(lc, `globToRegExp`, async () => {
38
+ await ifWe(lc, `converts wildcard pattern to RegExp`, async () => {
39
+ const rx = globToRegExp('*.ts');
40
+ iReckon(lc, rx.test('index.ts')).isGonnaBeTrue();
41
+ iReckon(lc, rx.test('index.js')).isGonnaBeFalse();
42
+ });
43
+
44
+ await ifWe(lc, `handles recursive double asterisk **`, async () => {
45
+ const rx = globToRegExp('src/**/*.ts');
46
+ iReckon(lc, rx.test('src/index.ts')).isGonnaBeTrue();
47
+ iReckon(lc, rx.test('src/engine/item/item.ts')).isGonnaBeTrue();
48
+ iReckon(lc, rx.test('test/index.ts')).isGonnaBeFalse();
49
+ });
50
+ });
51
+
52
+ await respecfully(lc, `isPathFiltered`, async () => {
53
+ await ifWe(lc, `filters node_modules directory`, async () => {
54
+ const patterns = ['node_modules/', '*.log'];
55
+ iReckon(lc, isPathFiltered({
56
+ relativePath: 'node_modules',
57
+ isDirectory: true,
58
+ filterPatterns: patterns
59
+ })).isGonnaBeTrue();
60
+
61
+ iReckon(lc, isPathFiltered({
62
+ relativePath: 'src/node_modules/pkg',
63
+ isDirectory: true,
64
+ filterPatterns: patterns
65
+ })).isGonnaBeTrue();
66
+ });
67
+
68
+ await ifWe(lc, `honors negation rule ! prefix (last match wins)`, async () => {
69
+ const patterns = ['*.log', '!important.log'];
70
+ iReckon(lc, isPathFiltered({
71
+ relativePath: 'debug.log',
72
+ filterPatterns: patterns
73
+ })).isGonnaBeTrue();
74
+
75
+ iReckon(lc, isPathFiltered({
76
+ relativePath: 'important.log',
77
+ filterPatterns: patterns
78
+ })).isGonnaBeFalse();
79
+ });
80
+
81
+ await ifWe(lc, `handles JS regex pattern fallback`, async () => {
82
+ const patterns = ['regexp js ^test_.*\\.json$'];
83
+ iReckon(lc, isPathFiltered({
84
+ relativePath: 'test_data.json',
85
+ filterPatterns: patterns
86
+ })).isGonnaBeTrue();
87
+
88
+ iReckon(lc, isPathFiltered({
89
+ relativePath: 'prod_data.json',
90
+ filterPatterns: patterns
91
+ })).isGonnaBeFalse();
92
+ });
93
+ });
94
+ });