@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.esm.js CHANGED
@@ -11,7 +11,6 @@ import { tmpdir, platform, arch } from 'node:os';
11
11
  *
12
12
  * @module @hyperfrontend/immutable-api-utils/built-in-copy/console
13
13
  */
14
- // Capture references at module initialization time
15
14
  const _console = globalThis.console;
16
15
  /**
17
16
  * (Safe copy) Outputs a message to the console.
@@ -86,44 +85,6 @@ _console.timeEnd.bind(_console);
86
85
  */
87
86
  _console.timeLog.bind(_console);
88
87
 
89
- /**
90
- * Safe copies of Array built-in static methods.
91
- *
92
- * These references are captured at module initialization time to protect against
93
- * prototype pollution attacks. Import only what you need for tree-shaking.
94
- *
95
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/array
96
- */
97
- // Capture references at module initialization time
98
- const _Array = globalThis.Array;
99
- /**
100
- * (Safe copy) Determines whether the passed value is an Array.
101
- */
102
- const isArray = _Array.isArray;
103
- /**
104
- * (Safe copy) Creates an array from an array-like or iterable object.
105
- */
106
- const from = _Array.from;
107
-
108
- /**
109
- * Safe copies of JSON built-in methods.
110
- *
111
- * These references are captured at module initialization time to protect against
112
- * prototype pollution attacks. Import only what you need for tree-shaking.
113
- *
114
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/json
115
- */
116
- // Capture references at module initialization time
117
- const _JSON = globalThis.JSON;
118
- /**
119
- * (Safe copy) Converts a JavaScript Object Notation (JSON) string into an object.
120
- */
121
- const parse = _JSON.parse;
122
- /**
123
- * (Safe copy) Converts a JavaScript value to a JavaScript Object Notation (JSON) string.
124
- */
125
- const stringify = _JSON.stringify;
126
-
127
88
  /**
128
89
  * Safe copies of Object built-in methods.
129
90
  *
@@ -132,7 +93,6 @@ const stringify = _JSON.stringify;
132
93
  *
133
94
  * @module @hyperfrontend/immutable-api-utils/built-in-copy/object
134
95
  */
135
- // Capture references at module initialization time
136
96
  const _Object = globalThis.Object;
137
97
  /**
138
98
  * (Safe copy) Prevents modification of existing property attributes and values,
@@ -160,30 +120,25 @@ const defineProperty = _Object.defineProperty;
160
120
  */
161
121
  const defineProperties = _Object.defineProperties;
162
122
 
123
+ const registeredClasses = [];
124
+
163
125
  /**
164
- * Safe copies of Set built-in via factory function.
165
- *
166
- * Since constructors cannot be safely captured via Object.assign, this module
167
- * provides a factory function that uses Reflect.construct internally.
126
+ * Safe copies of Array built-in static methods.
168
127
  *
169
128
  * These references are captured at module initialization time to protect against
170
129
  * prototype pollution attacks. Import only what you need for tree-shaking.
171
130
  *
172
- * @module @hyperfrontend/immutable-api-utils/built-in-copy/set
131
+ * @module @hyperfrontend/immutable-api-utils/built-in-copy/array
173
132
  */
174
- // Capture references at module initialization time
175
- const _Set = globalThis.Set;
176
- const _Reflect$3 = globalThis.Reflect;
133
+ const _Array = globalThis.Array;
177
134
  /**
178
- * (Safe copy) Creates a new Set using the captured Set constructor.
179
- * Use this instead of `new Set()`.
180
- *
181
- * @param iterable - Optional iterable of values.
182
- * @returns A new Set instance.
135
+ * (Safe copy) Determines whether the passed value is an Array.
183
136
  */
184
- const createSet = (iterable) => _Reflect$3.construct(_Set, iterable ? [iterable] : []);
185
-
186
- const registeredClasses = [];
137
+ const isArray = _Array.isArray;
138
+ /**
139
+ * (Safe copy) Creates an array from an array-like or iterable object.
140
+ */
141
+ const from = _Array.from;
187
142
 
188
143
  /**
189
144
  * Returns the data type of the target.
@@ -219,9 +174,8 @@ const getType = (target) => {
219
174
  *
220
175
  * @module @hyperfrontend/immutable-api-utils/built-in-copy/error
221
176
  */
222
- // Capture references at module initialization time
223
177
  const _Error = globalThis.Error;
224
- const _Reflect$2 = globalThis.Reflect;
178
+ const _Reflect$3 = globalThis.Reflect;
225
179
  /**
226
180
  * (Safe copy) Creates a new Error using the captured Error constructor.
227
181
  * Use this instead of `new Error()`.
@@ -230,7 +184,7 @@ const _Reflect$2 = globalThis.Reflect;
230
184
  * @param options - Optional error options.
231
185
  * @returns A new Error instance.
232
186
  */
233
- const createError = (message, options) => _Reflect$2.construct(_Error, [message, options]);
187
+ const createError = (message, options) => _Reflect$3.construct(_Error, [message, options]);
234
188
 
235
189
  /**
236
190
  * Safe copies of Map built-in via factory function.
@@ -243,9 +197,8 @@ const createError = (message, options) => _Reflect$2.construct(_Error, [message,
243
197
  *
244
198
  * @module @hyperfrontend/immutable-api-utils/built-in-copy/map
245
199
  */
246
- // Capture references at module initialization time
247
200
  const _Map = globalThis.Map;
248
- const _Reflect$1 = globalThis.Reflect;
201
+ const _Reflect$2 = globalThis.Reflect;
249
202
  /**
250
203
  * (Safe copy) Creates a new Map using the captured Map constructor.
251
204
  * Use this instead of `new Map()`.
@@ -253,7 +206,7 @@ const _Reflect$1 = globalThis.Reflect;
253
206
  * @param iterable - Optional iterable of key-value pairs.
254
207
  * @returns A new Map instance.
255
208
  */
256
- const createMap = (iterable) => _Reflect$1.construct(_Map, iterable ? [iterable] : []);
209
+ const createMap = (iterable) => _Reflect$2.construct(_Map, iterable ? [iterable] : []);
257
210
 
258
211
  /**
259
212
  * Safe copies of Date built-in via factory function and static methods.
@@ -266,11 +219,10 @@ const createMap = (iterable) => _Reflect$1.construct(_Map, iterable ? [iterable]
266
219
  *
267
220
  * @module @hyperfrontend/immutable-api-utils/built-in-copy/date
268
221
  */
269
- // Capture references at module initialization time
270
222
  const _Date = globalThis.Date;
271
- const _Reflect = globalThis.Reflect;
223
+ const _Reflect$1 = globalThis.Reflect;
272
224
  function createDate(...args) {
273
- return _Reflect.construct(_Date, args);
225
+ return _Reflect$1.construct(_Date, args);
274
226
  }
275
227
  /**
276
228
  * (Safe copy) Returns the number of milliseconds elapsed since January 1, 1970 00:00:00 UTC.
@@ -285,15 +237,11 @@ const dateNow = _Date.now;
285
237
  *
286
238
  * @module @hyperfrontend/immutable-api-utils/built-in-copy/math
287
239
  */
288
- // Capture references at module initialization time
289
240
  const _Math = globalThis.Math;
290
241
  /**
291
242
  * (Safe copy) Returns the value of a number rounded to the nearest integer.
292
243
  */
293
244
  const round = _Math.round;
294
- // ============================================================================
295
- // Min/Max
296
- // ============================================================================
297
245
  /**
298
246
  * (Safe copy) Returns the larger of zero or more numbers.
299
247
  */
@@ -303,6 +251,28 @@ const max = _Math.max;
303
251
  */
304
252
  const min = _Math.min;
305
253
 
254
+ /**
255
+ * Safe copies of Set built-in via factory function.
256
+ *
257
+ * Since constructors cannot be safely captured via Object.assign, this module
258
+ * provides a factory function that uses Reflect.construct internally.
259
+ *
260
+ * These references are captured at module initialization time to protect against
261
+ * prototype pollution attacks. Import only what you need for tree-shaking.
262
+ *
263
+ * @module @hyperfrontend/immutable-api-utils/built-in-copy/set
264
+ */
265
+ const _Set = globalThis.Set;
266
+ const _Reflect = globalThis.Reflect;
267
+ /**
268
+ * (Safe copy) Creates a new Set using the captured Set constructor.
269
+ * Use this instead of `new Set()`.
270
+ *
271
+ * @param iterable - Optional iterable of values.
272
+ * @returns A new Set instance.
273
+ */
274
+ const createSet = (iterable) => _Reflect.construct(_Set, iterable ? [iterable] : []);
275
+
306
276
  /* eslint-disable @typescript-eslint/no-explicit-any */
307
277
  /**
308
278
  * Creates a wrapper function that only executes the wrapped function if the condition function returns true.
@@ -466,6 +436,24 @@ function notFnMsg(label) {
466
436
 
467
437
  createLogger(error, warn, log, info, debug);
468
438
 
439
+ /**
440
+ * Safe copies of JSON built-in methods.
441
+ *
442
+ * These references are captured at module initialization time to protect against
443
+ * prototype pollution attacks. Import only what you need for tree-shaking.
444
+ *
445
+ * @module @hyperfrontend/immutable-api-utils/built-in-copy/json
446
+ */
447
+ const _JSON = globalThis.JSON;
448
+ /**
449
+ * (Safe copy) Converts a JavaScript Object Notation (JSON) string into an object.
450
+ */
451
+ const parse = _JSON.parse;
452
+ /**
453
+ * (Safe copy) Converts a JavaScript value to a JavaScript Object Notation (JSON) string.
454
+ */
455
+ const stringify = _JSON.stringify;
456
+
469
457
  /**
470
458
  * Global log level registry.
471
459
  * Tracks all created scoped loggers to allow global log level changes.
@@ -609,14 +597,11 @@ function formatMessage(namespace, message, meta) {
609
597
  */
610
598
  function createScopedLogger(namespace, options = {}) {
611
599
  const { level = 'error', sanitizeSecrets = true } = options;
612
- // Create wrapper functions that add namespace prefix and sanitization
613
600
  const createLogFn = (baseFn) => (message, meta) => {
614
601
  const processedMeta = sanitizeSecrets && meta ? sanitize(meta) : meta;
615
602
  baseFn(formatMessage(namespace, message, processedMeta));
616
603
  };
617
- // Create base logger with wrapped functions
618
604
  const baseLogger = createLogger(createLogFn(error), createLogFn(warn), createLogFn(log), createLogFn(info), createLogFn(debug));
619
- // Set initial log level (use global override if set)
620
605
  baseLogger.setLogLevel(globalLogLevel ?? level);
621
606
  const scopedLogger = freeze({
622
607
  error: (message, meta) => baseLogger.error(message, meta),
@@ -627,7 +612,6 @@ function createScopedLogger(namespace, options = {}) {
627
612
  setLogLevel: baseLogger.setLogLevel,
628
613
  getLogLevel: baseLogger.getLogLevel,
629
614
  });
630
- // Register logger for global level management
631
615
  loggerRegistry.add(scopedLogger);
632
616
  return scopedLogger;
633
617
  }
@@ -1063,17 +1047,14 @@ function readDirectoryRecursive(dirPath, options) {
1063
1047
  entries = readDirectory(currentPath);
1064
1048
  }
1065
1049
  catch {
1066
- // Skip inaccessible directories
1067
1050
  fsDirLogger.debug('Skipping inaccessible directory', { path: currentPath });
1068
1051
  return;
1069
1052
  }
1070
1053
  for (const entry of entries) {
1071
- // Skip hidden files/dirs if not included
1072
1054
  if (!includeHidden && entry.name.startsWith('.')) {
1073
1055
  continue;
1074
1056
  }
1075
1057
  results.push({ ...entry, depth });
1076
- // Recurse into directories
1077
1058
  if (entry.isDirectory || (entry.isSymlink && followSymlinks && isDirectory(entry.path))) {
1078
1059
  walk(entry.path, depth + 1);
1079
1060
  }
@@ -1141,7 +1122,6 @@ function joinPosix(...paths) {
1141
1122
  function normalizePath(filePath) {
1142
1123
  if (!filePath)
1143
1124
  return '';
1144
- // Normalize path and convert backslashes to forward slashes
1145
1125
  const normalized = normalize(filePath);
1146
1126
  return sep === '\\' ? normalized.replace(/\\/g, '/') : normalized;
1147
1127
  }
@@ -1353,7 +1333,6 @@ function traverseUpward(startPath, predicate) {
1353
1333
  }
1354
1334
  currentPath = dirname(currentPath);
1355
1335
  }
1356
- // Check root directory
1357
1336
  if (predicate(rootPath)) {
1358
1337
  fsTraversalLogger.debug('Upward traversal found match at root', { startPath, foundPath: rootPath });
1359
1338
  return rootPath;
@@ -1725,29 +1704,23 @@ const depsLogger = createScopedLogger('project-scope:heuristics:deps');
1725
1704
  */
1726
1705
  function extractImports(content) {
1727
1706
  const imports = [];
1728
- // ES import with 'from': import X from 'path' or import { X } from 'path'
1729
- // Use non-greedy match and avoid nested quantifiers by matching "from" keyword directly
1730
1707
  const esImportFromRegex = /import\s+.+?\s+from\s+['"]([^'"]+)['"]/g;
1731
1708
  let match;
1732
1709
  while ((match = esImportFromRegex.exec(content)) !== null) {
1733
1710
  imports.push(match[1]);
1734
1711
  }
1735
- // Side-effect import: import 'path'
1736
1712
  const sideEffectImportRegex = /import\s+['"]([^'"]+)['"]/g;
1737
1713
  while ((match = sideEffectImportRegex.exec(content)) !== null) {
1738
1714
  imports.push(match[1]);
1739
1715
  }
1740
- // Dynamic import: import('path')
1741
1716
  const dynamicImportRegex = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
1742
1717
  while ((match = dynamicImportRegex.exec(content)) !== null) {
1743
1718
  imports.push(match[1]);
1744
1719
  }
1745
- // require: require('path')
1746
1720
  const requireRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
1747
1721
  while ((match = requireRegex.exec(content)) !== null) {
1748
1722
  imports.push(match[1]);
1749
1723
  }
1750
- // Re-export: export * from 'path' or export { X } from 'path'
1751
1724
  const exportFromRegex = /export\s+.+?\s+from\s+['"]([^'"]+)['"]/g;
1752
1725
  while ((match = exportFromRegex.exec(content)) !== null) {
1753
1726
  imports.push(match[1]);
@@ -2061,7 +2034,6 @@ const cacheRegistry = createSet();
2061
2034
  function createCache(options) {
2062
2035
  const { ttl, maxSize } = options ?? {};
2063
2036
  const store = createMap();
2064
- // Track insertion order for FIFO eviction
2065
2037
  const insertionOrder = [];
2066
2038
  /**
2067
2039
  * Check if an entry is expired.
@@ -2112,12 +2084,10 @@ function createCache(options) {
2112
2084
  return entry.value;
2113
2085
  },
2114
2086
  set(key, value) {
2115
- // If key exists, remove from order first
2116
2087
  if (store.has(key)) {
2117
2088
  removeFromOrder(key);
2118
2089
  }
2119
2090
  else {
2120
- // Evict if needed before adding new entry
2121
2091
  evictIfNeeded();
2122
2092
  }
2123
2093
  // eslint-disable-next-line workspace/no-unsafe-builtin-methods -- Date.now() is needed for Jest fake timers compatibility
@@ -2150,7 +2120,6 @@ function createCache(options) {
2150
2120
  return [...insertionOrder];
2151
2121
  },
2152
2122
  };
2153
- // Register cache for global operations
2154
2123
  cacheRegistry.add(cache);
2155
2124
  return freeze(cache);
2156
2125
  }
@@ -2230,7 +2199,6 @@ function memoize(fn, options) {
2230
2199
  cache.set(key, result);
2231
2200
  return result;
2232
2201
  };
2233
- // Attach cache for direct access
2234
2202
  defineProperty(memoized, 'cache', {
2235
2203
  value: cache,
2236
2204
  writable: false,
@@ -2239,10 +2207,6 @@ function memoize(fn, options) {
2239
2207
  return memoized;
2240
2208
  }
2241
2209
 
2242
- /**
2243
- * Pattern matching utilities with ReDoS protection.
2244
- * Uses character-by-character matching instead of regex where possible.
2245
- */
2246
2210
  /**
2247
2211
  * Match path against glob pattern using safe character iteration.
2248
2212
  * Avoids regex to prevent ReDoS attacks.
@@ -2280,17 +2244,14 @@ function matchGlobPattern(path, pattern) {
2280
2244
  * @returns True if remaining segments match
2281
2245
  */
2282
2246
  function matchSegments(pathParts, patternParts, pathIdx, patternIdx) {
2283
- // Base cases
2284
2247
  if (pathIdx === pathParts.length && patternIdx === patternParts.length) {
2285
- return true; // Both exhausted = match
2248
+ return true;
2286
2249
  }
2287
2250
  if (patternIdx >= patternParts.length) {
2288
- return false; // Pattern exhausted but path remains
2251
+ return false;
2289
2252
  }
2290
2253
  const patternPart = patternParts[patternIdx];
2291
- // Handle ** (globstar) - matches zero or more directories
2292
2254
  if (patternPart === '**') {
2293
- // Try matching rest of pattern against current position and all future positions
2294
2255
  for (let i = pathIdx; i <= pathParts.length; i++) {
2295
2256
  if (matchSegments(pathParts, patternParts, i, patternIdx + 1)) {
2296
2257
  return true;
@@ -2299,10 +2260,9 @@ function matchSegments(pathParts, patternParts, pathIdx, patternIdx) {
2299
2260
  return false;
2300
2261
  }
2301
2262
  if (pathIdx >= pathParts.length) {
2302
- return false; // Path exhausted but pattern remains (and it's not **)
2263
+ return false;
2303
2264
  }
2304
2265
  const pathPart = pathParts[pathIdx];
2305
- // Match current segment
2306
2266
  if (matchSegment(pathPart, patternPart)) {
2307
2267
  return matchSegments(pathParts, patternParts, pathIdx + 1, patternIdx + 1);
2308
2268
  }
@@ -2322,12 +2282,10 @@ function matchSegment(text, pattern) {
2322
2282
  while (patternIdx < pattern.length) {
2323
2283
  const char = pattern[patternIdx];
2324
2284
  if (char === '*') {
2325
- // * matches zero or more characters
2326
2285
  patternIdx++;
2327
2286
  if (patternIdx === pattern.length) {
2328
- return true; // * at end matches rest of string
2287
+ return true;
2329
2288
  }
2330
- // Try matching rest of pattern at each position in text
2331
2289
  for (let i = textIdx; i <= text.length; i++) {
2332
2290
  if (matchSegmentFrom(text, i, pattern, patternIdx)) {
2333
2291
  return true;
@@ -2336,7 +2294,6 @@ function matchSegment(text, pattern) {
2336
2294
  return false;
2337
2295
  }
2338
2296
  else if (char === '?') {
2339
- // ? matches exactly one character
2340
2297
  if (textIdx >= text.length) {
2341
2298
  return false;
2342
2299
  }
@@ -2344,10 +2301,8 @@ function matchSegment(text, pattern) {
2344
2301
  patternIdx++;
2345
2302
  }
2346
2303
  else if (char === '{') {
2347
- // {a,b,c} matches any alternative
2348
2304
  const closeIdx = findClosingBrace(pattern, patternIdx);
2349
2305
  if (closeIdx === -1) {
2350
- // Unmatched brace, treat as literal
2351
2306
  if (textIdx >= text.length || text[textIdx] !== char) {
2352
2307
  return false;
2353
2308
  }
@@ -2365,7 +2320,6 @@ function matchSegment(text, pattern) {
2365
2320
  }
2366
2321
  }
2367
2322
  else {
2368
- // Literal character
2369
2323
  if (textIdx >= text.length || text[textIdx] !== char) {
2370
2324
  return false;
2371
2325
  }
@@ -2791,7 +2745,8 @@ const entryPointLogger = createScopedLogger('project-scope:heuristics:entry-poin
2791
2745
  */
2792
2746
  const entryPointCache = createCache({ ttl: 60000, maxSize: 50 });
2793
2747
  /**
2794
- * Common entry point patterns.
2748
+ * Common entry point patterns by project type.
2749
+ * Used for convention-based entry point discovery.
2795
2750
  */
2796
2751
  const ENTRY_POINT_PATTERNS = {
2797
2752
  /** Library entry patterns */
@@ -3074,7 +3029,6 @@ function collectAllDependencies(packageJson) {
3074
3029
  function parseVersionString(versionString) {
3075
3030
  if (versionString === undefined || versionString === null)
3076
3031
  return undefined;
3077
- // Manual parsing instead of regex to avoid ReDoS
3078
3032
  let start = 0;
3079
3033
  while (start < versionString.length) {
3080
3034
  const char = versionString[start];
@@ -3135,7 +3089,6 @@ function expressDetector(projectPath, packageJson) {
3135
3089
  version = parseVersionString(deps['express']);
3136
3090
  sources.push({ type: 'package.json', field: 'dependencies.express' });
3137
3091
  }
3138
- // @types/express (indicates usage)
3139
3092
  if (deps['@types/express']) {
3140
3093
  confidence += 10;
3141
3094
  sources.push({ type: 'package.json', field: 'dependencies.@types/express' });
@@ -3171,13 +3124,11 @@ function nestDetector(projectPath, packageJson) {
3171
3124
  let version;
3172
3125
  let configPath;
3173
3126
  const deps = collectAllDependencies(pkg);
3174
- // @nestjs/core package
3175
3127
  if (deps['@nestjs/core']) {
3176
3128
  confidence += 70;
3177
3129
  version = parseVersionString(deps['@nestjs/core']);
3178
3130
  sources.push({ type: 'package.json', field: 'dependencies.@nestjs/core' });
3179
3131
  }
3180
- // @nestjs/common
3181
3132
  if (deps['@nestjs/common']) {
3182
3133
  confidence += 15;
3183
3134
  sources.push({ type: 'package.json', field: 'dependencies.@nestjs/common' });
@@ -3228,7 +3179,6 @@ function fastifyDetector(projectPath, packageJson) {
3228
3179
  confidence += 15;
3229
3180
  sources.push({ type: 'package.json', field: 'dependencies (fastify plugins)' });
3230
3181
  }
3231
- // @types/fastify (older versions)
3232
3182
  if (deps['@types/fastify']) {
3233
3183
  confidence += 5;
3234
3184
  sources.push({ type: 'package.json', field: 'dependencies.@types/fastify' });
@@ -3263,7 +3213,6 @@ function koaDetector(projectPath, packageJson) {
3263
3213
  version = parseVersionString(deps['koa']);
3264
3214
  sources.push({ type: 'package.json', field: 'dependencies.koa' });
3265
3215
  }
3266
- // @types/koa
3267
3216
  if (deps['@types/koa']) {
3268
3217
  confidence += 10;
3269
3218
  sources.push({ type: 'package.json', field: 'dependencies.@types/koa' });
@@ -3842,7 +3791,6 @@ function remixDetector(projectPath, packageJson) {
3842
3791
  let confidence = 0;
3843
3792
  let version;
3844
3793
  const deps = collectAllDependencies(pkg);
3845
- // @remix-run packages
3846
3794
  if (deps['@remix-run/react']) {
3847
3795
  confidence += 70;
3848
3796
  version = parseVersionString(deps['@remix-run/react']);
@@ -4118,7 +4066,6 @@ function sveltekitDetector(projectPath, packageJson) {
4118
4066
  let confidence = 0;
4119
4067
  let version;
4120
4068
  const deps = collectAllDependencies(pkg);
4121
- // @sveltejs/kit package
4122
4069
  if (deps['@sveltejs/kit']) {
4123
4070
  confidence += 70;
4124
4071
  version = parseVersionString(deps['@sveltejs/kit']);
@@ -4197,13 +4144,11 @@ function qwikDetector(projectPath, packageJson) {
4197
4144
  let confidence = 0;
4198
4145
  let version;
4199
4146
  const deps = collectAllDependencies(pkg);
4200
- // @builder.io/qwik package
4201
4147
  if (deps['@builder.io/qwik']) {
4202
4148
  confidence += 70;
4203
4149
  version = parseVersionString(deps['@builder.io/qwik']);
4204
4150
  sources.push({ type: 'package.json', field: 'dependencies.@builder.io/qwik' });
4205
4151
  }
4206
- // @builder.io/qwik-city
4207
4152
  if (deps['@builder.io/qwik-city']) {
4208
4153
  confidence += 20;
4209
4154
  sources.push({ type: 'package.json', field: 'dependencies.@builder.io/qwik-city' });
@@ -4314,23 +4259,19 @@ function angularJSDetector(projectPath, packageJson) {
4314
4259
  let confidence = 0;
4315
4260
  let version;
4316
4261
  const deps = collectAllDependencies(pkg);
4317
- // AngularJS package (angular, not @angular/core)
4318
4262
  if (deps['angular']) {
4319
4263
  confidence += 70;
4320
4264
  version = parseVersionString(deps['angular']);
4321
4265
  sources.push({ type: 'package.json', field: 'dependencies.angular' });
4322
4266
  }
4323
- // AngularJS router
4324
4267
  if (deps['angular-route']) {
4325
4268
  confidence += 15;
4326
4269
  sources.push({ type: 'package.json', field: 'dependencies.angular-route' });
4327
4270
  }
4328
- // AngularJS resource
4329
4271
  if (deps['angular-resource']) {
4330
4272
  confidence += 10;
4331
4273
  sources.push({ type: 'package.json', field: 'dependencies.angular-resource' });
4332
4274
  }
4333
- // AngularJS animate
4334
4275
  if (deps['angular-animate']) {
4335
4276
  confidence += 5;
4336
4277
  sources.push({ type: 'package.json', field: 'dependencies.angular-animate' });
@@ -4361,23 +4302,19 @@ function backboneDetector(projectPath, packageJson) {
4361
4302
  let confidence = 0;
4362
4303
  let version;
4363
4304
  const deps = collectAllDependencies(pkg);
4364
- // Backbone package
4365
4305
  if (deps['backbone']) {
4366
4306
  confidence += 70;
4367
4307
  version = parseVersionString(deps['backbone']);
4368
4308
  sources.push({ type: 'package.json', field: 'dependencies.backbone' });
4369
- // Underscore (commonly used with Backbone)
4370
4309
  if (deps['underscore']) {
4371
4310
  confidence += 15;
4372
4311
  sources.push({ type: 'package.json', field: 'dependencies.underscore' });
4373
4312
  }
4374
- // Lodash can be used as underscore replacement
4375
4313
  if (deps['lodash']) {
4376
4314
  confidence += 5;
4377
4315
  sources.push({ type: 'package.json', field: 'dependencies.lodash' });
4378
4316
  }
4379
4317
  }
4380
- // Marionette (Backbone framework)
4381
4318
  if (deps['backbone.marionette'] || deps['marionette']) {
4382
4319
  confidence += 10;
4383
4320
  sources.push({ type: 'package.json', field: 'dependencies.backbone.marionette' });
@@ -4408,18 +4345,15 @@ function emberDetector(projectPath, packageJson) {
4408
4345
  let confidence = 0;
4409
4346
  let version;
4410
4347
  const deps = collectAllDependencies(pkg);
4411
- // Ember source package
4412
4348
  if (deps['ember-source']) {
4413
4349
  confidence += 70;
4414
4350
  version = parseVersionString(deps['ember-source']);
4415
4351
  sources.push({ type: 'package.json', field: 'dependencies.ember-source' });
4416
4352
  }
4417
- // Ember CLI
4418
4353
  if (deps['ember-cli']) {
4419
4354
  confidence += 20;
4420
4355
  sources.push({ type: 'package.json', field: 'devDependencies.ember-cli' });
4421
4356
  }
4422
- // Ember Data
4423
4357
  if (deps['ember-data']) {
4424
4358
  confidence += 10;
4425
4359
  sources.push({ type: 'package.json', field: 'dependencies.ember-data' });
@@ -4450,18 +4384,15 @@ function jqueryDetector(projectPath, packageJson) {
4450
4384
  let confidence = 0;
4451
4385
  let version;
4452
4386
  const deps = collectAllDependencies(pkg);
4453
- // jQuery package
4454
4387
  if (deps['jquery']) {
4455
4388
  confidence += 80;
4456
4389
  version = parseVersionString(deps['jquery']);
4457
4390
  sources.push({ type: 'package.json', field: 'dependencies.jquery' });
4458
4391
  }
4459
- // jQuery UI
4460
4392
  if (deps['jquery-ui']) {
4461
4393
  confidence += 10;
4462
4394
  sources.push({ type: 'package.json', field: 'dependencies.jquery-ui' });
4463
4395
  }
4464
- // jQuery plugins
4465
4396
  if (deps['jquery-validation']) {
4466
4397
  confidence += 5;
4467
4398
  sources.push({ type: 'package.json', field: 'dependencies.jquery-validation' });
@@ -4608,7 +4539,6 @@ function prettierDetector(projectPath, packageJson) {
4608
4539
  confidence += 30;
4609
4540
  sources.push({ type: 'package.json', field: 'prettier' });
4610
4541
  }
4611
- // .prettierignore file
4612
4542
  if (exists(join$1(projectPath, '.prettierignore'))) {
4613
4543
  confidence += 10;
4614
4544
  sources.push({ type: 'config-file', path: '.prettierignore' });
@@ -4703,7 +4633,6 @@ function biomeDetector(projectPath, packageJson) {
4703
4633
  let configPath;
4704
4634
  let version;
4705
4635
  const deps = collectAllDependencies(pkg);
4706
- // @biomejs/biome package
4707
4636
  if (deps['@biomejs/biome']) {
4708
4637
  confidence += 70;
4709
4638
  version = parseVersionString(deps['@biomejs/biome']);
@@ -4990,7 +4919,7 @@ function npmWorkspacesDetector(workspacePath, packageJson) {
4990
4919
  sources.push({ type: 'lockfile', path: 'package-lock.json' });
4991
4920
  }
4992
4921
  if (exists(join$1(workspacePath, 'yarn.lock'))) {
4993
- return null; // Let yarn workspace detector handle this
4922
+ return null;
4994
4923
  }
4995
4924
  if (confidence === 0) {
4996
4925
  return null;
@@ -5369,7 +5298,6 @@ function checkTsConfigStrict(projectPath) {
5369
5298
  if (!content)
5370
5299
  return undefined;
5371
5300
  try {
5372
- // Simple JSON parsing - doesn't handle comments but good enough for strict check
5373
5301
  const cleanContent = content.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '');
5374
5302
  const parsed = parse(cleanContent);
5375
5303
  return parsed?.compilerOptions?.strict === true;
@@ -5392,19 +5320,16 @@ function typescriptDetector(projectPath, packageJson) {
5392
5320
  let configPath;
5393
5321
  let version;
5394
5322
  const deps = collectAllDependencies(pkg);
5395
- // TypeScript package
5396
5323
  if (deps['typescript']) {
5397
5324
  confidence += 50;
5398
5325
  version = parseVersionString(deps['typescript']);
5399
5326
  sources.push({ type: 'package.json', field: 'dependencies.typescript' });
5400
5327
  }
5401
- // tsconfig.json
5402
5328
  if (exists(join$1(projectPath, 'tsconfig.json'))) {
5403
5329
  confidence += 40;
5404
5330
  configPath = 'tsconfig.json';
5405
5331
  sources.push({ type: 'config-file', path: 'tsconfig.json' });
5406
5332
  }
5407
- // tsconfig.*.json variants
5408
5333
  const tsconfigVariants = ['tsconfig.build.json', 'tsconfig.lib.json', 'tsconfig.spec.json', 'tsconfig.app.json'];
5409
5334
  for (const variant of tsconfigVariants) {
5410
5335
  if (exists(join$1(projectPath, variant))) {
@@ -5413,7 +5338,6 @@ function typescriptDetector(projectPath, packageJson) {
5413
5338
  break;
5414
5339
  }
5415
5340
  }
5416
- // @types packages
5417
5341
  const typePackages = keys(deps).filter((d) => d.startsWith('@types/'));
5418
5342
  if (typePackages.length > 0) {
5419
5343
  confidence += 10;
@@ -5447,24 +5371,20 @@ function flowDetector(projectPath, packageJson) {
5447
5371
  let configPath;
5448
5372
  let version;
5449
5373
  const deps = collectAllDependencies(pkg);
5450
- // flow-bin package
5451
5374
  if (deps['flow-bin']) {
5452
5375
  confidence += 60;
5453
5376
  version = parseVersionString(deps['flow-bin']);
5454
5377
  sources.push({ type: 'package.json', field: 'dependencies.flow-bin' });
5455
5378
  }
5456
- // .flowconfig
5457
5379
  if (exists(join$1(projectPath, '.flowconfig'))) {
5458
5380
  confidence += 40;
5459
5381
  configPath = '.flowconfig';
5460
5382
  sources.push({ type: 'config-file', path: '.flowconfig' });
5461
5383
  }
5462
- // flow-typed directory
5463
5384
  if (exists(join$1(projectPath, 'flow-typed'))) {
5464
5385
  confidence += 10;
5465
5386
  sources.push({ type: 'directory', path: 'flow-typed/' });
5466
5387
  }
5467
- // @babel/preset-flow
5468
5388
  if (deps['@babel/preset-flow']) {
5469
5389
  confidence += 10;
5470
5390
  sources.push({ type: 'package.json', field: 'dependencies.@babel/preset-flow' });
@@ -5488,7 +5408,6 @@ function flowDetector(projectPath, packageJson) {
5488
5408
  * @returns `true` if the content contains JSDoc type annotations.
5489
5409
  */
5490
5410
  function hasJsDocTypes(content) {
5491
- // Check for JSDoc type annotations
5492
5411
  return (content.includes('@type {') ||
5493
5412
  content.includes('@param {') ||
5494
5413
  content.includes('@returns {') ||
@@ -5507,14 +5426,11 @@ function jsdocDetector(projectPath, packageJson) {
5507
5426
  const sources = [];
5508
5427
  let confidence = 0;
5509
5428
  const deps = collectAllDependencies(pkg);
5510
- // jsdoc package
5511
5429
  if (deps['jsdoc']) {
5512
5430
  confidence += 30;
5513
5431
  sources.push({ type: 'package.json', field: 'dependencies.jsdoc' });
5514
5432
  }
5515
- // typescript with checkJs (JSDoc type checking)
5516
5433
  if (deps['typescript']) {
5517
- // Check if checkJs is enabled in tsconfig
5518
5434
  const tsconfigPath = join$1(projectPath, 'tsconfig.json');
5519
5435
  const content = readFileIfExists(tsconfigPath);
5520
5436
  if (content) {
@@ -5531,12 +5447,10 @@ function jsdocDetector(projectPath, packageJson) {
5531
5447
  }
5532
5448
  }
5533
5449
  }
5534
- // Check for jsconfig.json (VS Code JS type checking)
5535
5450
  if (exists(join$1(projectPath, 'jsconfig.json'))) {
5536
5451
  confidence += 40;
5537
5452
  sources.push({ type: 'config-file', path: 'jsconfig.json' });
5538
5453
  }
5539
- // Sample check for JSDoc annotations in source files
5540
5454
  const srcDir = join$1(projectPath, 'src');
5541
5455
  if (exists(srcDir)) {
5542
5456
  try {
@@ -5618,8 +5532,6 @@ const allDetectors = {
5618
5532
  function isDetectAllOptions(value) {
5619
5533
  if (typeof value !== 'object' || value === null)
5620
5534
  return false;
5621
- // DetectAllOptions has skipCache or packageJson fields specifically
5622
- // PackageJson never has skipCache field
5623
5535
  return 'skipCache' in value || 'packageJson' in value;
5624
5536
  }
5625
5537
  /**
@@ -5651,9 +5563,7 @@ function isDetectAllOptions(value) {
5651
5563
  * ```
5652
5564
  */
5653
5565
  function detectAll(projectPath, packageJsonOrOptions) {
5654
- // Handle backward-compatible arguments
5655
5566
  const options = isDetectAllOptions(packageJsonOrOptions) ? packageJsonOrOptions : { packageJson: packageJsonOrOptions };
5656
- // Check cache first (unless skipCache is true)
5657
5567
  if (!options.skipCache) {
5658
5568
  const cached = detectAllCache.get(projectPath);
5659
5569
  if (cached) {
@@ -5704,7 +5614,6 @@ function detectAll(projectPath, packageJsonOrOptions) {
5704
5614
  legacyFrameworks: result.legacyFrameworks.map((f) => f.id),
5705
5615
  testingFrameworks: result.testingFrameworks.map((f) => f.id),
5706
5616
  });
5707
- // Cache the result
5708
5617
  detectAllCache.set(projectPath, result);
5709
5618
  return result;
5710
5619
  }
@@ -6121,7 +6030,6 @@ function detectNxVersion(workspacePath) {
6121
6030
  if (packageJson) {
6122
6031
  const nxVersion = packageJson.devDependencies?.['nx'] ?? packageJson.dependencies?.['nx'];
6123
6032
  if (nxVersion) {
6124
- // Strip semver range characters (^, ~, >=, etc.)
6125
6033
  return nxVersion.replace(/^[\^~>=<]+/, '');
6126
6034
  }
6127
6035
  }
@@ -6150,14 +6058,12 @@ function getNxWorkspaceInfo(workspacePath) {
6150
6058
  }
6151
6059
  const nxJson = readJsonFileIfExists(join$1(workspacePath, 'nx.json'));
6152
6060
  if (!nxJson) {
6153
- // Check for workspace.json as fallback (older NX)
6154
6061
  const workspaceJson = readJsonFileIfExists(join$1(workspacePath, 'workspace.json'));
6155
6062
  if (!workspaceJson) {
6156
6063
  nxLogger.debug('No nx.json or workspace.json found', { workspacePath });
6157
6064
  return null;
6158
6065
  }
6159
6066
  nxLogger.debug('Using legacy workspace.json', { workspacePath });
6160
- // Create minimal nx.json from workspace.json
6161
6067
  return {
6162
6068
  root: workspacePath,
6163
6069
  version: detectNxVersion(workspacePath),
@@ -6206,7 +6112,6 @@ function tryLoadDevkit() {
6206
6112
  }
6207
6113
  devkitLogger.debug('Attempting to load @nx/devkit');
6208
6114
  try {
6209
- // Dynamic require to avoid bundling
6210
6115
  const devkit = require('@nx/devkit');
6211
6116
  devkitLogger.debug('@nx/devkit loaded successfully');
6212
6117
  cachedResult = { available: true, devkit };
@@ -6303,7 +6208,6 @@ function readProjectJson(projectPath) {
6303
6208
  */
6304
6209
  function getProjectConfig(projectPath, workspacePath) {
6305
6210
  nxConfigLogger.debug('Getting project config', { projectPath, workspacePath });
6306
- // Try project.json first
6307
6211
  const projectJson = readProjectJson(projectPath);
6308
6212
  if (projectJson) {
6309
6213
  nxConfigLogger.debug('Using project.json config', { projectPath, name: projectJson.name });
@@ -6312,7 +6216,6 @@ function getProjectConfig(projectPath, workspacePath) {
6312
6216
  root: projectJson.root ?? relative(workspacePath, projectPath),
6313
6217
  };
6314
6218
  }
6315
- // Try to infer from package.json nx field
6316
6219
  const packageJson = readPackageJsonIfExists(projectPath);
6317
6220
  if (packageJson && typeof packageJson['nx'] === 'object') {
6318
6221
  nxConfigLogger.debug('Using package.json nx field', { projectPath, name: packageJson.name });
@@ -6341,13 +6244,11 @@ function scanForProjects(dirPath, workspacePath, projects, maxDepth, currentDept
6341
6244
  try {
6342
6245
  const entries = readDirectory(dirPath);
6343
6246
  for (const entry of entries) {
6344
- // Skip node_modules and hidden directories
6345
6247
  if (entry.name.startsWith('.') || entry.name === 'node_modules' || entry.name === 'dist') {
6346
6248
  continue;
6347
6249
  }
6348
6250
  const fullPath = join$1(dirPath, entry.name);
6349
6251
  if (entry.isDirectory) {
6350
- // Check if this directory is an NX project
6351
6252
  if (isNxProject(fullPath)) {
6352
6253
  const config = getProjectConfig(fullPath, workspacePath);
6353
6254
  if (config) {
@@ -6359,7 +6260,6 @@ function scanForProjects(dirPath, workspacePath, projects, maxDepth, currentDept
6359
6260
  });
6360
6261
  }
6361
6262
  }
6362
- // Recursively scan subdirectories
6363
6263
  scanForProjects(fullPath, workspacePath, projects, maxDepth, currentDepth + 1);
6364
6264
  }
6365
6265
  }
@@ -6377,12 +6277,10 @@ function scanForProjects(dirPath, workspacePath, projects, maxDepth, currentDept
6377
6277
  */
6378
6278
  function discoverNxProjects(workspacePath) {
6379
6279
  const projects = createMap();
6380
- // Check for workspace.json (older NX format)
6381
6280
  const workspaceJson = readJsonFileIfExists(join$1(workspacePath, 'workspace.json'));
6382
6281
  if (workspaceJson?.projects) {
6383
6282
  for (const [name, config] of entries(workspaceJson.projects)) {
6384
6283
  if (typeof config === 'string') {
6385
- // Path reference to project directory
6386
6284
  const projectPath = join$1(workspacePath, config);
6387
6285
  const projectConfig = getProjectConfig(projectPath, workspacePath);
6388
6286
  if (projectConfig) {
@@ -6390,18 +6288,15 @@ function discoverNxProjects(workspacePath) {
6390
6288
  }
6391
6289
  }
6392
6290
  else if (typeof config === 'object' && config !== null) {
6393
- // Inline config
6394
6291
  projects.set(name, { name, ...config });
6395
6292
  }
6396
6293
  }
6397
6294
  return projects;
6398
6295
  }
6399
- // Scan for project.json files (newer NX format)
6400
6296
  const workspaceInfo = getNxWorkspaceInfo(workspacePath);
6401
6297
  const appsDir = workspaceInfo?.workspaceLayout.appsDir ?? 'apps';
6402
6298
  const libsDir = workspaceInfo?.workspaceLayout.libsDir ?? 'libs';
6403
6299
  const searchDirs = [appsDir, libsDir];
6404
- // Also check packages directory (common in some setups)
6405
6300
  if (exists(join$1(workspacePath, 'packages'))) {
6406
6301
  searchDirs.push('packages');
6407
6302
  }
@@ -6416,7 +6311,6 @@ function discoverNxProjects(workspacePath) {
6416
6311
  }
6417
6312
  }
6418
6313
  }
6419
- // Also check root-level projects (standalone projects in monorepo root)
6420
6314
  if (isNxProject(workspacePath)) {
6421
6315
  const config = readProjectJson(workspacePath);
6422
6316
  if (config) {
@@ -6449,10 +6343,8 @@ function buildSimpleProjectGraph(workspacePath, projects) {
6449
6343
  data: config,
6450
6344
  };
6451
6345
  dependencies[name] = [];
6452
- // Add implicit dependencies
6453
6346
  if (config.implicitDependencies) {
6454
6347
  for (const dep of config.implicitDependencies) {
6455
- // Skip negative dependencies (those starting with !)
6456
6348
  if (!dep.startsWith('!')) {
6457
6349
  dependencies[name].push({
6458
6350
  target: dep,
@@ -6469,7 +6361,6 @@ function buildSimpleProjectGraph(workspacePath, projects) {
6469
6361
  * Known configuration file patterns organized by type.
6470
6362
  */
6471
6363
  const CONFIG_PATTERNS = {
6472
- // Package Management
6473
6364
  'package.json': {
6474
6365
  patterns: ['package.json'],
6475
6366
  format: 'json',
@@ -6496,14 +6387,12 @@ const CONFIG_PATTERNS = {
6496
6387
  description: 'NPM configuration',
6497
6388
  sensitive: true,
6498
6389
  },
6499
- // TypeScript
6500
6390
  tsconfig: {
6501
6391
  patterns: ['tsconfig.json', 'tsconfig.*.json'],
6502
6392
  format: 'jsonc',
6503
6393
  description: 'TypeScript configuration',
6504
6394
  canExtend: true,
6505
6395
  },
6506
- // Monorepo
6507
6396
  nx: {
6508
6397
  patterns: ['nx.json'],
6509
6398
  format: 'json',
@@ -6529,7 +6418,6 @@ const CONFIG_PATTERNS = {
6529
6418
  format: 'json',
6530
6419
  description: 'Lerna configuration',
6531
6420
  },
6532
- // Build Tools
6533
6421
  webpack: {
6534
6422
  patterns: ['webpack.config.js', 'webpack.config.ts', 'webpack.config.cjs', 'webpack.config.mjs'],
6535
6423
  format: 'js',
@@ -6560,7 +6448,6 @@ const CONFIG_PATTERNS = {
6560
6448
  format: 'json',
6561
6449
  description: 'SWC configuration',
6562
6450
  },
6563
- // Testing
6564
6451
  jest: {
6565
6452
  patterns: ['jest.config.js', 'jest.config.ts', 'jest.config.mjs'],
6566
6453
  description: 'Jest configuration',
@@ -6577,7 +6464,6 @@ const CONFIG_PATTERNS = {
6577
6464
  patterns: ['playwright.config.js', 'playwright.config.ts'],
6578
6465
  description: 'Playwright configuration',
6579
6466
  },
6580
- // Framework configs
6581
6467
  next: {
6582
6468
  patterns: ['next.config.js', 'next.config.mjs', 'next.config.ts'],
6583
6469
  format: 'js',
@@ -6603,7 +6489,6 @@ const CONFIG_PATTERNS = {
6603
6489
  format: 'js',
6604
6490
  description: 'Astro configuration',
6605
6491
  },
6606
- // Linting & Formatting
6607
6492
  eslint: {
6608
6493
  patterns: [
6609
6494
  'eslint.config.js',
@@ -6622,14 +6507,12 @@ const CONFIG_PATTERNS = {
6622
6507
  format: 'json',
6623
6508
  description: 'Prettier configuration',
6624
6509
  },
6625
- // Environment (sensitive)
6626
6510
  env: {
6627
6511
  patterns: ['.env', '.env.*', '*.env'],
6628
6512
  format: 'dotenv',
6629
6513
  description: 'Environment variables',
6630
6514
  sensitive: true,
6631
6515
  },
6632
- // Git
6633
6516
  '.gitignore': {
6634
6517
  patterns: ['.gitignore'],
6635
6518
  format: 'text',
@@ -6800,12 +6683,8 @@ function getConfigPaths(type) {
6800
6683
  *
6801
6684
  * @module @hyperfrontend/immutable-api-utils/built-in-copy/number
6802
6685
  */
6803
- // Capture references at module initialization time
6804
6686
  const _parseInt = globalThis.parseInt;
6805
6687
  const _parseFloat = globalThis.parseFloat;
6806
- // ============================================================================
6807
- // Parsing
6808
- // ============================================================================
6809
6688
  /**
6810
6689
  * (Safe copy) Parses a string and returns an integer.
6811
6690
  */
@@ -7160,11 +7039,9 @@ const analyzeLogger = createScopedLogger('project-scope:analyze');
7160
7039
  * @returns Detected workspace type
7161
7040
  */
7162
7041
  function detectWorkspaceType(projectPath) {
7163
- // Check for NX
7164
7042
  if (isNxWorkspace(projectPath) || findNxWorkspaceRoot(projectPath) !== null) {
7165
7043
  return 'nx';
7166
7044
  }
7167
- // Check for common monorepo markers
7168
7045
  if (exists(resolve(projectPath, 'turbo.json'))) {
7169
7046
  return 'turborepo';
7170
7047
  }
@@ -7174,7 +7051,6 @@ function detectWorkspaceType(projectPath) {
7174
7051
  if (exists(resolve(projectPath, 'pnpm-workspace.yaml'))) {
7175
7052
  return 'pnpm';
7176
7053
  }
7177
- // Check package.json for workspaces
7178
7054
  const pkg = readPackageJsonIfExists(projectPath);
7179
7055
  if (pkg?.workspaces) {
7180
7056
  if (exists(resolve(projectPath, 'yarn.lock'))) {
@@ -7183,7 +7059,7 @@ function detectWorkspaceType(projectPath) {
7183
7059
  if (exists(resolve(projectPath, 'package-lock.json'))) {
7184
7060
  return 'npm';
7185
7061
  }
7186
- return 'npm'; // default for workspaces
7062
+ return 'npm';
7187
7063
  }
7188
7064
  return 'standalone';
7189
7065
  }
@@ -7195,15 +7071,12 @@ function detectWorkspaceType(projectPath) {
7195
7071
  * @returns True if the analysis should be performed
7196
7072
  */
7197
7073
  function shouldInclude(type, options) {
7198
- // If include list is specified, item must be in it
7199
7074
  if (options?.include && options.include.length > 0) {
7200
7075
  return options.include.includes(type);
7201
7076
  }
7202
- // If exclude list is specified, item must not be in it
7203
7077
  if (options?.exclude && options.exclude.length > 0) {
7204
7078
  return !options.exclude.includes(type);
7205
7079
  }
7206
- // Default: include everything
7207
7080
  return true;
7208
7081
  }
7209
7082
  /**
@@ -7252,28 +7125,22 @@ function normalizeConfigFormat(format) {
7252
7125
  function analyzeProject(projectPath, options) {
7253
7126
  const startTime = dateNow();
7254
7127
  const resolvedPath = resolve(projectPath);
7255
- // Set log level based on verbose option
7256
7128
  if (options?.verbose) {
7257
7129
  analyzeLogger.setLogLevel('debug');
7258
7130
  }
7259
7131
  analyzeLogger.debug('Starting project analysis', { path: resolvedPath, options });
7260
- // Read package.json once for reuse
7261
7132
  const packageJson = readPackageJsonIfExists(resolvedPath);
7262
7133
  analyzeLogger.debug('Package.json loaded', { found: !!packageJson, name: packageJson?.name });
7263
- // Detect project and workspace types
7264
7134
  const projectTypeDetection = detectProjectType(resolvedPath, {
7265
7135
  skipTechDetection: options?.depth === 'basic',
7266
7136
  });
7267
7137
  const projectType = projectTypeDetection.type;
7268
7138
  const workspaceType = detectWorkspaceType(resolvedPath);
7269
7139
  analyzeLogger.debug('Project type detected', { projectType, workspaceType });
7270
- // Get project name from package.json or directory name
7271
7140
  const projectName = packageJson?.name ?? basename(resolvedPath);
7272
- // Run all technology detectors
7273
7141
  const detections = shouldInclude('frameworks', options) || shouldInclude('buildTools', options) || shouldInclude('testing', options)
7274
7142
  ? detectAll(resolvedPath, packageJson ?? undefined)
7275
7143
  : null;
7276
- // Convert framework detections to FrameworkInfo
7277
7144
  const frameworks = shouldInclude('frameworks', options) && detections
7278
7145
  ? [
7279
7146
  ...detections.frontendFrameworks.map((d) => ({
@@ -7293,7 +7160,6 @@ function analyzeProject(projectPath, options) {
7293
7160
  })),
7294
7161
  ]
7295
7162
  : [];
7296
- // Convert build tool detections
7297
7163
  const buildTools = shouldInclude('buildTools', options) && detections
7298
7164
  ? detections.buildTools.map((d) => ({
7299
7165
  id: d.id,
@@ -7303,7 +7169,6 @@ function analyzeProject(projectPath, options) {
7303
7169
  confidence: d.confidence,
7304
7170
  }))
7305
7171
  : [];
7306
- // Convert testing framework detections
7307
7172
  const testingFrameworks = shouldInclude('testing', options) && detections
7308
7173
  ? detections.testingFrameworks.map((d) => ({
7309
7174
  id: d.id,
@@ -7314,7 +7179,6 @@ function analyzeProject(projectPath, options) {
7314
7179
  confidence: d.confidence,
7315
7180
  }))
7316
7181
  : [];
7317
- // Discover entry points
7318
7182
  const entryPoints = shouldInclude('entryPoints', options)
7319
7183
  ? discoverEntryPoints(resolvedPath).map((e) => ({
7320
7184
  path: e.path,
@@ -7322,7 +7186,6 @@ function analyzeProject(projectPath, options) {
7322
7186
  confidence: e.confidence,
7323
7187
  }))
7324
7188
  : [];
7325
- // Detect configuration files
7326
7189
  const configFiles = shouldInclude('configs', options)
7327
7190
  ? detectConfigs(resolvedPath).map((c) => ({
7328
7191
  path: c.path,
@@ -7331,7 +7194,6 @@ function analyzeProject(projectPath, options) {
7331
7194
  tool: c.type,
7332
7195
  }))
7333
7196
  : [];
7334
- // Get dependency summary
7335
7197
  let dependencies;
7336
7198
  if (shouldInclude('dependencies', options)) {
7337
7199
  const deps = getProjectDependencies(resolvedPath);
@@ -7352,7 +7214,6 @@ function analyzeProject(projectPath, options) {
7352
7214
  total: 0,
7353
7215
  };
7354
7216
  }
7355
- // Build metadata
7356
7217
  const metadata = {
7357
7218
  timestamp: createDate(),
7358
7219
  durationMs: dateNow() - startTime,
@@ -7427,15 +7288,12 @@ function formatWorkspaceType(type) {
7427
7288
  */
7428
7289
  function formatAnalysisText(result) {
7429
7290
  const lines = [];
7430
- // Header
7431
7291
  lines.push(`Project Analysis: ${result.name}`);
7432
7292
  lines.push('='.repeat(30));
7433
7293
  lines.push('');
7434
- // Basic info
7435
7294
  lines.push(`Type: ${formatProjectType(result.projectType)}`);
7436
7295
  lines.push(`Workspace: ${formatWorkspaceType(result.workspaceType)}`);
7437
7296
  lines.push('');
7438
- // Frameworks
7439
7297
  if (result.frameworks.length > 0) {
7440
7298
  lines.push('Frameworks:');
7441
7299
  for (const framework of result.frameworks) {
@@ -7449,7 +7307,6 @@ function formatAnalysisText(result) {
7449
7307
  }
7450
7308
  lines.push('');
7451
7309
  }
7452
- // Build tools
7453
7310
  if (result.buildTools.length > 0) {
7454
7311
  lines.push('Build Tools:');
7455
7312
  for (const tool of result.buildTools) {
@@ -7458,7 +7315,6 @@ function formatAnalysisText(result) {
7458
7315
  }
7459
7316
  lines.push('');
7460
7317
  }
7461
- // Testing
7462
7318
  if (result.testingFrameworks.length > 0) {
7463
7319
  lines.push('Testing:');
7464
7320
  for (const framework of result.testingFrameworks) {
@@ -7467,7 +7323,6 @@ function formatAnalysisText(result) {
7467
7323
  }
7468
7324
  lines.push('');
7469
7325
  }
7470
- // Entry points
7471
7326
  if (result.entryPoints.length > 0) {
7472
7327
  lines.push('Entry Points:');
7473
7328
  for (const entry of result.entryPoints.slice(0, 5)) {
@@ -7478,7 +7333,6 @@ function formatAnalysisText(result) {
7478
7333
  }
7479
7334
  lines.push('');
7480
7335
  }
7481
- // Configurations
7482
7336
  if (result.configFiles.length > 0) {
7483
7337
  lines.push('Configurations:');
7484
7338
  for (const config of result.configFiles.slice(0, 8)) {
@@ -7489,7 +7343,6 @@ function formatAnalysisText(result) {
7489
7343
  }
7490
7344
  lines.push('');
7491
7345
  }
7492
- // Dependencies summary
7493
7346
  lines.push('Dependencies:');
7494
7347
  lines.push(` Production: ${result.dependencies.production}`);
7495
7348
  lines.push(` Development: ${result.dependencies.development}`);
@@ -7576,7 +7429,7 @@ function parseAnalyzeArgs(args) {
7576
7429
  exclude: { type: 'string', short: 'e' },
7577
7430
  },
7578
7431
  allowPositionals: true,
7579
- strict: false, // Allow global options to pass through
7432
+ strict: false,
7580
7433
  });
7581
7434
  const format = values.format;
7582
7435
  const depth = values.depth;
@@ -7635,7 +7488,6 @@ const analyzeCommandDef = {
7635
7488
  description: 'Analyze project structure and tech stack',
7636
7489
  execute(args, globalOptions) {
7637
7490
  const options = parseAnalyzeArgs(args);
7638
- // Global --json flag overrides format
7639
7491
  if (globalOptions.json) {
7640
7492
  options.format = 'json';
7641
7493
  }
@@ -7942,12 +7794,10 @@ Examples:
7942
7794
  function formatDependencyList(deps, maxItems = 20) {
7943
7795
  const lines = [];
7944
7796
  const depEntries = entries(deps);
7945
- // Sort alphabetically
7946
7797
  depEntries.sort((a, b) => a[0].localeCompare(b[0]));
7947
7798
  const displayCount = min(depEntries.length, maxItems);
7948
7799
  for (let i = 0; i < displayCount; i++) {
7949
7800
  const [name, version] = depEntries[i];
7950
- // Pad name to align versions
7951
7801
  const paddedName = name.padEnd(30);
7952
7802
  lines.push(` ${paddedName} ${version}`);
7953
7803
  }
@@ -8190,13 +8040,10 @@ function buildTree(rootPath, walkEntries, options) {
8190
8040
  isDirectory: true,
8191
8041
  children: [],
8192
8042
  };
8193
- // Create a map for quick lookup
8194
8043
  const nodeMap = createMap();
8195
8044
  nodeMap.set('.', root);
8196
- // Sort entries by path for proper parent-child relationships
8197
8045
  const sortedEntries = [...walkEntries].sort((a, b) => a.relativePath.localeCompare(b.relativePath));
8198
8046
  for (const entry of sortedEntries) {
8199
- // Skip based on filters
8200
8047
  if (options.dirsOnly && !entry.isDirectory)
8201
8048
  continue;
8202
8049
  if (options.filesOnly && entry.isDirectory)
@@ -8207,7 +8054,6 @@ function buildTree(rootPath, walkEntries, options) {
8207
8054
  isDirectory: entry.isDirectory,
8208
8055
  children: [],
8209
8056
  };
8210
- // Add size/modified if requested
8211
8057
  if ((options.showSize || options.showModified) && entry.isFile) {
8212
8058
  const stats = getFileStat(entry.path);
8213
8059
  if (stats) {
@@ -8217,9 +8063,8 @@ function buildTree(rootPath, walkEntries, options) {
8217
8063
  node.modified = stats.modified;
8218
8064
  }
8219
8065
  }
8220
- // Find parent
8221
8066
  const parts = entry.relativePath.split('/');
8222
- parts.pop(); // Remove current entry name
8067
+ parts.pop();
8223
8068
  const parentPath = parts.join('/') || '.';
8224
8069
  const parent = nodeMap.get(parentPath);
8225
8070
  if (parent) {
@@ -8240,11 +8085,9 @@ function buildTree(rootPath, walkEntries, options) {
8240
8085
  */
8241
8086
  function renderTreeText(node, options, prefix = '', isLast = true) {
8242
8087
  const lines = [];
8243
- // Current line
8244
8088
  const connector = isLast ? '└── ' : '├── ';
8245
8089
  const dirMark = node.isDirectory ? '/' : '';
8246
8090
  let line = `${prefix}${connector}${node.name}${dirMark}`;
8247
- // Add size/modified
8248
8091
  const meta = [];
8249
8092
  if (options.showSize && node.size !== undefined) {
8250
8093
  meta.push(formatSize(node.size));
@@ -8256,10 +8099,8 @@ function renderTreeText(node, options, prefix = '', isLast = true) {
8256
8099
  line += ` [${meta.join(' ')}]`;
8257
8100
  }
8258
8101
  lines.push(line);
8259
- // Process children
8260
8102
  const childPrefix = prefix + (isLast ? ' ' : '│ ');
8261
8103
  const sortedChildren = [...node.children].sort((a, b) => {
8262
- // Directories first, then alphabetically
8263
8104
  if (a.isDirectory && !b.isDirectory)
8264
8105
  return -1;
8265
8106
  if (!a.isDirectory && b.isDirectory)
@@ -8283,9 +8124,7 @@ function renderTreeText(node, options, prefix = '', isLast = true) {
8283
8124
  */
8284
8125
  function formatTreeText(rootPath, tree, options) {
8285
8126
  const lines = [];
8286
- // Root line
8287
8127
  lines.push(basename(rootPath));
8288
- // Count stats
8289
8128
  let dirCount = 0;
8290
8129
  let fileCount = 0;
8291
8130
  /**
@@ -8304,7 +8143,6 @@ function formatTreeText(rootPath, tree, options) {
8304
8143
  countNodes(child);
8305
8144
  }
8306
8145
  }
8307
- // Render children of root
8308
8146
  const sortedChildren = [...tree.children].sort((a, b) => {
8309
8147
  if (a.isDirectory && !b.isDirectory)
8310
8148
  return -1;
@@ -8318,7 +8156,6 @@ function formatTreeText(rootPath, tree, options) {
8318
8156
  lines.push(...renderTreeText(child, options, '', isLast));
8319
8157
  countNodes(child);
8320
8158
  }
8321
- // Summary
8322
8159
  lines.push('');
8323
8160
  const dirText = dirCount === 1 ? '1 directory' : `${dirCount} directories`;
8324
8161
  const fileText = fileCount === 1 ? '1 file' : `${fileCount} files`;
@@ -8379,7 +8216,6 @@ function parseTreeArgs(args) {
8379
8216
  function treeCommand(options) {
8380
8217
  const rootPath = options.path ? resolve(options.path) : process.cwd();
8381
8218
  try {
8382
- // Collect entries via walk
8383
8219
  const walkEntries = [];
8384
8220
  walkDirectory(rootPath, (entry) => {
8385
8221
  walkEntries.push(entry);
@@ -8389,9 +8225,7 @@ function treeCommand(options) {
8389
8225
  ignorePatterns: options.ignore,
8390
8226
  includeHidden: false,
8391
8227
  });
8392
- // Build tree structure
8393
8228
  const tree = buildTree(rootPath, walkEntries, options);
8394
- // Format output
8395
8229
  let output;
8396
8230
  if (options.format === 'json') {
8397
8231
  output = formatTreeJson(tree);
@@ -8450,6 +8284,9 @@ Examples:
8450
8284
 
8451
8285
  /** Logger for CLI operations */
8452
8286
  const cliLogger = createScopedLogger('project-scope:cli');
8287
+ /** Output printer for user-facing CLI output (help, version, command results). */
8288
+ const output = createLogger(error, undefined, log);
8289
+ output.setLogLevel('log');
8453
8290
  /** Library version */
8454
8291
  const VERSION = '0.1.0';
8455
8292
  /**
@@ -8465,7 +8302,7 @@ const commands = {
8465
8302
  * Print general help information.
8466
8303
  */
8467
8304
  function printHelp() {
8468
- log(`
8305
+ output.log(`
8469
8306
  project-scope <command> [options]
8470
8307
 
8471
8308
  A tool for analyzing JavaScript/TypeScript project structure and tech stack.
@@ -8497,7 +8334,7 @@ Examples:
8497
8334
  * Print CLI version.
8498
8335
  */
8499
8336
  function printVersion() {
8500
- log(`project-scope v${VERSION}`);
8337
+ output.log(`project-scope v${VERSION}`);
8501
8338
  }
8502
8339
  /**
8503
8340
  * Parse global options from command line arguments.
@@ -8558,24 +8395,24 @@ function run(args) {
8558
8395
  setGlobalLogLevel('debug');
8559
8396
  }
8560
8397
  cliLogger.debug('CLI invoked', { args, globalOptions });
8398
+ const commandName = args[0];
8561
8399
  if (globalOptions.version) {
8562
8400
  printVersion();
8563
8401
  return { exitCode: 0 };
8564
8402
  }
8565
- if (globalOptions.help && (args.length === 1 || !commands[args[0]])) {
8403
+ if (globalOptions.help && (args.length === 1 || !commands[commandName])) {
8566
8404
  printHelp();
8567
8405
  return { exitCode: 0 };
8568
8406
  }
8569
- const commandName = args[0];
8570
8407
  const command = commands[commandName];
8571
8408
  if (!command) {
8572
8409
  cliLogger.warn('Unknown command requested', { commandName });
8573
- error(`Unknown command: ${commandName}`);
8574
- error('Run "project-scope --help" for usage information.');
8410
+ output.error(`Unknown command: ${commandName}`);
8411
+ output.error('Run "project-scope --help" for usage information.');
8575
8412
  return { exitCode: 1, error: `Unknown command: ${commandName}` };
8576
8413
  }
8577
8414
  if (globalOptions.help) {
8578
- log(command.getHelp());
8415
+ output.log(command.getHelp());
8579
8416
  return { exitCode: 0 };
8580
8417
  }
8581
8418
  const commandArgs = args.slice(1);
@@ -8583,11 +8420,11 @@ function run(args) {
8583
8420
  const result = command.execute(commandArgs, globalOptions);
8584
8421
  cliLogger.debug('Command completed', { commandName, exitCode: result.exitCode });
8585
8422
  if (result.output) {
8586
- log(result.output);
8423
+ output.log(result.output);
8587
8424
  }
8588
8425
  if (result.error) {
8589
8426
  cliLogger.error('Command error', { commandName, error: result.error });
8590
- error(result.error);
8427
+ output.error(result.error);
8591
8428
  }
8592
8429
  return result;
8593
8430
  }
@@ -8623,25 +8460,22 @@ const BINARY_SIGNATURES = [
8623
8460
  */
8624
8461
  function detectEncodingInfo(buffer) {
8625
8462
  encodingLogger.debug('Detecting encoding info', { bufferSize: buffer.length });
8626
- // Check for UTF-8 BOM
8627
8463
  if (buffer.length >= 3) {
8628
8464
  if (buffer[0] === UTF8_BOM_BYTES[0] && buffer[1] === UTF8_BOM_BYTES[1] && buffer[2] === UTF8_BOM_BYTES[2]) {
8629
8465
  encodingLogger.debug('Detected UTF-8 BOM');
8630
8466
  return { type: 'text', encoding: 'utf-8', hasBom: true };
8631
8467
  }
8632
8468
  }
8633
- // Check for UTF-16 BOMs
8634
8469
  if (buffer.length >= 2) {
8635
8470
  if (buffer[0] === UTF16_BE_BOM_BYTES[0] && buffer[1] === UTF16_BE_BOM_BYTES[1]) {
8636
8471
  encodingLogger.debug('Detected UTF-16 BE BOM');
8637
- return { type: 'text', encoding: 'utf16le', hasBom: true }; // Node treats BE through utf16le
8472
+ return { type: 'text', encoding: 'utf16le', hasBom: true };
8638
8473
  }
8639
8474
  if (buffer[0] === UTF16_LE_BOM_BYTES[0] && buffer[1] === UTF16_LE_BOM_BYTES[1]) {
8640
8475
  encodingLogger.debug('Detected UTF-16 LE BOM');
8641
8476
  return { type: 'text', encoding: 'utf16le', hasBom: true };
8642
8477
  }
8643
8478
  }
8644
- // Check for binary signatures
8645
8479
  for (const { signature, description } of BINARY_SIGNATURES) {
8646
8480
  if (buffer.length >= signature.length) {
8647
8481
  let matches = true;
@@ -8657,7 +8491,6 @@ function detectEncodingInfo(buffer) {
8657
8491
  }
8658
8492
  }
8659
8493
  }
8660
- // Check for null bytes (usually indicates binary)
8661
8494
  const sampleSize = min(buffer.length, 8000);
8662
8495
  for (let i = 0; i < sampleSize; i++) {
8663
8496
  if (buffer[i] === 0) {
@@ -8676,22 +8509,18 @@ function detectEncodingInfo(buffer) {
8676
8509
  */
8677
8510
  function detectEncoding(buffer) {
8678
8511
  if (buffer.length >= 3) {
8679
- // Check for UTF-8 BOM
8680
8512
  if (buffer[0] === UTF8_BOM_BYTES[0] && buffer[1] === UTF8_BOM_BYTES[1] && buffer[2] === UTF8_BOM_BYTES[2]) {
8681
8513
  return 'utf-8';
8682
8514
  }
8683
8515
  }
8684
8516
  if (buffer.length >= 2) {
8685
- // Check for UTF-16 LE BOM
8686
8517
  if (buffer[0] === UTF16_LE_BOM_BYTES[0] && buffer[1] === UTF16_LE_BOM_BYTES[1]) {
8687
8518
  return 'utf16le';
8688
8519
  }
8689
- // Check for UTF-16 BE BOM
8690
8520
  if (buffer[0] === UTF16_BE_BOM_BYTES[0] && buffer[1] === UTF16_BE_BOM_BYTES[1]) {
8691
- return 'utf16le'; // Node.js handles BE through utf16le
8521
+ return 'utf16le';
8692
8522
  }
8693
8523
  }
8694
- // Default to UTF-8
8695
8524
  return 'utf-8';
8696
8525
  }
8697
8526
  /**
@@ -8761,10 +8590,8 @@ function bufferToString(content, encoding) {
8761
8590
  convertLogger.debug('Using provided encoding', { encoding });
8762
8591
  return content.toString(encoding);
8763
8592
  }
8764
- // Auto-detect and convert
8765
8593
  const info = detectEncodingInfo(content);
8766
8594
  if (info.type === 'text') {
8767
- // Remove BOM if present
8768
8595
  let offset = 0;
8769
8596
  if (info.hasBom) {
8770
8597
  offset = info.encoding === 'utf-8' ? 3 : 2;
@@ -8817,19 +8644,14 @@ function detectCaseSensitivity() {
8817
8644
  if (cachedCaseSensitive !== null) {
8818
8645
  return cachedCaseSensitive;
8819
8646
  }
8820
- // Quick check based on platform
8821
8647
  if (process.platform === 'win32') {
8822
8648
  cachedCaseSensitive = false;
8823
8649
  return false;
8824
8650
  }
8825
- // macOS is typically case-insensitive by default
8826
8651
  if (process.platform === 'darwin') {
8827
- // Could be case-sensitive HFS+/APFS, but assume insensitive by default
8828
8652
  cachedCaseSensitive = false;
8829
8653
  return false;
8830
8654
  }
8831
- // Test actual file system behavior for Linux and others
8832
- // Use mkdtempSync to create a secure temporary directory
8833
8655
  let secureTestDir = null;
8834
8656
  try {
8835
8657
  secureTestDir = mkdtempSync(join$1(tmpdir(), 'case-sensitivity-test-'));
@@ -8840,12 +8662,9 @@ function detectCaseSensitivity() {
8840
8662
  unlinkSync(testFile);
8841
8663
  }
8842
8664
  catch {
8843
- // Default to case-sensitive on Linux/Unix if test fails
8844
- // (win32 and darwin already returned early, so we're on a case-sensitive platform)
8845
8665
  cachedCaseSensitive = true;
8846
8666
  }
8847
8667
  finally {
8848
- // Clean up the secure temporary directory
8849
8668
  if (secureTestDir) {
8850
8669
  try {
8851
8670
  rmdirSync(secureTestDir);
@@ -8980,7 +8799,6 @@ function normalizeLineEndings(content, style = 'lf') {
8980
8799
  else {
8981
8800
  target = style === 'crlf' ? CRLF : LF;
8982
8801
  }
8983
- // First normalize all to LF, then convert to target
8984
8802
  const normalized = content.replace(/\r\n/g, LF).replace(/\r/g, LF);
8985
8803
  if (target === LF) {
8986
8804
  return normalized;
@@ -8995,7 +8813,6 @@ function normalizeLineEndings(content, style = 'lf') {
8995
8813
  */
8996
8814
  function detectLineEnding(content) {
8997
8815
  const hasCRLF = content.includes(CRLF);
8998
- // Check for LF that is NOT part of CRLF
8999
8816
  const hasLFOnly = content.includes('\n') && content.replace(/\r\n/g, '').includes('\n');
9000
8817
  if (hasCRLF && hasLFOnly)
9001
8818
  return 'mixed';
@@ -9473,7 +9290,6 @@ function createFsTree(root, options) {
9473
9290
  }
9474
9291
  const prefix = normalPath === '.' || normalPath === '' ? '' : normalPath + '/';
9475
9292
  for (const [changedPath, change] of _changes) {
9476
- // Handle root-level files
9477
9293
  if (prefix === '') {
9478
9294
  const childName = changedPath.split('/')[0];
9479
9295
  if (change.type === 'DELETE' && !changedPath.includes('/')) {
@@ -9490,7 +9306,6 @@ function createFsTree(root, options) {
9490
9306
  const relativePath = changedPath.slice(prefix.length);
9491
9307
  const childName = relativePath.split('/')[0];
9492
9308
  if (change.type === 'DELETE') {
9493
- // Only remove if it's a direct child being deleted
9494
9309
  if (!relativePath.includes('/')) {
9495
9310
  childSet.delete(childName);
9496
9311
  }
@@ -9576,12 +9391,10 @@ const factoryLogger = createScopedLogger('project-scope:vfs:factory');
9576
9391
  function createTree(root, options) {
9577
9392
  const normalizedRoot = normalizePath(root);
9578
9393
  factoryLogger.debug('createTree', { root: normalizedRoot });
9579
- // Validate root exists
9580
9394
  if (!exists(normalizedRoot)) {
9581
9395
  factoryLogger.warn('createTree failed: root does not exist', { root: normalizedRoot });
9582
9396
  throw createError(`Root directory does not exist: ${normalizedRoot}`);
9583
9397
  }
9584
- // Validate root is a directory
9585
9398
  if (!isDirectory(normalizedRoot)) {
9586
9399
  factoryLogger.warn('createTree failed: root is not a directory', { root: normalizedRoot });
9587
9400
  throw createError(`Root path is not a directory: ${normalizedRoot}`);
@@ -9628,7 +9441,6 @@ const vfsLogger = createScopedLogger('project-scope:vfs');
9628
9441
  function commitChanges(tree, options) {
9629
9442
  const changes = tree.listChanges();
9630
9443
  const appliedChanges = [];
9631
- // Set log level based on verbose option
9632
9444
  if (options?.verbose) {
9633
9445
  vfsLogger.setLogLevel('debug');
9634
9446
  }
@@ -9640,7 +9452,6 @@ function commitChanges(tree, options) {
9640
9452
  changes: [],
9641
9453
  dryRun: options?.dryRun ?? false,
9642
9454
  };
9643
- // Dry run - just count changes without writing
9644
9455
  if (options?.dryRun) {
9645
9456
  for (const change of changes) {
9646
9457
  switch (change.type) {
@@ -9658,7 +9469,6 @@ function commitChanges(tree, options) {
9658
9469
  result.changes = changes;
9659
9470
  return result;
9660
9471
  }
9661
- // Sort changes: deletes first (to free names), then creates, then updates
9662
9472
  const sortedChanges = [...changes].sort((a, b) => {
9663
9473
  const order = { DELETE: 0, CREATE: 1, UPDATE: 2 };
9664
9474
  return order[a.type] - order[b.type];
@@ -9671,11 +9481,9 @@ function commitChanges(tree, options) {
9671
9481
  case 'UPDATE':
9672
9482
  /* istanbul ignore if -- content is always defined for CREATE/UPDATE from tree.write() */
9673
9483
  if (change.content !== undefined) {
9674
- // Ensure directory exists
9675
9484
  const dir = dirname(absPath);
9676
9485
  ensureDir(dir);
9677
9486
  writeFileSync(absPath, change.content);
9678
- // Apply permissions if specified
9679
9487
  if (change.mode !== undefined) {
9680
9488
  chmodSync(absPath, change.mode);
9681
9489
  }
@@ -9693,7 +9501,6 @@ function commitChanges(tree, options) {
9693
9501
  unlinkSync(absPath);
9694
9502
  }
9695
9503
  catch {
9696
- // Try recursive delete for directories
9697
9504
  rmSync(absPath, { recursive: true });
9698
9505
  }
9699
9506
  }
@@ -9707,7 +9514,6 @@ function commitChanges(tree, options) {
9707
9514
  }
9708
9515
  }
9709
9516
  catch (error) {
9710
- // On error, throw with context
9711
9517
  vfsLogger.error('Commit failed', { path: change.path, type: change.type });
9712
9518
  const message = error instanceof Error
9713
9519
  ? `Failed to ${change.type.toLowerCase()} ${change.path}: ${error.message}`
@@ -9716,7 +9522,6 @@ function commitChanges(tree, options) {
9716
9522
  }
9717
9523
  }
9718
9524
  result.changes = appliedChanges;
9719
- // Clear the tree's pending changes after successful commit
9720
9525
  tree.clearChanges();
9721
9526
  return result;
9722
9527
  }
@@ -9792,18 +9597,14 @@ function backtrackLcs(table, oldLines, newLines) {
9792
9597
  * @returns Filtered DiffLine array
9793
9598
  */
9794
9599
  function operationsToDiffLines(operations, contextLines) {
9795
- // Mark which lines should be included (changes + context)
9796
9600
  const include = new Array(operations.length).fill(false);
9797
- // First pass: mark all changes
9798
9601
  for (let i = 0; i < operations.length; i++) {
9799
9602
  if (operations[i].type !== 'same') {
9800
- // Mark this line and context around it
9801
9603
  for (let j = max(0, i - contextLines); j <= min(operations.length - 1, i + contextLines); j++) {
9802
9604
  include[j] = true;
9803
9605
  }
9804
9606
  }
9805
9607
  }
9806
- // Second pass: convert to DiffLine
9807
9608
  const lines = [];
9808
9609
  let oldLineNum = 1;
9809
9610
  let newLineNum = 1;
@@ -9825,7 +9626,6 @@ function operationsToDiffLines(operations, contextLines) {
9825
9626
  }
9826
9627
  }
9827
9628
  else {
9828
- // Skip but update line numbers
9829
9629
  if (op.type === 'same') {
9830
9630
  oldLineNum++;
9831
9631
  newLineNum++;
@@ -9852,7 +9652,6 @@ function bufferToLines(content) {
9852
9652
  if (!content)
9853
9653
  return [];
9854
9654
  const text = content.toString('utf-8');
9855
- // Split by newline, keeping empty last line if present
9856
9655
  return text.split('\n');
9857
9656
  }
9858
9657
  /**
@@ -9879,9 +9678,7 @@ function generateDiff(change, options = {}) {
9879
9678
  diffLogger.debug('generateDiff', { path: change.path, type: change.type, contextLines });
9880
9679
  const oldLines = bufferToLines(change.originalContent);
9881
9680
  const newLines = bufferToLines(change.content);
9882
- // Handle edge cases
9883
9681
  if (change.type === 'CREATE') {
9884
- // All lines are additions
9885
9682
  const lines = newLines
9886
9683
  .filter((line) => line !== '' || newLines.indexOf(line) !== newLines.length - 1 || newLines.length === 1)
9887
9684
  .map((content, idx) => ({
@@ -9889,7 +9686,6 @@ function generateDiff(change, options = {}) {
9889
9686
  line: idx + 1,
9890
9687
  content,
9891
9688
  }));
9892
- // Filter out empty trailing line from split
9893
9689
  const filteredLines = lines.filter((l, i) => !(i === lines.length - 1 && l.content === '' && lines.length > 1));
9894
9690
  return {
9895
9691
  path: change.path,
@@ -9899,13 +9695,11 @@ function generateDiff(change, options = {}) {
9899
9695
  };
9900
9696
  }
9901
9697
  if (change.type === 'DELETE') {
9902
- // All lines are deletions
9903
9698
  const lines = oldLines.map((content, idx) => ({
9904
9699
  type: 'remove',
9905
9700
  line: idx + 1,
9906
9701
  content,
9907
9702
  }));
9908
- // Filter out empty trailing line from split
9909
9703
  const filteredLines = lines.filter((l, i) => !(i === lines.length - 1 && l.content === '' && lines.length > 1));
9910
9704
  return {
9911
9705
  path: change.path,
@@ -9914,7 +9708,6 @@ function generateDiff(change, options = {}) {
9914
9708
  deletions: filteredLines.length,
9915
9709
  };
9916
9710
  }
9917
- // UPDATE: compute actual diff
9918
9711
  const table = computeLcsTable(oldLines, newLines);
9919
9712
  const operations = backtrackLcs(table, oldLines, newLines);
9920
9713
  const lines = operationsToDiffLines(operations, contextLines);
@@ -9948,13 +9741,11 @@ function generateDiff(change, options = {}) {
9948
9741
  */
9949
9742
  function formatUnifiedDiff(diff) {
9950
9743
  const lines = [];
9951
- // Header
9952
9744
  lines.push(`--- a/${diff.path}`);
9953
9745
  lines.push(`+++ b/${diff.path}`);
9954
9746
  if (diff.lines.length === 0) {
9955
9747
  return lines.join('\n');
9956
9748
  }
9957
- // Group lines into hunks
9958
9749
  const hunks = [];
9959
9750
  const currentHunk = [];
9960
9751
  for (const line of diff.lines) {
@@ -9963,9 +9754,7 @@ function formatUnifiedDiff(diff) {
9963
9754
  if (currentHunk.length > 0) {
9964
9755
  hunks.push(currentHunk);
9965
9756
  }
9966
- // Output hunks
9967
9757
  for (const hunk of hunks) {
9968
- // Calculate hunk header
9969
9758
  const contextAndRemove = hunk.filter((l) => l.type === 'context' || l.type === 'remove');
9970
9759
  const contextAndAdd = hunk.filter((l) => l.type === 'context' || l.type === 'add');
9971
9760
  const oldStart = hunk.find((l) => l.type === 'context' || l.type === 'remove')?.line ?? 1;
@@ -9973,7 +9762,6 @@ function formatUnifiedDiff(diff) {
9973
9762
  const oldCount = contextAndRemove.length;
9974
9763
  const newCount = contextAndAdd.length;
9975
9764
  lines.push(`@@ -${oldStart},${oldCount} +${newStart},${newCount} @@`);
9976
- // Output lines
9977
9765
  for (const line of hunk) {
9978
9766
  const prefix = line.type === 'add' ? '+' : line.type === 'remove' ? '-' : ' ';
9979
9767
  lines.push(`${prefix}${line.content}`);