@hyperfrontend/project-scope 0.2.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 (176) hide show
  1. package/CHANGELOG.md +8 -16
  2. package/analyze.d.ts.map +1 -1
  3. package/cli/commands/analyze.d.ts +8 -0
  4. package/cli/commands/analyze.d.ts.map +1 -1
  5. package/cli/commands/config.d.ts +7 -0
  6. package/cli/commands/config.d.ts.map +1 -1
  7. package/cli/commands/deps.d.ts +6 -0
  8. package/cli/commands/deps.d.ts.map +1 -1
  9. package/cli/commands/tree.d.ts +12 -0
  10. package/cli/commands/tree.d.ts.map +1 -1
  11. package/cli/index.cjs.js +73 -206
  12. package/cli/index.cjs.js.map +1 -1
  13. package/cli/index.esm.js +73 -206
  14. package/cli/index.esm.js.map +1 -1
  15. package/cli/run.d.ts.map +1 -1
  16. package/core/cache.d.ts +1 -0
  17. package/core/cache.d.ts.map +1 -1
  18. package/core/encoding/convert.d.ts.map +1 -1
  19. package/core/encoding/detect.d.ts +5 -0
  20. package/core/encoding/detect.d.ts.map +1 -1
  21. package/core/encoding/index.cjs.js +2 -23
  22. package/core/encoding/index.cjs.js.map +1 -1
  23. package/core/encoding/index.esm.js +2 -23
  24. package/core/encoding/index.esm.js.map +1 -1
  25. package/core/errors/structured-errors.d.ts +2 -0
  26. package/core/errors/structured-errors.d.ts.map +1 -1
  27. package/core/fs/directory.d.ts +3 -0
  28. package/core/fs/directory.d.ts.map +1 -1
  29. package/core/fs/index.cjs.js +0 -14
  30. package/core/fs/index.cjs.js.map +1 -1
  31. package/core/fs/index.esm.js +0 -14
  32. package/core/fs/index.esm.js.map +1 -1
  33. package/core/fs/read.d.ts +11 -3
  34. package/core/fs/read.d.ts.map +1 -1
  35. package/core/fs/traversal.d.ts.map +1 -1
  36. package/core/index.cjs.js +6 -62
  37. package/core/index.cjs.js.map +1 -1
  38. package/core/index.esm.js +6 -62
  39. package/core/index.esm.js.map +1 -1
  40. package/core/logger.d.ts.map +1 -1
  41. package/core/path/index.cjs.js +0 -1
  42. package/core/path/index.cjs.js.map +1 -1
  43. package/core/path/index.esm.js +0 -1
  44. package/core/path/index.esm.js.map +1 -1
  45. package/core/path/normalize.d.ts.map +1 -1
  46. package/core/patterns/glob.d.ts +0 -4
  47. package/core/patterns/glob.d.ts.map +1 -1
  48. package/core/platform/detect.d.ts.map +1 -1
  49. package/core/platform/index.cjs.js +0 -10
  50. package/core/platform/index.cjs.js.map +1 -1
  51. package/core/platform/index.esm.js +0 -10
  52. package/core/platform/index.esm.js.map +1 -1
  53. package/core/platform/line-endings.d.ts.map +1 -1
  54. package/heuristics/dependencies/analyze.d.ts.map +1 -1
  55. package/heuristics/dependencies/index.cjs.js +0 -17
  56. package/heuristics/dependencies/index.cjs.js.map +1 -1
  57. package/heuristics/dependencies/index.esm.js +0 -17
  58. package/heuristics/dependencies/index.esm.js.map +1 -1
  59. package/heuristics/entry-points/discover.d.ts +34 -7
  60. package/heuristics/entry-points/discover.d.ts.map +1 -1
  61. package/heuristics/entry-points/index.cjs.js +6 -34
  62. package/heuristics/entry-points/index.cjs.js.map +1 -1
  63. package/heuristics/entry-points/index.esm.js +6 -34
  64. package/heuristics/entry-points/index.esm.js.map +1 -1
  65. package/heuristics/framework/index.cjs.js +1 -63
  66. package/heuristics/framework/index.cjs.js.map +1 -1
  67. package/heuristics/framework/index.esm.js +1 -63
  68. package/heuristics/framework/index.esm.js.map +1 -1
  69. package/heuristics/index.cjs.js +7 -88
  70. package/heuristics/index.cjs.js.map +1 -1
  71. package/heuristics/index.esm.js +7 -88
  72. package/heuristics/index.esm.js.map +1 -1
  73. package/heuristics/project-type/index.cjs.js +1 -63
  74. package/heuristics/project-type/index.cjs.js.map +1 -1
  75. package/heuristics/project-type/index.esm.js +1 -63
  76. package/heuristics/project-type/index.esm.js.map +1 -1
  77. package/index.cjs.js +81 -293
  78. package/index.cjs.js.map +1 -1
  79. package/index.esm.js +81 -293
  80. package/index.esm.js.map +1 -1
  81. package/nx/detect.d.ts.map +1 -1
  82. package/nx/devkit-loader.d.ts.map +1 -1
  83. package/nx/index.cjs.js +0 -29
  84. package/nx/index.cjs.js.map +1 -1
  85. package/nx/index.esm.js +0 -29
  86. package/nx/index.esm.js.map +1 -1
  87. package/nx/project-config.d.ts +2 -0
  88. package/nx/project-config.d.ts.map +1 -1
  89. package/package.json +1 -1
  90. package/project/config/index.cjs.js +4 -46
  91. package/project/config/index.cjs.js.map +1 -1
  92. package/project/config/index.esm.js +4 -46
  93. package/project/config/index.esm.js.map +1 -1
  94. package/project/config/patterns.d.ts.map +1 -1
  95. package/project/index.cjs.js +4 -47
  96. package/project/index.cjs.js.map +1 -1
  97. package/project/index.esm.js +4 -47
  98. package/project/index.esm.js.map +1 -1
  99. package/project/package/index.cjs.js +0 -11
  100. package/project/package/index.cjs.js.map +1 -1
  101. package/project/package/index.esm.js +0 -11
  102. package/project/package/index.esm.js.map +1 -1
  103. package/project/package/read.d.ts +1 -0
  104. package/project/package/read.d.ts.map +1 -1
  105. package/project/root/index.cjs.js +0 -11
  106. package/project/root/index.cjs.js.map +1 -1
  107. package/project/root/index.esm.js +0 -11
  108. package/project/root/index.esm.js.map +1 -1
  109. package/project/traversal/index.cjs.js +4 -28
  110. package/project/traversal/index.cjs.js.map +1 -1
  111. package/project/traversal/index.esm.js +4 -28
  112. package/project/traversal/index.esm.js.map +1 -1
  113. package/tech/backend/express.d.ts.map +1 -1
  114. package/tech/backend/fastify.d.ts.map +1 -1
  115. package/tech/backend/index.cjs.js +0 -17
  116. package/tech/backend/index.cjs.js.map +1 -1
  117. package/tech/backend/index.esm.js +0 -17
  118. package/tech/backend/index.esm.js.map +1 -1
  119. package/tech/backend/koa.d.ts.map +1 -1
  120. package/tech/backend/nestjs.d.ts.map +1 -1
  121. package/tech/build/index.cjs.js +0 -12
  122. package/tech/build/index.cjs.js.map +1 -1
  123. package/tech/build/index.esm.js +0 -12
  124. package/tech/build/index.esm.js.map +1 -1
  125. package/tech/frontend/index.cjs.js +0 -16
  126. package/tech/frontend/index.cjs.js.map +1 -1
  127. package/tech/frontend/index.esm.js +0 -16
  128. package/tech/frontend/index.esm.js.map +1 -1
  129. package/tech/frontend/qwik.d.ts.map +1 -1
  130. package/tech/frontend/remix.d.ts.map +1 -1
  131. package/tech/frontend/sveltekit.d.ts.map +1 -1
  132. package/tech/index.cjs.js +1 -63
  133. package/tech/index.cjs.js.map +1 -1
  134. package/tech/index.d.ts.map +1 -1
  135. package/tech/index.esm.js +1 -63
  136. package/tech/index.esm.js.map +1 -1
  137. package/tech/legacy/angularjs.d.ts.map +1 -1
  138. package/tech/legacy/backbone.d.ts.map +1 -1
  139. package/tech/legacy/ember.d.ts.map +1 -1
  140. package/tech/legacy/index.cjs.js +0 -26
  141. package/tech/legacy/index.cjs.js.map +1 -1
  142. package/tech/legacy/index.esm.js +0 -26
  143. package/tech/legacy/index.esm.js.map +1 -1
  144. package/tech/legacy/jquery.d.ts.map +1 -1
  145. package/tech/linting/biome.d.ts.map +1 -1
  146. package/tech/linting/index.cjs.js +0 -14
  147. package/tech/linting/index.cjs.js.map +1 -1
  148. package/tech/linting/index.esm.js +0 -14
  149. package/tech/linting/index.esm.js.map +1 -1
  150. package/tech/linting/prettier.d.ts.map +1 -1
  151. package/tech/monorepo/index.cjs.js +1 -13
  152. package/tech/monorepo/index.cjs.js.map +1 -1
  153. package/tech/monorepo/index.esm.js +1 -13
  154. package/tech/monorepo/index.esm.js.map +1 -1
  155. package/tech/monorepo/types.d.ts +1 -1
  156. package/tech/monorepo/types.d.ts.map +1 -1
  157. package/tech/shared-utils/detector-helpers.d.ts.map +1 -1
  158. package/tech/testing/index.cjs.js +0 -12
  159. package/tech/testing/index.cjs.js.map +1 -1
  160. package/tech/testing/index.esm.js +0 -12
  161. package/tech/testing/index.esm.js.map +1 -1
  162. package/tech/types/detectors.d.ts.map +1 -1
  163. package/tech/types/index.cjs.js +0 -27
  164. package/tech/types/index.cjs.js.map +1 -1
  165. package/tech/types/index.esm.js +0 -27
  166. package/tech/types/index.esm.js.map +1 -1
  167. package/vfs/commit.d.ts.map +1 -1
  168. package/vfs/diff.d.ts.map +1 -1
  169. package/vfs/factory.d.ts.map +1 -1
  170. package/vfs/fs-tree.d.ts.map +1 -1
  171. package/vfs/index.cjs.js +0 -45
  172. package/vfs/index.cjs.js.map +1 -1
  173. package/vfs/index.esm.js +0 -45
  174. package/vfs/index.esm.js.map +1 -1
  175. package/vfs/types.d.ts +1 -0
  176. 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
  }
@@ -1355,7 +1335,6 @@ function traverseUpward(startPath, predicate) {
1355
1335
  }
1356
1336
  currentPath = node_path.dirname(currentPath);
1357
1337
  }
1358
- // Check root directory
1359
1338
  if (predicate(rootPath)) {
1360
1339
  fsTraversalLogger.debug('Upward traversal found match at root', { startPath, foundPath: rootPath });
1361
1340
  return rootPath;
@@ -1727,29 +1706,23 @@ const depsLogger = createScopedLogger('project-scope:heuristics:deps');
1727
1706
  */
1728
1707
  function extractImports(content) {
1729
1708
  const imports = [];
1730
- // ES import with 'from': import X from 'path' or import { X } from 'path'
1731
- // Use non-greedy match and avoid nested quantifiers by matching "from" keyword directly
1732
1709
  const esImportFromRegex = /import\s+.+?\s+from\s+['"]([^'"]+)['"]/g;
1733
1710
  let match;
1734
1711
  while ((match = esImportFromRegex.exec(content)) !== null) {
1735
1712
  imports.push(match[1]);
1736
1713
  }
1737
- // Side-effect import: import 'path'
1738
1714
  const sideEffectImportRegex = /import\s+['"]([^'"]+)['"]/g;
1739
1715
  while ((match = sideEffectImportRegex.exec(content)) !== null) {
1740
1716
  imports.push(match[1]);
1741
1717
  }
1742
- // Dynamic import: import('path')
1743
1718
  const dynamicImportRegex = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
1744
1719
  while ((match = dynamicImportRegex.exec(content)) !== null) {
1745
1720
  imports.push(match[1]);
1746
1721
  }
1747
- // require: require('path')
1748
1722
  const requireRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
1749
1723
  while ((match = requireRegex.exec(content)) !== null) {
1750
1724
  imports.push(match[1]);
1751
1725
  }
1752
- // Re-export: export * from 'path' or export { X } from 'path'
1753
1726
  const exportFromRegex = /export\s+.+?\s+from\s+['"]([^'"]+)['"]/g;
1754
1727
  while ((match = exportFromRegex.exec(content)) !== null) {
1755
1728
  imports.push(match[1]);
@@ -2063,7 +2036,6 @@ const cacheRegistry = createSet();
2063
2036
  function createCache(options) {
2064
2037
  const { ttl, maxSize } = options ?? {};
2065
2038
  const store = createMap();
2066
- // Track insertion order for FIFO eviction
2067
2039
  const insertionOrder = [];
2068
2040
  /**
2069
2041
  * Check if an entry is expired.
@@ -2114,12 +2086,10 @@ function createCache(options) {
2114
2086
  return entry.value;
2115
2087
  },
2116
2088
  set(key, value) {
2117
- // If key exists, remove from order first
2118
2089
  if (store.has(key)) {
2119
2090
  removeFromOrder(key);
2120
2091
  }
2121
2092
  else {
2122
- // Evict if needed before adding new entry
2123
2093
  evictIfNeeded();
2124
2094
  }
2125
2095
  // eslint-disable-next-line workspace/no-unsafe-builtin-methods -- Date.now() is needed for Jest fake timers compatibility
@@ -2152,7 +2122,6 @@ function createCache(options) {
2152
2122
  return [...insertionOrder];
2153
2123
  },
2154
2124
  };
2155
- // Register cache for global operations
2156
2125
  cacheRegistry.add(cache);
2157
2126
  return freeze(cache);
2158
2127
  }
@@ -2232,7 +2201,6 @@ function memoize(fn, options) {
2232
2201
  cache.set(key, result);
2233
2202
  return result;
2234
2203
  };
2235
- // Attach cache for direct access
2236
2204
  defineProperty(memoized, 'cache', {
2237
2205
  value: cache,
2238
2206
  writable: false,
@@ -2241,10 +2209,6 @@ function memoize(fn, options) {
2241
2209
  return memoized;
2242
2210
  }
2243
2211
 
2244
- /**
2245
- * Pattern matching utilities with ReDoS protection.
2246
- * Uses character-by-character matching instead of regex where possible.
2247
- */
2248
2212
  /**
2249
2213
  * Match path against glob pattern using safe character iteration.
2250
2214
  * Avoids regex to prevent ReDoS attacks.
@@ -2282,17 +2246,14 @@ function matchGlobPattern(path, pattern) {
2282
2246
  * @returns True if remaining segments match
2283
2247
  */
2284
2248
  function matchSegments(pathParts, patternParts, pathIdx, patternIdx) {
2285
- // Base cases
2286
2249
  if (pathIdx === pathParts.length && patternIdx === patternParts.length) {
2287
- return true; // Both exhausted = match
2250
+ return true;
2288
2251
  }
2289
2252
  if (patternIdx >= patternParts.length) {
2290
- return false; // Pattern exhausted but path remains
2253
+ return false;
2291
2254
  }
2292
2255
  const patternPart = patternParts[patternIdx];
2293
- // Handle ** (globstar) - matches zero or more directories
2294
2256
  if (patternPart === '**') {
2295
- // Try matching rest of pattern against current position and all future positions
2296
2257
  for (let i = pathIdx; i <= pathParts.length; i++) {
2297
2258
  if (matchSegments(pathParts, patternParts, i, patternIdx + 1)) {
2298
2259
  return true;
@@ -2301,10 +2262,9 @@ function matchSegments(pathParts, patternParts, pathIdx, patternIdx) {
2301
2262
  return false;
2302
2263
  }
2303
2264
  if (pathIdx >= pathParts.length) {
2304
- return false; // Path exhausted but pattern remains (and it's not **)
2265
+ return false;
2305
2266
  }
2306
2267
  const pathPart = pathParts[pathIdx];
2307
- // Match current segment
2308
2268
  if (matchSegment(pathPart, patternPart)) {
2309
2269
  return matchSegments(pathParts, patternParts, pathIdx + 1, patternIdx + 1);
2310
2270
  }
@@ -2324,12 +2284,10 @@ function matchSegment(text, pattern) {
2324
2284
  while (patternIdx < pattern.length) {
2325
2285
  const char = pattern[patternIdx];
2326
2286
  if (char === '*') {
2327
- // * matches zero or more characters
2328
2287
  patternIdx++;
2329
2288
  if (patternIdx === pattern.length) {
2330
- return true; // * at end matches rest of string
2289
+ return true;
2331
2290
  }
2332
- // Try matching rest of pattern at each position in text
2333
2291
  for (let i = textIdx; i <= text.length; i++) {
2334
2292
  if (matchSegmentFrom(text, i, pattern, patternIdx)) {
2335
2293
  return true;
@@ -2338,7 +2296,6 @@ function matchSegment(text, pattern) {
2338
2296
  return false;
2339
2297
  }
2340
2298
  else if (char === '?') {
2341
- // ? matches exactly one character
2342
2299
  if (textIdx >= text.length) {
2343
2300
  return false;
2344
2301
  }
@@ -2346,10 +2303,8 @@ function matchSegment(text, pattern) {
2346
2303
  patternIdx++;
2347
2304
  }
2348
2305
  else if (char === '{') {
2349
- // {a,b,c} matches any alternative
2350
2306
  const closeIdx = findClosingBrace(pattern, patternIdx);
2351
2307
  if (closeIdx === -1) {
2352
- // Unmatched brace, treat as literal
2353
2308
  if (textIdx >= text.length || text[textIdx] !== char) {
2354
2309
  return false;
2355
2310
  }
@@ -2367,7 +2322,6 @@ function matchSegment(text, pattern) {
2367
2322
  }
2368
2323
  }
2369
2324
  else {
2370
- // Literal character
2371
2325
  if (textIdx >= text.length || text[textIdx] !== char) {
2372
2326
  return false;
2373
2327
  }
@@ -2793,7 +2747,8 @@ const entryPointLogger = createScopedLogger('project-scope:heuristics:entry-poin
2793
2747
  */
2794
2748
  const entryPointCache = createCache({ ttl: 60000, maxSize: 50 });
2795
2749
  /**
2796
- * Common entry point patterns.
2750
+ * Common entry point patterns by project type.
2751
+ * Used for convention-based entry point discovery.
2797
2752
  */
2798
2753
  const ENTRY_POINT_PATTERNS = {
2799
2754
  /** Library entry patterns */
@@ -3076,7 +3031,6 @@ function collectAllDependencies(packageJson) {
3076
3031
  function parseVersionString(versionString) {
3077
3032
  if (versionString === undefined || versionString === null)
3078
3033
  return undefined;
3079
- // Manual parsing instead of regex to avoid ReDoS
3080
3034
  let start = 0;
3081
3035
  while (start < versionString.length) {
3082
3036
  const char = versionString[start];
@@ -3137,7 +3091,6 @@ function expressDetector(projectPath, packageJson) {
3137
3091
  version = parseVersionString(deps['express']);
3138
3092
  sources.push({ type: 'package.json', field: 'dependencies.express' });
3139
3093
  }
3140
- // @types/express (indicates usage)
3141
3094
  if (deps['@types/express']) {
3142
3095
  confidence += 10;
3143
3096
  sources.push({ type: 'package.json', field: 'dependencies.@types/express' });
@@ -3173,13 +3126,11 @@ function nestDetector(projectPath, packageJson) {
3173
3126
  let version;
3174
3127
  let configPath;
3175
3128
  const deps = collectAllDependencies(pkg);
3176
- // @nestjs/core package
3177
3129
  if (deps['@nestjs/core']) {
3178
3130
  confidence += 70;
3179
3131
  version = parseVersionString(deps['@nestjs/core']);
3180
3132
  sources.push({ type: 'package.json', field: 'dependencies.@nestjs/core' });
3181
3133
  }
3182
- // @nestjs/common
3183
3134
  if (deps['@nestjs/common']) {
3184
3135
  confidence += 15;
3185
3136
  sources.push({ type: 'package.json', field: 'dependencies.@nestjs/common' });
@@ -3230,7 +3181,6 @@ function fastifyDetector(projectPath, packageJson) {
3230
3181
  confidence += 15;
3231
3182
  sources.push({ type: 'package.json', field: 'dependencies (fastify plugins)' });
3232
3183
  }
3233
- // @types/fastify (older versions)
3234
3184
  if (deps['@types/fastify']) {
3235
3185
  confidence += 5;
3236
3186
  sources.push({ type: 'package.json', field: 'dependencies.@types/fastify' });
@@ -3265,7 +3215,6 @@ function koaDetector(projectPath, packageJson) {
3265
3215
  version = parseVersionString(deps['koa']);
3266
3216
  sources.push({ type: 'package.json', field: 'dependencies.koa' });
3267
3217
  }
3268
- // @types/koa
3269
3218
  if (deps['@types/koa']) {
3270
3219
  confidence += 10;
3271
3220
  sources.push({ type: 'package.json', field: 'dependencies.@types/koa' });
@@ -3844,7 +3793,6 @@ function remixDetector(projectPath, packageJson) {
3844
3793
  let confidence = 0;
3845
3794
  let version;
3846
3795
  const deps = collectAllDependencies(pkg);
3847
- // @remix-run packages
3848
3796
  if (deps['@remix-run/react']) {
3849
3797
  confidence += 70;
3850
3798
  version = parseVersionString(deps['@remix-run/react']);
@@ -4120,7 +4068,6 @@ function sveltekitDetector(projectPath, packageJson) {
4120
4068
  let confidence = 0;
4121
4069
  let version;
4122
4070
  const deps = collectAllDependencies(pkg);
4123
- // @sveltejs/kit package
4124
4071
  if (deps['@sveltejs/kit']) {
4125
4072
  confidence += 70;
4126
4073
  version = parseVersionString(deps['@sveltejs/kit']);
@@ -4199,13 +4146,11 @@ function qwikDetector(projectPath, packageJson) {
4199
4146
  let confidence = 0;
4200
4147
  let version;
4201
4148
  const deps = collectAllDependencies(pkg);
4202
- // @builder.io/qwik package
4203
4149
  if (deps['@builder.io/qwik']) {
4204
4150
  confidence += 70;
4205
4151
  version = parseVersionString(deps['@builder.io/qwik']);
4206
4152
  sources.push({ type: 'package.json', field: 'dependencies.@builder.io/qwik' });
4207
4153
  }
4208
- // @builder.io/qwik-city
4209
4154
  if (deps['@builder.io/qwik-city']) {
4210
4155
  confidence += 20;
4211
4156
  sources.push({ type: 'package.json', field: 'dependencies.@builder.io/qwik-city' });
@@ -4316,23 +4261,19 @@ function angularJSDetector(projectPath, packageJson) {
4316
4261
  let confidence = 0;
4317
4262
  let version;
4318
4263
  const deps = collectAllDependencies(pkg);
4319
- // AngularJS package (angular, not @angular/core)
4320
4264
  if (deps['angular']) {
4321
4265
  confidence += 70;
4322
4266
  version = parseVersionString(deps['angular']);
4323
4267
  sources.push({ type: 'package.json', field: 'dependencies.angular' });
4324
4268
  }
4325
- // AngularJS router
4326
4269
  if (deps['angular-route']) {
4327
4270
  confidence += 15;
4328
4271
  sources.push({ type: 'package.json', field: 'dependencies.angular-route' });
4329
4272
  }
4330
- // AngularJS resource
4331
4273
  if (deps['angular-resource']) {
4332
4274
  confidence += 10;
4333
4275
  sources.push({ type: 'package.json', field: 'dependencies.angular-resource' });
4334
4276
  }
4335
- // AngularJS animate
4336
4277
  if (deps['angular-animate']) {
4337
4278
  confidence += 5;
4338
4279
  sources.push({ type: 'package.json', field: 'dependencies.angular-animate' });
@@ -4363,23 +4304,19 @@ function backboneDetector(projectPath, packageJson) {
4363
4304
  let confidence = 0;
4364
4305
  let version;
4365
4306
  const deps = collectAllDependencies(pkg);
4366
- // Backbone package
4367
4307
  if (deps['backbone']) {
4368
4308
  confidence += 70;
4369
4309
  version = parseVersionString(deps['backbone']);
4370
4310
  sources.push({ type: 'package.json', field: 'dependencies.backbone' });
4371
- // Underscore (commonly used with Backbone)
4372
4311
  if (deps['underscore']) {
4373
4312
  confidence += 15;
4374
4313
  sources.push({ type: 'package.json', field: 'dependencies.underscore' });
4375
4314
  }
4376
- // Lodash can be used as underscore replacement
4377
4315
  if (deps['lodash']) {
4378
4316
  confidence += 5;
4379
4317
  sources.push({ type: 'package.json', field: 'dependencies.lodash' });
4380
4318
  }
4381
4319
  }
4382
- // Marionette (Backbone framework)
4383
4320
  if (deps['backbone.marionette'] || deps['marionette']) {
4384
4321
  confidence += 10;
4385
4322
  sources.push({ type: 'package.json', field: 'dependencies.backbone.marionette' });
@@ -4410,18 +4347,15 @@ function emberDetector(projectPath, packageJson) {
4410
4347
  let confidence = 0;
4411
4348
  let version;
4412
4349
  const deps = collectAllDependencies(pkg);
4413
- // Ember source package
4414
4350
  if (deps['ember-source']) {
4415
4351
  confidence += 70;
4416
4352
  version = parseVersionString(deps['ember-source']);
4417
4353
  sources.push({ type: 'package.json', field: 'dependencies.ember-source' });
4418
4354
  }
4419
- // Ember CLI
4420
4355
  if (deps['ember-cli']) {
4421
4356
  confidence += 20;
4422
4357
  sources.push({ type: 'package.json', field: 'devDependencies.ember-cli' });
4423
4358
  }
4424
- // Ember Data
4425
4359
  if (deps['ember-data']) {
4426
4360
  confidence += 10;
4427
4361
  sources.push({ type: 'package.json', field: 'dependencies.ember-data' });
@@ -4452,18 +4386,15 @@ function jqueryDetector(projectPath, packageJson) {
4452
4386
  let confidence = 0;
4453
4387
  let version;
4454
4388
  const deps = collectAllDependencies(pkg);
4455
- // jQuery package
4456
4389
  if (deps['jquery']) {
4457
4390
  confidence += 80;
4458
4391
  version = parseVersionString(deps['jquery']);
4459
4392
  sources.push({ type: 'package.json', field: 'dependencies.jquery' });
4460
4393
  }
4461
- // jQuery UI
4462
4394
  if (deps['jquery-ui']) {
4463
4395
  confidence += 10;
4464
4396
  sources.push({ type: 'package.json', field: 'dependencies.jquery-ui' });
4465
4397
  }
4466
- // jQuery plugins
4467
4398
  if (deps['jquery-validation']) {
4468
4399
  confidence += 5;
4469
4400
  sources.push({ type: 'package.json', field: 'dependencies.jquery-validation' });
@@ -4610,7 +4541,6 @@ function prettierDetector(projectPath, packageJson) {
4610
4541
  confidence += 30;
4611
4542
  sources.push({ type: 'package.json', field: 'prettier' });
4612
4543
  }
4613
- // .prettierignore file
4614
4544
  if (exists(node_path.join(projectPath, '.prettierignore'))) {
4615
4545
  confidence += 10;
4616
4546
  sources.push({ type: 'config-file', path: '.prettierignore' });
@@ -4705,7 +4635,6 @@ function biomeDetector(projectPath, packageJson) {
4705
4635
  let configPath;
4706
4636
  let version;
4707
4637
  const deps = collectAllDependencies(pkg);
4708
- // @biomejs/biome package
4709
4638
  if (deps['@biomejs/biome']) {
4710
4639
  confidence += 70;
4711
4640
  version = parseVersionString(deps['@biomejs/biome']);
@@ -4992,7 +4921,7 @@ function npmWorkspacesDetector(workspacePath, packageJson) {
4992
4921
  sources.push({ type: 'lockfile', path: 'package-lock.json' });
4993
4922
  }
4994
4923
  if (exists(node_path.join(workspacePath, 'yarn.lock'))) {
4995
- return null; // Let yarn workspace detector handle this
4924
+ return null;
4996
4925
  }
4997
4926
  if (confidence === 0) {
4998
4927
  return null;
@@ -5371,7 +5300,6 @@ function checkTsConfigStrict(projectPath) {
5371
5300
  if (!content)
5372
5301
  return undefined;
5373
5302
  try {
5374
- // Simple JSON parsing - doesn't handle comments but good enough for strict check
5375
5303
  const cleanContent = content.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '');
5376
5304
  const parsed = parse(cleanContent);
5377
5305
  return parsed?.compilerOptions?.strict === true;
@@ -5394,19 +5322,16 @@ function typescriptDetector(projectPath, packageJson) {
5394
5322
  let configPath;
5395
5323
  let version;
5396
5324
  const deps = collectAllDependencies(pkg);
5397
- // TypeScript package
5398
5325
  if (deps['typescript']) {
5399
5326
  confidence += 50;
5400
5327
  version = parseVersionString(deps['typescript']);
5401
5328
  sources.push({ type: 'package.json', field: 'dependencies.typescript' });
5402
5329
  }
5403
- // tsconfig.json
5404
5330
  if (exists(node_path.join(projectPath, 'tsconfig.json'))) {
5405
5331
  confidence += 40;
5406
5332
  configPath = 'tsconfig.json';
5407
5333
  sources.push({ type: 'config-file', path: 'tsconfig.json' });
5408
5334
  }
5409
- // tsconfig.*.json variants
5410
5335
  const tsconfigVariants = ['tsconfig.build.json', 'tsconfig.lib.json', 'tsconfig.spec.json', 'tsconfig.app.json'];
5411
5336
  for (const variant of tsconfigVariants) {
5412
5337
  if (exists(node_path.join(projectPath, variant))) {
@@ -5415,7 +5340,6 @@ function typescriptDetector(projectPath, packageJson) {
5415
5340
  break;
5416
5341
  }
5417
5342
  }
5418
- // @types packages
5419
5343
  const typePackages = keys(deps).filter((d) => d.startsWith('@types/'));
5420
5344
  if (typePackages.length > 0) {
5421
5345
  confidence += 10;
@@ -5449,24 +5373,20 @@ function flowDetector(projectPath, packageJson) {
5449
5373
  let configPath;
5450
5374
  let version;
5451
5375
  const deps = collectAllDependencies(pkg);
5452
- // flow-bin package
5453
5376
  if (deps['flow-bin']) {
5454
5377
  confidence += 60;
5455
5378
  version = parseVersionString(deps['flow-bin']);
5456
5379
  sources.push({ type: 'package.json', field: 'dependencies.flow-bin' });
5457
5380
  }
5458
- // .flowconfig
5459
5381
  if (exists(node_path.join(projectPath, '.flowconfig'))) {
5460
5382
  confidence += 40;
5461
5383
  configPath = '.flowconfig';
5462
5384
  sources.push({ type: 'config-file', path: '.flowconfig' });
5463
5385
  }
5464
- // flow-typed directory
5465
5386
  if (exists(node_path.join(projectPath, 'flow-typed'))) {
5466
5387
  confidence += 10;
5467
5388
  sources.push({ type: 'directory', path: 'flow-typed/' });
5468
5389
  }
5469
- // @babel/preset-flow
5470
5390
  if (deps['@babel/preset-flow']) {
5471
5391
  confidence += 10;
5472
5392
  sources.push({ type: 'package.json', field: 'dependencies.@babel/preset-flow' });
@@ -5490,7 +5410,6 @@ function flowDetector(projectPath, packageJson) {
5490
5410
  * @returns `true` if the content contains JSDoc type annotations.
5491
5411
  */
5492
5412
  function hasJsDocTypes(content) {
5493
- // Check for JSDoc type annotations
5494
5413
  return (content.includes('@type {') ||
5495
5414
  content.includes('@param {') ||
5496
5415
  content.includes('@returns {') ||
@@ -5509,14 +5428,11 @@ function jsdocDetector(projectPath, packageJson) {
5509
5428
  const sources = [];
5510
5429
  let confidence = 0;
5511
5430
  const deps = collectAllDependencies(pkg);
5512
- // jsdoc package
5513
5431
  if (deps['jsdoc']) {
5514
5432
  confidence += 30;
5515
5433
  sources.push({ type: 'package.json', field: 'dependencies.jsdoc' });
5516
5434
  }
5517
- // typescript with checkJs (JSDoc type checking)
5518
5435
  if (deps['typescript']) {
5519
- // Check if checkJs is enabled in tsconfig
5520
5436
  const tsconfigPath = node_path.join(projectPath, 'tsconfig.json');
5521
5437
  const content = readFileIfExists(tsconfigPath);
5522
5438
  if (content) {
@@ -5533,12 +5449,10 @@ function jsdocDetector(projectPath, packageJson) {
5533
5449
  }
5534
5450
  }
5535
5451
  }
5536
- // Check for jsconfig.json (VS Code JS type checking)
5537
5452
  if (exists(node_path.join(projectPath, 'jsconfig.json'))) {
5538
5453
  confidence += 40;
5539
5454
  sources.push({ type: 'config-file', path: 'jsconfig.json' });
5540
5455
  }
5541
- // Sample check for JSDoc annotations in source files
5542
5456
  const srcDir = node_path.join(projectPath, 'src');
5543
5457
  if (exists(srcDir)) {
5544
5458
  try {
@@ -5620,8 +5534,6 @@ const allDetectors = {
5620
5534
  function isDetectAllOptions(value) {
5621
5535
  if (typeof value !== 'object' || value === null)
5622
5536
  return false;
5623
- // DetectAllOptions has skipCache or packageJson fields specifically
5624
- // PackageJson never has skipCache field
5625
5537
  return 'skipCache' in value || 'packageJson' in value;
5626
5538
  }
5627
5539
  /**
@@ -5653,9 +5565,7 @@ function isDetectAllOptions(value) {
5653
5565
  * ```
5654
5566
  */
5655
5567
  function detectAll(projectPath, packageJsonOrOptions) {
5656
- // Handle backward-compatible arguments
5657
5568
  const options = isDetectAllOptions(packageJsonOrOptions) ? packageJsonOrOptions : { packageJson: packageJsonOrOptions };
5658
- // Check cache first (unless skipCache is true)
5659
5569
  if (!options.skipCache) {
5660
5570
  const cached = detectAllCache.get(projectPath);
5661
5571
  if (cached) {
@@ -5706,7 +5616,6 @@ function detectAll(projectPath, packageJsonOrOptions) {
5706
5616
  legacyFrameworks: result.legacyFrameworks.map((f) => f.id),
5707
5617
  testingFrameworks: result.testingFrameworks.map((f) => f.id),
5708
5618
  });
5709
- // Cache the result
5710
5619
  detectAllCache.set(projectPath, result);
5711
5620
  return result;
5712
5621
  }
@@ -6123,7 +6032,6 @@ function detectNxVersion(workspacePath) {
6123
6032
  if (packageJson) {
6124
6033
  const nxVersion = packageJson.devDependencies?.['nx'] ?? packageJson.dependencies?.['nx'];
6125
6034
  if (nxVersion) {
6126
- // Strip semver range characters (^, ~, >=, etc.)
6127
6035
  return nxVersion.replace(/^[\^~>=<]+/, '');
6128
6036
  }
6129
6037
  }
@@ -6152,14 +6060,12 @@ function getNxWorkspaceInfo(workspacePath) {
6152
6060
  }
6153
6061
  const nxJson = readJsonFileIfExists(node_path.join(workspacePath, 'nx.json'));
6154
6062
  if (!nxJson) {
6155
- // Check for workspace.json as fallback (older NX)
6156
6063
  const workspaceJson = readJsonFileIfExists(node_path.join(workspacePath, 'workspace.json'));
6157
6064
  if (!workspaceJson) {
6158
6065
  nxLogger.debug('No nx.json or workspace.json found', { workspacePath });
6159
6066
  return null;
6160
6067
  }
6161
6068
  nxLogger.debug('Using legacy workspace.json', { workspacePath });
6162
- // Create minimal nx.json from workspace.json
6163
6069
  return {
6164
6070
  root: workspacePath,
6165
6071
  version: detectNxVersion(workspacePath),
@@ -6208,7 +6114,6 @@ function tryLoadDevkit() {
6208
6114
  }
6209
6115
  devkitLogger.debug('Attempting to load @nx/devkit');
6210
6116
  try {
6211
- // Dynamic require to avoid bundling
6212
6117
  const devkit = require('@nx/devkit');
6213
6118
  devkitLogger.debug('@nx/devkit loaded successfully');
6214
6119
  cachedResult = { available: true, devkit };
@@ -6305,7 +6210,6 @@ function readProjectJson(projectPath) {
6305
6210
  */
6306
6211
  function getProjectConfig(projectPath, workspacePath) {
6307
6212
  nxConfigLogger.debug('Getting project config', { projectPath, workspacePath });
6308
- // Try project.json first
6309
6213
  const projectJson = readProjectJson(projectPath);
6310
6214
  if (projectJson) {
6311
6215
  nxConfigLogger.debug('Using project.json config', { projectPath, name: projectJson.name });
@@ -6314,7 +6218,6 @@ function getProjectConfig(projectPath, workspacePath) {
6314
6218
  root: projectJson.root ?? node_path.relative(workspacePath, projectPath),
6315
6219
  };
6316
6220
  }
6317
- // Try to infer from package.json nx field
6318
6221
  const packageJson = readPackageJsonIfExists(projectPath);
6319
6222
  if (packageJson && typeof packageJson['nx'] === 'object') {
6320
6223
  nxConfigLogger.debug('Using package.json nx field', { projectPath, name: packageJson.name });
@@ -6343,13 +6246,11 @@ function scanForProjects(dirPath, workspacePath, projects, maxDepth, currentDept
6343
6246
  try {
6344
6247
  const entries = readDirectory(dirPath);
6345
6248
  for (const entry of entries) {
6346
- // Skip node_modules and hidden directories
6347
6249
  if (entry.name.startsWith('.') || entry.name === 'node_modules' || entry.name === 'dist') {
6348
6250
  continue;
6349
6251
  }
6350
6252
  const fullPath = node_path.join(dirPath, entry.name);
6351
6253
  if (entry.isDirectory) {
6352
- // Check if this directory is an NX project
6353
6254
  if (isNxProject(fullPath)) {
6354
6255
  const config = getProjectConfig(fullPath, workspacePath);
6355
6256
  if (config) {
@@ -6361,7 +6262,6 @@ function scanForProjects(dirPath, workspacePath, projects, maxDepth, currentDept
6361
6262
  });
6362
6263
  }
6363
6264
  }
6364
- // Recursively scan subdirectories
6365
6265
  scanForProjects(fullPath, workspacePath, projects, maxDepth, currentDepth + 1);
6366
6266
  }
6367
6267
  }
@@ -6379,12 +6279,10 @@ function scanForProjects(dirPath, workspacePath, projects, maxDepth, currentDept
6379
6279
  */
6380
6280
  function discoverNxProjects(workspacePath) {
6381
6281
  const projects = createMap();
6382
- // Check for workspace.json (older NX format)
6383
6282
  const workspaceJson = readJsonFileIfExists(node_path.join(workspacePath, 'workspace.json'));
6384
6283
  if (workspaceJson?.projects) {
6385
6284
  for (const [name, config] of entries(workspaceJson.projects)) {
6386
6285
  if (typeof config === 'string') {
6387
- // Path reference to project directory
6388
6286
  const projectPath = node_path.join(workspacePath, config);
6389
6287
  const projectConfig = getProjectConfig(projectPath, workspacePath);
6390
6288
  if (projectConfig) {
@@ -6392,18 +6290,15 @@ function discoverNxProjects(workspacePath) {
6392
6290
  }
6393
6291
  }
6394
6292
  else if (typeof config === 'object' && config !== null) {
6395
- // Inline config
6396
6293
  projects.set(name, { name, ...config });
6397
6294
  }
6398
6295
  }
6399
6296
  return projects;
6400
6297
  }
6401
- // Scan for project.json files (newer NX format)
6402
6298
  const workspaceInfo = getNxWorkspaceInfo(workspacePath);
6403
6299
  const appsDir = workspaceInfo?.workspaceLayout.appsDir ?? 'apps';
6404
6300
  const libsDir = workspaceInfo?.workspaceLayout.libsDir ?? 'libs';
6405
6301
  const searchDirs = [appsDir, libsDir];
6406
- // Also check packages directory (common in some setups)
6407
6302
  if (exists(node_path.join(workspacePath, 'packages'))) {
6408
6303
  searchDirs.push('packages');
6409
6304
  }
@@ -6418,7 +6313,6 @@ function discoverNxProjects(workspacePath) {
6418
6313
  }
6419
6314
  }
6420
6315
  }
6421
- // Also check root-level projects (standalone projects in monorepo root)
6422
6316
  if (isNxProject(workspacePath)) {
6423
6317
  const config = readProjectJson(workspacePath);
6424
6318
  if (config) {
@@ -6451,10 +6345,8 @@ function buildSimpleProjectGraph(workspacePath, projects) {
6451
6345
  data: config,
6452
6346
  };
6453
6347
  dependencies[name] = [];
6454
- // Add implicit dependencies
6455
6348
  if (config.implicitDependencies) {
6456
6349
  for (const dep of config.implicitDependencies) {
6457
- // Skip negative dependencies (those starting with !)
6458
6350
  if (!dep.startsWith('!')) {
6459
6351
  dependencies[name].push({
6460
6352
  target: dep,
@@ -6471,7 +6363,6 @@ function buildSimpleProjectGraph(workspacePath, projects) {
6471
6363
  * Known configuration file patterns organized by type.
6472
6364
  */
6473
6365
  const CONFIG_PATTERNS = {
6474
- // Package Management
6475
6366
  'package.json': {
6476
6367
  patterns: ['package.json'],
6477
6368
  format: 'json',
@@ -6498,14 +6389,12 @@ const CONFIG_PATTERNS = {
6498
6389
  description: 'NPM configuration',
6499
6390
  sensitive: true,
6500
6391
  },
6501
- // TypeScript
6502
6392
  tsconfig: {
6503
6393
  patterns: ['tsconfig.json', 'tsconfig.*.json'],
6504
6394
  format: 'jsonc',
6505
6395
  description: 'TypeScript configuration',
6506
6396
  canExtend: true,
6507
6397
  },
6508
- // Monorepo
6509
6398
  nx: {
6510
6399
  patterns: ['nx.json'],
6511
6400
  format: 'json',
@@ -6531,7 +6420,6 @@ const CONFIG_PATTERNS = {
6531
6420
  format: 'json',
6532
6421
  description: 'Lerna configuration',
6533
6422
  },
6534
- // Build Tools
6535
6423
  webpack: {
6536
6424
  patterns: ['webpack.config.js', 'webpack.config.ts', 'webpack.config.cjs', 'webpack.config.mjs'],
6537
6425
  format: 'js',
@@ -6562,7 +6450,6 @@ const CONFIG_PATTERNS = {
6562
6450
  format: 'json',
6563
6451
  description: 'SWC configuration',
6564
6452
  },
6565
- // Testing
6566
6453
  jest: {
6567
6454
  patterns: ['jest.config.js', 'jest.config.ts', 'jest.config.mjs'],
6568
6455
  description: 'Jest configuration',
@@ -6579,7 +6466,6 @@ const CONFIG_PATTERNS = {
6579
6466
  patterns: ['playwright.config.js', 'playwright.config.ts'],
6580
6467
  description: 'Playwright configuration',
6581
6468
  },
6582
- // Framework configs
6583
6469
  next: {
6584
6470
  patterns: ['next.config.js', 'next.config.mjs', 'next.config.ts'],
6585
6471
  format: 'js',
@@ -6605,7 +6491,6 @@ const CONFIG_PATTERNS = {
6605
6491
  format: 'js',
6606
6492
  description: 'Astro configuration',
6607
6493
  },
6608
- // Linting & Formatting
6609
6494
  eslint: {
6610
6495
  patterns: [
6611
6496
  'eslint.config.js',
@@ -6624,14 +6509,12 @@ const CONFIG_PATTERNS = {
6624
6509
  format: 'json',
6625
6510
  description: 'Prettier configuration',
6626
6511
  },
6627
- // Environment (sensitive)
6628
6512
  env: {
6629
6513
  patterns: ['.env', '.env.*', '*.env'],
6630
6514
  format: 'dotenv',
6631
6515
  description: 'Environment variables',
6632
6516
  sensitive: true,
6633
6517
  },
6634
- // Git
6635
6518
  '.gitignore': {
6636
6519
  patterns: ['.gitignore'],
6637
6520
  format: 'text',
@@ -6802,12 +6685,8 @@ function getConfigPaths(type) {
6802
6685
  *
6803
6686
  * @module @hyperfrontend/immutable-api-utils/built-in-copy/number
6804
6687
  */
6805
- // Capture references at module initialization time
6806
6688
  const _parseInt = globalThis.parseInt;
6807
6689
  const _parseFloat = globalThis.parseFloat;
6808
- // ============================================================================
6809
- // Parsing
6810
- // ============================================================================
6811
6690
  /**
6812
6691
  * (Safe copy) Parses a string and returns an integer.
6813
6692
  */
@@ -7162,11 +7041,9 @@ const analyzeLogger = createScopedLogger('project-scope:analyze');
7162
7041
  * @returns Detected workspace type
7163
7042
  */
7164
7043
  function detectWorkspaceType(projectPath) {
7165
- // Check for NX
7166
7044
  if (isNxWorkspace(projectPath) || findNxWorkspaceRoot(projectPath) !== null) {
7167
7045
  return 'nx';
7168
7046
  }
7169
- // Check for common monorepo markers
7170
7047
  if (exists(node_path.resolve(projectPath, 'turbo.json'))) {
7171
7048
  return 'turborepo';
7172
7049
  }
@@ -7176,7 +7053,6 @@ function detectWorkspaceType(projectPath) {
7176
7053
  if (exists(node_path.resolve(projectPath, 'pnpm-workspace.yaml'))) {
7177
7054
  return 'pnpm';
7178
7055
  }
7179
- // Check package.json for workspaces
7180
7056
  const pkg = readPackageJsonIfExists(projectPath);
7181
7057
  if (pkg?.workspaces) {
7182
7058
  if (exists(node_path.resolve(projectPath, 'yarn.lock'))) {
@@ -7185,7 +7061,7 @@ function detectWorkspaceType(projectPath) {
7185
7061
  if (exists(node_path.resolve(projectPath, 'package-lock.json'))) {
7186
7062
  return 'npm';
7187
7063
  }
7188
- return 'npm'; // default for workspaces
7064
+ return 'npm';
7189
7065
  }
7190
7066
  return 'standalone';
7191
7067
  }
@@ -7197,15 +7073,12 @@ function detectWorkspaceType(projectPath) {
7197
7073
  * @returns True if the analysis should be performed
7198
7074
  */
7199
7075
  function shouldInclude(type, options) {
7200
- // If include list is specified, item must be in it
7201
7076
  if (options?.include && options.include.length > 0) {
7202
7077
  return options.include.includes(type);
7203
7078
  }
7204
- // If exclude list is specified, item must not be in it
7205
7079
  if (options?.exclude && options.exclude.length > 0) {
7206
7080
  return !options.exclude.includes(type);
7207
7081
  }
7208
- // Default: include everything
7209
7082
  return true;
7210
7083
  }
7211
7084
  /**
@@ -7254,28 +7127,22 @@ function normalizeConfigFormat(format) {
7254
7127
  function analyzeProject(projectPath, options) {
7255
7128
  const startTime = dateNow();
7256
7129
  const resolvedPath = node_path.resolve(projectPath);
7257
- // Set log level based on verbose option
7258
7130
  if (options?.verbose) {
7259
7131
  analyzeLogger.setLogLevel('debug');
7260
7132
  }
7261
7133
  analyzeLogger.debug('Starting project analysis', { path: resolvedPath, options });
7262
- // Read package.json once for reuse
7263
7134
  const packageJson = readPackageJsonIfExists(resolvedPath);
7264
7135
  analyzeLogger.debug('Package.json loaded', { found: !!packageJson, name: packageJson?.name });
7265
- // Detect project and workspace types
7266
7136
  const projectTypeDetection = detectProjectType(resolvedPath, {
7267
7137
  skipTechDetection: options?.depth === 'basic',
7268
7138
  });
7269
7139
  const projectType = projectTypeDetection.type;
7270
7140
  const workspaceType = detectWorkspaceType(resolvedPath);
7271
7141
  analyzeLogger.debug('Project type detected', { projectType, workspaceType });
7272
- // Get project name from package.json or directory name
7273
7142
  const projectName = packageJson?.name ?? node_path.basename(resolvedPath);
7274
- // Run all technology detectors
7275
7143
  const detections = shouldInclude('frameworks', options) || shouldInclude('buildTools', options) || shouldInclude('testing', options)
7276
7144
  ? detectAll(resolvedPath, packageJson ?? undefined)
7277
7145
  : null;
7278
- // Convert framework detections to FrameworkInfo
7279
7146
  const frameworks = shouldInclude('frameworks', options) && detections
7280
7147
  ? [
7281
7148
  ...detections.frontendFrameworks.map((d) => ({
@@ -7295,7 +7162,6 @@ function analyzeProject(projectPath, options) {
7295
7162
  })),
7296
7163
  ]
7297
7164
  : [];
7298
- // Convert build tool detections
7299
7165
  const buildTools = shouldInclude('buildTools', options) && detections
7300
7166
  ? detections.buildTools.map((d) => ({
7301
7167
  id: d.id,
@@ -7305,7 +7171,6 @@ function analyzeProject(projectPath, options) {
7305
7171
  confidence: d.confidence,
7306
7172
  }))
7307
7173
  : [];
7308
- // Convert testing framework detections
7309
7174
  const testingFrameworks = shouldInclude('testing', options) && detections
7310
7175
  ? detections.testingFrameworks.map((d) => ({
7311
7176
  id: d.id,
@@ -7316,7 +7181,6 @@ function analyzeProject(projectPath, options) {
7316
7181
  confidence: d.confidence,
7317
7182
  }))
7318
7183
  : [];
7319
- // Discover entry points
7320
7184
  const entryPoints = shouldInclude('entryPoints', options)
7321
7185
  ? discoverEntryPoints(resolvedPath).map((e) => ({
7322
7186
  path: e.path,
@@ -7324,7 +7188,6 @@ function analyzeProject(projectPath, options) {
7324
7188
  confidence: e.confidence,
7325
7189
  }))
7326
7190
  : [];
7327
- // Detect configuration files
7328
7191
  const configFiles = shouldInclude('configs', options)
7329
7192
  ? detectConfigs(resolvedPath).map((c) => ({
7330
7193
  path: c.path,
@@ -7333,7 +7196,6 @@ function analyzeProject(projectPath, options) {
7333
7196
  tool: c.type,
7334
7197
  }))
7335
7198
  : [];
7336
- // Get dependency summary
7337
7199
  let dependencies;
7338
7200
  if (shouldInclude('dependencies', options)) {
7339
7201
  const deps = getProjectDependencies(resolvedPath);
@@ -7354,7 +7216,6 @@ function analyzeProject(projectPath, options) {
7354
7216
  total: 0,
7355
7217
  };
7356
7218
  }
7357
- // Build metadata
7358
7219
  const metadata = {
7359
7220
  timestamp: createDate(),
7360
7221
  durationMs: dateNow() - startTime,
@@ -7429,15 +7290,12 @@ function formatWorkspaceType(type) {
7429
7290
  */
7430
7291
  function formatAnalysisText(result) {
7431
7292
  const lines = [];
7432
- // Header
7433
7293
  lines.push(`Project Analysis: ${result.name}`);
7434
7294
  lines.push('='.repeat(30));
7435
7295
  lines.push('');
7436
- // Basic info
7437
7296
  lines.push(`Type: ${formatProjectType(result.projectType)}`);
7438
7297
  lines.push(`Workspace: ${formatWorkspaceType(result.workspaceType)}`);
7439
7298
  lines.push('');
7440
- // Frameworks
7441
7299
  if (result.frameworks.length > 0) {
7442
7300
  lines.push('Frameworks:');
7443
7301
  for (const framework of result.frameworks) {
@@ -7451,7 +7309,6 @@ function formatAnalysisText(result) {
7451
7309
  }
7452
7310
  lines.push('');
7453
7311
  }
7454
- // Build tools
7455
7312
  if (result.buildTools.length > 0) {
7456
7313
  lines.push('Build Tools:');
7457
7314
  for (const tool of result.buildTools) {
@@ -7460,7 +7317,6 @@ function formatAnalysisText(result) {
7460
7317
  }
7461
7318
  lines.push('');
7462
7319
  }
7463
- // Testing
7464
7320
  if (result.testingFrameworks.length > 0) {
7465
7321
  lines.push('Testing:');
7466
7322
  for (const framework of result.testingFrameworks) {
@@ -7469,7 +7325,6 @@ function formatAnalysisText(result) {
7469
7325
  }
7470
7326
  lines.push('');
7471
7327
  }
7472
- // Entry points
7473
7328
  if (result.entryPoints.length > 0) {
7474
7329
  lines.push('Entry Points:');
7475
7330
  for (const entry of result.entryPoints.slice(0, 5)) {
@@ -7480,7 +7335,6 @@ function formatAnalysisText(result) {
7480
7335
  }
7481
7336
  lines.push('');
7482
7337
  }
7483
- // Configurations
7484
7338
  if (result.configFiles.length > 0) {
7485
7339
  lines.push('Configurations:');
7486
7340
  for (const config of result.configFiles.slice(0, 8)) {
@@ -7491,7 +7345,6 @@ function formatAnalysisText(result) {
7491
7345
  }
7492
7346
  lines.push('');
7493
7347
  }
7494
- // Dependencies summary
7495
7348
  lines.push('Dependencies:');
7496
7349
  lines.push(` Production: ${result.dependencies.production}`);
7497
7350
  lines.push(` Development: ${result.dependencies.development}`);
@@ -7578,7 +7431,7 @@ function parseAnalyzeArgs(args) {
7578
7431
  exclude: { type: 'string', short: 'e' },
7579
7432
  },
7580
7433
  allowPositionals: true,
7581
- strict: false, // Allow global options to pass through
7434
+ strict: false,
7582
7435
  });
7583
7436
  const format = values.format;
7584
7437
  const depth = values.depth;
@@ -7637,7 +7490,6 @@ const analyzeCommandDef = {
7637
7490
  description: 'Analyze project structure and tech stack',
7638
7491
  execute(args, globalOptions) {
7639
7492
  const options = parseAnalyzeArgs(args);
7640
- // Global --json flag overrides format
7641
7493
  if (globalOptions.json) {
7642
7494
  options.format = 'json';
7643
7495
  }
@@ -7944,12 +7796,10 @@ Examples:
7944
7796
  function formatDependencyList(deps, maxItems = 20) {
7945
7797
  const lines = [];
7946
7798
  const depEntries = entries(deps);
7947
- // Sort alphabetically
7948
7799
  depEntries.sort((a, b) => a[0].localeCompare(b[0]));
7949
7800
  const displayCount = min(depEntries.length, maxItems);
7950
7801
  for (let i = 0; i < displayCount; i++) {
7951
7802
  const [name, version] = depEntries[i];
7952
- // Pad name to align versions
7953
7803
  const paddedName = name.padEnd(30);
7954
7804
  lines.push(` ${paddedName} ${version}`);
7955
7805
  }
@@ -8192,13 +8042,10 @@ function buildTree(rootPath, walkEntries, options) {
8192
8042
  isDirectory: true,
8193
8043
  children: [],
8194
8044
  };
8195
- // Create a map for quick lookup
8196
8045
  const nodeMap = createMap();
8197
8046
  nodeMap.set('.', root);
8198
- // Sort entries by path for proper parent-child relationships
8199
8047
  const sortedEntries = [...walkEntries].sort((a, b) => a.relativePath.localeCompare(b.relativePath));
8200
8048
  for (const entry of sortedEntries) {
8201
- // Skip based on filters
8202
8049
  if (options.dirsOnly && !entry.isDirectory)
8203
8050
  continue;
8204
8051
  if (options.filesOnly && entry.isDirectory)
@@ -8209,7 +8056,6 @@ function buildTree(rootPath, walkEntries, options) {
8209
8056
  isDirectory: entry.isDirectory,
8210
8057
  children: [],
8211
8058
  };
8212
- // Add size/modified if requested
8213
8059
  if ((options.showSize || options.showModified) && entry.isFile) {
8214
8060
  const stats = getFileStat(entry.path);
8215
8061
  if (stats) {
@@ -8219,9 +8065,8 @@ function buildTree(rootPath, walkEntries, options) {
8219
8065
  node.modified = stats.modified;
8220
8066
  }
8221
8067
  }
8222
- // Find parent
8223
8068
  const parts = entry.relativePath.split('/');
8224
- parts.pop(); // Remove current entry name
8069
+ parts.pop();
8225
8070
  const parentPath = parts.join('/') || '.';
8226
8071
  const parent = nodeMap.get(parentPath);
8227
8072
  if (parent) {
@@ -8242,11 +8087,9 @@ function buildTree(rootPath, walkEntries, options) {
8242
8087
  */
8243
8088
  function renderTreeText(node, options, prefix = '', isLast = true) {
8244
8089
  const lines = [];
8245
- // Current line
8246
8090
  const connector = isLast ? '└── ' : '├── ';
8247
8091
  const dirMark = node.isDirectory ? '/' : '';
8248
8092
  let line = `${prefix}${connector}${node.name}${dirMark}`;
8249
- // Add size/modified
8250
8093
  const meta = [];
8251
8094
  if (options.showSize && node.size !== undefined) {
8252
8095
  meta.push(formatSize(node.size));
@@ -8258,10 +8101,8 @@ function renderTreeText(node, options, prefix = '', isLast = true) {
8258
8101
  line += ` [${meta.join(' ')}]`;
8259
8102
  }
8260
8103
  lines.push(line);
8261
- // Process children
8262
8104
  const childPrefix = prefix + (isLast ? ' ' : '│ ');
8263
8105
  const sortedChildren = [...node.children].sort((a, b) => {
8264
- // Directories first, then alphabetically
8265
8106
  if (a.isDirectory && !b.isDirectory)
8266
8107
  return -1;
8267
8108
  if (!a.isDirectory && b.isDirectory)
@@ -8285,9 +8126,7 @@ function renderTreeText(node, options, prefix = '', isLast = true) {
8285
8126
  */
8286
8127
  function formatTreeText(rootPath, tree, options) {
8287
8128
  const lines = [];
8288
- // Root line
8289
8129
  lines.push(node_path.basename(rootPath));
8290
- // Count stats
8291
8130
  let dirCount = 0;
8292
8131
  let fileCount = 0;
8293
8132
  /**
@@ -8306,7 +8145,6 @@ function formatTreeText(rootPath, tree, options) {
8306
8145
  countNodes(child);
8307
8146
  }
8308
8147
  }
8309
- // Render children of root
8310
8148
  const sortedChildren = [...tree.children].sort((a, b) => {
8311
8149
  if (a.isDirectory && !b.isDirectory)
8312
8150
  return -1;
@@ -8320,7 +8158,6 @@ function formatTreeText(rootPath, tree, options) {
8320
8158
  lines.push(...renderTreeText(child, options, '', isLast));
8321
8159
  countNodes(child);
8322
8160
  }
8323
- // Summary
8324
8161
  lines.push('');
8325
8162
  const dirText = dirCount === 1 ? '1 directory' : `${dirCount} directories`;
8326
8163
  const fileText = fileCount === 1 ? '1 file' : `${fileCount} files`;
@@ -8381,7 +8218,6 @@ function parseTreeArgs(args) {
8381
8218
  function treeCommand(options) {
8382
8219
  const rootPath = options.path ? node_path.resolve(options.path) : process.cwd();
8383
8220
  try {
8384
- // Collect entries via walk
8385
8221
  const walkEntries = [];
8386
8222
  walkDirectory(rootPath, (entry) => {
8387
8223
  walkEntries.push(entry);
@@ -8391,9 +8227,7 @@ function treeCommand(options) {
8391
8227
  ignorePatterns: options.ignore,
8392
8228
  includeHidden: false,
8393
8229
  });
8394
- // Build tree structure
8395
8230
  const tree = buildTree(rootPath, walkEntries, options);
8396
- // Format output
8397
8231
  let output;
8398
8232
  if (options.format === 'json') {
8399
8233
  output = formatTreeJson(tree);
@@ -8452,6 +8286,9 @@ Examples:
8452
8286
 
8453
8287
  /** Logger for CLI operations */
8454
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');
8455
8292
  /** Library version */
8456
8293
  const VERSION = '0.1.0';
8457
8294
  /**
@@ -8467,7 +8304,7 @@ const commands = {
8467
8304
  * Print general help information.
8468
8305
  */
8469
8306
  function printHelp() {
8470
- log(`
8307
+ output.log(`
8471
8308
  project-scope <command> [options]
8472
8309
 
8473
8310
  A tool for analyzing JavaScript/TypeScript project structure and tech stack.
@@ -8499,7 +8336,7 @@ Examples:
8499
8336
  * Print CLI version.
8500
8337
  */
8501
8338
  function printVersion() {
8502
- log(`project-scope v${VERSION}`);
8339
+ output.log(`project-scope v${VERSION}`);
8503
8340
  }
8504
8341
  /**
8505
8342
  * Parse global options from command line arguments.
@@ -8560,24 +8397,24 @@ function run(args) {
8560
8397
  setGlobalLogLevel('debug');
8561
8398
  }
8562
8399
  cliLogger.debug('CLI invoked', { args, globalOptions });
8400
+ const commandName = args[0];
8563
8401
  if (globalOptions.version) {
8564
8402
  printVersion();
8565
8403
  return { exitCode: 0 };
8566
8404
  }
8567
- if (globalOptions.help && (args.length === 1 || !commands[args[0]])) {
8405
+ if (globalOptions.help && (args.length === 1 || !commands[commandName])) {
8568
8406
  printHelp();
8569
8407
  return { exitCode: 0 };
8570
8408
  }
8571
- const commandName = args[0];
8572
8409
  const command = commands[commandName];
8573
8410
  if (!command) {
8574
8411
  cliLogger.warn('Unknown command requested', { commandName });
8575
- error(`Unknown command: ${commandName}`);
8576
- error('Run "project-scope --help" for usage information.');
8412
+ output.error(`Unknown command: ${commandName}`);
8413
+ output.error('Run "project-scope --help" for usage information.');
8577
8414
  return { exitCode: 1, error: `Unknown command: ${commandName}` };
8578
8415
  }
8579
8416
  if (globalOptions.help) {
8580
- log(command.getHelp());
8417
+ output.log(command.getHelp());
8581
8418
  return { exitCode: 0 };
8582
8419
  }
8583
8420
  const commandArgs = args.slice(1);
@@ -8585,11 +8422,11 @@ function run(args) {
8585
8422
  const result = command.execute(commandArgs, globalOptions);
8586
8423
  cliLogger.debug('Command completed', { commandName, exitCode: result.exitCode });
8587
8424
  if (result.output) {
8588
- log(result.output);
8425
+ output.log(result.output);
8589
8426
  }
8590
8427
  if (result.error) {
8591
8428
  cliLogger.error('Command error', { commandName, error: result.error });
8592
- error(result.error);
8429
+ output.error(result.error);
8593
8430
  }
8594
8431
  return result;
8595
8432
  }
@@ -8625,25 +8462,22 @@ const BINARY_SIGNATURES = [
8625
8462
  */
8626
8463
  function detectEncodingInfo(buffer) {
8627
8464
  encodingLogger.debug('Detecting encoding info', { bufferSize: buffer.length });
8628
- // Check for UTF-8 BOM
8629
8465
  if (buffer.length >= 3) {
8630
8466
  if (buffer[0] === UTF8_BOM_BYTES[0] && buffer[1] === UTF8_BOM_BYTES[1] && buffer[2] === UTF8_BOM_BYTES[2]) {
8631
8467
  encodingLogger.debug('Detected UTF-8 BOM');
8632
8468
  return { type: 'text', encoding: 'utf-8', hasBom: true };
8633
8469
  }
8634
8470
  }
8635
- // Check for UTF-16 BOMs
8636
8471
  if (buffer.length >= 2) {
8637
8472
  if (buffer[0] === UTF16_BE_BOM_BYTES[0] && buffer[1] === UTF16_BE_BOM_BYTES[1]) {
8638
8473
  encodingLogger.debug('Detected UTF-16 BE BOM');
8639
- return { type: 'text', encoding: 'utf16le', hasBom: true }; // Node treats BE through utf16le
8474
+ return { type: 'text', encoding: 'utf16le', hasBom: true };
8640
8475
  }
8641
8476
  if (buffer[0] === UTF16_LE_BOM_BYTES[0] && buffer[1] === UTF16_LE_BOM_BYTES[1]) {
8642
8477
  encodingLogger.debug('Detected UTF-16 LE BOM');
8643
8478
  return { type: 'text', encoding: 'utf16le', hasBom: true };
8644
8479
  }
8645
8480
  }
8646
- // Check for binary signatures
8647
8481
  for (const { signature, description } of BINARY_SIGNATURES) {
8648
8482
  if (buffer.length >= signature.length) {
8649
8483
  let matches = true;
@@ -8659,7 +8493,6 @@ function detectEncodingInfo(buffer) {
8659
8493
  }
8660
8494
  }
8661
8495
  }
8662
- // Check for null bytes (usually indicates binary)
8663
8496
  const sampleSize = min(buffer.length, 8000);
8664
8497
  for (let i = 0; i < sampleSize; i++) {
8665
8498
  if (buffer[i] === 0) {
@@ -8678,22 +8511,18 @@ function detectEncodingInfo(buffer) {
8678
8511
  */
8679
8512
  function detectEncoding(buffer) {
8680
8513
  if (buffer.length >= 3) {
8681
- // Check for UTF-8 BOM
8682
8514
  if (buffer[0] === UTF8_BOM_BYTES[0] && buffer[1] === UTF8_BOM_BYTES[1] && buffer[2] === UTF8_BOM_BYTES[2]) {
8683
8515
  return 'utf-8';
8684
8516
  }
8685
8517
  }
8686
8518
  if (buffer.length >= 2) {
8687
- // Check for UTF-16 LE BOM
8688
8519
  if (buffer[0] === UTF16_LE_BOM_BYTES[0] && buffer[1] === UTF16_LE_BOM_BYTES[1]) {
8689
8520
  return 'utf16le';
8690
8521
  }
8691
- // Check for UTF-16 BE BOM
8692
8522
  if (buffer[0] === UTF16_BE_BOM_BYTES[0] && buffer[1] === UTF16_BE_BOM_BYTES[1]) {
8693
- return 'utf16le'; // Node.js handles BE through utf16le
8523
+ return 'utf16le';
8694
8524
  }
8695
8525
  }
8696
- // Default to UTF-8
8697
8526
  return 'utf-8';
8698
8527
  }
8699
8528
  /**
@@ -8763,10 +8592,8 @@ function bufferToString(content, encoding) {
8763
8592
  convertLogger.debug('Using provided encoding', { encoding });
8764
8593
  return content.toString(encoding);
8765
8594
  }
8766
- // Auto-detect and convert
8767
8595
  const info = detectEncodingInfo(content);
8768
8596
  if (info.type === 'text') {
8769
- // Remove BOM if present
8770
8597
  let offset = 0;
8771
8598
  if (info.hasBom) {
8772
8599
  offset = info.encoding === 'utf-8' ? 3 : 2;
@@ -8819,19 +8646,14 @@ function detectCaseSensitivity() {
8819
8646
  if (cachedCaseSensitive !== null) {
8820
8647
  return cachedCaseSensitive;
8821
8648
  }
8822
- // Quick check based on platform
8823
8649
  if (process.platform === 'win32') {
8824
8650
  cachedCaseSensitive = false;
8825
8651
  return false;
8826
8652
  }
8827
- // macOS is typically case-insensitive by default
8828
8653
  if (process.platform === 'darwin') {
8829
- // Could be case-sensitive HFS+/APFS, but assume insensitive by default
8830
8654
  cachedCaseSensitive = false;
8831
8655
  return false;
8832
8656
  }
8833
- // Test actual file system behavior for Linux and others
8834
- // Use mkdtempSync to create a secure temporary directory
8835
8657
  let secureTestDir = null;
8836
8658
  try {
8837
8659
  secureTestDir = node_fs.mkdtempSync(node_path.join(node_os.tmpdir(), 'case-sensitivity-test-'));
@@ -8842,12 +8664,9 @@ function detectCaseSensitivity() {
8842
8664
  node_fs.unlinkSync(testFile);
8843
8665
  }
8844
8666
  catch {
8845
- // Default to case-sensitive on Linux/Unix if test fails
8846
- // (win32 and darwin already returned early, so we're on a case-sensitive platform)
8847
8667
  cachedCaseSensitive = true;
8848
8668
  }
8849
8669
  finally {
8850
- // Clean up the secure temporary directory
8851
8670
  if (secureTestDir) {
8852
8671
  try {
8853
8672
  node_fs.rmdirSync(secureTestDir);
@@ -8982,7 +8801,6 @@ function normalizeLineEndings(content, style = 'lf') {
8982
8801
  else {
8983
8802
  target = style === 'crlf' ? CRLF : LF;
8984
8803
  }
8985
- // First normalize all to LF, then convert to target
8986
8804
  const normalized = content.replace(/\r\n/g, LF).replace(/\r/g, LF);
8987
8805
  if (target === LF) {
8988
8806
  return normalized;
@@ -8997,7 +8815,6 @@ function normalizeLineEndings(content, style = 'lf') {
8997
8815
  */
8998
8816
  function detectLineEnding(content) {
8999
8817
  const hasCRLF = content.includes(CRLF);
9000
- // Check for LF that is NOT part of CRLF
9001
8818
  const hasLFOnly = content.includes('\n') && content.replace(/\r\n/g, '').includes('\n');
9002
8819
  if (hasCRLF && hasLFOnly)
9003
8820
  return 'mixed';
@@ -9475,7 +9292,6 @@ function createFsTree(root, options) {
9475
9292
  }
9476
9293
  const prefix = normalPath === '.' || normalPath === '' ? '' : normalPath + '/';
9477
9294
  for (const [changedPath, change] of _changes) {
9478
- // Handle root-level files
9479
9295
  if (prefix === '') {
9480
9296
  const childName = changedPath.split('/')[0];
9481
9297
  if (change.type === 'DELETE' && !changedPath.includes('/')) {
@@ -9492,7 +9308,6 @@ function createFsTree(root, options) {
9492
9308
  const relativePath = changedPath.slice(prefix.length);
9493
9309
  const childName = relativePath.split('/')[0];
9494
9310
  if (change.type === 'DELETE') {
9495
- // Only remove if it's a direct child being deleted
9496
9311
  if (!relativePath.includes('/')) {
9497
9312
  childSet.delete(childName);
9498
9313
  }
@@ -9578,12 +9393,10 @@ const factoryLogger = createScopedLogger('project-scope:vfs:factory');
9578
9393
  function createTree(root, options) {
9579
9394
  const normalizedRoot = normalizePath(root);
9580
9395
  factoryLogger.debug('createTree', { root: normalizedRoot });
9581
- // Validate root exists
9582
9396
  if (!exists(normalizedRoot)) {
9583
9397
  factoryLogger.warn('createTree failed: root does not exist', { root: normalizedRoot });
9584
9398
  throw createError(`Root directory does not exist: ${normalizedRoot}`);
9585
9399
  }
9586
- // Validate root is a directory
9587
9400
  if (!isDirectory(normalizedRoot)) {
9588
9401
  factoryLogger.warn('createTree failed: root is not a directory', { root: normalizedRoot });
9589
9402
  throw createError(`Root path is not a directory: ${normalizedRoot}`);
@@ -9630,7 +9443,6 @@ const vfsLogger = createScopedLogger('project-scope:vfs');
9630
9443
  function commitChanges(tree, options) {
9631
9444
  const changes = tree.listChanges();
9632
9445
  const appliedChanges = [];
9633
- // Set log level based on verbose option
9634
9446
  if (options?.verbose) {
9635
9447
  vfsLogger.setLogLevel('debug');
9636
9448
  }
@@ -9642,7 +9454,6 @@ function commitChanges(tree, options) {
9642
9454
  changes: [],
9643
9455
  dryRun: options?.dryRun ?? false,
9644
9456
  };
9645
- // Dry run - just count changes without writing
9646
9457
  if (options?.dryRun) {
9647
9458
  for (const change of changes) {
9648
9459
  switch (change.type) {
@@ -9660,7 +9471,6 @@ function commitChanges(tree, options) {
9660
9471
  result.changes = changes;
9661
9472
  return result;
9662
9473
  }
9663
- // Sort changes: deletes first (to free names), then creates, then updates
9664
9474
  const sortedChanges = [...changes].sort((a, b) => {
9665
9475
  const order = { DELETE: 0, CREATE: 1, UPDATE: 2 };
9666
9476
  return order[a.type] - order[b.type];
@@ -9673,11 +9483,9 @@ function commitChanges(tree, options) {
9673
9483
  case 'UPDATE':
9674
9484
  /* istanbul ignore if -- content is always defined for CREATE/UPDATE from tree.write() */
9675
9485
  if (change.content !== undefined) {
9676
- // Ensure directory exists
9677
9486
  const dir = node_path.dirname(absPath);
9678
9487
  ensureDir(dir);
9679
9488
  node_fs.writeFileSync(absPath, change.content);
9680
- // Apply permissions if specified
9681
9489
  if (change.mode !== undefined) {
9682
9490
  node_fs.chmodSync(absPath, change.mode);
9683
9491
  }
@@ -9695,7 +9503,6 @@ function commitChanges(tree, options) {
9695
9503
  node_fs.unlinkSync(absPath);
9696
9504
  }
9697
9505
  catch {
9698
- // Try recursive delete for directories
9699
9506
  node_fs.rmSync(absPath, { recursive: true });
9700
9507
  }
9701
9508
  }
@@ -9709,7 +9516,6 @@ function commitChanges(tree, options) {
9709
9516
  }
9710
9517
  }
9711
9518
  catch (error) {
9712
- // On error, throw with context
9713
9519
  vfsLogger.error('Commit failed', { path: change.path, type: change.type });
9714
9520
  const message = error instanceof Error
9715
9521
  ? `Failed to ${change.type.toLowerCase()} ${change.path}: ${error.message}`
@@ -9718,7 +9524,6 @@ function commitChanges(tree, options) {
9718
9524
  }
9719
9525
  }
9720
9526
  result.changes = appliedChanges;
9721
- // Clear the tree's pending changes after successful commit
9722
9527
  tree.clearChanges();
9723
9528
  return result;
9724
9529
  }
@@ -9794,18 +9599,14 @@ function backtrackLcs(table, oldLines, newLines) {
9794
9599
  * @returns Filtered DiffLine array
9795
9600
  */
9796
9601
  function operationsToDiffLines(operations, contextLines) {
9797
- // Mark which lines should be included (changes + context)
9798
9602
  const include = new Array(operations.length).fill(false);
9799
- // First pass: mark all changes
9800
9603
  for (let i = 0; i < operations.length; i++) {
9801
9604
  if (operations[i].type !== 'same') {
9802
- // Mark this line and context around it
9803
9605
  for (let j = max(0, i - contextLines); j <= min(operations.length - 1, i + contextLines); j++) {
9804
9606
  include[j] = true;
9805
9607
  }
9806
9608
  }
9807
9609
  }
9808
- // Second pass: convert to DiffLine
9809
9610
  const lines = [];
9810
9611
  let oldLineNum = 1;
9811
9612
  let newLineNum = 1;
@@ -9827,7 +9628,6 @@ function operationsToDiffLines(operations, contextLines) {
9827
9628
  }
9828
9629
  }
9829
9630
  else {
9830
- // Skip but update line numbers
9831
9631
  if (op.type === 'same') {
9832
9632
  oldLineNum++;
9833
9633
  newLineNum++;
@@ -9854,7 +9654,6 @@ function bufferToLines(content) {
9854
9654
  if (!content)
9855
9655
  return [];
9856
9656
  const text = content.toString('utf-8');
9857
- // Split by newline, keeping empty last line if present
9858
9657
  return text.split('\n');
9859
9658
  }
9860
9659
  /**
@@ -9881,9 +9680,7 @@ function generateDiff(change, options = {}) {
9881
9680
  diffLogger.debug('generateDiff', { path: change.path, type: change.type, contextLines });
9882
9681
  const oldLines = bufferToLines(change.originalContent);
9883
9682
  const newLines = bufferToLines(change.content);
9884
- // Handle edge cases
9885
9683
  if (change.type === 'CREATE') {
9886
- // All lines are additions
9887
9684
  const lines = newLines
9888
9685
  .filter((line) => line !== '' || newLines.indexOf(line) !== newLines.length - 1 || newLines.length === 1)
9889
9686
  .map((content, idx) => ({
@@ -9891,7 +9688,6 @@ function generateDiff(change, options = {}) {
9891
9688
  line: idx + 1,
9892
9689
  content,
9893
9690
  }));
9894
- // Filter out empty trailing line from split
9895
9691
  const filteredLines = lines.filter((l, i) => !(i === lines.length - 1 && l.content === '' && lines.length > 1));
9896
9692
  return {
9897
9693
  path: change.path,
@@ -9901,13 +9697,11 @@ function generateDiff(change, options = {}) {
9901
9697
  };
9902
9698
  }
9903
9699
  if (change.type === 'DELETE') {
9904
- // All lines are deletions
9905
9700
  const lines = oldLines.map((content, idx) => ({
9906
9701
  type: 'remove',
9907
9702
  line: idx + 1,
9908
9703
  content,
9909
9704
  }));
9910
- // Filter out empty trailing line from split
9911
9705
  const filteredLines = lines.filter((l, i) => !(i === lines.length - 1 && l.content === '' && lines.length > 1));
9912
9706
  return {
9913
9707
  path: change.path,
@@ -9916,7 +9710,6 @@ function generateDiff(change, options = {}) {
9916
9710
  deletions: filteredLines.length,
9917
9711
  };
9918
9712
  }
9919
- // UPDATE: compute actual diff
9920
9713
  const table = computeLcsTable(oldLines, newLines);
9921
9714
  const operations = backtrackLcs(table, oldLines, newLines);
9922
9715
  const lines = operationsToDiffLines(operations, contextLines);
@@ -9950,13 +9743,11 @@ function generateDiff(change, options = {}) {
9950
9743
  */
9951
9744
  function formatUnifiedDiff(diff) {
9952
9745
  const lines = [];
9953
- // Header
9954
9746
  lines.push(`--- a/${diff.path}`);
9955
9747
  lines.push(`+++ b/${diff.path}`);
9956
9748
  if (diff.lines.length === 0) {
9957
9749
  return lines.join('\n');
9958
9750
  }
9959
- // Group lines into hunks
9960
9751
  const hunks = [];
9961
9752
  const currentHunk = [];
9962
9753
  for (const line of diff.lines) {
@@ -9965,9 +9756,7 @@ function formatUnifiedDiff(diff) {
9965
9756
  if (currentHunk.length > 0) {
9966
9757
  hunks.push(currentHunk);
9967
9758
  }
9968
- // Output hunks
9969
9759
  for (const hunk of hunks) {
9970
- // Calculate hunk header
9971
9760
  const contextAndRemove = hunk.filter((l) => l.type === 'context' || l.type === 'remove');
9972
9761
  const contextAndAdd = hunk.filter((l) => l.type === 'context' || l.type === 'add');
9973
9762
  const oldStart = hunk.find((l) => l.type === 'context' || l.type === 'remove')?.line ?? 1;
@@ -9975,7 +9764,6 @@ function formatUnifiedDiff(diff) {
9975
9764
  const oldCount = contextAndRemove.length;
9976
9765
  const newCount = contextAndAdd.length;
9977
9766
  lines.push(`@@ -${oldStart},${oldCount} +${newStart},${newCount} @@`);
9978
- // Output lines
9979
9767
  for (const line of hunk) {
9980
9768
  const prefix = line.type === 'add' ? '+' : line.type === 'remove' ? '-' : ' ';
9981
9769
  lines.push(`${prefix}${line.content}`);