@hyperfrontend/project-scope 0.1.0 → 0.2.1

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 (177) hide show
  1. package/CHANGELOG.md +15 -3
  2. package/README.md +2 -0
  3. package/analyze.d.ts.map +1 -1
  4. package/cli/commands/analyze.d.ts +8 -0
  5. package/cli/commands/analyze.d.ts.map +1 -1
  6. package/cli/commands/config.d.ts +7 -0
  7. package/cli/commands/config.d.ts.map +1 -1
  8. package/cli/commands/deps.d.ts +6 -0
  9. package/cli/commands/deps.d.ts.map +1 -1
  10. package/cli/commands/tree.d.ts +12 -0
  11. package/cli/commands/tree.d.ts.map +1 -1
  12. package/cli/index.cjs.js +73 -206
  13. package/cli/index.cjs.js.map +1 -1
  14. package/cli/index.esm.js +73 -206
  15. package/cli/index.esm.js.map +1 -1
  16. package/cli/run.d.ts.map +1 -1
  17. package/core/cache.d.ts +1 -0
  18. package/core/cache.d.ts.map +1 -1
  19. package/core/encoding/convert.d.ts.map +1 -1
  20. package/core/encoding/detect.d.ts +5 -0
  21. package/core/encoding/detect.d.ts.map +1 -1
  22. package/core/encoding/index.cjs.js +2 -23
  23. package/core/encoding/index.cjs.js.map +1 -1
  24. package/core/encoding/index.esm.js +2 -23
  25. package/core/encoding/index.esm.js.map +1 -1
  26. package/core/errors/structured-errors.d.ts +2 -0
  27. package/core/errors/structured-errors.d.ts.map +1 -1
  28. package/core/fs/directory.d.ts +3 -0
  29. package/core/fs/directory.d.ts.map +1 -1
  30. package/core/fs/index.cjs.js +0 -14
  31. package/core/fs/index.cjs.js.map +1 -1
  32. package/core/fs/index.esm.js +0 -14
  33. package/core/fs/index.esm.js.map +1 -1
  34. package/core/fs/read.d.ts +11 -3
  35. package/core/fs/read.d.ts.map +1 -1
  36. package/core/fs/traversal.d.ts.map +1 -1
  37. package/core/index.cjs.js +11 -63
  38. package/core/index.cjs.js.map +1 -1
  39. package/core/index.esm.js +11 -63
  40. package/core/index.esm.js.map +1 -1
  41. package/core/logger.d.ts.map +1 -1
  42. package/core/path/index.cjs.js +5 -2
  43. package/core/path/index.cjs.js.map +1 -1
  44. package/core/path/index.esm.js +5 -2
  45. package/core/path/index.esm.js.map +1 -1
  46. package/core/path/normalize.d.ts.map +1 -1
  47. package/core/patterns/glob.d.ts +0 -4
  48. package/core/patterns/glob.d.ts.map +1 -1
  49. package/core/platform/detect.d.ts.map +1 -1
  50. package/core/platform/index.cjs.js +0 -10
  51. package/core/platform/index.cjs.js.map +1 -1
  52. package/core/platform/index.esm.js +0 -10
  53. package/core/platform/index.esm.js.map +1 -1
  54. package/core/platform/line-endings.d.ts.map +1 -1
  55. package/heuristics/dependencies/analyze.d.ts.map +1 -1
  56. package/heuristics/dependencies/index.cjs.js +0 -17
  57. package/heuristics/dependencies/index.cjs.js.map +1 -1
  58. package/heuristics/dependencies/index.esm.js +0 -17
  59. package/heuristics/dependencies/index.esm.js.map +1 -1
  60. package/heuristics/entry-points/discover.d.ts +34 -7
  61. package/heuristics/entry-points/discover.d.ts.map +1 -1
  62. package/heuristics/entry-points/index.cjs.js +6 -34
  63. package/heuristics/entry-points/index.cjs.js.map +1 -1
  64. package/heuristics/entry-points/index.esm.js +6 -34
  65. package/heuristics/entry-points/index.esm.js.map +1 -1
  66. package/heuristics/framework/index.cjs.js +1 -63
  67. package/heuristics/framework/index.cjs.js.map +1 -1
  68. package/heuristics/framework/index.esm.js +1 -63
  69. package/heuristics/framework/index.esm.js.map +1 -1
  70. package/heuristics/index.cjs.js +7 -88
  71. package/heuristics/index.cjs.js.map +1 -1
  72. package/heuristics/index.esm.js +7 -88
  73. package/heuristics/index.esm.js.map +1 -1
  74. package/heuristics/project-type/index.cjs.js +1 -63
  75. package/heuristics/project-type/index.cjs.js.map +1 -1
  76. package/heuristics/project-type/index.esm.js +1 -63
  77. package/heuristics/project-type/index.esm.js.map +1 -1
  78. package/index.cjs.js +86 -294
  79. package/index.cjs.js.map +1 -1
  80. package/index.esm.js +86 -294
  81. package/index.esm.js.map +1 -1
  82. package/nx/detect.d.ts.map +1 -1
  83. package/nx/devkit-loader.d.ts.map +1 -1
  84. package/nx/index.cjs.js +0 -29
  85. package/nx/index.cjs.js.map +1 -1
  86. package/nx/index.esm.js +0 -29
  87. package/nx/index.esm.js.map +1 -1
  88. package/nx/project-config.d.ts +2 -0
  89. package/nx/project-config.d.ts.map +1 -1
  90. package/package.json +9 -9
  91. package/project/config/index.cjs.js +4 -46
  92. package/project/config/index.cjs.js.map +1 -1
  93. package/project/config/index.esm.js +4 -46
  94. package/project/config/index.esm.js.map +1 -1
  95. package/project/config/patterns.d.ts.map +1 -1
  96. package/project/index.cjs.js +4 -47
  97. package/project/index.cjs.js.map +1 -1
  98. package/project/index.esm.js +4 -47
  99. package/project/index.esm.js.map +1 -1
  100. package/project/package/index.cjs.js +0 -11
  101. package/project/package/index.cjs.js.map +1 -1
  102. package/project/package/index.esm.js +0 -11
  103. package/project/package/index.esm.js.map +1 -1
  104. package/project/package/read.d.ts +1 -0
  105. package/project/package/read.d.ts.map +1 -1
  106. package/project/root/index.cjs.js +0 -11
  107. package/project/root/index.cjs.js.map +1 -1
  108. package/project/root/index.esm.js +0 -11
  109. package/project/root/index.esm.js.map +1 -1
  110. package/project/traversal/index.cjs.js +4 -28
  111. package/project/traversal/index.cjs.js.map +1 -1
  112. package/project/traversal/index.esm.js +4 -28
  113. package/project/traversal/index.esm.js.map +1 -1
  114. package/tech/backend/express.d.ts.map +1 -1
  115. package/tech/backend/fastify.d.ts.map +1 -1
  116. package/tech/backend/index.cjs.js +0 -17
  117. package/tech/backend/index.cjs.js.map +1 -1
  118. package/tech/backend/index.esm.js +0 -17
  119. package/tech/backend/index.esm.js.map +1 -1
  120. package/tech/backend/koa.d.ts.map +1 -1
  121. package/tech/backend/nestjs.d.ts.map +1 -1
  122. package/tech/build/index.cjs.js +0 -12
  123. package/tech/build/index.cjs.js.map +1 -1
  124. package/tech/build/index.esm.js +0 -12
  125. package/tech/build/index.esm.js.map +1 -1
  126. package/tech/frontend/index.cjs.js +0 -16
  127. package/tech/frontend/index.cjs.js.map +1 -1
  128. package/tech/frontend/index.esm.js +0 -16
  129. package/tech/frontend/index.esm.js.map +1 -1
  130. package/tech/frontend/qwik.d.ts.map +1 -1
  131. package/tech/frontend/remix.d.ts.map +1 -1
  132. package/tech/frontend/sveltekit.d.ts.map +1 -1
  133. package/tech/index.cjs.js +1 -63
  134. package/tech/index.cjs.js.map +1 -1
  135. package/tech/index.d.ts.map +1 -1
  136. package/tech/index.esm.js +1 -63
  137. package/tech/index.esm.js.map +1 -1
  138. package/tech/legacy/angularjs.d.ts.map +1 -1
  139. package/tech/legacy/backbone.d.ts.map +1 -1
  140. package/tech/legacy/ember.d.ts.map +1 -1
  141. package/tech/legacy/index.cjs.js +0 -26
  142. package/tech/legacy/index.cjs.js.map +1 -1
  143. package/tech/legacy/index.esm.js +0 -26
  144. package/tech/legacy/index.esm.js.map +1 -1
  145. package/tech/legacy/jquery.d.ts.map +1 -1
  146. package/tech/linting/biome.d.ts.map +1 -1
  147. package/tech/linting/index.cjs.js +0 -14
  148. package/tech/linting/index.cjs.js.map +1 -1
  149. package/tech/linting/index.esm.js +0 -14
  150. package/tech/linting/index.esm.js.map +1 -1
  151. package/tech/linting/prettier.d.ts.map +1 -1
  152. package/tech/monorepo/index.cjs.js +1 -13
  153. package/tech/monorepo/index.cjs.js.map +1 -1
  154. package/tech/monorepo/index.esm.js +1 -13
  155. package/tech/monorepo/index.esm.js.map +1 -1
  156. package/tech/monorepo/types.d.ts +1 -1
  157. package/tech/monorepo/types.d.ts.map +1 -1
  158. package/tech/shared-utils/detector-helpers.d.ts.map +1 -1
  159. package/tech/testing/index.cjs.js +0 -12
  160. package/tech/testing/index.cjs.js.map +1 -1
  161. package/tech/testing/index.esm.js +0 -12
  162. package/tech/testing/index.esm.js.map +1 -1
  163. package/tech/types/detectors.d.ts.map +1 -1
  164. package/tech/types/index.cjs.js +0 -27
  165. package/tech/types/index.cjs.js.map +1 -1
  166. package/tech/types/index.esm.js +0 -27
  167. package/tech/types/index.esm.js.map +1 -1
  168. package/vfs/commit.d.ts.map +1 -1
  169. package/vfs/diff.d.ts.map +1 -1
  170. package/vfs/factory.d.ts.map +1 -1
  171. package/vfs/fs-tree.d.ts.map +1 -1
  172. package/vfs/index.cjs.js +5 -46
  173. package/vfs/index.cjs.js.map +1 -1
  174. package/vfs/index.esm.js +5 -46
  175. package/vfs/index.esm.js.map +1 -1
  176. package/vfs/types.d.ts +1 -0
  177. package/vfs/types.d.ts.map +1 -1
package/index.cjs.js CHANGED
@@ -13,7 +13,6 @@ var node_os = require('node:os');
13
13
  *
14
14
  * @module @hyperfrontend/immutable-api-utils/built-in-copy/console
15
15
  */
16
- // Capture references at module initialization time
17
16
  const _console = globalThis.console;
18
17
  /**
19
18
  * (Safe copy) Outputs a message to the console.
@@ -88,44 +87,6 @@ _console.timeEnd.bind(_console);
88
87
  */
89
88
  _console.timeLog.bind(_console);
90
89
 
91
- /**
92
- * Safe copies of Array built-in static methods.
93
- *
94
- * These references are captured at module initialization time to protect against
95
- * prototype pollution attacks. Import only what you need for tree-shaking.
96
- *
97
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/array
98
- */
99
- // Capture references at module initialization time
100
- const _Array = globalThis.Array;
101
- /**
102
- * (Safe copy) Determines whether the passed value is an Array.
103
- */
104
- const isArray = _Array.isArray;
105
- /**
106
- * (Safe copy) Creates an array from an array-like or iterable object.
107
- */
108
- const from = _Array.from;
109
-
110
- /**
111
- * Safe copies of JSON built-in methods.
112
- *
113
- * These references are captured at module initialization time to protect against
114
- * prototype pollution attacks. Import only what you need for tree-shaking.
115
- *
116
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/json
117
- */
118
- // Capture references at module initialization time
119
- const _JSON = globalThis.JSON;
120
- /**
121
- * (Safe copy) Converts a JavaScript Object Notation (JSON) string into an object.
122
- */
123
- const parse = _JSON.parse;
124
- /**
125
- * (Safe copy) Converts a JavaScript value to a JavaScript Object Notation (JSON) string.
126
- */
127
- const stringify = _JSON.stringify;
128
-
129
90
  /**
130
91
  * Safe copies of Object built-in methods.
131
92
  *
@@ -134,7 +95,6 @@ const stringify = _JSON.stringify;
134
95
  *
135
96
  * @module @hyperfrontend/immutable-api-utils/built-in-copy/object
136
97
  */
137
- // Capture references at module initialization time
138
98
  const _Object = globalThis.Object;
139
99
  /**
140
100
  * (Safe copy) Prevents modification of existing property attributes and values,
@@ -162,30 +122,25 @@ const defineProperty = _Object.defineProperty;
162
122
  */
163
123
  const defineProperties = _Object.defineProperties;
164
124
 
125
+ const registeredClasses = [];
126
+
165
127
  /**
166
- * Safe copies of Set built-in via factory function.
167
- *
168
- * Since constructors cannot be safely captured via Object.assign, this module
169
- * provides a factory function that uses Reflect.construct internally.
128
+ * Safe copies of Array built-in static methods.
170
129
  *
171
130
  * These references are captured at module initialization time to protect against
172
131
  * prototype pollution attacks. Import only what you need for tree-shaking.
173
132
  *
174
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/set
133
+ * @module @hyperfrontend/immutable-api-utils/built-in-copy/array
175
134
  */
176
- // Capture references at module initialization time
177
- const _Set = globalThis.Set;
178
- const _Reflect$3 = globalThis.Reflect;
135
+ const _Array = globalThis.Array;
179
136
  /**
180
- * (Safe copy) Creates a new Set using the captured Set constructor.
181
- * Use this instead of `new Set()`.
182
- *
183
- * @param iterable - Optional iterable of values.
184
- * @returns A new Set instance.
137
+ * (Safe copy) Determines whether the passed value is an Array.
185
138
  */
186
- const createSet = (iterable) => _Reflect$3.construct(_Set, iterable ? [iterable] : []);
187
-
188
- const registeredClasses = [];
139
+ const isArray = _Array.isArray;
140
+ /**
141
+ * (Safe copy) Creates an array from an array-like or iterable object.
142
+ */
143
+ const from = _Array.from;
189
144
 
190
145
  /**
191
146
  * Returns the data type of the target.
@@ -221,9 +176,8 @@ const getType = (target) => {
221
176
  *
222
177
  * @module @hyperfrontend/immutable-api-utils/built-in-copy/error
223
178
  */
224
- // Capture references at module initialization time
225
179
  const _Error = globalThis.Error;
226
- const _Reflect$2 = globalThis.Reflect;
180
+ const _Reflect$3 = globalThis.Reflect;
227
181
  /**
228
182
  * (Safe copy) Creates a new Error using the captured Error constructor.
229
183
  * Use this instead of `new Error()`.
@@ -232,7 +186,7 @@ const _Reflect$2 = globalThis.Reflect;
232
186
  * @param options - Optional error options.
233
187
  * @returns A new Error instance.
234
188
  */
235
- const createError = (message, options) => _Reflect$2.construct(_Error, [message, options]);
189
+ const createError = (message, options) => _Reflect$3.construct(_Error, [message, options]);
236
190
 
237
191
  /**
238
192
  * Safe copies of Map built-in via factory function.
@@ -245,9 +199,8 @@ const createError = (message, options) => _Reflect$2.construct(_Error, [message,
245
199
  *
246
200
  * @module @hyperfrontend/immutable-api-utils/built-in-copy/map
247
201
  */
248
- // Capture references at module initialization time
249
202
  const _Map = globalThis.Map;
250
- const _Reflect$1 = globalThis.Reflect;
203
+ const _Reflect$2 = globalThis.Reflect;
251
204
  /**
252
205
  * (Safe copy) Creates a new Map using the captured Map constructor.
253
206
  * Use this instead of `new Map()`.
@@ -255,7 +208,7 @@ const _Reflect$1 = globalThis.Reflect;
255
208
  * @param iterable - Optional iterable of key-value pairs.
256
209
  * @returns A new Map instance.
257
210
  */
258
- const createMap = (iterable) => _Reflect$1.construct(_Map, iterable ? [iterable] : []);
211
+ const createMap = (iterable) => _Reflect$2.construct(_Map, iterable ? [iterable] : []);
259
212
 
260
213
  /**
261
214
  * Safe copies of Date built-in via factory function and static methods.
@@ -268,11 +221,10 @@ const createMap = (iterable) => _Reflect$1.construct(_Map, iterable ? [iterable]
268
221
  *
269
222
  * @module @hyperfrontend/immutable-api-utils/built-in-copy/date
270
223
  */
271
- // Capture references at module initialization time
272
224
  const _Date = globalThis.Date;
273
- const _Reflect = globalThis.Reflect;
225
+ const _Reflect$1 = globalThis.Reflect;
274
226
  function createDate(...args) {
275
- return _Reflect.construct(_Date, args);
227
+ return _Reflect$1.construct(_Date, args);
276
228
  }
277
229
  /**
278
230
  * (Safe copy) Returns the number of milliseconds elapsed since January 1, 1970 00:00:00 UTC.
@@ -287,15 +239,11 @@ const dateNow = _Date.now;
287
239
  *
288
240
  * @module @hyperfrontend/immutable-api-utils/built-in-copy/math
289
241
  */
290
- // Capture references at module initialization time
291
242
  const _Math = globalThis.Math;
292
243
  /**
293
244
  * (Safe copy) Returns the value of a number rounded to the nearest integer.
294
245
  */
295
246
  const round = _Math.round;
296
- // ============================================================================
297
- // Min/Max
298
- // ============================================================================
299
247
  /**
300
248
  * (Safe copy) Returns the larger of zero or more numbers.
301
249
  */
@@ -305,6 +253,28 @@ const max = _Math.max;
305
253
  */
306
254
  const min = _Math.min;
307
255
 
256
+ /**
257
+ * Safe copies of Set built-in via factory function.
258
+ *
259
+ * Since constructors cannot be safely captured via Object.assign, this module
260
+ * provides a factory function that uses Reflect.construct internally.
261
+ *
262
+ * These references are captured at module initialization time to protect against
263
+ * prototype pollution attacks. Import only what you need for tree-shaking.
264
+ *
265
+ * @module @hyperfrontend/immutable-api-utils/built-in-copy/set
266
+ */
267
+ const _Set = globalThis.Set;
268
+ const _Reflect = globalThis.Reflect;
269
+ /**
270
+ * (Safe copy) Creates a new Set using the captured Set constructor.
271
+ * Use this instead of `new Set()`.
272
+ *
273
+ * @param iterable - Optional iterable of values.
274
+ * @returns A new Set instance.
275
+ */
276
+ const createSet = (iterable) => _Reflect.construct(_Set, iterable ? [iterable] : []);
277
+
308
278
  /* eslint-disable @typescript-eslint/no-explicit-any */
309
279
  /**
310
280
  * Creates a wrapper function that only executes the wrapped function if the condition function returns true.
@@ -468,6 +438,24 @@ function notFnMsg(label) {
468
438
 
469
439
  createLogger(error, warn, log, info, debug);
470
440
 
441
+ /**
442
+ * Safe copies of JSON built-in methods.
443
+ *
444
+ * These references are captured at module initialization time to protect against
445
+ * prototype pollution attacks. Import only what you need for tree-shaking.
446
+ *
447
+ * @module @hyperfrontend/immutable-api-utils/built-in-copy/json
448
+ */
449
+ const _JSON = globalThis.JSON;
450
+ /**
451
+ * (Safe copy) Converts a JavaScript Object Notation (JSON) string into an object.
452
+ */
453
+ const parse = _JSON.parse;
454
+ /**
455
+ * (Safe copy) Converts a JavaScript value to a JavaScript Object Notation (JSON) string.
456
+ */
457
+ const stringify = _JSON.stringify;
458
+
471
459
  /**
472
460
  * Global log level registry.
473
461
  * Tracks all created scoped loggers to allow global log level changes.
@@ -611,14 +599,11 @@ function formatMessage(namespace, message, meta) {
611
599
  */
612
600
  function createScopedLogger(namespace, options = {}) {
613
601
  const { level = 'error', sanitizeSecrets = true } = options;
614
- // Create wrapper functions that add namespace prefix and sanitization
615
602
  const createLogFn = (baseFn) => (message, meta) => {
616
603
  const processedMeta = sanitizeSecrets && meta ? sanitize(meta) : meta;
617
604
  baseFn(formatMessage(namespace, message, processedMeta));
618
605
  };
619
- // Create base logger with wrapped functions
620
606
  const baseLogger = createLogger(createLogFn(error), createLogFn(warn), createLogFn(log), createLogFn(info), createLogFn(debug));
621
- // Set initial log level (use global override if set)
622
607
  baseLogger.setLogLevel(globalLogLevel ?? level);
623
608
  const scopedLogger = freeze({
624
609
  error: (message, meta) => baseLogger.error(message, meta),
@@ -629,7 +614,6 @@ function createScopedLogger(namespace, options = {}) {
629
614
  setLogLevel: baseLogger.setLogLevel,
630
615
  getLogLevel: baseLogger.getLogLevel,
631
616
  });
632
- // Register logger for global level management
633
617
  loggerRegistry.add(scopedLogger);
634
618
  return scopedLogger;
635
619
  }
@@ -1065,17 +1049,14 @@ function readDirectoryRecursive(dirPath, options) {
1065
1049
  entries = readDirectory(currentPath);
1066
1050
  }
1067
1051
  catch {
1068
- // Skip inaccessible directories
1069
1052
  fsDirLogger.debug('Skipping inaccessible directory', { path: currentPath });
1070
1053
  return;
1071
1054
  }
1072
1055
  for (const entry of entries) {
1073
- // Skip hidden files/dirs if not included
1074
1056
  if (!includeHidden && entry.name.startsWith('.')) {
1075
1057
  continue;
1076
1058
  }
1077
1059
  results.push({ ...entry, depth });
1078
- // Recurse into directories
1079
1060
  if (entry.isDirectory || (entry.isSymlink && followSymlinks && isDirectory(entry.path))) {
1080
1061
  walk(entry.path, depth + 1);
1081
1062
  }
@@ -1143,7 +1124,6 @@ function joinPosix(...paths) {
1143
1124
  function normalizePath(filePath) {
1144
1125
  if (!filePath)
1145
1126
  return '';
1146
- // Normalize path and convert backslashes to forward slashes
1147
1127
  const normalized = node_path.normalize(filePath);
1148
1128
  return node_path.sep === '\\' ? normalized.replace(/\\/g, '/') : normalized;
1149
1129
  }
@@ -1177,7 +1157,11 @@ function normalizeToNative(filePath) {
1177
1157
  * @returns Path with trailing slashes removed
1178
1158
  */
1179
1159
  function removeTrailingSlash(filePath) {
1180
- return filePath.replace(/[/\\]+$/, '');
1160
+ let i = filePath.length;
1161
+ while (i > 0 && (filePath[i - 1] === '/' || filePath[i - 1] === '\\')) {
1162
+ i--;
1163
+ }
1164
+ return filePath.slice(0, i);
1181
1165
  }
1182
1166
  /**
1183
1167
  * Append a forward slash to the path if not already present.
@@ -1351,7 +1335,6 @@ function traverseUpward(startPath, predicate) {
1351
1335
  }
1352
1336
  currentPath = node_path.dirname(currentPath);
1353
1337
  }
1354
- // Check root directory
1355
1338
  if (predicate(rootPath)) {
1356
1339
  fsTraversalLogger.debug('Upward traversal found match at root', { startPath, foundPath: rootPath });
1357
1340
  return rootPath;
@@ -1723,29 +1706,23 @@ const depsLogger = createScopedLogger('project-scope:heuristics:deps');
1723
1706
  */
1724
1707
  function extractImports(content) {
1725
1708
  const imports = [];
1726
- // ES import with 'from': import X from 'path' or import { X } from 'path'
1727
- // Use non-greedy match and avoid nested quantifiers by matching "from" keyword directly
1728
1709
  const esImportFromRegex = /import\s+.+?\s+from\s+['"]([^'"]+)['"]/g;
1729
1710
  let match;
1730
1711
  while ((match = esImportFromRegex.exec(content)) !== null) {
1731
1712
  imports.push(match[1]);
1732
1713
  }
1733
- // Side-effect import: import 'path'
1734
1714
  const sideEffectImportRegex = /import\s+['"]([^'"]+)['"]/g;
1735
1715
  while ((match = sideEffectImportRegex.exec(content)) !== null) {
1736
1716
  imports.push(match[1]);
1737
1717
  }
1738
- // Dynamic import: import('path')
1739
1718
  const dynamicImportRegex = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
1740
1719
  while ((match = dynamicImportRegex.exec(content)) !== null) {
1741
1720
  imports.push(match[1]);
1742
1721
  }
1743
- // require: require('path')
1744
1722
  const requireRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
1745
1723
  while ((match = requireRegex.exec(content)) !== null) {
1746
1724
  imports.push(match[1]);
1747
1725
  }
1748
- // Re-export: export * from 'path' or export { X } from 'path'
1749
1726
  const exportFromRegex = /export\s+.+?\s+from\s+['"]([^'"]+)['"]/g;
1750
1727
  while ((match = exportFromRegex.exec(content)) !== null) {
1751
1728
  imports.push(match[1]);
@@ -2059,7 +2036,6 @@ const cacheRegistry = createSet();
2059
2036
  function createCache(options) {
2060
2037
  const { ttl, maxSize } = options ?? {};
2061
2038
  const store = createMap();
2062
- // Track insertion order for FIFO eviction
2063
2039
  const insertionOrder = [];
2064
2040
  /**
2065
2041
  * Check if an entry is expired.
@@ -2110,12 +2086,10 @@ function createCache(options) {
2110
2086
  return entry.value;
2111
2087
  },
2112
2088
  set(key, value) {
2113
- // If key exists, remove from order first
2114
2089
  if (store.has(key)) {
2115
2090
  removeFromOrder(key);
2116
2091
  }
2117
2092
  else {
2118
- // Evict if needed before adding new entry
2119
2093
  evictIfNeeded();
2120
2094
  }
2121
2095
  // eslint-disable-next-line workspace/no-unsafe-builtin-methods -- Date.now() is needed for Jest fake timers compatibility
@@ -2148,7 +2122,6 @@ function createCache(options) {
2148
2122
  return [...insertionOrder];
2149
2123
  },
2150
2124
  };
2151
- // Register cache for global operations
2152
2125
  cacheRegistry.add(cache);
2153
2126
  return freeze(cache);
2154
2127
  }
@@ -2228,7 +2201,6 @@ function memoize(fn, options) {
2228
2201
  cache.set(key, result);
2229
2202
  return result;
2230
2203
  };
2231
- // Attach cache for direct access
2232
2204
  defineProperty(memoized, 'cache', {
2233
2205
  value: cache,
2234
2206
  writable: false,
@@ -2237,10 +2209,6 @@ function memoize(fn, options) {
2237
2209
  return memoized;
2238
2210
  }
2239
2211
 
2240
- /**
2241
- * Pattern matching utilities with ReDoS protection.
2242
- * Uses character-by-character matching instead of regex where possible.
2243
- */
2244
2212
  /**
2245
2213
  * Match path against glob pattern using safe character iteration.
2246
2214
  * Avoids regex to prevent ReDoS attacks.
@@ -2278,17 +2246,14 @@ function matchGlobPattern(path, pattern) {
2278
2246
  * @returns True if remaining segments match
2279
2247
  */
2280
2248
  function matchSegments(pathParts, patternParts, pathIdx, patternIdx) {
2281
- // Base cases
2282
2249
  if (pathIdx === pathParts.length && patternIdx === patternParts.length) {
2283
- return true; // Both exhausted = match
2250
+ return true;
2284
2251
  }
2285
2252
  if (patternIdx >= patternParts.length) {
2286
- return false; // Pattern exhausted but path remains
2253
+ return false;
2287
2254
  }
2288
2255
  const patternPart = patternParts[patternIdx];
2289
- // Handle ** (globstar) - matches zero or more directories
2290
2256
  if (patternPart === '**') {
2291
- // Try matching rest of pattern against current position and all future positions
2292
2257
  for (let i = pathIdx; i <= pathParts.length; i++) {
2293
2258
  if (matchSegments(pathParts, patternParts, i, patternIdx + 1)) {
2294
2259
  return true;
@@ -2297,10 +2262,9 @@ function matchSegments(pathParts, patternParts, pathIdx, patternIdx) {
2297
2262
  return false;
2298
2263
  }
2299
2264
  if (pathIdx >= pathParts.length) {
2300
- return false; // Path exhausted but pattern remains (and it's not **)
2265
+ return false;
2301
2266
  }
2302
2267
  const pathPart = pathParts[pathIdx];
2303
- // Match current segment
2304
2268
  if (matchSegment(pathPart, patternPart)) {
2305
2269
  return matchSegments(pathParts, patternParts, pathIdx + 1, patternIdx + 1);
2306
2270
  }
@@ -2320,12 +2284,10 @@ function matchSegment(text, pattern) {
2320
2284
  while (patternIdx < pattern.length) {
2321
2285
  const char = pattern[patternIdx];
2322
2286
  if (char === '*') {
2323
- // * matches zero or more characters
2324
2287
  patternIdx++;
2325
2288
  if (patternIdx === pattern.length) {
2326
- return true; // * at end matches rest of string
2289
+ return true;
2327
2290
  }
2328
- // Try matching rest of pattern at each position in text
2329
2291
  for (let i = textIdx; i <= text.length; i++) {
2330
2292
  if (matchSegmentFrom(text, i, pattern, patternIdx)) {
2331
2293
  return true;
@@ -2334,7 +2296,6 @@ function matchSegment(text, pattern) {
2334
2296
  return false;
2335
2297
  }
2336
2298
  else if (char === '?') {
2337
- // ? matches exactly one character
2338
2299
  if (textIdx >= text.length) {
2339
2300
  return false;
2340
2301
  }
@@ -2342,10 +2303,8 @@ function matchSegment(text, pattern) {
2342
2303
  patternIdx++;
2343
2304
  }
2344
2305
  else if (char === '{') {
2345
- // {a,b,c} matches any alternative
2346
2306
  const closeIdx = findClosingBrace(pattern, patternIdx);
2347
2307
  if (closeIdx === -1) {
2348
- // Unmatched brace, treat as literal
2349
2308
  if (textIdx >= text.length || text[textIdx] !== char) {
2350
2309
  return false;
2351
2310
  }
@@ -2363,7 +2322,6 @@ function matchSegment(text, pattern) {
2363
2322
  }
2364
2323
  }
2365
2324
  else {
2366
- // Literal character
2367
2325
  if (textIdx >= text.length || text[textIdx] !== char) {
2368
2326
  return false;
2369
2327
  }
@@ -2789,7 +2747,8 @@ const entryPointLogger = createScopedLogger('project-scope:heuristics:entry-poin
2789
2747
  */
2790
2748
  const entryPointCache = createCache({ ttl: 60000, maxSize: 50 });
2791
2749
  /**
2792
- * Common entry point patterns.
2750
+ * Common entry point patterns by project type.
2751
+ * Used for convention-based entry point discovery.
2793
2752
  */
2794
2753
  const ENTRY_POINT_PATTERNS = {
2795
2754
  /** Library entry patterns */
@@ -3072,7 +3031,6 @@ function collectAllDependencies(packageJson) {
3072
3031
  function parseVersionString(versionString) {
3073
3032
  if (versionString === undefined || versionString === null)
3074
3033
  return undefined;
3075
- // Manual parsing instead of regex to avoid ReDoS
3076
3034
  let start = 0;
3077
3035
  while (start < versionString.length) {
3078
3036
  const char = versionString[start];
@@ -3133,7 +3091,6 @@ function expressDetector(projectPath, packageJson) {
3133
3091
  version = parseVersionString(deps['express']);
3134
3092
  sources.push({ type: 'package.json', field: 'dependencies.express' });
3135
3093
  }
3136
- // @types/express (indicates usage)
3137
3094
  if (deps['@types/express']) {
3138
3095
  confidence += 10;
3139
3096
  sources.push({ type: 'package.json', field: 'dependencies.@types/express' });
@@ -3169,13 +3126,11 @@ function nestDetector(projectPath, packageJson) {
3169
3126
  let version;
3170
3127
  let configPath;
3171
3128
  const deps = collectAllDependencies(pkg);
3172
- // @nestjs/core package
3173
3129
  if (deps['@nestjs/core']) {
3174
3130
  confidence += 70;
3175
3131
  version = parseVersionString(deps['@nestjs/core']);
3176
3132
  sources.push({ type: 'package.json', field: 'dependencies.@nestjs/core' });
3177
3133
  }
3178
- // @nestjs/common
3179
3134
  if (deps['@nestjs/common']) {
3180
3135
  confidence += 15;
3181
3136
  sources.push({ type: 'package.json', field: 'dependencies.@nestjs/common' });
@@ -3226,7 +3181,6 @@ function fastifyDetector(projectPath, packageJson) {
3226
3181
  confidence += 15;
3227
3182
  sources.push({ type: 'package.json', field: 'dependencies (fastify plugins)' });
3228
3183
  }
3229
- // @types/fastify (older versions)
3230
3184
  if (deps['@types/fastify']) {
3231
3185
  confidence += 5;
3232
3186
  sources.push({ type: 'package.json', field: 'dependencies.@types/fastify' });
@@ -3261,7 +3215,6 @@ function koaDetector(projectPath, packageJson) {
3261
3215
  version = parseVersionString(deps['koa']);
3262
3216
  sources.push({ type: 'package.json', field: 'dependencies.koa' });
3263
3217
  }
3264
- // @types/koa
3265
3218
  if (deps['@types/koa']) {
3266
3219
  confidence += 10;
3267
3220
  sources.push({ type: 'package.json', field: 'dependencies.@types/koa' });
@@ -3840,7 +3793,6 @@ function remixDetector(projectPath, packageJson) {
3840
3793
  let confidence = 0;
3841
3794
  let version;
3842
3795
  const deps = collectAllDependencies(pkg);
3843
- // @remix-run packages
3844
3796
  if (deps['@remix-run/react']) {
3845
3797
  confidence += 70;
3846
3798
  version = parseVersionString(deps['@remix-run/react']);
@@ -4116,7 +4068,6 @@ function sveltekitDetector(projectPath, packageJson) {
4116
4068
  let confidence = 0;
4117
4069
  let version;
4118
4070
  const deps = collectAllDependencies(pkg);
4119
- // @sveltejs/kit package
4120
4071
  if (deps['@sveltejs/kit']) {
4121
4072
  confidence += 70;
4122
4073
  version = parseVersionString(deps['@sveltejs/kit']);
@@ -4195,13 +4146,11 @@ function qwikDetector(projectPath, packageJson) {
4195
4146
  let confidence = 0;
4196
4147
  let version;
4197
4148
  const deps = collectAllDependencies(pkg);
4198
- // @builder.io/qwik package
4199
4149
  if (deps['@builder.io/qwik']) {
4200
4150
  confidence += 70;
4201
4151
  version = parseVersionString(deps['@builder.io/qwik']);
4202
4152
  sources.push({ type: 'package.json', field: 'dependencies.@builder.io/qwik' });
4203
4153
  }
4204
- // @builder.io/qwik-city
4205
4154
  if (deps['@builder.io/qwik-city']) {
4206
4155
  confidence += 20;
4207
4156
  sources.push({ type: 'package.json', field: 'dependencies.@builder.io/qwik-city' });
@@ -4312,23 +4261,19 @@ function angularJSDetector(projectPath, packageJson) {
4312
4261
  let confidence = 0;
4313
4262
  let version;
4314
4263
  const deps = collectAllDependencies(pkg);
4315
- // AngularJS package (angular, not @angular/core)
4316
4264
  if (deps['angular']) {
4317
4265
  confidence += 70;
4318
4266
  version = parseVersionString(deps['angular']);
4319
4267
  sources.push({ type: 'package.json', field: 'dependencies.angular' });
4320
4268
  }
4321
- // AngularJS router
4322
4269
  if (deps['angular-route']) {
4323
4270
  confidence += 15;
4324
4271
  sources.push({ type: 'package.json', field: 'dependencies.angular-route' });
4325
4272
  }
4326
- // AngularJS resource
4327
4273
  if (deps['angular-resource']) {
4328
4274
  confidence += 10;
4329
4275
  sources.push({ type: 'package.json', field: 'dependencies.angular-resource' });
4330
4276
  }
4331
- // AngularJS animate
4332
4277
  if (deps['angular-animate']) {
4333
4278
  confidence += 5;
4334
4279
  sources.push({ type: 'package.json', field: 'dependencies.angular-animate' });
@@ -4359,23 +4304,19 @@ function backboneDetector(projectPath, packageJson) {
4359
4304
  let confidence = 0;
4360
4305
  let version;
4361
4306
  const deps = collectAllDependencies(pkg);
4362
- // Backbone package
4363
4307
  if (deps['backbone']) {
4364
4308
  confidence += 70;
4365
4309
  version = parseVersionString(deps['backbone']);
4366
4310
  sources.push({ type: 'package.json', field: 'dependencies.backbone' });
4367
- // Underscore (commonly used with Backbone)
4368
4311
  if (deps['underscore']) {
4369
4312
  confidence += 15;
4370
4313
  sources.push({ type: 'package.json', field: 'dependencies.underscore' });
4371
4314
  }
4372
- // Lodash can be used as underscore replacement
4373
4315
  if (deps['lodash']) {
4374
4316
  confidence += 5;
4375
4317
  sources.push({ type: 'package.json', field: 'dependencies.lodash' });
4376
4318
  }
4377
4319
  }
4378
- // Marionette (Backbone framework)
4379
4320
  if (deps['backbone.marionette'] || deps['marionette']) {
4380
4321
  confidence += 10;
4381
4322
  sources.push({ type: 'package.json', field: 'dependencies.backbone.marionette' });
@@ -4406,18 +4347,15 @@ function emberDetector(projectPath, packageJson) {
4406
4347
  let confidence = 0;
4407
4348
  let version;
4408
4349
  const deps = collectAllDependencies(pkg);
4409
- // Ember source package
4410
4350
  if (deps['ember-source']) {
4411
4351
  confidence += 70;
4412
4352
  version = parseVersionString(deps['ember-source']);
4413
4353
  sources.push({ type: 'package.json', field: 'dependencies.ember-source' });
4414
4354
  }
4415
- // Ember CLI
4416
4355
  if (deps['ember-cli']) {
4417
4356
  confidence += 20;
4418
4357
  sources.push({ type: 'package.json', field: 'devDependencies.ember-cli' });
4419
4358
  }
4420
- // Ember Data
4421
4359
  if (deps['ember-data']) {
4422
4360
  confidence += 10;
4423
4361
  sources.push({ type: 'package.json', field: 'dependencies.ember-data' });
@@ -4448,18 +4386,15 @@ function jqueryDetector(projectPath, packageJson) {
4448
4386
  let confidence = 0;
4449
4387
  let version;
4450
4388
  const deps = collectAllDependencies(pkg);
4451
- // jQuery package
4452
4389
  if (deps['jquery']) {
4453
4390
  confidence += 80;
4454
4391
  version = parseVersionString(deps['jquery']);
4455
4392
  sources.push({ type: 'package.json', field: 'dependencies.jquery' });
4456
4393
  }
4457
- // jQuery UI
4458
4394
  if (deps['jquery-ui']) {
4459
4395
  confidence += 10;
4460
4396
  sources.push({ type: 'package.json', field: 'dependencies.jquery-ui' });
4461
4397
  }
4462
- // jQuery plugins
4463
4398
  if (deps['jquery-validation']) {
4464
4399
  confidence += 5;
4465
4400
  sources.push({ type: 'package.json', field: 'dependencies.jquery-validation' });
@@ -4606,7 +4541,6 @@ function prettierDetector(projectPath, packageJson) {
4606
4541
  confidence += 30;
4607
4542
  sources.push({ type: 'package.json', field: 'prettier' });
4608
4543
  }
4609
- // .prettierignore file
4610
4544
  if (exists(node_path.join(projectPath, '.prettierignore'))) {
4611
4545
  confidence += 10;
4612
4546
  sources.push({ type: 'config-file', path: '.prettierignore' });
@@ -4701,7 +4635,6 @@ function biomeDetector(projectPath, packageJson) {
4701
4635
  let configPath;
4702
4636
  let version;
4703
4637
  const deps = collectAllDependencies(pkg);
4704
- // @biomejs/biome package
4705
4638
  if (deps['@biomejs/biome']) {
4706
4639
  confidence += 70;
4707
4640
  version = parseVersionString(deps['@biomejs/biome']);
@@ -4988,7 +4921,7 @@ function npmWorkspacesDetector(workspacePath, packageJson) {
4988
4921
  sources.push({ type: 'lockfile', path: 'package-lock.json' });
4989
4922
  }
4990
4923
  if (exists(node_path.join(workspacePath, 'yarn.lock'))) {
4991
- return null; // Let yarn workspace detector handle this
4924
+ return null;
4992
4925
  }
4993
4926
  if (confidence === 0) {
4994
4927
  return null;
@@ -5367,7 +5300,6 @@ function checkTsConfigStrict(projectPath) {
5367
5300
  if (!content)
5368
5301
  return undefined;
5369
5302
  try {
5370
- // Simple JSON parsing - doesn't handle comments but good enough for strict check
5371
5303
  const cleanContent = content.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '');
5372
5304
  const parsed = parse(cleanContent);
5373
5305
  return parsed?.compilerOptions?.strict === true;
@@ -5390,19 +5322,16 @@ function typescriptDetector(projectPath, packageJson) {
5390
5322
  let configPath;
5391
5323
  let version;
5392
5324
  const deps = collectAllDependencies(pkg);
5393
- // TypeScript package
5394
5325
  if (deps['typescript']) {
5395
5326
  confidence += 50;
5396
5327
  version = parseVersionString(deps['typescript']);
5397
5328
  sources.push({ type: 'package.json', field: 'dependencies.typescript' });
5398
5329
  }
5399
- // tsconfig.json
5400
5330
  if (exists(node_path.join(projectPath, 'tsconfig.json'))) {
5401
5331
  confidence += 40;
5402
5332
  configPath = 'tsconfig.json';
5403
5333
  sources.push({ type: 'config-file', path: 'tsconfig.json' });
5404
5334
  }
5405
- // tsconfig.*.json variants
5406
5335
  const tsconfigVariants = ['tsconfig.build.json', 'tsconfig.lib.json', 'tsconfig.spec.json', 'tsconfig.app.json'];
5407
5336
  for (const variant of tsconfigVariants) {
5408
5337
  if (exists(node_path.join(projectPath, variant))) {
@@ -5411,7 +5340,6 @@ function typescriptDetector(projectPath, packageJson) {
5411
5340
  break;
5412
5341
  }
5413
5342
  }
5414
- // @types packages
5415
5343
  const typePackages = keys(deps).filter((d) => d.startsWith('@types/'));
5416
5344
  if (typePackages.length > 0) {
5417
5345
  confidence += 10;
@@ -5445,24 +5373,20 @@ function flowDetector(projectPath, packageJson) {
5445
5373
  let configPath;
5446
5374
  let version;
5447
5375
  const deps = collectAllDependencies(pkg);
5448
- // flow-bin package
5449
5376
  if (deps['flow-bin']) {
5450
5377
  confidence += 60;
5451
5378
  version = parseVersionString(deps['flow-bin']);
5452
5379
  sources.push({ type: 'package.json', field: 'dependencies.flow-bin' });
5453
5380
  }
5454
- // .flowconfig
5455
5381
  if (exists(node_path.join(projectPath, '.flowconfig'))) {
5456
5382
  confidence += 40;
5457
5383
  configPath = '.flowconfig';
5458
5384
  sources.push({ type: 'config-file', path: '.flowconfig' });
5459
5385
  }
5460
- // flow-typed directory
5461
5386
  if (exists(node_path.join(projectPath, 'flow-typed'))) {
5462
5387
  confidence += 10;
5463
5388
  sources.push({ type: 'directory', path: 'flow-typed/' });
5464
5389
  }
5465
- // @babel/preset-flow
5466
5390
  if (deps['@babel/preset-flow']) {
5467
5391
  confidence += 10;
5468
5392
  sources.push({ type: 'package.json', field: 'dependencies.@babel/preset-flow' });
@@ -5486,7 +5410,6 @@ function flowDetector(projectPath, packageJson) {
5486
5410
  * @returns `true` if the content contains JSDoc type annotations.
5487
5411
  */
5488
5412
  function hasJsDocTypes(content) {
5489
- // Check for JSDoc type annotations
5490
5413
  return (content.includes('@type {') ||
5491
5414
  content.includes('@param {') ||
5492
5415
  content.includes('@returns {') ||
@@ -5505,14 +5428,11 @@ function jsdocDetector(projectPath, packageJson) {
5505
5428
  const sources = [];
5506
5429
  let confidence = 0;
5507
5430
  const deps = collectAllDependencies(pkg);
5508
- // jsdoc package
5509
5431
  if (deps['jsdoc']) {
5510
5432
  confidence += 30;
5511
5433
  sources.push({ type: 'package.json', field: 'dependencies.jsdoc' });
5512
5434
  }
5513
- // typescript with checkJs (JSDoc type checking)
5514
5435
  if (deps['typescript']) {
5515
- // Check if checkJs is enabled in tsconfig
5516
5436
  const tsconfigPath = node_path.join(projectPath, 'tsconfig.json');
5517
5437
  const content = readFileIfExists(tsconfigPath);
5518
5438
  if (content) {
@@ -5529,12 +5449,10 @@ function jsdocDetector(projectPath, packageJson) {
5529
5449
  }
5530
5450
  }
5531
5451
  }
5532
- // Check for jsconfig.json (VS Code JS type checking)
5533
5452
  if (exists(node_path.join(projectPath, 'jsconfig.json'))) {
5534
5453
  confidence += 40;
5535
5454
  sources.push({ type: 'config-file', path: 'jsconfig.json' });
5536
5455
  }
5537
- // Sample check for JSDoc annotations in source files
5538
5456
  const srcDir = node_path.join(projectPath, 'src');
5539
5457
  if (exists(srcDir)) {
5540
5458
  try {
@@ -5616,8 +5534,6 @@ const allDetectors = {
5616
5534
  function isDetectAllOptions(value) {
5617
5535
  if (typeof value !== 'object' || value === null)
5618
5536
  return false;
5619
- // DetectAllOptions has skipCache or packageJson fields specifically
5620
- // PackageJson never has skipCache field
5621
5537
  return 'skipCache' in value || 'packageJson' in value;
5622
5538
  }
5623
5539
  /**
@@ -5649,9 +5565,7 @@ function isDetectAllOptions(value) {
5649
5565
  * ```
5650
5566
  */
5651
5567
  function detectAll(projectPath, packageJsonOrOptions) {
5652
- // Handle backward-compatible arguments
5653
5568
  const options = isDetectAllOptions(packageJsonOrOptions) ? packageJsonOrOptions : { packageJson: packageJsonOrOptions };
5654
- // Check cache first (unless skipCache is true)
5655
5569
  if (!options.skipCache) {
5656
5570
  const cached = detectAllCache.get(projectPath);
5657
5571
  if (cached) {
@@ -5702,7 +5616,6 @@ function detectAll(projectPath, packageJsonOrOptions) {
5702
5616
  legacyFrameworks: result.legacyFrameworks.map((f) => f.id),
5703
5617
  testingFrameworks: result.testingFrameworks.map((f) => f.id),
5704
5618
  });
5705
- // Cache the result
5706
5619
  detectAllCache.set(projectPath, result);
5707
5620
  return result;
5708
5621
  }
@@ -6119,7 +6032,6 @@ function detectNxVersion(workspacePath) {
6119
6032
  if (packageJson) {
6120
6033
  const nxVersion = packageJson.devDependencies?.['nx'] ?? packageJson.dependencies?.['nx'];
6121
6034
  if (nxVersion) {
6122
- // Strip semver range characters (^, ~, >=, etc.)
6123
6035
  return nxVersion.replace(/^[\^~>=<]+/, '');
6124
6036
  }
6125
6037
  }
@@ -6148,14 +6060,12 @@ function getNxWorkspaceInfo(workspacePath) {
6148
6060
  }
6149
6061
  const nxJson = readJsonFileIfExists(node_path.join(workspacePath, 'nx.json'));
6150
6062
  if (!nxJson) {
6151
- // Check for workspace.json as fallback (older NX)
6152
6063
  const workspaceJson = readJsonFileIfExists(node_path.join(workspacePath, 'workspace.json'));
6153
6064
  if (!workspaceJson) {
6154
6065
  nxLogger.debug('No nx.json or workspace.json found', { workspacePath });
6155
6066
  return null;
6156
6067
  }
6157
6068
  nxLogger.debug('Using legacy workspace.json', { workspacePath });
6158
- // Create minimal nx.json from workspace.json
6159
6069
  return {
6160
6070
  root: workspacePath,
6161
6071
  version: detectNxVersion(workspacePath),
@@ -6204,7 +6114,6 @@ function tryLoadDevkit() {
6204
6114
  }
6205
6115
  devkitLogger.debug('Attempting to load @nx/devkit');
6206
6116
  try {
6207
- // Dynamic require to avoid bundling
6208
6117
  const devkit = require('@nx/devkit');
6209
6118
  devkitLogger.debug('@nx/devkit loaded successfully');
6210
6119
  cachedResult = { available: true, devkit };
@@ -6301,7 +6210,6 @@ function readProjectJson(projectPath) {
6301
6210
  */
6302
6211
  function getProjectConfig(projectPath, workspacePath) {
6303
6212
  nxConfigLogger.debug('Getting project config', { projectPath, workspacePath });
6304
- // Try project.json first
6305
6213
  const projectJson = readProjectJson(projectPath);
6306
6214
  if (projectJson) {
6307
6215
  nxConfigLogger.debug('Using project.json config', { projectPath, name: projectJson.name });
@@ -6310,7 +6218,6 @@ function getProjectConfig(projectPath, workspacePath) {
6310
6218
  root: projectJson.root ?? node_path.relative(workspacePath, projectPath),
6311
6219
  };
6312
6220
  }
6313
- // Try to infer from package.json nx field
6314
6221
  const packageJson = readPackageJsonIfExists(projectPath);
6315
6222
  if (packageJson && typeof packageJson['nx'] === 'object') {
6316
6223
  nxConfigLogger.debug('Using package.json nx field', { projectPath, name: packageJson.name });
@@ -6339,13 +6246,11 @@ function scanForProjects(dirPath, workspacePath, projects, maxDepth, currentDept
6339
6246
  try {
6340
6247
  const entries = readDirectory(dirPath);
6341
6248
  for (const entry of entries) {
6342
- // Skip node_modules and hidden directories
6343
6249
  if (entry.name.startsWith('.') || entry.name === 'node_modules' || entry.name === 'dist') {
6344
6250
  continue;
6345
6251
  }
6346
6252
  const fullPath = node_path.join(dirPath, entry.name);
6347
6253
  if (entry.isDirectory) {
6348
- // Check if this directory is an NX project
6349
6254
  if (isNxProject(fullPath)) {
6350
6255
  const config = getProjectConfig(fullPath, workspacePath);
6351
6256
  if (config) {
@@ -6357,7 +6262,6 @@ function scanForProjects(dirPath, workspacePath, projects, maxDepth, currentDept
6357
6262
  });
6358
6263
  }
6359
6264
  }
6360
- // Recursively scan subdirectories
6361
6265
  scanForProjects(fullPath, workspacePath, projects, maxDepth, currentDepth + 1);
6362
6266
  }
6363
6267
  }
@@ -6375,12 +6279,10 @@ function scanForProjects(dirPath, workspacePath, projects, maxDepth, currentDept
6375
6279
  */
6376
6280
  function discoverNxProjects(workspacePath) {
6377
6281
  const projects = createMap();
6378
- // Check for workspace.json (older NX format)
6379
6282
  const workspaceJson = readJsonFileIfExists(node_path.join(workspacePath, 'workspace.json'));
6380
6283
  if (workspaceJson?.projects) {
6381
6284
  for (const [name, config] of entries(workspaceJson.projects)) {
6382
6285
  if (typeof config === 'string') {
6383
- // Path reference to project directory
6384
6286
  const projectPath = node_path.join(workspacePath, config);
6385
6287
  const projectConfig = getProjectConfig(projectPath, workspacePath);
6386
6288
  if (projectConfig) {
@@ -6388,18 +6290,15 @@ function discoverNxProjects(workspacePath) {
6388
6290
  }
6389
6291
  }
6390
6292
  else if (typeof config === 'object' && config !== null) {
6391
- // Inline config
6392
6293
  projects.set(name, { name, ...config });
6393
6294
  }
6394
6295
  }
6395
6296
  return projects;
6396
6297
  }
6397
- // Scan for project.json files (newer NX format)
6398
6298
  const workspaceInfo = getNxWorkspaceInfo(workspacePath);
6399
6299
  const appsDir = workspaceInfo?.workspaceLayout.appsDir ?? 'apps';
6400
6300
  const libsDir = workspaceInfo?.workspaceLayout.libsDir ?? 'libs';
6401
6301
  const searchDirs = [appsDir, libsDir];
6402
- // Also check packages directory (common in some setups)
6403
6302
  if (exists(node_path.join(workspacePath, 'packages'))) {
6404
6303
  searchDirs.push('packages');
6405
6304
  }
@@ -6414,7 +6313,6 @@ function discoverNxProjects(workspacePath) {
6414
6313
  }
6415
6314
  }
6416
6315
  }
6417
- // Also check root-level projects (standalone projects in monorepo root)
6418
6316
  if (isNxProject(workspacePath)) {
6419
6317
  const config = readProjectJson(workspacePath);
6420
6318
  if (config) {
@@ -6447,10 +6345,8 @@ function buildSimpleProjectGraph(workspacePath, projects) {
6447
6345
  data: config,
6448
6346
  };
6449
6347
  dependencies[name] = [];
6450
- // Add implicit dependencies
6451
6348
  if (config.implicitDependencies) {
6452
6349
  for (const dep of config.implicitDependencies) {
6453
- // Skip negative dependencies (those starting with !)
6454
6350
  if (!dep.startsWith('!')) {
6455
6351
  dependencies[name].push({
6456
6352
  target: dep,
@@ -6467,7 +6363,6 @@ function buildSimpleProjectGraph(workspacePath, projects) {
6467
6363
  * Known configuration file patterns organized by type.
6468
6364
  */
6469
6365
  const CONFIG_PATTERNS = {
6470
- // Package Management
6471
6366
  'package.json': {
6472
6367
  patterns: ['package.json'],
6473
6368
  format: 'json',
@@ -6494,14 +6389,12 @@ const CONFIG_PATTERNS = {
6494
6389
  description: 'NPM configuration',
6495
6390
  sensitive: true,
6496
6391
  },
6497
- // TypeScript
6498
6392
  tsconfig: {
6499
6393
  patterns: ['tsconfig.json', 'tsconfig.*.json'],
6500
6394
  format: 'jsonc',
6501
6395
  description: 'TypeScript configuration',
6502
6396
  canExtend: true,
6503
6397
  },
6504
- // Monorepo
6505
6398
  nx: {
6506
6399
  patterns: ['nx.json'],
6507
6400
  format: 'json',
@@ -6527,7 +6420,6 @@ const CONFIG_PATTERNS = {
6527
6420
  format: 'json',
6528
6421
  description: 'Lerna configuration',
6529
6422
  },
6530
- // Build Tools
6531
6423
  webpack: {
6532
6424
  patterns: ['webpack.config.js', 'webpack.config.ts', 'webpack.config.cjs', 'webpack.config.mjs'],
6533
6425
  format: 'js',
@@ -6558,7 +6450,6 @@ const CONFIG_PATTERNS = {
6558
6450
  format: 'json',
6559
6451
  description: 'SWC configuration',
6560
6452
  },
6561
- // Testing
6562
6453
  jest: {
6563
6454
  patterns: ['jest.config.js', 'jest.config.ts', 'jest.config.mjs'],
6564
6455
  description: 'Jest configuration',
@@ -6575,7 +6466,6 @@ const CONFIG_PATTERNS = {
6575
6466
  patterns: ['playwright.config.js', 'playwright.config.ts'],
6576
6467
  description: 'Playwright configuration',
6577
6468
  },
6578
- // Framework configs
6579
6469
  next: {
6580
6470
  patterns: ['next.config.js', 'next.config.mjs', 'next.config.ts'],
6581
6471
  format: 'js',
@@ -6601,7 +6491,6 @@ const CONFIG_PATTERNS = {
6601
6491
  format: 'js',
6602
6492
  description: 'Astro configuration',
6603
6493
  },
6604
- // Linting & Formatting
6605
6494
  eslint: {
6606
6495
  patterns: [
6607
6496
  'eslint.config.js',
@@ -6620,14 +6509,12 @@ const CONFIG_PATTERNS = {
6620
6509
  format: 'json',
6621
6510
  description: 'Prettier configuration',
6622
6511
  },
6623
- // Environment (sensitive)
6624
6512
  env: {
6625
6513
  patterns: ['.env', '.env.*', '*.env'],
6626
6514
  format: 'dotenv',
6627
6515
  description: 'Environment variables',
6628
6516
  sensitive: true,
6629
6517
  },
6630
- // Git
6631
6518
  '.gitignore': {
6632
6519
  patterns: ['.gitignore'],
6633
6520
  format: 'text',
@@ -6798,12 +6685,8 @@ function getConfigPaths(type) {
6798
6685
  *
6799
6686
  * @module @hyperfrontend/immutable-api-utils/built-in-copy/number
6800
6687
  */
6801
- // Capture references at module initialization time
6802
6688
  const _parseInt = globalThis.parseInt;
6803
6689
  const _parseFloat = globalThis.parseFloat;
6804
- // ============================================================================
6805
- // Parsing
6806
- // ============================================================================
6807
6690
  /**
6808
6691
  * (Safe copy) Parses a string and returns an integer.
6809
6692
  */
@@ -7158,11 +7041,9 @@ const analyzeLogger = createScopedLogger('project-scope:analyze');
7158
7041
  * @returns Detected workspace type
7159
7042
  */
7160
7043
  function detectWorkspaceType(projectPath) {
7161
- // Check for NX
7162
7044
  if (isNxWorkspace(projectPath) || findNxWorkspaceRoot(projectPath) !== null) {
7163
7045
  return 'nx';
7164
7046
  }
7165
- // Check for common monorepo markers
7166
7047
  if (exists(node_path.resolve(projectPath, 'turbo.json'))) {
7167
7048
  return 'turborepo';
7168
7049
  }
@@ -7172,7 +7053,6 @@ function detectWorkspaceType(projectPath) {
7172
7053
  if (exists(node_path.resolve(projectPath, 'pnpm-workspace.yaml'))) {
7173
7054
  return 'pnpm';
7174
7055
  }
7175
- // Check package.json for workspaces
7176
7056
  const pkg = readPackageJsonIfExists(projectPath);
7177
7057
  if (pkg?.workspaces) {
7178
7058
  if (exists(node_path.resolve(projectPath, 'yarn.lock'))) {
@@ -7181,7 +7061,7 @@ function detectWorkspaceType(projectPath) {
7181
7061
  if (exists(node_path.resolve(projectPath, 'package-lock.json'))) {
7182
7062
  return 'npm';
7183
7063
  }
7184
- return 'npm'; // default for workspaces
7064
+ return 'npm';
7185
7065
  }
7186
7066
  return 'standalone';
7187
7067
  }
@@ -7193,15 +7073,12 @@ function detectWorkspaceType(projectPath) {
7193
7073
  * @returns True if the analysis should be performed
7194
7074
  */
7195
7075
  function shouldInclude(type, options) {
7196
- // If include list is specified, item must be in it
7197
7076
  if (options?.include && options.include.length > 0) {
7198
7077
  return options.include.includes(type);
7199
7078
  }
7200
- // If exclude list is specified, item must not be in it
7201
7079
  if (options?.exclude && options.exclude.length > 0) {
7202
7080
  return !options.exclude.includes(type);
7203
7081
  }
7204
- // Default: include everything
7205
7082
  return true;
7206
7083
  }
7207
7084
  /**
@@ -7250,28 +7127,22 @@ function normalizeConfigFormat(format) {
7250
7127
  function analyzeProject(projectPath, options) {
7251
7128
  const startTime = dateNow();
7252
7129
  const resolvedPath = node_path.resolve(projectPath);
7253
- // Set log level based on verbose option
7254
7130
  if (options?.verbose) {
7255
7131
  analyzeLogger.setLogLevel('debug');
7256
7132
  }
7257
7133
  analyzeLogger.debug('Starting project analysis', { path: resolvedPath, options });
7258
- // Read package.json once for reuse
7259
7134
  const packageJson = readPackageJsonIfExists(resolvedPath);
7260
7135
  analyzeLogger.debug('Package.json loaded', { found: !!packageJson, name: packageJson?.name });
7261
- // Detect project and workspace types
7262
7136
  const projectTypeDetection = detectProjectType(resolvedPath, {
7263
7137
  skipTechDetection: options?.depth === 'basic',
7264
7138
  });
7265
7139
  const projectType = projectTypeDetection.type;
7266
7140
  const workspaceType = detectWorkspaceType(resolvedPath);
7267
7141
  analyzeLogger.debug('Project type detected', { projectType, workspaceType });
7268
- // Get project name from package.json or directory name
7269
7142
  const projectName = packageJson?.name ?? node_path.basename(resolvedPath);
7270
- // Run all technology detectors
7271
7143
  const detections = shouldInclude('frameworks', options) || shouldInclude('buildTools', options) || shouldInclude('testing', options)
7272
7144
  ? detectAll(resolvedPath, packageJson ?? undefined)
7273
7145
  : null;
7274
- // Convert framework detections to FrameworkInfo
7275
7146
  const frameworks = shouldInclude('frameworks', options) && detections
7276
7147
  ? [
7277
7148
  ...detections.frontendFrameworks.map((d) => ({
@@ -7291,7 +7162,6 @@ function analyzeProject(projectPath, options) {
7291
7162
  })),
7292
7163
  ]
7293
7164
  : [];
7294
- // Convert build tool detections
7295
7165
  const buildTools = shouldInclude('buildTools', options) && detections
7296
7166
  ? detections.buildTools.map((d) => ({
7297
7167
  id: d.id,
@@ -7301,7 +7171,6 @@ function analyzeProject(projectPath, options) {
7301
7171
  confidence: d.confidence,
7302
7172
  }))
7303
7173
  : [];
7304
- // Convert testing framework detections
7305
7174
  const testingFrameworks = shouldInclude('testing', options) && detections
7306
7175
  ? detections.testingFrameworks.map((d) => ({
7307
7176
  id: d.id,
@@ -7312,7 +7181,6 @@ function analyzeProject(projectPath, options) {
7312
7181
  confidence: d.confidence,
7313
7182
  }))
7314
7183
  : [];
7315
- // Discover entry points
7316
7184
  const entryPoints = shouldInclude('entryPoints', options)
7317
7185
  ? discoverEntryPoints(resolvedPath).map((e) => ({
7318
7186
  path: e.path,
@@ -7320,7 +7188,6 @@ function analyzeProject(projectPath, options) {
7320
7188
  confidence: e.confidence,
7321
7189
  }))
7322
7190
  : [];
7323
- // Detect configuration files
7324
7191
  const configFiles = shouldInclude('configs', options)
7325
7192
  ? detectConfigs(resolvedPath).map((c) => ({
7326
7193
  path: c.path,
@@ -7329,7 +7196,6 @@ function analyzeProject(projectPath, options) {
7329
7196
  tool: c.type,
7330
7197
  }))
7331
7198
  : [];
7332
- // Get dependency summary
7333
7199
  let dependencies;
7334
7200
  if (shouldInclude('dependencies', options)) {
7335
7201
  const deps = getProjectDependencies(resolvedPath);
@@ -7350,7 +7216,6 @@ function analyzeProject(projectPath, options) {
7350
7216
  total: 0,
7351
7217
  };
7352
7218
  }
7353
- // Build metadata
7354
7219
  const metadata = {
7355
7220
  timestamp: createDate(),
7356
7221
  durationMs: dateNow() - startTime,
@@ -7425,15 +7290,12 @@ function formatWorkspaceType(type) {
7425
7290
  */
7426
7291
  function formatAnalysisText(result) {
7427
7292
  const lines = [];
7428
- // Header
7429
7293
  lines.push(`Project Analysis: ${result.name}`);
7430
7294
  lines.push('='.repeat(30));
7431
7295
  lines.push('');
7432
- // Basic info
7433
7296
  lines.push(`Type: ${formatProjectType(result.projectType)}`);
7434
7297
  lines.push(`Workspace: ${formatWorkspaceType(result.workspaceType)}`);
7435
7298
  lines.push('');
7436
- // Frameworks
7437
7299
  if (result.frameworks.length > 0) {
7438
7300
  lines.push('Frameworks:');
7439
7301
  for (const framework of result.frameworks) {
@@ -7447,7 +7309,6 @@ function formatAnalysisText(result) {
7447
7309
  }
7448
7310
  lines.push('');
7449
7311
  }
7450
- // Build tools
7451
7312
  if (result.buildTools.length > 0) {
7452
7313
  lines.push('Build Tools:');
7453
7314
  for (const tool of result.buildTools) {
@@ -7456,7 +7317,6 @@ function formatAnalysisText(result) {
7456
7317
  }
7457
7318
  lines.push('');
7458
7319
  }
7459
- // Testing
7460
7320
  if (result.testingFrameworks.length > 0) {
7461
7321
  lines.push('Testing:');
7462
7322
  for (const framework of result.testingFrameworks) {
@@ -7465,7 +7325,6 @@ function formatAnalysisText(result) {
7465
7325
  }
7466
7326
  lines.push('');
7467
7327
  }
7468
- // Entry points
7469
7328
  if (result.entryPoints.length > 0) {
7470
7329
  lines.push('Entry Points:');
7471
7330
  for (const entry of result.entryPoints.slice(0, 5)) {
@@ -7476,7 +7335,6 @@ function formatAnalysisText(result) {
7476
7335
  }
7477
7336
  lines.push('');
7478
7337
  }
7479
- // Configurations
7480
7338
  if (result.configFiles.length > 0) {
7481
7339
  lines.push('Configurations:');
7482
7340
  for (const config of result.configFiles.slice(0, 8)) {
@@ -7487,7 +7345,6 @@ function formatAnalysisText(result) {
7487
7345
  }
7488
7346
  lines.push('');
7489
7347
  }
7490
- // Dependencies summary
7491
7348
  lines.push('Dependencies:');
7492
7349
  lines.push(` Production: ${result.dependencies.production}`);
7493
7350
  lines.push(` Development: ${result.dependencies.development}`);
@@ -7574,7 +7431,7 @@ function parseAnalyzeArgs(args) {
7574
7431
  exclude: { type: 'string', short: 'e' },
7575
7432
  },
7576
7433
  allowPositionals: true,
7577
- strict: false, // Allow global options to pass through
7434
+ strict: false,
7578
7435
  });
7579
7436
  const format = values.format;
7580
7437
  const depth = values.depth;
@@ -7633,7 +7490,6 @@ const analyzeCommandDef = {
7633
7490
  description: 'Analyze project structure and tech stack',
7634
7491
  execute(args, globalOptions) {
7635
7492
  const options = parseAnalyzeArgs(args);
7636
- // Global --json flag overrides format
7637
7493
  if (globalOptions.json) {
7638
7494
  options.format = 'json';
7639
7495
  }
@@ -7940,12 +7796,10 @@ Examples:
7940
7796
  function formatDependencyList(deps, maxItems = 20) {
7941
7797
  const lines = [];
7942
7798
  const depEntries = entries(deps);
7943
- // Sort alphabetically
7944
7799
  depEntries.sort((a, b) => a[0].localeCompare(b[0]));
7945
7800
  const displayCount = min(depEntries.length, maxItems);
7946
7801
  for (let i = 0; i < displayCount; i++) {
7947
7802
  const [name, version] = depEntries[i];
7948
- // Pad name to align versions
7949
7803
  const paddedName = name.padEnd(30);
7950
7804
  lines.push(` ${paddedName} ${version}`);
7951
7805
  }
@@ -8188,13 +8042,10 @@ function buildTree(rootPath, walkEntries, options) {
8188
8042
  isDirectory: true,
8189
8043
  children: [],
8190
8044
  };
8191
- // Create a map for quick lookup
8192
8045
  const nodeMap = createMap();
8193
8046
  nodeMap.set('.', root);
8194
- // Sort entries by path for proper parent-child relationships
8195
8047
  const sortedEntries = [...walkEntries].sort((a, b) => a.relativePath.localeCompare(b.relativePath));
8196
8048
  for (const entry of sortedEntries) {
8197
- // Skip based on filters
8198
8049
  if (options.dirsOnly && !entry.isDirectory)
8199
8050
  continue;
8200
8051
  if (options.filesOnly && entry.isDirectory)
@@ -8205,7 +8056,6 @@ function buildTree(rootPath, walkEntries, options) {
8205
8056
  isDirectory: entry.isDirectory,
8206
8057
  children: [],
8207
8058
  };
8208
- // Add size/modified if requested
8209
8059
  if ((options.showSize || options.showModified) && entry.isFile) {
8210
8060
  const stats = getFileStat(entry.path);
8211
8061
  if (stats) {
@@ -8215,9 +8065,8 @@ function buildTree(rootPath, walkEntries, options) {
8215
8065
  node.modified = stats.modified;
8216
8066
  }
8217
8067
  }
8218
- // Find parent
8219
8068
  const parts = entry.relativePath.split('/');
8220
- parts.pop(); // Remove current entry name
8069
+ parts.pop();
8221
8070
  const parentPath = parts.join('/') || '.';
8222
8071
  const parent = nodeMap.get(parentPath);
8223
8072
  if (parent) {
@@ -8238,11 +8087,9 @@ function buildTree(rootPath, walkEntries, options) {
8238
8087
  */
8239
8088
  function renderTreeText(node, options, prefix = '', isLast = true) {
8240
8089
  const lines = [];
8241
- // Current line
8242
8090
  const connector = isLast ? '└── ' : '├── ';
8243
8091
  const dirMark = node.isDirectory ? '/' : '';
8244
8092
  let line = `${prefix}${connector}${node.name}${dirMark}`;
8245
- // Add size/modified
8246
8093
  const meta = [];
8247
8094
  if (options.showSize && node.size !== undefined) {
8248
8095
  meta.push(formatSize(node.size));
@@ -8254,10 +8101,8 @@ function renderTreeText(node, options, prefix = '', isLast = true) {
8254
8101
  line += ` [${meta.join(' ')}]`;
8255
8102
  }
8256
8103
  lines.push(line);
8257
- // Process children
8258
8104
  const childPrefix = prefix + (isLast ? ' ' : '│ ');
8259
8105
  const sortedChildren = [...node.children].sort((a, b) => {
8260
- // Directories first, then alphabetically
8261
8106
  if (a.isDirectory && !b.isDirectory)
8262
8107
  return -1;
8263
8108
  if (!a.isDirectory && b.isDirectory)
@@ -8281,9 +8126,7 @@ function renderTreeText(node, options, prefix = '', isLast = true) {
8281
8126
  */
8282
8127
  function formatTreeText(rootPath, tree, options) {
8283
8128
  const lines = [];
8284
- // Root line
8285
8129
  lines.push(node_path.basename(rootPath));
8286
- // Count stats
8287
8130
  let dirCount = 0;
8288
8131
  let fileCount = 0;
8289
8132
  /**
@@ -8302,7 +8145,6 @@ function formatTreeText(rootPath, tree, options) {
8302
8145
  countNodes(child);
8303
8146
  }
8304
8147
  }
8305
- // Render children of root
8306
8148
  const sortedChildren = [...tree.children].sort((a, b) => {
8307
8149
  if (a.isDirectory && !b.isDirectory)
8308
8150
  return -1;
@@ -8316,7 +8158,6 @@ function formatTreeText(rootPath, tree, options) {
8316
8158
  lines.push(...renderTreeText(child, options, '', isLast));
8317
8159
  countNodes(child);
8318
8160
  }
8319
- // Summary
8320
8161
  lines.push('');
8321
8162
  const dirText = dirCount === 1 ? '1 directory' : `${dirCount} directories`;
8322
8163
  const fileText = fileCount === 1 ? '1 file' : `${fileCount} files`;
@@ -8377,7 +8218,6 @@ function parseTreeArgs(args) {
8377
8218
  function treeCommand(options) {
8378
8219
  const rootPath = options.path ? node_path.resolve(options.path) : process.cwd();
8379
8220
  try {
8380
- // Collect entries via walk
8381
8221
  const walkEntries = [];
8382
8222
  walkDirectory(rootPath, (entry) => {
8383
8223
  walkEntries.push(entry);
@@ -8387,9 +8227,7 @@ function treeCommand(options) {
8387
8227
  ignorePatterns: options.ignore,
8388
8228
  includeHidden: false,
8389
8229
  });
8390
- // Build tree structure
8391
8230
  const tree = buildTree(rootPath, walkEntries, options);
8392
- // Format output
8393
8231
  let output;
8394
8232
  if (options.format === 'json') {
8395
8233
  output = formatTreeJson(tree);
@@ -8448,6 +8286,9 @@ Examples:
8448
8286
 
8449
8287
  /** Logger for CLI operations */
8450
8288
  const cliLogger = createScopedLogger('project-scope:cli');
8289
+ /** Output printer for user-facing CLI output (help, version, command results). */
8290
+ const output = createLogger(error, undefined, log);
8291
+ output.setLogLevel('log');
8451
8292
  /** Library version */
8452
8293
  const VERSION = '0.1.0';
8453
8294
  /**
@@ -8463,7 +8304,7 @@ const commands = {
8463
8304
  * Print general help information.
8464
8305
  */
8465
8306
  function printHelp() {
8466
- log(`
8307
+ output.log(`
8467
8308
  project-scope <command> [options]
8468
8309
 
8469
8310
  A tool for analyzing JavaScript/TypeScript project structure and tech stack.
@@ -8495,7 +8336,7 @@ Examples:
8495
8336
  * Print CLI version.
8496
8337
  */
8497
8338
  function printVersion() {
8498
- log(`project-scope v${VERSION}`);
8339
+ output.log(`project-scope v${VERSION}`);
8499
8340
  }
8500
8341
  /**
8501
8342
  * Parse global options from command line arguments.
@@ -8556,24 +8397,24 @@ function run(args) {
8556
8397
  setGlobalLogLevel('debug');
8557
8398
  }
8558
8399
  cliLogger.debug('CLI invoked', { args, globalOptions });
8400
+ const commandName = args[0];
8559
8401
  if (globalOptions.version) {
8560
8402
  printVersion();
8561
8403
  return { exitCode: 0 };
8562
8404
  }
8563
- if (globalOptions.help && (args.length === 1 || !commands[args[0]])) {
8405
+ if (globalOptions.help && (args.length === 1 || !commands[commandName])) {
8564
8406
  printHelp();
8565
8407
  return { exitCode: 0 };
8566
8408
  }
8567
- const commandName = args[0];
8568
8409
  const command = commands[commandName];
8569
8410
  if (!command) {
8570
8411
  cliLogger.warn('Unknown command requested', { commandName });
8571
- error(`Unknown command: ${commandName}`);
8572
- error('Run "project-scope --help" for usage information.');
8412
+ output.error(`Unknown command: ${commandName}`);
8413
+ output.error('Run "project-scope --help" for usage information.');
8573
8414
  return { exitCode: 1, error: `Unknown command: ${commandName}` };
8574
8415
  }
8575
8416
  if (globalOptions.help) {
8576
- log(command.getHelp());
8417
+ output.log(command.getHelp());
8577
8418
  return { exitCode: 0 };
8578
8419
  }
8579
8420
  const commandArgs = args.slice(1);
@@ -8581,11 +8422,11 @@ function run(args) {
8581
8422
  const result = command.execute(commandArgs, globalOptions);
8582
8423
  cliLogger.debug('Command completed', { commandName, exitCode: result.exitCode });
8583
8424
  if (result.output) {
8584
- log(result.output);
8425
+ output.log(result.output);
8585
8426
  }
8586
8427
  if (result.error) {
8587
8428
  cliLogger.error('Command error', { commandName, error: result.error });
8588
- error(result.error);
8429
+ output.error(result.error);
8589
8430
  }
8590
8431
  return result;
8591
8432
  }
@@ -8621,25 +8462,22 @@ const BINARY_SIGNATURES = [
8621
8462
  */
8622
8463
  function detectEncodingInfo(buffer) {
8623
8464
  encodingLogger.debug('Detecting encoding info', { bufferSize: buffer.length });
8624
- // Check for UTF-8 BOM
8625
8465
  if (buffer.length >= 3) {
8626
8466
  if (buffer[0] === UTF8_BOM_BYTES[0] && buffer[1] === UTF8_BOM_BYTES[1] && buffer[2] === UTF8_BOM_BYTES[2]) {
8627
8467
  encodingLogger.debug('Detected UTF-8 BOM');
8628
8468
  return { type: 'text', encoding: 'utf-8', hasBom: true };
8629
8469
  }
8630
8470
  }
8631
- // Check for UTF-16 BOMs
8632
8471
  if (buffer.length >= 2) {
8633
8472
  if (buffer[0] === UTF16_BE_BOM_BYTES[0] && buffer[1] === UTF16_BE_BOM_BYTES[1]) {
8634
8473
  encodingLogger.debug('Detected UTF-16 BE BOM');
8635
- return { type: 'text', encoding: 'utf16le', hasBom: true }; // Node treats BE through utf16le
8474
+ return { type: 'text', encoding: 'utf16le', hasBom: true };
8636
8475
  }
8637
8476
  if (buffer[0] === UTF16_LE_BOM_BYTES[0] && buffer[1] === UTF16_LE_BOM_BYTES[1]) {
8638
8477
  encodingLogger.debug('Detected UTF-16 LE BOM');
8639
8478
  return { type: 'text', encoding: 'utf16le', hasBom: true };
8640
8479
  }
8641
8480
  }
8642
- // Check for binary signatures
8643
8481
  for (const { signature, description } of BINARY_SIGNATURES) {
8644
8482
  if (buffer.length >= signature.length) {
8645
8483
  let matches = true;
@@ -8655,7 +8493,6 @@ function detectEncodingInfo(buffer) {
8655
8493
  }
8656
8494
  }
8657
8495
  }
8658
- // Check for null bytes (usually indicates binary)
8659
8496
  const sampleSize = min(buffer.length, 8000);
8660
8497
  for (let i = 0; i < sampleSize; i++) {
8661
8498
  if (buffer[i] === 0) {
@@ -8674,22 +8511,18 @@ function detectEncodingInfo(buffer) {
8674
8511
  */
8675
8512
  function detectEncoding(buffer) {
8676
8513
  if (buffer.length >= 3) {
8677
- // Check for UTF-8 BOM
8678
8514
  if (buffer[0] === UTF8_BOM_BYTES[0] && buffer[1] === UTF8_BOM_BYTES[1] && buffer[2] === UTF8_BOM_BYTES[2]) {
8679
8515
  return 'utf-8';
8680
8516
  }
8681
8517
  }
8682
8518
  if (buffer.length >= 2) {
8683
- // Check for UTF-16 LE BOM
8684
8519
  if (buffer[0] === UTF16_LE_BOM_BYTES[0] && buffer[1] === UTF16_LE_BOM_BYTES[1]) {
8685
8520
  return 'utf16le';
8686
8521
  }
8687
- // Check for UTF-16 BE BOM
8688
8522
  if (buffer[0] === UTF16_BE_BOM_BYTES[0] && buffer[1] === UTF16_BE_BOM_BYTES[1]) {
8689
- return 'utf16le'; // Node.js handles BE through utf16le
8523
+ return 'utf16le';
8690
8524
  }
8691
8525
  }
8692
- // Default to UTF-8
8693
8526
  return 'utf-8';
8694
8527
  }
8695
8528
  /**
@@ -8759,10 +8592,8 @@ function bufferToString(content, encoding) {
8759
8592
  convertLogger.debug('Using provided encoding', { encoding });
8760
8593
  return content.toString(encoding);
8761
8594
  }
8762
- // Auto-detect and convert
8763
8595
  const info = detectEncodingInfo(content);
8764
8596
  if (info.type === 'text') {
8765
- // Remove BOM if present
8766
8597
  let offset = 0;
8767
8598
  if (info.hasBom) {
8768
8599
  offset = info.encoding === 'utf-8' ? 3 : 2;
@@ -8815,19 +8646,14 @@ function detectCaseSensitivity() {
8815
8646
  if (cachedCaseSensitive !== null) {
8816
8647
  return cachedCaseSensitive;
8817
8648
  }
8818
- // Quick check based on platform
8819
8649
  if (process.platform === 'win32') {
8820
8650
  cachedCaseSensitive = false;
8821
8651
  return false;
8822
8652
  }
8823
- // macOS is typically case-insensitive by default
8824
8653
  if (process.platform === 'darwin') {
8825
- // Could be case-sensitive HFS+/APFS, but assume insensitive by default
8826
8654
  cachedCaseSensitive = false;
8827
8655
  return false;
8828
8656
  }
8829
- // Test actual file system behavior for Linux and others
8830
- // Use mkdtempSync to create a secure temporary directory
8831
8657
  let secureTestDir = null;
8832
8658
  try {
8833
8659
  secureTestDir = node_fs.mkdtempSync(node_path.join(node_os.tmpdir(), 'case-sensitivity-test-'));
@@ -8838,12 +8664,9 @@ function detectCaseSensitivity() {
8838
8664
  node_fs.unlinkSync(testFile);
8839
8665
  }
8840
8666
  catch {
8841
- // Default to case-sensitive on Linux/Unix if test fails
8842
- // (win32 and darwin already returned early, so we're on a case-sensitive platform)
8843
8667
  cachedCaseSensitive = true;
8844
8668
  }
8845
8669
  finally {
8846
- // Clean up the secure temporary directory
8847
8670
  if (secureTestDir) {
8848
8671
  try {
8849
8672
  node_fs.rmdirSync(secureTestDir);
@@ -8978,7 +8801,6 @@ function normalizeLineEndings(content, style = 'lf') {
8978
8801
  else {
8979
8802
  target = style === 'crlf' ? CRLF : LF;
8980
8803
  }
8981
- // First normalize all to LF, then convert to target
8982
8804
  const normalized = content.replace(/\r\n/g, LF).replace(/\r/g, LF);
8983
8805
  if (target === LF) {
8984
8806
  return normalized;
@@ -8993,7 +8815,6 @@ function normalizeLineEndings(content, style = 'lf') {
8993
8815
  */
8994
8816
  function detectLineEnding(content) {
8995
8817
  const hasCRLF = content.includes(CRLF);
8996
- // Check for LF that is NOT part of CRLF
8997
8818
  const hasLFOnly = content.includes('\n') && content.replace(/\r\n/g, '').includes('\n');
8998
8819
  if (hasCRLF && hasLFOnly)
8999
8820
  return 'mixed';
@@ -9471,7 +9292,6 @@ function createFsTree(root, options) {
9471
9292
  }
9472
9293
  const prefix = normalPath === '.' || normalPath === '' ? '' : normalPath + '/';
9473
9294
  for (const [changedPath, change] of _changes) {
9474
- // Handle root-level files
9475
9295
  if (prefix === '') {
9476
9296
  const childName = changedPath.split('/')[0];
9477
9297
  if (change.type === 'DELETE' && !changedPath.includes('/')) {
@@ -9488,7 +9308,6 @@ function createFsTree(root, options) {
9488
9308
  const relativePath = changedPath.slice(prefix.length);
9489
9309
  const childName = relativePath.split('/')[0];
9490
9310
  if (change.type === 'DELETE') {
9491
- // Only remove if it's a direct child being deleted
9492
9311
  if (!relativePath.includes('/')) {
9493
9312
  childSet.delete(childName);
9494
9313
  }
@@ -9574,12 +9393,10 @@ const factoryLogger = createScopedLogger('project-scope:vfs:factory');
9574
9393
  function createTree(root, options) {
9575
9394
  const normalizedRoot = normalizePath(root);
9576
9395
  factoryLogger.debug('createTree', { root: normalizedRoot });
9577
- // Validate root exists
9578
9396
  if (!exists(normalizedRoot)) {
9579
9397
  factoryLogger.warn('createTree failed: root does not exist', { root: normalizedRoot });
9580
9398
  throw createError(`Root directory does not exist: ${normalizedRoot}`);
9581
9399
  }
9582
- // Validate root is a directory
9583
9400
  if (!isDirectory(normalizedRoot)) {
9584
9401
  factoryLogger.warn('createTree failed: root is not a directory', { root: normalizedRoot });
9585
9402
  throw createError(`Root path is not a directory: ${normalizedRoot}`);
@@ -9626,7 +9443,6 @@ const vfsLogger = createScopedLogger('project-scope:vfs');
9626
9443
  function commitChanges(tree, options) {
9627
9444
  const changes = tree.listChanges();
9628
9445
  const appliedChanges = [];
9629
- // Set log level based on verbose option
9630
9446
  if (options?.verbose) {
9631
9447
  vfsLogger.setLogLevel('debug');
9632
9448
  }
@@ -9638,7 +9454,6 @@ function commitChanges(tree, options) {
9638
9454
  changes: [],
9639
9455
  dryRun: options?.dryRun ?? false,
9640
9456
  };
9641
- // Dry run - just count changes without writing
9642
9457
  if (options?.dryRun) {
9643
9458
  for (const change of changes) {
9644
9459
  switch (change.type) {
@@ -9656,7 +9471,6 @@ function commitChanges(tree, options) {
9656
9471
  result.changes = changes;
9657
9472
  return result;
9658
9473
  }
9659
- // Sort changes: deletes first (to free names), then creates, then updates
9660
9474
  const sortedChanges = [...changes].sort((a, b) => {
9661
9475
  const order = { DELETE: 0, CREATE: 1, UPDATE: 2 };
9662
9476
  return order[a.type] - order[b.type];
@@ -9669,11 +9483,9 @@ function commitChanges(tree, options) {
9669
9483
  case 'UPDATE':
9670
9484
  /* istanbul ignore if -- content is always defined for CREATE/UPDATE from tree.write() */
9671
9485
  if (change.content !== undefined) {
9672
- // Ensure directory exists
9673
9486
  const dir = node_path.dirname(absPath);
9674
9487
  ensureDir(dir);
9675
9488
  node_fs.writeFileSync(absPath, change.content);
9676
- // Apply permissions if specified
9677
9489
  if (change.mode !== undefined) {
9678
9490
  node_fs.chmodSync(absPath, change.mode);
9679
9491
  }
@@ -9691,7 +9503,6 @@ function commitChanges(tree, options) {
9691
9503
  node_fs.unlinkSync(absPath);
9692
9504
  }
9693
9505
  catch {
9694
- // Try recursive delete for directories
9695
9506
  node_fs.rmSync(absPath, { recursive: true });
9696
9507
  }
9697
9508
  }
@@ -9705,7 +9516,6 @@ function commitChanges(tree, options) {
9705
9516
  }
9706
9517
  }
9707
9518
  catch (error) {
9708
- // On error, throw with context
9709
9519
  vfsLogger.error('Commit failed', { path: change.path, type: change.type });
9710
9520
  const message = error instanceof Error
9711
9521
  ? `Failed to ${change.type.toLowerCase()} ${change.path}: ${error.message}`
@@ -9714,7 +9524,6 @@ function commitChanges(tree, options) {
9714
9524
  }
9715
9525
  }
9716
9526
  result.changes = appliedChanges;
9717
- // Clear the tree's pending changes after successful commit
9718
9527
  tree.clearChanges();
9719
9528
  return result;
9720
9529
  }
@@ -9790,18 +9599,14 @@ function backtrackLcs(table, oldLines, newLines) {
9790
9599
  * @returns Filtered DiffLine array
9791
9600
  */
9792
9601
  function operationsToDiffLines(operations, contextLines) {
9793
- // Mark which lines should be included (changes + context)
9794
9602
  const include = new Array(operations.length).fill(false);
9795
- // First pass: mark all changes
9796
9603
  for (let i = 0; i < operations.length; i++) {
9797
9604
  if (operations[i].type !== 'same') {
9798
- // Mark this line and context around it
9799
9605
  for (let j = max(0, i - contextLines); j <= min(operations.length - 1, i + contextLines); j++) {
9800
9606
  include[j] = true;
9801
9607
  }
9802
9608
  }
9803
9609
  }
9804
- // Second pass: convert to DiffLine
9805
9610
  const lines = [];
9806
9611
  let oldLineNum = 1;
9807
9612
  let newLineNum = 1;
@@ -9823,7 +9628,6 @@ function operationsToDiffLines(operations, contextLines) {
9823
9628
  }
9824
9629
  }
9825
9630
  else {
9826
- // Skip but update line numbers
9827
9631
  if (op.type === 'same') {
9828
9632
  oldLineNum++;
9829
9633
  newLineNum++;
@@ -9850,7 +9654,6 @@ function bufferToLines(content) {
9850
9654
  if (!content)
9851
9655
  return [];
9852
9656
  const text = content.toString('utf-8');
9853
- // Split by newline, keeping empty last line if present
9854
9657
  return text.split('\n');
9855
9658
  }
9856
9659
  /**
@@ -9877,9 +9680,7 @@ function generateDiff(change, options = {}) {
9877
9680
  diffLogger.debug('generateDiff', { path: change.path, type: change.type, contextLines });
9878
9681
  const oldLines = bufferToLines(change.originalContent);
9879
9682
  const newLines = bufferToLines(change.content);
9880
- // Handle edge cases
9881
9683
  if (change.type === 'CREATE') {
9882
- // All lines are additions
9883
9684
  const lines = newLines
9884
9685
  .filter((line) => line !== '' || newLines.indexOf(line) !== newLines.length - 1 || newLines.length === 1)
9885
9686
  .map((content, idx) => ({
@@ -9887,7 +9688,6 @@ function generateDiff(change, options = {}) {
9887
9688
  line: idx + 1,
9888
9689
  content,
9889
9690
  }));
9890
- // Filter out empty trailing line from split
9891
9691
  const filteredLines = lines.filter((l, i) => !(i === lines.length - 1 && l.content === '' && lines.length > 1));
9892
9692
  return {
9893
9693
  path: change.path,
@@ -9897,13 +9697,11 @@ function generateDiff(change, options = {}) {
9897
9697
  };
9898
9698
  }
9899
9699
  if (change.type === 'DELETE') {
9900
- // All lines are deletions
9901
9700
  const lines = oldLines.map((content, idx) => ({
9902
9701
  type: 'remove',
9903
9702
  line: idx + 1,
9904
9703
  content,
9905
9704
  }));
9906
- // Filter out empty trailing line from split
9907
9705
  const filteredLines = lines.filter((l, i) => !(i === lines.length - 1 && l.content === '' && lines.length > 1));
9908
9706
  return {
9909
9707
  path: change.path,
@@ -9912,7 +9710,6 @@ function generateDiff(change, options = {}) {
9912
9710
  deletions: filteredLines.length,
9913
9711
  };
9914
9712
  }
9915
- // UPDATE: compute actual diff
9916
9713
  const table = computeLcsTable(oldLines, newLines);
9917
9714
  const operations = backtrackLcs(table, oldLines, newLines);
9918
9715
  const lines = operationsToDiffLines(operations, contextLines);
@@ -9946,13 +9743,11 @@ function generateDiff(change, options = {}) {
9946
9743
  */
9947
9744
  function formatUnifiedDiff(diff) {
9948
9745
  const lines = [];
9949
- // Header
9950
9746
  lines.push(`--- a/${diff.path}`);
9951
9747
  lines.push(`+++ b/${diff.path}`);
9952
9748
  if (diff.lines.length === 0) {
9953
9749
  return lines.join('\n');
9954
9750
  }
9955
- // Group lines into hunks
9956
9751
  const hunks = [];
9957
9752
  const currentHunk = [];
9958
9753
  for (const line of diff.lines) {
@@ -9961,9 +9756,7 @@ function formatUnifiedDiff(diff) {
9961
9756
  if (currentHunk.length > 0) {
9962
9757
  hunks.push(currentHunk);
9963
9758
  }
9964
- // Output hunks
9965
9759
  for (const hunk of hunks) {
9966
- // Calculate hunk header
9967
9760
  const contextAndRemove = hunk.filter((l) => l.type === 'context' || l.type === 'remove');
9968
9761
  const contextAndAdd = hunk.filter((l) => l.type === 'context' || l.type === 'add');
9969
9762
  const oldStart = hunk.find((l) => l.type === 'context' || l.type === 'remove')?.line ?? 1;
@@ -9971,7 +9764,6 @@ function formatUnifiedDiff(diff) {
9971
9764
  const oldCount = contextAndRemove.length;
9972
9765
  const newCount = contextAndAdd.length;
9973
9766
  lines.push(`@@ -${oldStart},${oldCount} +${newStart},${newCount} @@`);
9974
- // Output lines
9975
9767
  for (const line of hunk) {
9976
9768
  const prefix = line.type === 'add' ? '+' : line.type === 'remove' ? '-' : ' ';
9977
9769
  lines.push(`${prefix}${line.content}`);