@hyperfrontend/project-scope 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/CHANGELOG.md +15 -3
  2. package/README.md +2 -0
  3. package/analyze.d.ts.map +1 -1
  4. package/cli/commands/analyze.d.ts +8 -0
  5. package/cli/commands/analyze.d.ts.map +1 -1
  6. package/cli/commands/config.d.ts +7 -0
  7. package/cli/commands/config.d.ts.map +1 -1
  8. package/cli/commands/deps.d.ts +6 -0
  9. package/cli/commands/deps.d.ts.map +1 -1
  10. package/cli/commands/tree.d.ts +12 -0
  11. package/cli/commands/tree.d.ts.map +1 -1
  12. package/cli/index.cjs.js +73 -206
  13. package/cli/index.cjs.js.map +1 -1
  14. package/cli/index.esm.js +73 -206
  15. package/cli/index.esm.js.map +1 -1
  16. package/cli/run.d.ts.map +1 -1
  17. package/core/cache.d.ts +1 -0
  18. package/core/cache.d.ts.map +1 -1
  19. package/core/encoding/convert.d.ts.map +1 -1
  20. package/core/encoding/detect.d.ts +5 -0
  21. package/core/encoding/detect.d.ts.map +1 -1
  22. package/core/encoding/index.cjs.js +2 -23
  23. package/core/encoding/index.cjs.js.map +1 -1
  24. package/core/encoding/index.esm.js +2 -23
  25. package/core/encoding/index.esm.js.map +1 -1
  26. package/core/errors/structured-errors.d.ts +2 -0
  27. package/core/errors/structured-errors.d.ts.map +1 -1
  28. package/core/fs/directory.d.ts +3 -0
  29. package/core/fs/directory.d.ts.map +1 -1
  30. package/core/fs/index.cjs.js +0 -14
  31. package/core/fs/index.cjs.js.map +1 -1
  32. package/core/fs/index.esm.js +0 -14
  33. package/core/fs/index.esm.js.map +1 -1
  34. package/core/fs/read.d.ts +11 -3
  35. package/core/fs/read.d.ts.map +1 -1
  36. package/core/fs/traversal.d.ts.map +1 -1
  37. package/core/index.cjs.js +11 -63
  38. package/core/index.cjs.js.map +1 -1
  39. package/core/index.esm.js +11 -63
  40. package/core/index.esm.js.map +1 -1
  41. package/core/logger.d.ts.map +1 -1
  42. package/core/path/index.cjs.js +5 -2
  43. package/core/path/index.cjs.js.map +1 -1
  44. package/core/path/index.esm.js +5 -2
  45. package/core/path/index.esm.js.map +1 -1
  46. package/core/path/normalize.d.ts.map +1 -1
  47. package/core/patterns/glob.d.ts +0 -4
  48. package/core/patterns/glob.d.ts.map +1 -1
  49. package/core/platform/detect.d.ts.map +1 -1
  50. package/core/platform/index.cjs.js +0 -10
  51. package/core/platform/index.cjs.js.map +1 -1
  52. package/core/platform/index.esm.js +0 -10
  53. package/core/platform/index.esm.js.map +1 -1
  54. package/core/platform/line-endings.d.ts.map +1 -1
  55. package/heuristics/dependencies/analyze.d.ts.map +1 -1
  56. package/heuristics/dependencies/index.cjs.js +0 -17
  57. package/heuristics/dependencies/index.cjs.js.map +1 -1
  58. package/heuristics/dependencies/index.esm.js +0 -17
  59. package/heuristics/dependencies/index.esm.js.map +1 -1
  60. package/heuristics/entry-points/discover.d.ts +34 -7
  61. package/heuristics/entry-points/discover.d.ts.map +1 -1
  62. package/heuristics/entry-points/index.cjs.js +6 -34
  63. package/heuristics/entry-points/index.cjs.js.map +1 -1
  64. package/heuristics/entry-points/index.esm.js +6 -34
  65. package/heuristics/entry-points/index.esm.js.map +1 -1
  66. package/heuristics/framework/index.cjs.js +1 -63
  67. package/heuristics/framework/index.cjs.js.map +1 -1
  68. package/heuristics/framework/index.esm.js +1 -63
  69. package/heuristics/framework/index.esm.js.map +1 -1
  70. package/heuristics/index.cjs.js +7 -88
  71. package/heuristics/index.cjs.js.map +1 -1
  72. package/heuristics/index.esm.js +7 -88
  73. package/heuristics/index.esm.js.map +1 -1
  74. package/heuristics/project-type/index.cjs.js +1 -63
  75. package/heuristics/project-type/index.cjs.js.map +1 -1
  76. package/heuristics/project-type/index.esm.js +1 -63
  77. package/heuristics/project-type/index.esm.js.map +1 -1
  78. package/index.cjs.js +86 -294
  79. package/index.cjs.js.map +1 -1
  80. package/index.esm.js +86 -294
  81. package/index.esm.js.map +1 -1
  82. package/nx/detect.d.ts.map +1 -1
  83. package/nx/devkit-loader.d.ts.map +1 -1
  84. package/nx/index.cjs.js +0 -29
  85. package/nx/index.cjs.js.map +1 -1
  86. package/nx/index.esm.js +0 -29
  87. package/nx/index.esm.js.map +1 -1
  88. package/nx/project-config.d.ts +2 -0
  89. package/nx/project-config.d.ts.map +1 -1
  90. package/package.json +9 -9
  91. package/project/config/index.cjs.js +4 -46
  92. package/project/config/index.cjs.js.map +1 -1
  93. package/project/config/index.esm.js +4 -46
  94. package/project/config/index.esm.js.map +1 -1
  95. package/project/config/patterns.d.ts.map +1 -1
  96. package/project/index.cjs.js +4 -47
  97. package/project/index.cjs.js.map +1 -1
  98. package/project/index.esm.js +4 -47
  99. package/project/index.esm.js.map +1 -1
  100. package/project/package/index.cjs.js +0 -11
  101. package/project/package/index.cjs.js.map +1 -1
  102. package/project/package/index.esm.js +0 -11
  103. package/project/package/index.esm.js.map +1 -1
  104. package/project/package/read.d.ts +1 -0
  105. package/project/package/read.d.ts.map +1 -1
  106. package/project/root/index.cjs.js +0 -11
  107. package/project/root/index.cjs.js.map +1 -1
  108. package/project/root/index.esm.js +0 -11
  109. package/project/root/index.esm.js.map +1 -1
  110. package/project/traversal/index.cjs.js +4 -28
  111. package/project/traversal/index.cjs.js.map +1 -1
  112. package/project/traversal/index.esm.js +4 -28
  113. package/project/traversal/index.esm.js.map +1 -1
  114. package/tech/backend/express.d.ts.map +1 -1
  115. package/tech/backend/fastify.d.ts.map +1 -1
  116. package/tech/backend/index.cjs.js +0 -17
  117. package/tech/backend/index.cjs.js.map +1 -1
  118. package/tech/backend/index.esm.js +0 -17
  119. package/tech/backend/index.esm.js.map +1 -1
  120. package/tech/backend/koa.d.ts.map +1 -1
  121. package/tech/backend/nestjs.d.ts.map +1 -1
  122. package/tech/build/index.cjs.js +0 -12
  123. package/tech/build/index.cjs.js.map +1 -1
  124. package/tech/build/index.esm.js +0 -12
  125. package/tech/build/index.esm.js.map +1 -1
  126. package/tech/frontend/index.cjs.js +0 -16
  127. package/tech/frontend/index.cjs.js.map +1 -1
  128. package/tech/frontend/index.esm.js +0 -16
  129. package/tech/frontend/index.esm.js.map +1 -1
  130. package/tech/frontend/qwik.d.ts.map +1 -1
  131. package/tech/frontend/remix.d.ts.map +1 -1
  132. package/tech/frontend/sveltekit.d.ts.map +1 -1
  133. package/tech/index.cjs.js +1 -63
  134. package/tech/index.cjs.js.map +1 -1
  135. package/tech/index.d.ts.map +1 -1
  136. package/tech/index.esm.js +1 -63
  137. package/tech/index.esm.js.map +1 -1
  138. package/tech/legacy/angularjs.d.ts.map +1 -1
  139. package/tech/legacy/backbone.d.ts.map +1 -1
  140. package/tech/legacy/ember.d.ts.map +1 -1
  141. package/tech/legacy/index.cjs.js +0 -26
  142. package/tech/legacy/index.cjs.js.map +1 -1
  143. package/tech/legacy/index.esm.js +0 -26
  144. package/tech/legacy/index.esm.js.map +1 -1
  145. package/tech/legacy/jquery.d.ts.map +1 -1
  146. package/tech/linting/biome.d.ts.map +1 -1
  147. package/tech/linting/index.cjs.js +0 -14
  148. package/tech/linting/index.cjs.js.map +1 -1
  149. package/tech/linting/index.esm.js +0 -14
  150. package/tech/linting/index.esm.js.map +1 -1
  151. package/tech/linting/prettier.d.ts.map +1 -1
  152. package/tech/monorepo/index.cjs.js +1 -13
  153. package/tech/monorepo/index.cjs.js.map +1 -1
  154. package/tech/monorepo/index.esm.js +1 -13
  155. package/tech/monorepo/index.esm.js.map +1 -1
  156. package/tech/monorepo/types.d.ts +1 -1
  157. package/tech/monorepo/types.d.ts.map +1 -1
  158. package/tech/shared-utils/detector-helpers.d.ts.map +1 -1
  159. package/tech/testing/index.cjs.js +0 -12
  160. package/tech/testing/index.cjs.js.map +1 -1
  161. package/tech/testing/index.esm.js +0 -12
  162. package/tech/testing/index.esm.js.map +1 -1
  163. package/tech/types/detectors.d.ts.map +1 -1
  164. package/tech/types/index.cjs.js +0 -27
  165. package/tech/types/index.cjs.js.map +1 -1
  166. package/tech/types/index.esm.js +0 -27
  167. package/tech/types/index.esm.js.map +1 -1
  168. package/vfs/commit.d.ts.map +1 -1
  169. package/vfs/diff.d.ts.map +1 -1
  170. package/vfs/factory.d.ts.map +1 -1
  171. package/vfs/fs-tree.d.ts.map +1 -1
  172. package/vfs/index.cjs.js +5 -46
  173. package/vfs/index.cjs.js.map +1 -1
  174. package/vfs/index.esm.js +5 -46
  175. package/vfs/index.esm.js.map +1 -1
  176. package/vfs/types.d.ts +1 -0
  177. package/vfs/types.d.ts.map +1 -1
package/index.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
  }
@@ -1175,7 +1155,11 @@ function normalizeToNative(filePath) {
1175
1155
  * @returns Path with trailing slashes removed
1176
1156
  */
1177
1157
  function removeTrailingSlash(filePath) {
1178
- return filePath.replace(/[/\\]+$/, '');
1158
+ let i = filePath.length;
1159
+ while (i > 0 && (filePath[i - 1] === '/' || filePath[i - 1] === '\\')) {
1160
+ i--;
1161
+ }
1162
+ return filePath.slice(0, i);
1179
1163
  }
1180
1164
  /**
1181
1165
  * Append a forward slash to the path if not already present.
@@ -1349,7 +1333,6 @@ function traverseUpward(startPath, predicate) {
1349
1333
  }
1350
1334
  currentPath = dirname(currentPath);
1351
1335
  }
1352
- // Check root directory
1353
1336
  if (predicate(rootPath)) {
1354
1337
  fsTraversalLogger.debug('Upward traversal found match at root', { startPath, foundPath: rootPath });
1355
1338
  return rootPath;
@@ -1721,29 +1704,23 @@ const depsLogger = createScopedLogger('project-scope:heuristics:deps');
1721
1704
  */
1722
1705
  function extractImports(content) {
1723
1706
  const imports = [];
1724
- // ES import with 'from': import X from 'path' or import { X } from 'path'
1725
- // Use non-greedy match and avoid nested quantifiers by matching "from" keyword directly
1726
1707
  const esImportFromRegex = /import\s+.+?\s+from\s+['"]([^'"]+)['"]/g;
1727
1708
  let match;
1728
1709
  while ((match = esImportFromRegex.exec(content)) !== null) {
1729
1710
  imports.push(match[1]);
1730
1711
  }
1731
- // Side-effect import: import 'path'
1732
1712
  const sideEffectImportRegex = /import\s+['"]([^'"]+)['"]/g;
1733
1713
  while ((match = sideEffectImportRegex.exec(content)) !== null) {
1734
1714
  imports.push(match[1]);
1735
1715
  }
1736
- // Dynamic import: import('path')
1737
1716
  const dynamicImportRegex = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
1738
1717
  while ((match = dynamicImportRegex.exec(content)) !== null) {
1739
1718
  imports.push(match[1]);
1740
1719
  }
1741
- // require: require('path')
1742
1720
  const requireRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
1743
1721
  while ((match = requireRegex.exec(content)) !== null) {
1744
1722
  imports.push(match[1]);
1745
1723
  }
1746
- // Re-export: export * from 'path' or export { X } from 'path'
1747
1724
  const exportFromRegex = /export\s+.+?\s+from\s+['"]([^'"]+)['"]/g;
1748
1725
  while ((match = exportFromRegex.exec(content)) !== null) {
1749
1726
  imports.push(match[1]);
@@ -2057,7 +2034,6 @@ const cacheRegistry = createSet();
2057
2034
  function createCache(options) {
2058
2035
  const { ttl, maxSize } = options ?? {};
2059
2036
  const store = createMap();
2060
- // Track insertion order for FIFO eviction
2061
2037
  const insertionOrder = [];
2062
2038
  /**
2063
2039
  * Check if an entry is expired.
@@ -2108,12 +2084,10 @@ function createCache(options) {
2108
2084
  return entry.value;
2109
2085
  },
2110
2086
  set(key, value) {
2111
- // If key exists, remove from order first
2112
2087
  if (store.has(key)) {
2113
2088
  removeFromOrder(key);
2114
2089
  }
2115
2090
  else {
2116
- // Evict if needed before adding new entry
2117
2091
  evictIfNeeded();
2118
2092
  }
2119
2093
  // eslint-disable-next-line workspace/no-unsafe-builtin-methods -- Date.now() is needed for Jest fake timers compatibility
@@ -2146,7 +2120,6 @@ function createCache(options) {
2146
2120
  return [...insertionOrder];
2147
2121
  },
2148
2122
  };
2149
- // Register cache for global operations
2150
2123
  cacheRegistry.add(cache);
2151
2124
  return freeze(cache);
2152
2125
  }
@@ -2226,7 +2199,6 @@ function memoize(fn, options) {
2226
2199
  cache.set(key, result);
2227
2200
  return result;
2228
2201
  };
2229
- // Attach cache for direct access
2230
2202
  defineProperty(memoized, 'cache', {
2231
2203
  value: cache,
2232
2204
  writable: false,
@@ -2235,10 +2207,6 @@ function memoize(fn, options) {
2235
2207
  return memoized;
2236
2208
  }
2237
2209
 
2238
- /**
2239
- * Pattern matching utilities with ReDoS protection.
2240
- * Uses character-by-character matching instead of regex where possible.
2241
- */
2242
2210
  /**
2243
2211
  * Match path against glob pattern using safe character iteration.
2244
2212
  * Avoids regex to prevent ReDoS attacks.
@@ -2276,17 +2244,14 @@ function matchGlobPattern(path, pattern) {
2276
2244
  * @returns True if remaining segments match
2277
2245
  */
2278
2246
  function matchSegments(pathParts, patternParts, pathIdx, patternIdx) {
2279
- // Base cases
2280
2247
  if (pathIdx === pathParts.length && patternIdx === patternParts.length) {
2281
- return true; // Both exhausted = match
2248
+ return true;
2282
2249
  }
2283
2250
  if (patternIdx >= patternParts.length) {
2284
- return false; // Pattern exhausted but path remains
2251
+ return false;
2285
2252
  }
2286
2253
  const patternPart = patternParts[patternIdx];
2287
- // Handle ** (globstar) - matches zero or more directories
2288
2254
  if (patternPart === '**') {
2289
- // Try matching rest of pattern against current position and all future positions
2290
2255
  for (let i = pathIdx; i <= pathParts.length; i++) {
2291
2256
  if (matchSegments(pathParts, patternParts, i, patternIdx + 1)) {
2292
2257
  return true;
@@ -2295,10 +2260,9 @@ function matchSegments(pathParts, patternParts, pathIdx, patternIdx) {
2295
2260
  return false;
2296
2261
  }
2297
2262
  if (pathIdx >= pathParts.length) {
2298
- return false; // Path exhausted but pattern remains (and it's not **)
2263
+ return false;
2299
2264
  }
2300
2265
  const pathPart = pathParts[pathIdx];
2301
- // Match current segment
2302
2266
  if (matchSegment(pathPart, patternPart)) {
2303
2267
  return matchSegments(pathParts, patternParts, pathIdx + 1, patternIdx + 1);
2304
2268
  }
@@ -2318,12 +2282,10 @@ function matchSegment(text, pattern) {
2318
2282
  while (patternIdx < pattern.length) {
2319
2283
  const char = pattern[patternIdx];
2320
2284
  if (char === '*') {
2321
- // * matches zero or more characters
2322
2285
  patternIdx++;
2323
2286
  if (patternIdx === pattern.length) {
2324
- return true; // * at end matches rest of string
2287
+ return true;
2325
2288
  }
2326
- // Try matching rest of pattern at each position in text
2327
2289
  for (let i = textIdx; i <= text.length; i++) {
2328
2290
  if (matchSegmentFrom(text, i, pattern, patternIdx)) {
2329
2291
  return true;
@@ -2332,7 +2294,6 @@ function matchSegment(text, pattern) {
2332
2294
  return false;
2333
2295
  }
2334
2296
  else if (char === '?') {
2335
- // ? matches exactly one character
2336
2297
  if (textIdx >= text.length) {
2337
2298
  return false;
2338
2299
  }
@@ -2340,10 +2301,8 @@ function matchSegment(text, pattern) {
2340
2301
  patternIdx++;
2341
2302
  }
2342
2303
  else if (char === '{') {
2343
- // {a,b,c} matches any alternative
2344
2304
  const closeIdx = findClosingBrace(pattern, patternIdx);
2345
2305
  if (closeIdx === -1) {
2346
- // Unmatched brace, treat as literal
2347
2306
  if (textIdx >= text.length || text[textIdx] !== char) {
2348
2307
  return false;
2349
2308
  }
@@ -2361,7 +2320,6 @@ function matchSegment(text, pattern) {
2361
2320
  }
2362
2321
  }
2363
2322
  else {
2364
- // Literal character
2365
2323
  if (textIdx >= text.length || text[textIdx] !== char) {
2366
2324
  return false;
2367
2325
  }
@@ -2787,7 +2745,8 @@ const entryPointLogger = createScopedLogger('project-scope:heuristics:entry-poin
2787
2745
  */
2788
2746
  const entryPointCache = createCache({ ttl: 60000, maxSize: 50 });
2789
2747
  /**
2790
- * Common entry point patterns.
2748
+ * Common entry point patterns by project type.
2749
+ * Used for convention-based entry point discovery.
2791
2750
  */
2792
2751
  const ENTRY_POINT_PATTERNS = {
2793
2752
  /** Library entry patterns */
@@ -3070,7 +3029,6 @@ function collectAllDependencies(packageJson) {
3070
3029
  function parseVersionString(versionString) {
3071
3030
  if (versionString === undefined || versionString === null)
3072
3031
  return undefined;
3073
- // Manual parsing instead of regex to avoid ReDoS
3074
3032
  let start = 0;
3075
3033
  while (start < versionString.length) {
3076
3034
  const char = versionString[start];
@@ -3131,7 +3089,6 @@ function expressDetector(projectPath, packageJson) {
3131
3089
  version = parseVersionString(deps['express']);
3132
3090
  sources.push({ type: 'package.json', field: 'dependencies.express' });
3133
3091
  }
3134
- // @types/express (indicates usage)
3135
3092
  if (deps['@types/express']) {
3136
3093
  confidence += 10;
3137
3094
  sources.push({ type: 'package.json', field: 'dependencies.@types/express' });
@@ -3167,13 +3124,11 @@ function nestDetector(projectPath, packageJson) {
3167
3124
  let version;
3168
3125
  let configPath;
3169
3126
  const deps = collectAllDependencies(pkg);
3170
- // @nestjs/core package
3171
3127
  if (deps['@nestjs/core']) {
3172
3128
  confidence += 70;
3173
3129
  version = parseVersionString(deps['@nestjs/core']);
3174
3130
  sources.push({ type: 'package.json', field: 'dependencies.@nestjs/core' });
3175
3131
  }
3176
- // @nestjs/common
3177
3132
  if (deps['@nestjs/common']) {
3178
3133
  confidence += 15;
3179
3134
  sources.push({ type: 'package.json', field: 'dependencies.@nestjs/common' });
@@ -3224,7 +3179,6 @@ function fastifyDetector(projectPath, packageJson) {
3224
3179
  confidence += 15;
3225
3180
  sources.push({ type: 'package.json', field: 'dependencies (fastify plugins)' });
3226
3181
  }
3227
- // @types/fastify (older versions)
3228
3182
  if (deps['@types/fastify']) {
3229
3183
  confidence += 5;
3230
3184
  sources.push({ type: 'package.json', field: 'dependencies.@types/fastify' });
@@ -3259,7 +3213,6 @@ function koaDetector(projectPath, packageJson) {
3259
3213
  version = parseVersionString(deps['koa']);
3260
3214
  sources.push({ type: 'package.json', field: 'dependencies.koa' });
3261
3215
  }
3262
- // @types/koa
3263
3216
  if (deps['@types/koa']) {
3264
3217
  confidence += 10;
3265
3218
  sources.push({ type: 'package.json', field: 'dependencies.@types/koa' });
@@ -3838,7 +3791,6 @@ function remixDetector(projectPath, packageJson) {
3838
3791
  let confidence = 0;
3839
3792
  let version;
3840
3793
  const deps = collectAllDependencies(pkg);
3841
- // @remix-run packages
3842
3794
  if (deps['@remix-run/react']) {
3843
3795
  confidence += 70;
3844
3796
  version = parseVersionString(deps['@remix-run/react']);
@@ -4114,7 +4066,6 @@ function sveltekitDetector(projectPath, packageJson) {
4114
4066
  let confidence = 0;
4115
4067
  let version;
4116
4068
  const deps = collectAllDependencies(pkg);
4117
- // @sveltejs/kit package
4118
4069
  if (deps['@sveltejs/kit']) {
4119
4070
  confidence += 70;
4120
4071
  version = parseVersionString(deps['@sveltejs/kit']);
@@ -4193,13 +4144,11 @@ function qwikDetector(projectPath, packageJson) {
4193
4144
  let confidence = 0;
4194
4145
  let version;
4195
4146
  const deps = collectAllDependencies(pkg);
4196
- // @builder.io/qwik package
4197
4147
  if (deps['@builder.io/qwik']) {
4198
4148
  confidence += 70;
4199
4149
  version = parseVersionString(deps['@builder.io/qwik']);
4200
4150
  sources.push({ type: 'package.json', field: 'dependencies.@builder.io/qwik' });
4201
4151
  }
4202
- // @builder.io/qwik-city
4203
4152
  if (deps['@builder.io/qwik-city']) {
4204
4153
  confidence += 20;
4205
4154
  sources.push({ type: 'package.json', field: 'dependencies.@builder.io/qwik-city' });
@@ -4310,23 +4259,19 @@ function angularJSDetector(projectPath, packageJson) {
4310
4259
  let confidence = 0;
4311
4260
  let version;
4312
4261
  const deps = collectAllDependencies(pkg);
4313
- // AngularJS package (angular, not @angular/core)
4314
4262
  if (deps['angular']) {
4315
4263
  confidence += 70;
4316
4264
  version = parseVersionString(deps['angular']);
4317
4265
  sources.push({ type: 'package.json', field: 'dependencies.angular' });
4318
4266
  }
4319
- // AngularJS router
4320
4267
  if (deps['angular-route']) {
4321
4268
  confidence += 15;
4322
4269
  sources.push({ type: 'package.json', field: 'dependencies.angular-route' });
4323
4270
  }
4324
- // AngularJS resource
4325
4271
  if (deps['angular-resource']) {
4326
4272
  confidence += 10;
4327
4273
  sources.push({ type: 'package.json', field: 'dependencies.angular-resource' });
4328
4274
  }
4329
- // AngularJS animate
4330
4275
  if (deps['angular-animate']) {
4331
4276
  confidence += 5;
4332
4277
  sources.push({ type: 'package.json', field: 'dependencies.angular-animate' });
@@ -4357,23 +4302,19 @@ function backboneDetector(projectPath, packageJson) {
4357
4302
  let confidence = 0;
4358
4303
  let version;
4359
4304
  const deps = collectAllDependencies(pkg);
4360
- // Backbone package
4361
4305
  if (deps['backbone']) {
4362
4306
  confidence += 70;
4363
4307
  version = parseVersionString(deps['backbone']);
4364
4308
  sources.push({ type: 'package.json', field: 'dependencies.backbone' });
4365
- // Underscore (commonly used with Backbone)
4366
4309
  if (deps['underscore']) {
4367
4310
  confidence += 15;
4368
4311
  sources.push({ type: 'package.json', field: 'dependencies.underscore' });
4369
4312
  }
4370
- // Lodash can be used as underscore replacement
4371
4313
  if (deps['lodash']) {
4372
4314
  confidence += 5;
4373
4315
  sources.push({ type: 'package.json', field: 'dependencies.lodash' });
4374
4316
  }
4375
4317
  }
4376
- // Marionette (Backbone framework)
4377
4318
  if (deps['backbone.marionette'] || deps['marionette']) {
4378
4319
  confidence += 10;
4379
4320
  sources.push({ type: 'package.json', field: 'dependencies.backbone.marionette' });
@@ -4404,18 +4345,15 @@ function emberDetector(projectPath, packageJson) {
4404
4345
  let confidence = 0;
4405
4346
  let version;
4406
4347
  const deps = collectAllDependencies(pkg);
4407
- // Ember source package
4408
4348
  if (deps['ember-source']) {
4409
4349
  confidence += 70;
4410
4350
  version = parseVersionString(deps['ember-source']);
4411
4351
  sources.push({ type: 'package.json', field: 'dependencies.ember-source' });
4412
4352
  }
4413
- // Ember CLI
4414
4353
  if (deps['ember-cli']) {
4415
4354
  confidence += 20;
4416
4355
  sources.push({ type: 'package.json', field: 'devDependencies.ember-cli' });
4417
4356
  }
4418
- // Ember Data
4419
4357
  if (deps['ember-data']) {
4420
4358
  confidence += 10;
4421
4359
  sources.push({ type: 'package.json', field: 'dependencies.ember-data' });
@@ -4446,18 +4384,15 @@ function jqueryDetector(projectPath, packageJson) {
4446
4384
  let confidence = 0;
4447
4385
  let version;
4448
4386
  const deps = collectAllDependencies(pkg);
4449
- // jQuery package
4450
4387
  if (deps['jquery']) {
4451
4388
  confidence += 80;
4452
4389
  version = parseVersionString(deps['jquery']);
4453
4390
  sources.push({ type: 'package.json', field: 'dependencies.jquery' });
4454
4391
  }
4455
- // jQuery UI
4456
4392
  if (deps['jquery-ui']) {
4457
4393
  confidence += 10;
4458
4394
  sources.push({ type: 'package.json', field: 'dependencies.jquery-ui' });
4459
4395
  }
4460
- // jQuery plugins
4461
4396
  if (deps['jquery-validation']) {
4462
4397
  confidence += 5;
4463
4398
  sources.push({ type: 'package.json', field: 'dependencies.jquery-validation' });
@@ -4604,7 +4539,6 @@ function prettierDetector(projectPath, packageJson) {
4604
4539
  confidence += 30;
4605
4540
  sources.push({ type: 'package.json', field: 'prettier' });
4606
4541
  }
4607
- // .prettierignore file
4608
4542
  if (exists(join$1(projectPath, '.prettierignore'))) {
4609
4543
  confidence += 10;
4610
4544
  sources.push({ type: 'config-file', path: '.prettierignore' });
@@ -4699,7 +4633,6 @@ function biomeDetector(projectPath, packageJson) {
4699
4633
  let configPath;
4700
4634
  let version;
4701
4635
  const deps = collectAllDependencies(pkg);
4702
- // @biomejs/biome package
4703
4636
  if (deps['@biomejs/biome']) {
4704
4637
  confidence += 70;
4705
4638
  version = parseVersionString(deps['@biomejs/biome']);
@@ -4986,7 +4919,7 @@ function npmWorkspacesDetector(workspacePath, packageJson) {
4986
4919
  sources.push({ type: 'lockfile', path: 'package-lock.json' });
4987
4920
  }
4988
4921
  if (exists(join$1(workspacePath, 'yarn.lock'))) {
4989
- return null; // Let yarn workspace detector handle this
4922
+ return null;
4990
4923
  }
4991
4924
  if (confidence === 0) {
4992
4925
  return null;
@@ -5365,7 +5298,6 @@ function checkTsConfigStrict(projectPath) {
5365
5298
  if (!content)
5366
5299
  return undefined;
5367
5300
  try {
5368
- // Simple JSON parsing - doesn't handle comments but good enough for strict check
5369
5301
  const cleanContent = content.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '');
5370
5302
  const parsed = parse(cleanContent);
5371
5303
  return parsed?.compilerOptions?.strict === true;
@@ -5388,19 +5320,16 @@ function typescriptDetector(projectPath, packageJson) {
5388
5320
  let configPath;
5389
5321
  let version;
5390
5322
  const deps = collectAllDependencies(pkg);
5391
- // TypeScript package
5392
5323
  if (deps['typescript']) {
5393
5324
  confidence += 50;
5394
5325
  version = parseVersionString(deps['typescript']);
5395
5326
  sources.push({ type: 'package.json', field: 'dependencies.typescript' });
5396
5327
  }
5397
- // tsconfig.json
5398
5328
  if (exists(join$1(projectPath, 'tsconfig.json'))) {
5399
5329
  confidence += 40;
5400
5330
  configPath = 'tsconfig.json';
5401
5331
  sources.push({ type: 'config-file', path: 'tsconfig.json' });
5402
5332
  }
5403
- // tsconfig.*.json variants
5404
5333
  const tsconfigVariants = ['tsconfig.build.json', 'tsconfig.lib.json', 'tsconfig.spec.json', 'tsconfig.app.json'];
5405
5334
  for (const variant of tsconfigVariants) {
5406
5335
  if (exists(join$1(projectPath, variant))) {
@@ -5409,7 +5338,6 @@ function typescriptDetector(projectPath, packageJson) {
5409
5338
  break;
5410
5339
  }
5411
5340
  }
5412
- // @types packages
5413
5341
  const typePackages = keys(deps).filter((d) => d.startsWith('@types/'));
5414
5342
  if (typePackages.length > 0) {
5415
5343
  confidence += 10;
@@ -5443,24 +5371,20 @@ function flowDetector(projectPath, packageJson) {
5443
5371
  let configPath;
5444
5372
  let version;
5445
5373
  const deps = collectAllDependencies(pkg);
5446
- // flow-bin package
5447
5374
  if (deps['flow-bin']) {
5448
5375
  confidence += 60;
5449
5376
  version = parseVersionString(deps['flow-bin']);
5450
5377
  sources.push({ type: 'package.json', field: 'dependencies.flow-bin' });
5451
5378
  }
5452
- // .flowconfig
5453
5379
  if (exists(join$1(projectPath, '.flowconfig'))) {
5454
5380
  confidence += 40;
5455
5381
  configPath = '.flowconfig';
5456
5382
  sources.push({ type: 'config-file', path: '.flowconfig' });
5457
5383
  }
5458
- // flow-typed directory
5459
5384
  if (exists(join$1(projectPath, 'flow-typed'))) {
5460
5385
  confidence += 10;
5461
5386
  sources.push({ type: 'directory', path: 'flow-typed/' });
5462
5387
  }
5463
- // @babel/preset-flow
5464
5388
  if (deps['@babel/preset-flow']) {
5465
5389
  confidence += 10;
5466
5390
  sources.push({ type: 'package.json', field: 'dependencies.@babel/preset-flow' });
@@ -5484,7 +5408,6 @@ function flowDetector(projectPath, packageJson) {
5484
5408
  * @returns `true` if the content contains JSDoc type annotations.
5485
5409
  */
5486
5410
  function hasJsDocTypes(content) {
5487
- // Check for JSDoc type annotations
5488
5411
  return (content.includes('@type {') ||
5489
5412
  content.includes('@param {') ||
5490
5413
  content.includes('@returns {') ||
@@ -5503,14 +5426,11 @@ function jsdocDetector(projectPath, packageJson) {
5503
5426
  const sources = [];
5504
5427
  let confidence = 0;
5505
5428
  const deps = collectAllDependencies(pkg);
5506
- // jsdoc package
5507
5429
  if (deps['jsdoc']) {
5508
5430
  confidence += 30;
5509
5431
  sources.push({ type: 'package.json', field: 'dependencies.jsdoc' });
5510
5432
  }
5511
- // typescript with checkJs (JSDoc type checking)
5512
5433
  if (deps['typescript']) {
5513
- // Check if checkJs is enabled in tsconfig
5514
5434
  const tsconfigPath = join$1(projectPath, 'tsconfig.json');
5515
5435
  const content = readFileIfExists(tsconfigPath);
5516
5436
  if (content) {
@@ -5527,12 +5447,10 @@ function jsdocDetector(projectPath, packageJson) {
5527
5447
  }
5528
5448
  }
5529
5449
  }
5530
- // Check for jsconfig.json (VS Code JS type checking)
5531
5450
  if (exists(join$1(projectPath, 'jsconfig.json'))) {
5532
5451
  confidence += 40;
5533
5452
  sources.push({ type: 'config-file', path: 'jsconfig.json' });
5534
5453
  }
5535
- // Sample check for JSDoc annotations in source files
5536
5454
  const srcDir = join$1(projectPath, 'src');
5537
5455
  if (exists(srcDir)) {
5538
5456
  try {
@@ -5614,8 +5532,6 @@ const allDetectors = {
5614
5532
  function isDetectAllOptions(value) {
5615
5533
  if (typeof value !== 'object' || value === null)
5616
5534
  return false;
5617
- // DetectAllOptions has skipCache or packageJson fields specifically
5618
- // PackageJson never has skipCache field
5619
5535
  return 'skipCache' in value || 'packageJson' in value;
5620
5536
  }
5621
5537
  /**
@@ -5647,9 +5563,7 @@ function isDetectAllOptions(value) {
5647
5563
  * ```
5648
5564
  */
5649
5565
  function detectAll(projectPath, packageJsonOrOptions) {
5650
- // Handle backward-compatible arguments
5651
5566
  const options = isDetectAllOptions(packageJsonOrOptions) ? packageJsonOrOptions : { packageJson: packageJsonOrOptions };
5652
- // Check cache first (unless skipCache is true)
5653
5567
  if (!options.skipCache) {
5654
5568
  const cached = detectAllCache.get(projectPath);
5655
5569
  if (cached) {
@@ -5700,7 +5614,6 @@ function detectAll(projectPath, packageJsonOrOptions) {
5700
5614
  legacyFrameworks: result.legacyFrameworks.map((f) => f.id),
5701
5615
  testingFrameworks: result.testingFrameworks.map((f) => f.id),
5702
5616
  });
5703
- // Cache the result
5704
5617
  detectAllCache.set(projectPath, result);
5705
5618
  return result;
5706
5619
  }
@@ -6117,7 +6030,6 @@ function detectNxVersion(workspacePath) {
6117
6030
  if (packageJson) {
6118
6031
  const nxVersion = packageJson.devDependencies?.['nx'] ?? packageJson.dependencies?.['nx'];
6119
6032
  if (nxVersion) {
6120
- // Strip semver range characters (^, ~, >=, etc.)
6121
6033
  return nxVersion.replace(/^[\^~>=<]+/, '');
6122
6034
  }
6123
6035
  }
@@ -6146,14 +6058,12 @@ function getNxWorkspaceInfo(workspacePath) {
6146
6058
  }
6147
6059
  const nxJson = readJsonFileIfExists(join$1(workspacePath, 'nx.json'));
6148
6060
  if (!nxJson) {
6149
- // Check for workspace.json as fallback (older NX)
6150
6061
  const workspaceJson = readJsonFileIfExists(join$1(workspacePath, 'workspace.json'));
6151
6062
  if (!workspaceJson) {
6152
6063
  nxLogger.debug('No nx.json or workspace.json found', { workspacePath });
6153
6064
  return null;
6154
6065
  }
6155
6066
  nxLogger.debug('Using legacy workspace.json', { workspacePath });
6156
- // Create minimal nx.json from workspace.json
6157
6067
  return {
6158
6068
  root: workspacePath,
6159
6069
  version: detectNxVersion(workspacePath),
@@ -6202,7 +6112,6 @@ function tryLoadDevkit() {
6202
6112
  }
6203
6113
  devkitLogger.debug('Attempting to load @nx/devkit');
6204
6114
  try {
6205
- // Dynamic require to avoid bundling
6206
6115
  const devkit = require('@nx/devkit');
6207
6116
  devkitLogger.debug('@nx/devkit loaded successfully');
6208
6117
  cachedResult = { available: true, devkit };
@@ -6299,7 +6208,6 @@ function readProjectJson(projectPath) {
6299
6208
  */
6300
6209
  function getProjectConfig(projectPath, workspacePath) {
6301
6210
  nxConfigLogger.debug('Getting project config', { projectPath, workspacePath });
6302
- // Try project.json first
6303
6211
  const projectJson = readProjectJson(projectPath);
6304
6212
  if (projectJson) {
6305
6213
  nxConfigLogger.debug('Using project.json config', { projectPath, name: projectJson.name });
@@ -6308,7 +6216,6 @@ function getProjectConfig(projectPath, workspacePath) {
6308
6216
  root: projectJson.root ?? relative(workspacePath, projectPath),
6309
6217
  };
6310
6218
  }
6311
- // Try to infer from package.json nx field
6312
6219
  const packageJson = readPackageJsonIfExists(projectPath);
6313
6220
  if (packageJson && typeof packageJson['nx'] === 'object') {
6314
6221
  nxConfigLogger.debug('Using package.json nx field', { projectPath, name: packageJson.name });
@@ -6337,13 +6244,11 @@ function scanForProjects(dirPath, workspacePath, projects, maxDepth, currentDept
6337
6244
  try {
6338
6245
  const entries = readDirectory(dirPath);
6339
6246
  for (const entry of entries) {
6340
- // Skip node_modules and hidden directories
6341
6247
  if (entry.name.startsWith('.') || entry.name === 'node_modules' || entry.name === 'dist') {
6342
6248
  continue;
6343
6249
  }
6344
6250
  const fullPath = join$1(dirPath, entry.name);
6345
6251
  if (entry.isDirectory) {
6346
- // Check if this directory is an NX project
6347
6252
  if (isNxProject(fullPath)) {
6348
6253
  const config = getProjectConfig(fullPath, workspacePath);
6349
6254
  if (config) {
@@ -6355,7 +6260,6 @@ function scanForProjects(dirPath, workspacePath, projects, maxDepth, currentDept
6355
6260
  });
6356
6261
  }
6357
6262
  }
6358
- // Recursively scan subdirectories
6359
6263
  scanForProjects(fullPath, workspacePath, projects, maxDepth, currentDepth + 1);
6360
6264
  }
6361
6265
  }
@@ -6373,12 +6277,10 @@ function scanForProjects(dirPath, workspacePath, projects, maxDepth, currentDept
6373
6277
  */
6374
6278
  function discoverNxProjects(workspacePath) {
6375
6279
  const projects = createMap();
6376
- // Check for workspace.json (older NX format)
6377
6280
  const workspaceJson = readJsonFileIfExists(join$1(workspacePath, 'workspace.json'));
6378
6281
  if (workspaceJson?.projects) {
6379
6282
  for (const [name, config] of entries(workspaceJson.projects)) {
6380
6283
  if (typeof config === 'string') {
6381
- // Path reference to project directory
6382
6284
  const projectPath = join$1(workspacePath, config);
6383
6285
  const projectConfig = getProjectConfig(projectPath, workspacePath);
6384
6286
  if (projectConfig) {
@@ -6386,18 +6288,15 @@ function discoverNxProjects(workspacePath) {
6386
6288
  }
6387
6289
  }
6388
6290
  else if (typeof config === 'object' && config !== null) {
6389
- // Inline config
6390
6291
  projects.set(name, { name, ...config });
6391
6292
  }
6392
6293
  }
6393
6294
  return projects;
6394
6295
  }
6395
- // Scan for project.json files (newer NX format)
6396
6296
  const workspaceInfo = getNxWorkspaceInfo(workspacePath);
6397
6297
  const appsDir = workspaceInfo?.workspaceLayout.appsDir ?? 'apps';
6398
6298
  const libsDir = workspaceInfo?.workspaceLayout.libsDir ?? 'libs';
6399
6299
  const searchDirs = [appsDir, libsDir];
6400
- // Also check packages directory (common in some setups)
6401
6300
  if (exists(join$1(workspacePath, 'packages'))) {
6402
6301
  searchDirs.push('packages');
6403
6302
  }
@@ -6412,7 +6311,6 @@ function discoverNxProjects(workspacePath) {
6412
6311
  }
6413
6312
  }
6414
6313
  }
6415
- // Also check root-level projects (standalone projects in monorepo root)
6416
6314
  if (isNxProject(workspacePath)) {
6417
6315
  const config = readProjectJson(workspacePath);
6418
6316
  if (config) {
@@ -6445,10 +6343,8 @@ function buildSimpleProjectGraph(workspacePath, projects) {
6445
6343
  data: config,
6446
6344
  };
6447
6345
  dependencies[name] = [];
6448
- // Add implicit dependencies
6449
6346
  if (config.implicitDependencies) {
6450
6347
  for (const dep of config.implicitDependencies) {
6451
- // Skip negative dependencies (those starting with !)
6452
6348
  if (!dep.startsWith('!')) {
6453
6349
  dependencies[name].push({
6454
6350
  target: dep,
@@ -6465,7 +6361,6 @@ function buildSimpleProjectGraph(workspacePath, projects) {
6465
6361
  * Known configuration file patterns organized by type.
6466
6362
  */
6467
6363
  const CONFIG_PATTERNS = {
6468
- // Package Management
6469
6364
  'package.json': {
6470
6365
  patterns: ['package.json'],
6471
6366
  format: 'json',
@@ -6492,14 +6387,12 @@ const CONFIG_PATTERNS = {
6492
6387
  description: 'NPM configuration',
6493
6388
  sensitive: true,
6494
6389
  },
6495
- // TypeScript
6496
6390
  tsconfig: {
6497
6391
  patterns: ['tsconfig.json', 'tsconfig.*.json'],
6498
6392
  format: 'jsonc',
6499
6393
  description: 'TypeScript configuration',
6500
6394
  canExtend: true,
6501
6395
  },
6502
- // Monorepo
6503
6396
  nx: {
6504
6397
  patterns: ['nx.json'],
6505
6398
  format: 'json',
@@ -6525,7 +6418,6 @@ const CONFIG_PATTERNS = {
6525
6418
  format: 'json',
6526
6419
  description: 'Lerna configuration',
6527
6420
  },
6528
- // Build Tools
6529
6421
  webpack: {
6530
6422
  patterns: ['webpack.config.js', 'webpack.config.ts', 'webpack.config.cjs', 'webpack.config.mjs'],
6531
6423
  format: 'js',
@@ -6556,7 +6448,6 @@ const CONFIG_PATTERNS = {
6556
6448
  format: 'json',
6557
6449
  description: 'SWC configuration',
6558
6450
  },
6559
- // Testing
6560
6451
  jest: {
6561
6452
  patterns: ['jest.config.js', 'jest.config.ts', 'jest.config.mjs'],
6562
6453
  description: 'Jest configuration',
@@ -6573,7 +6464,6 @@ const CONFIG_PATTERNS = {
6573
6464
  patterns: ['playwright.config.js', 'playwright.config.ts'],
6574
6465
  description: 'Playwright configuration',
6575
6466
  },
6576
- // Framework configs
6577
6467
  next: {
6578
6468
  patterns: ['next.config.js', 'next.config.mjs', 'next.config.ts'],
6579
6469
  format: 'js',
@@ -6599,7 +6489,6 @@ const CONFIG_PATTERNS = {
6599
6489
  format: 'js',
6600
6490
  description: 'Astro configuration',
6601
6491
  },
6602
- // Linting & Formatting
6603
6492
  eslint: {
6604
6493
  patterns: [
6605
6494
  'eslint.config.js',
@@ -6618,14 +6507,12 @@ const CONFIG_PATTERNS = {
6618
6507
  format: 'json',
6619
6508
  description: 'Prettier configuration',
6620
6509
  },
6621
- // Environment (sensitive)
6622
6510
  env: {
6623
6511
  patterns: ['.env', '.env.*', '*.env'],
6624
6512
  format: 'dotenv',
6625
6513
  description: 'Environment variables',
6626
6514
  sensitive: true,
6627
6515
  },
6628
- // Git
6629
6516
  '.gitignore': {
6630
6517
  patterns: ['.gitignore'],
6631
6518
  format: 'text',
@@ -6796,12 +6683,8 @@ function getConfigPaths(type) {
6796
6683
  *
6797
6684
  * @module @hyperfrontend/immutable-api-utils/built-in-copy/number
6798
6685
  */
6799
- // Capture references at module initialization time
6800
6686
  const _parseInt = globalThis.parseInt;
6801
6687
  const _parseFloat = globalThis.parseFloat;
6802
- // ============================================================================
6803
- // Parsing
6804
- // ============================================================================
6805
6688
  /**
6806
6689
  * (Safe copy) Parses a string and returns an integer.
6807
6690
  */
@@ -7156,11 +7039,9 @@ const analyzeLogger = createScopedLogger('project-scope:analyze');
7156
7039
  * @returns Detected workspace type
7157
7040
  */
7158
7041
  function detectWorkspaceType(projectPath) {
7159
- // Check for NX
7160
7042
  if (isNxWorkspace(projectPath) || findNxWorkspaceRoot(projectPath) !== null) {
7161
7043
  return 'nx';
7162
7044
  }
7163
- // Check for common monorepo markers
7164
7045
  if (exists(resolve(projectPath, 'turbo.json'))) {
7165
7046
  return 'turborepo';
7166
7047
  }
@@ -7170,7 +7051,6 @@ function detectWorkspaceType(projectPath) {
7170
7051
  if (exists(resolve(projectPath, 'pnpm-workspace.yaml'))) {
7171
7052
  return 'pnpm';
7172
7053
  }
7173
- // Check package.json for workspaces
7174
7054
  const pkg = readPackageJsonIfExists(projectPath);
7175
7055
  if (pkg?.workspaces) {
7176
7056
  if (exists(resolve(projectPath, 'yarn.lock'))) {
@@ -7179,7 +7059,7 @@ function detectWorkspaceType(projectPath) {
7179
7059
  if (exists(resolve(projectPath, 'package-lock.json'))) {
7180
7060
  return 'npm';
7181
7061
  }
7182
- return 'npm'; // default for workspaces
7062
+ return 'npm';
7183
7063
  }
7184
7064
  return 'standalone';
7185
7065
  }
@@ -7191,15 +7071,12 @@ function detectWorkspaceType(projectPath) {
7191
7071
  * @returns True if the analysis should be performed
7192
7072
  */
7193
7073
  function shouldInclude(type, options) {
7194
- // If include list is specified, item must be in it
7195
7074
  if (options?.include && options.include.length > 0) {
7196
7075
  return options.include.includes(type);
7197
7076
  }
7198
- // If exclude list is specified, item must not be in it
7199
7077
  if (options?.exclude && options.exclude.length > 0) {
7200
7078
  return !options.exclude.includes(type);
7201
7079
  }
7202
- // Default: include everything
7203
7080
  return true;
7204
7081
  }
7205
7082
  /**
@@ -7248,28 +7125,22 @@ function normalizeConfigFormat(format) {
7248
7125
  function analyzeProject(projectPath, options) {
7249
7126
  const startTime = dateNow();
7250
7127
  const resolvedPath = resolve(projectPath);
7251
- // Set log level based on verbose option
7252
7128
  if (options?.verbose) {
7253
7129
  analyzeLogger.setLogLevel('debug');
7254
7130
  }
7255
7131
  analyzeLogger.debug('Starting project analysis', { path: resolvedPath, options });
7256
- // Read package.json once for reuse
7257
7132
  const packageJson = readPackageJsonIfExists(resolvedPath);
7258
7133
  analyzeLogger.debug('Package.json loaded', { found: !!packageJson, name: packageJson?.name });
7259
- // Detect project and workspace types
7260
7134
  const projectTypeDetection = detectProjectType(resolvedPath, {
7261
7135
  skipTechDetection: options?.depth === 'basic',
7262
7136
  });
7263
7137
  const projectType = projectTypeDetection.type;
7264
7138
  const workspaceType = detectWorkspaceType(resolvedPath);
7265
7139
  analyzeLogger.debug('Project type detected', { projectType, workspaceType });
7266
- // Get project name from package.json or directory name
7267
7140
  const projectName = packageJson?.name ?? basename(resolvedPath);
7268
- // Run all technology detectors
7269
7141
  const detections = shouldInclude('frameworks', options) || shouldInclude('buildTools', options) || shouldInclude('testing', options)
7270
7142
  ? detectAll(resolvedPath, packageJson ?? undefined)
7271
7143
  : null;
7272
- // Convert framework detections to FrameworkInfo
7273
7144
  const frameworks = shouldInclude('frameworks', options) && detections
7274
7145
  ? [
7275
7146
  ...detections.frontendFrameworks.map((d) => ({
@@ -7289,7 +7160,6 @@ function analyzeProject(projectPath, options) {
7289
7160
  })),
7290
7161
  ]
7291
7162
  : [];
7292
- // Convert build tool detections
7293
7163
  const buildTools = shouldInclude('buildTools', options) && detections
7294
7164
  ? detections.buildTools.map((d) => ({
7295
7165
  id: d.id,
@@ -7299,7 +7169,6 @@ function analyzeProject(projectPath, options) {
7299
7169
  confidence: d.confidence,
7300
7170
  }))
7301
7171
  : [];
7302
- // Convert testing framework detections
7303
7172
  const testingFrameworks = shouldInclude('testing', options) && detections
7304
7173
  ? detections.testingFrameworks.map((d) => ({
7305
7174
  id: d.id,
@@ -7310,7 +7179,6 @@ function analyzeProject(projectPath, options) {
7310
7179
  confidence: d.confidence,
7311
7180
  }))
7312
7181
  : [];
7313
- // Discover entry points
7314
7182
  const entryPoints = shouldInclude('entryPoints', options)
7315
7183
  ? discoverEntryPoints(resolvedPath).map((e) => ({
7316
7184
  path: e.path,
@@ -7318,7 +7186,6 @@ function analyzeProject(projectPath, options) {
7318
7186
  confidence: e.confidence,
7319
7187
  }))
7320
7188
  : [];
7321
- // Detect configuration files
7322
7189
  const configFiles = shouldInclude('configs', options)
7323
7190
  ? detectConfigs(resolvedPath).map((c) => ({
7324
7191
  path: c.path,
@@ -7327,7 +7194,6 @@ function analyzeProject(projectPath, options) {
7327
7194
  tool: c.type,
7328
7195
  }))
7329
7196
  : [];
7330
- // Get dependency summary
7331
7197
  let dependencies;
7332
7198
  if (shouldInclude('dependencies', options)) {
7333
7199
  const deps = getProjectDependencies(resolvedPath);
@@ -7348,7 +7214,6 @@ function analyzeProject(projectPath, options) {
7348
7214
  total: 0,
7349
7215
  };
7350
7216
  }
7351
- // Build metadata
7352
7217
  const metadata = {
7353
7218
  timestamp: createDate(),
7354
7219
  durationMs: dateNow() - startTime,
@@ -7423,15 +7288,12 @@ function formatWorkspaceType(type) {
7423
7288
  */
7424
7289
  function formatAnalysisText(result) {
7425
7290
  const lines = [];
7426
- // Header
7427
7291
  lines.push(`Project Analysis: ${result.name}`);
7428
7292
  lines.push('='.repeat(30));
7429
7293
  lines.push('');
7430
- // Basic info
7431
7294
  lines.push(`Type: ${formatProjectType(result.projectType)}`);
7432
7295
  lines.push(`Workspace: ${formatWorkspaceType(result.workspaceType)}`);
7433
7296
  lines.push('');
7434
- // Frameworks
7435
7297
  if (result.frameworks.length > 0) {
7436
7298
  lines.push('Frameworks:');
7437
7299
  for (const framework of result.frameworks) {
@@ -7445,7 +7307,6 @@ function formatAnalysisText(result) {
7445
7307
  }
7446
7308
  lines.push('');
7447
7309
  }
7448
- // Build tools
7449
7310
  if (result.buildTools.length > 0) {
7450
7311
  lines.push('Build Tools:');
7451
7312
  for (const tool of result.buildTools) {
@@ -7454,7 +7315,6 @@ function formatAnalysisText(result) {
7454
7315
  }
7455
7316
  lines.push('');
7456
7317
  }
7457
- // Testing
7458
7318
  if (result.testingFrameworks.length > 0) {
7459
7319
  lines.push('Testing:');
7460
7320
  for (const framework of result.testingFrameworks) {
@@ -7463,7 +7323,6 @@ function formatAnalysisText(result) {
7463
7323
  }
7464
7324
  lines.push('');
7465
7325
  }
7466
- // Entry points
7467
7326
  if (result.entryPoints.length > 0) {
7468
7327
  lines.push('Entry Points:');
7469
7328
  for (const entry of result.entryPoints.slice(0, 5)) {
@@ -7474,7 +7333,6 @@ function formatAnalysisText(result) {
7474
7333
  }
7475
7334
  lines.push('');
7476
7335
  }
7477
- // Configurations
7478
7336
  if (result.configFiles.length > 0) {
7479
7337
  lines.push('Configurations:');
7480
7338
  for (const config of result.configFiles.slice(0, 8)) {
@@ -7485,7 +7343,6 @@ function formatAnalysisText(result) {
7485
7343
  }
7486
7344
  lines.push('');
7487
7345
  }
7488
- // Dependencies summary
7489
7346
  lines.push('Dependencies:');
7490
7347
  lines.push(` Production: ${result.dependencies.production}`);
7491
7348
  lines.push(` Development: ${result.dependencies.development}`);
@@ -7572,7 +7429,7 @@ function parseAnalyzeArgs(args) {
7572
7429
  exclude: { type: 'string', short: 'e' },
7573
7430
  },
7574
7431
  allowPositionals: true,
7575
- strict: false, // Allow global options to pass through
7432
+ strict: false,
7576
7433
  });
7577
7434
  const format = values.format;
7578
7435
  const depth = values.depth;
@@ -7631,7 +7488,6 @@ const analyzeCommandDef = {
7631
7488
  description: 'Analyze project structure and tech stack',
7632
7489
  execute(args, globalOptions) {
7633
7490
  const options = parseAnalyzeArgs(args);
7634
- // Global --json flag overrides format
7635
7491
  if (globalOptions.json) {
7636
7492
  options.format = 'json';
7637
7493
  }
@@ -7938,12 +7794,10 @@ Examples:
7938
7794
  function formatDependencyList(deps, maxItems = 20) {
7939
7795
  const lines = [];
7940
7796
  const depEntries = entries(deps);
7941
- // Sort alphabetically
7942
7797
  depEntries.sort((a, b) => a[0].localeCompare(b[0]));
7943
7798
  const displayCount = min(depEntries.length, maxItems);
7944
7799
  for (let i = 0; i < displayCount; i++) {
7945
7800
  const [name, version] = depEntries[i];
7946
- // Pad name to align versions
7947
7801
  const paddedName = name.padEnd(30);
7948
7802
  lines.push(` ${paddedName} ${version}`);
7949
7803
  }
@@ -8186,13 +8040,10 @@ function buildTree(rootPath, walkEntries, options) {
8186
8040
  isDirectory: true,
8187
8041
  children: [],
8188
8042
  };
8189
- // Create a map for quick lookup
8190
8043
  const nodeMap = createMap();
8191
8044
  nodeMap.set('.', root);
8192
- // Sort entries by path for proper parent-child relationships
8193
8045
  const sortedEntries = [...walkEntries].sort((a, b) => a.relativePath.localeCompare(b.relativePath));
8194
8046
  for (const entry of sortedEntries) {
8195
- // Skip based on filters
8196
8047
  if (options.dirsOnly && !entry.isDirectory)
8197
8048
  continue;
8198
8049
  if (options.filesOnly && entry.isDirectory)
@@ -8203,7 +8054,6 @@ function buildTree(rootPath, walkEntries, options) {
8203
8054
  isDirectory: entry.isDirectory,
8204
8055
  children: [],
8205
8056
  };
8206
- // Add size/modified if requested
8207
8057
  if ((options.showSize || options.showModified) && entry.isFile) {
8208
8058
  const stats = getFileStat(entry.path);
8209
8059
  if (stats) {
@@ -8213,9 +8063,8 @@ function buildTree(rootPath, walkEntries, options) {
8213
8063
  node.modified = stats.modified;
8214
8064
  }
8215
8065
  }
8216
- // Find parent
8217
8066
  const parts = entry.relativePath.split('/');
8218
- parts.pop(); // Remove current entry name
8067
+ parts.pop();
8219
8068
  const parentPath = parts.join('/') || '.';
8220
8069
  const parent = nodeMap.get(parentPath);
8221
8070
  if (parent) {
@@ -8236,11 +8085,9 @@ function buildTree(rootPath, walkEntries, options) {
8236
8085
  */
8237
8086
  function renderTreeText(node, options, prefix = '', isLast = true) {
8238
8087
  const lines = [];
8239
- // Current line
8240
8088
  const connector = isLast ? '└── ' : '├── ';
8241
8089
  const dirMark = node.isDirectory ? '/' : '';
8242
8090
  let line = `${prefix}${connector}${node.name}${dirMark}`;
8243
- // Add size/modified
8244
8091
  const meta = [];
8245
8092
  if (options.showSize && node.size !== undefined) {
8246
8093
  meta.push(formatSize(node.size));
@@ -8252,10 +8099,8 @@ function renderTreeText(node, options, prefix = '', isLast = true) {
8252
8099
  line += ` [${meta.join(' ')}]`;
8253
8100
  }
8254
8101
  lines.push(line);
8255
- // Process children
8256
8102
  const childPrefix = prefix + (isLast ? ' ' : '│ ');
8257
8103
  const sortedChildren = [...node.children].sort((a, b) => {
8258
- // Directories first, then alphabetically
8259
8104
  if (a.isDirectory && !b.isDirectory)
8260
8105
  return -1;
8261
8106
  if (!a.isDirectory && b.isDirectory)
@@ -8279,9 +8124,7 @@ function renderTreeText(node, options, prefix = '', isLast = true) {
8279
8124
  */
8280
8125
  function formatTreeText(rootPath, tree, options) {
8281
8126
  const lines = [];
8282
- // Root line
8283
8127
  lines.push(basename(rootPath));
8284
- // Count stats
8285
8128
  let dirCount = 0;
8286
8129
  let fileCount = 0;
8287
8130
  /**
@@ -8300,7 +8143,6 @@ function formatTreeText(rootPath, tree, options) {
8300
8143
  countNodes(child);
8301
8144
  }
8302
8145
  }
8303
- // Render children of root
8304
8146
  const sortedChildren = [...tree.children].sort((a, b) => {
8305
8147
  if (a.isDirectory && !b.isDirectory)
8306
8148
  return -1;
@@ -8314,7 +8156,6 @@ function formatTreeText(rootPath, tree, options) {
8314
8156
  lines.push(...renderTreeText(child, options, '', isLast));
8315
8157
  countNodes(child);
8316
8158
  }
8317
- // Summary
8318
8159
  lines.push('');
8319
8160
  const dirText = dirCount === 1 ? '1 directory' : `${dirCount} directories`;
8320
8161
  const fileText = fileCount === 1 ? '1 file' : `${fileCount} files`;
@@ -8375,7 +8216,6 @@ function parseTreeArgs(args) {
8375
8216
  function treeCommand(options) {
8376
8217
  const rootPath = options.path ? resolve(options.path) : process.cwd();
8377
8218
  try {
8378
- // Collect entries via walk
8379
8219
  const walkEntries = [];
8380
8220
  walkDirectory(rootPath, (entry) => {
8381
8221
  walkEntries.push(entry);
@@ -8385,9 +8225,7 @@ function treeCommand(options) {
8385
8225
  ignorePatterns: options.ignore,
8386
8226
  includeHidden: false,
8387
8227
  });
8388
- // Build tree structure
8389
8228
  const tree = buildTree(rootPath, walkEntries, options);
8390
- // Format output
8391
8229
  let output;
8392
8230
  if (options.format === 'json') {
8393
8231
  output = formatTreeJson(tree);
@@ -8446,6 +8284,9 @@ Examples:
8446
8284
 
8447
8285
  /** Logger for CLI operations */
8448
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');
8449
8290
  /** Library version */
8450
8291
  const VERSION = '0.1.0';
8451
8292
  /**
@@ -8461,7 +8302,7 @@ const commands = {
8461
8302
  * Print general help information.
8462
8303
  */
8463
8304
  function printHelp() {
8464
- log(`
8305
+ output.log(`
8465
8306
  project-scope <command> [options]
8466
8307
 
8467
8308
  A tool for analyzing JavaScript/TypeScript project structure and tech stack.
@@ -8493,7 +8334,7 @@ Examples:
8493
8334
  * Print CLI version.
8494
8335
  */
8495
8336
  function printVersion() {
8496
- log(`project-scope v${VERSION}`);
8337
+ output.log(`project-scope v${VERSION}`);
8497
8338
  }
8498
8339
  /**
8499
8340
  * Parse global options from command line arguments.
@@ -8554,24 +8395,24 @@ function run(args) {
8554
8395
  setGlobalLogLevel('debug');
8555
8396
  }
8556
8397
  cliLogger.debug('CLI invoked', { args, globalOptions });
8398
+ const commandName = args[0];
8557
8399
  if (globalOptions.version) {
8558
8400
  printVersion();
8559
8401
  return { exitCode: 0 };
8560
8402
  }
8561
- if (globalOptions.help && (args.length === 1 || !commands[args[0]])) {
8403
+ if (globalOptions.help && (args.length === 1 || !commands[commandName])) {
8562
8404
  printHelp();
8563
8405
  return { exitCode: 0 };
8564
8406
  }
8565
- const commandName = args[0];
8566
8407
  const command = commands[commandName];
8567
8408
  if (!command) {
8568
8409
  cliLogger.warn('Unknown command requested', { commandName });
8569
- error(`Unknown command: ${commandName}`);
8570
- error('Run "project-scope --help" for usage information.');
8410
+ output.error(`Unknown command: ${commandName}`);
8411
+ output.error('Run "project-scope --help" for usage information.');
8571
8412
  return { exitCode: 1, error: `Unknown command: ${commandName}` };
8572
8413
  }
8573
8414
  if (globalOptions.help) {
8574
- log(command.getHelp());
8415
+ output.log(command.getHelp());
8575
8416
  return { exitCode: 0 };
8576
8417
  }
8577
8418
  const commandArgs = args.slice(1);
@@ -8579,11 +8420,11 @@ function run(args) {
8579
8420
  const result = command.execute(commandArgs, globalOptions);
8580
8421
  cliLogger.debug('Command completed', { commandName, exitCode: result.exitCode });
8581
8422
  if (result.output) {
8582
- log(result.output);
8423
+ output.log(result.output);
8583
8424
  }
8584
8425
  if (result.error) {
8585
8426
  cliLogger.error('Command error', { commandName, error: result.error });
8586
- error(result.error);
8427
+ output.error(result.error);
8587
8428
  }
8588
8429
  return result;
8589
8430
  }
@@ -8619,25 +8460,22 @@ const BINARY_SIGNATURES = [
8619
8460
  */
8620
8461
  function detectEncodingInfo(buffer) {
8621
8462
  encodingLogger.debug('Detecting encoding info', { bufferSize: buffer.length });
8622
- // Check for UTF-8 BOM
8623
8463
  if (buffer.length >= 3) {
8624
8464
  if (buffer[0] === UTF8_BOM_BYTES[0] && buffer[1] === UTF8_BOM_BYTES[1] && buffer[2] === UTF8_BOM_BYTES[2]) {
8625
8465
  encodingLogger.debug('Detected UTF-8 BOM');
8626
8466
  return { type: 'text', encoding: 'utf-8', hasBom: true };
8627
8467
  }
8628
8468
  }
8629
- // Check for UTF-16 BOMs
8630
8469
  if (buffer.length >= 2) {
8631
8470
  if (buffer[0] === UTF16_BE_BOM_BYTES[0] && buffer[1] === UTF16_BE_BOM_BYTES[1]) {
8632
8471
  encodingLogger.debug('Detected UTF-16 BE BOM');
8633
- return { type: 'text', encoding: 'utf16le', hasBom: true }; // Node treats BE through utf16le
8472
+ return { type: 'text', encoding: 'utf16le', hasBom: true };
8634
8473
  }
8635
8474
  if (buffer[0] === UTF16_LE_BOM_BYTES[0] && buffer[1] === UTF16_LE_BOM_BYTES[1]) {
8636
8475
  encodingLogger.debug('Detected UTF-16 LE BOM');
8637
8476
  return { type: 'text', encoding: 'utf16le', hasBom: true };
8638
8477
  }
8639
8478
  }
8640
- // Check for binary signatures
8641
8479
  for (const { signature, description } of BINARY_SIGNATURES) {
8642
8480
  if (buffer.length >= signature.length) {
8643
8481
  let matches = true;
@@ -8653,7 +8491,6 @@ function detectEncodingInfo(buffer) {
8653
8491
  }
8654
8492
  }
8655
8493
  }
8656
- // Check for null bytes (usually indicates binary)
8657
8494
  const sampleSize = min(buffer.length, 8000);
8658
8495
  for (let i = 0; i < sampleSize; i++) {
8659
8496
  if (buffer[i] === 0) {
@@ -8672,22 +8509,18 @@ function detectEncodingInfo(buffer) {
8672
8509
  */
8673
8510
  function detectEncoding(buffer) {
8674
8511
  if (buffer.length >= 3) {
8675
- // Check for UTF-8 BOM
8676
8512
  if (buffer[0] === UTF8_BOM_BYTES[0] && buffer[1] === UTF8_BOM_BYTES[1] && buffer[2] === UTF8_BOM_BYTES[2]) {
8677
8513
  return 'utf-8';
8678
8514
  }
8679
8515
  }
8680
8516
  if (buffer.length >= 2) {
8681
- // Check for UTF-16 LE BOM
8682
8517
  if (buffer[0] === UTF16_LE_BOM_BYTES[0] && buffer[1] === UTF16_LE_BOM_BYTES[1]) {
8683
8518
  return 'utf16le';
8684
8519
  }
8685
- // Check for UTF-16 BE BOM
8686
8520
  if (buffer[0] === UTF16_BE_BOM_BYTES[0] && buffer[1] === UTF16_BE_BOM_BYTES[1]) {
8687
- return 'utf16le'; // Node.js handles BE through utf16le
8521
+ return 'utf16le';
8688
8522
  }
8689
8523
  }
8690
- // Default to UTF-8
8691
8524
  return 'utf-8';
8692
8525
  }
8693
8526
  /**
@@ -8757,10 +8590,8 @@ function bufferToString(content, encoding) {
8757
8590
  convertLogger.debug('Using provided encoding', { encoding });
8758
8591
  return content.toString(encoding);
8759
8592
  }
8760
- // Auto-detect and convert
8761
8593
  const info = detectEncodingInfo(content);
8762
8594
  if (info.type === 'text') {
8763
- // Remove BOM if present
8764
8595
  let offset = 0;
8765
8596
  if (info.hasBom) {
8766
8597
  offset = info.encoding === 'utf-8' ? 3 : 2;
@@ -8813,19 +8644,14 @@ function detectCaseSensitivity() {
8813
8644
  if (cachedCaseSensitive !== null) {
8814
8645
  return cachedCaseSensitive;
8815
8646
  }
8816
- // Quick check based on platform
8817
8647
  if (process.platform === 'win32') {
8818
8648
  cachedCaseSensitive = false;
8819
8649
  return false;
8820
8650
  }
8821
- // macOS is typically case-insensitive by default
8822
8651
  if (process.platform === 'darwin') {
8823
- // Could be case-sensitive HFS+/APFS, but assume insensitive by default
8824
8652
  cachedCaseSensitive = false;
8825
8653
  return false;
8826
8654
  }
8827
- // Test actual file system behavior for Linux and others
8828
- // Use mkdtempSync to create a secure temporary directory
8829
8655
  let secureTestDir = null;
8830
8656
  try {
8831
8657
  secureTestDir = mkdtempSync(join$1(tmpdir(), 'case-sensitivity-test-'));
@@ -8836,12 +8662,9 @@ function detectCaseSensitivity() {
8836
8662
  unlinkSync(testFile);
8837
8663
  }
8838
8664
  catch {
8839
- // Default to case-sensitive on Linux/Unix if test fails
8840
- // (win32 and darwin already returned early, so we're on a case-sensitive platform)
8841
8665
  cachedCaseSensitive = true;
8842
8666
  }
8843
8667
  finally {
8844
- // Clean up the secure temporary directory
8845
8668
  if (secureTestDir) {
8846
8669
  try {
8847
8670
  rmdirSync(secureTestDir);
@@ -8976,7 +8799,6 @@ function normalizeLineEndings(content, style = 'lf') {
8976
8799
  else {
8977
8800
  target = style === 'crlf' ? CRLF : LF;
8978
8801
  }
8979
- // First normalize all to LF, then convert to target
8980
8802
  const normalized = content.replace(/\r\n/g, LF).replace(/\r/g, LF);
8981
8803
  if (target === LF) {
8982
8804
  return normalized;
@@ -8991,7 +8813,6 @@ function normalizeLineEndings(content, style = 'lf') {
8991
8813
  */
8992
8814
  function detectLineEnding(content) {
8993
8815
  const hasCRLF = content.includes(CRLF);
8994
- // Check for LF that is NOT part of CRLF
8995
8816
  const hasLFOnly = content.includes('\n') && content.replace(/\r\n/g, '').includes('\n');
8996
8817
  if (hasCRLF && hasLFOnly)
8997
8818
  return 'mixed';
@@ -9469,7 +9290,6 @@ function createFsTree(root, options) {
9469
9290
  }
9470
9291
  const prefix = normalPath === '.' || normalPath === '' ? '' : normalPath + '/';
9471
9292
  for (const [changedPath, change] of _changes) {
9472
- // Handle root-level files
9473
9293
  if (prefix === '') {
9474
9294
  const childName = changedPath.split('/')[0];
9475
9295
  if (change.type === 'DELETE' && !changedPath.includes('/')) {
@@ -9486,7 +9306,6 @@ function createFsTree(root, options) {
9486
9306
  const relativePath = changedPath.slice(prefix.length);
9487
9307
  const childName = relativePath.split('/')[0];
9488
9308
  if (change.type === 'DELETE') {
9489
- // Only remove if it's a direct child being deleted
9490
9309
  if (!relativePath.includes('/')) {
9491
9310
  childSet.delete(childName);
9492
9311
  }
@@ -9572,12 +9391,10 @@ const factoryLogger = createScopedLogger('project-scope:vfs:factory');
9572
9391
  function createTree(root, options) {
9573
9392
  const normalizedRoot = normalizePath(root);
9574
9393
  factoryLogger.debug('createTree', { root: normalizedRoot });
9575
- // Validate root exists
9576
9394
  if (!exists(normalizedRoot)) {
9577
9395
  factoryLogger.warn('createTree failed: root does not exist', { root: normalizedRoot });
9578
9396
  throw createError(`Root directory does not exist: ${normalizedRoot}`);
9579
9397
  }
9580
- // Validate root is a directory
9581
9398
  if (!isDirectory(normalizedRoot)) {
9582
9399
  factoryLogger.warn('createTree failed: root is not a directory', { root: normalizedRoot });
9583
9400
  throw createError(`Root path is not a directory: ${normalizedRoot}`);
@@ -9624,7 +9441,6 @@ const vfsLogger = createScopedLogger('project-scope:vfs');
9624
9441
  function commitChanges(tree, options) {
9625
9442
  const changes = tree.listChanges();
9626
9443
  const appliedChanges = [];
9627
- // Set log level based on verbose option
9628
9444
  if (options?.verbose) {
9629
9445
  vfsLogger.setLogLevel('debug');
9630
9446
  }
@@ -9636,7 +9452,6 @@ function commitChanges(tree, options) {
9636
9452
  changes: [],
9637
9453
  dryRun: options?.dryRun ?? false,
9638
9454
  };
9639
- // Dry run - just count changes without writing
9640
9455
  if (options?.dryRun) {
9641
9456
  for (const change of changes) {
9642
9457
  switch (change.type) {
@@ -9654,7 +9469,6 @@ function commitChanges(tree, options) {
9654
9469
  result.changes = changes;
9655
9470
  return result;
9656
9471
  }
9657
- // Sort changes: deletes first (to free names), then creates, then updates
9658
9472
  const sortedChanges = [...changes].sort((a, b) => {
9659
9473
  const order = { DELETE: 0, CREATE: 1, UPDATE: 2 };
9660
9474
  return order[a.type] - order[b.type];
@@ -9667,11 +9481,9 @@ function commitChanges(tree, options) {
9667
9481
  case 'UPDATE':
9668
9482
  /* istanbul ignore if -- content is always defined for CREATE/UPDATE from tree.write() */
9669
9483
  if (change.content !== undefined) {
9670
- // Ensure directory exists
9671
9484
  const dir = dirname(absPath);
9672
9485
  ensureDir(dir);
9673
9486
  writeFileSync(absPath, change.content);
9674
- // Apply permissions if specified
9675
9487
  if (change.mode !== undefined) {
9676
9488
  chmodSync(absPath, change.mode);
9677
9489
  }
@@ -9689,7 +9501,6 @@ function commitChanges(tree, options) {
9689
9501
  unlinkSync(absPath);
9690
9502
  }
9691
9503
  catch {
9692
- // Try recursive delete for directories
9693
9504
  rmSync(absPath, { recursive: true });
9694
9505
  }
9695
9506
  }
@@ -9703,7 +9514,6 @@ function commitChanges(tree, options) {
9703
9514
  }
9704
9515
  }
9705
9516
  catch (error) {
9706
- // On error, throw with context
9707
9517
  vfsLogger.error('Commit failed', { path: change.path, type: change.type });
9708
9518
  const message = error instanceof Error
9709
9519
  ? `Failed to ${change.type.toLowerCase()} ${change.path}: ${error.message}`
@@ -9712,7 +9522,6 @@ function commitChanges(tree, options) {
9712
9522
  }
9713
9523
  }
9714
9524
  result.changes = appliedChanges;
9715
- // Clear the tree's pending changes after successful commit
9716
9525
  tree.clearChanges();
9717
9526
  return result;
9718
9527
  }
@@ -9788,18 +9597,14 @@ function backtrackLcs(table, oldLines, newLines) {
9788
9597
  * @returns Filtered DiffLine array
9789
9598
  */
9790
9599
  function operationsToDiffLines(operations, contextLines) {
9791
- // Mark which lines should be included (changes + context)
9792
9600
  const include = new Array(operations.length).fill(false);
9793
- // First pass: mark all changes
9794
9601
  for (let i = 0; i < operations.length; i++) {
9795
9602
  if (operations[i].type !== 'same') {
9796
- // Mark this line and context around it
9797
9603
  for (let j = max(0, i - contextLines); j <= min(operations.length - 1, i + contextLines); j++) {
9798
9604
  include[j] = true;
9799
9605
  }
9800
9606
  }
9801
9607
  }
9802
- // Second pass: convert to DiffLine
9803
9608
  const lines = [];
9804
9609
  let oldLineNum = 1;
9805
9610
  let newLineNum = 1;
@@ -9821,7 +9626,6 @@ function operationsToDiffLines(operations, contextLines) {
9821
9626
  }
9822
9627
  }
9823
9628
  else {
9824
- // Skip but update line numbers
9825
9629
  if (op.type === 'same') {
9826
9630
  oldLineNum++;
9827
9631
  newLineNum++;
@@ -9848,7 +9652,6 @@ function bufferToLines(content) {
9848
9652
  if (!content)
9849
9653
  return [];
9850
9654
  const text = content.toString('utf-8');
9851
- // Split by newline, keeping empty last line if present
9852
9655
  return text.split('\n');
9853
9656
  }
9854
9657
  /**
@@ -9875,9 +9678,7 @@ function generateDiff(change, options = {}) {
9875
9678
  diffLogger.debug('generateDiff', { path: change.path, type: change.type, contextLines });
9876
9679
  const oldLines = bufferToLines(change.originalContent);
9877
9680
  const newLines = bufferToLines(change.content);
9878
- // Handle edge cases
9879
9681
  if (change.type === 'CREATE') {
9880
- // All lines are additions
9881
9682
  const lines = newLines
9882
9683
  .filter((line) => line !== '' || newLines.indexOf(line) !== newLines.length - 1 || newLines.length === 1)
9883
9684
  .map((content, idx) => ({
@@ -9885,7 +9686,6 @@ function generateDiff(change, options = {}) {
9885
9686
  line: idx + 1,
9886
9687
  content,
9887
9688
  }));
9888
- // Filter out empty trailing line from split
9889
9689
  const filteredLines = lines.filter((l, i) => !(i === lines.length - 1 && l.content === '' && lines.length > 1));
9890
9690
  return {
9891
9691
  path: change.path,
@@ -9895,13 +9695,11 @@ function generateDiff(change, options = {}) {
9895
9695
  };
9896
9696
  }
9897
9697
  if (change.type === 'DELETE') {
9898
- // All lines are deletions
9899
9698
  const lines = oldLines.map((content, idx) => ({
9900
9699
  type: 'remove',
9901
9700
  line: idx + 1,
9902
9701
  content,
9903
9702
  }));
9904
- // Filter out empty trailing line from split
9905
9703
  const filteredLines = lines.filter((l, i) => !(i === lines.length - 1 && l.content === '' && lines.length > 1));
9906
9704
  return {
9907
9705
  path: change.path,
@@ -9910,7 +9708,6 @@ function generateDiff(change, options = {}) {
9910
9708
  deletions: filteredLines.length,
9911
9709
  };
9912
9710
  }
9913
- // UPDATE: compute actual diff
9914
9711
  const table = computeLcsTable(oldLines, newLines);
9915
9712
  const operations = backtrackLcs(table, oldLines, newLines);
9916
9713
  const lines = operationsToDiffLines(operations, contextLines);
@@ -9944,13 +9741,11 @@ function generateDiff(change, options = {}) {
9944
9741
  */
9945
9742
  function formatUnifiedDiff(diff) {
9946
9743
  const lines = [];
9947
- // Header
9948
9744
  lines.push(`--- a/${diff.path}`);
9949
9745
  lines.push(`+++ b/${diff.path}`);
9950
9746
  if (diff.lines.length === 0) {
9951
9747
  return lines.join('\n');
9952
9748
  }
9953
- // Group lines into hunks
9954
9749
  const hunks = [];
9955
9750
  const currentHunk = [];
9956
9751
  for (const line of diff.lines) {
@@ -9959,9 +9754,7 @@ function formatUnifiedDiff(diff) {
9959
9754
  if (currentHunk.length > 0) {
9960
9755
  hunks.push(currentHunk);
9961
9756
  }
9962
- // Output hunks
9963
9757
  for (const hunk of hunks) {
9964
- // Calculate hunk header
9965
9758
  const contextAndRemove = hunk.filter((l) => l.type === 'context' || l.type === 'remove');
9966
9759
  const contextAndAdd = hunk.filter((l) => l.type === 'context' || l.type === 'add');
9967
9760
  const oldStart = hunk.find((l) => l.type === 'context' || l.type === 'remove')?.line ?? 1;
@@ -9969,7 +9762,6 @@ function formatUnifiedDiff(diff) {
9969
9762
  const oldCount = contextAndRemove.length;
9970
9763
  const newCount = contextAndAdd.length;
9971
9764
  lines.push(`@@ -${oldStart},${oldCount} +${newStart},${newCount} @@`);
9972
- // Output lines
9973
9765
  for (const line of hunk) {
9974
9766
  const prefix = line.type === 'add' ? '+' : line.type === 'remove' ? '-' : ' ';
9975
9767
  lines.push(`${prefix}${line.content}`);