ctxman 3.0.0 → 3.1.0

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 (223) hide show
  1. package/.ctxman/llm-profiles.json +1067 -39
  2. package/.ctxman/profiles/backend-team.json +13 -0
  3. package/.ctxman/profiles/default.json +12 -0
  4. package/.ctxman/profiles/devops-team.json +21 -0
  5. package/.ctxman/profiles/frontend-team.json +20 -0
  6. package/.ctxman/templates/bug-fix.json +17 -0
  7. package/.ctxman/templates/code-review.json +9 -0
  8. package/.ctxman/templates/documentation.json +9 -0
  9. package/.ctxman/templates/feature.json +9 -0
  10. package/.ctxman/templates/full-context.json +9 -0
  11. package/.ctxman/templates/refactoring.json +18 -0
  12. package/.ctxman/templates/testing.json +9 -0
  13. package/.ctxman/wizard-profiles/security-audit/profile.json +7 -1
  14. package/LICENSE +21 -0
  15. package/README.md +380 -136
  16. package/bin/cli.js +1880 -529
  17. package/bin/cm-ask.js +18 -13
  18. package/bin/cm-audit.js +124 -0
  19. package/bin/cm-count.js +151 -0
  20. package/bin/cm-gitingest.js +128 -115
  21. package/bin/cm-map.js +242 -0
  22. package/bin/cm-mcp.js +293 -0
  23. package/bin/cm-pack.js +405 -0
  24. package/bin/cm-policy.js +170 -0
  25. package/bin/cm-pr.js +248 -0
  26. package/bin/cm-search.js +178 -0
  27. package/bin/cm-update.js +128 -128
  28. package/bin/cm-why.js +197 -0
  29. package/bin/mcp-server.js +8 -24
  30. package/ctxman.js +237 -183
  31. package/examples/README.md +19 -4
  32. package/examples/github-actions/ctxman-check.yml +49 -0
  33. package/examples/wizard-profiles/security-audit/profile.json +7 -1
  34. package/index.d.ts +1422 -0
  35. package/index.js +55 -31
  36. package/lib/agents/pr-failure-agent.js +48 -45
  37. package/lib/analyzers/ai-suggester.js +451 -0
  38. package/lib/analyzers/dependency-scanner.js +505 -0
  39. package/lib/analyzers/duplicate-detector.js +264 -0
  40. package/lib/analyzers/go-method-analyzer.js +151 -110
  41. package/lib/analyzers/import-tracker.js +431 -0
  42. package/lib/analyzers/method-analyzer.js +1055 -434
  43. package/lib/analyzers/performance-dashboard.js +683 -0
  44. package/lib/analyzers/token-calculator.js +895 -504
  45. package/lib/analyzers/unused-exports-detector.js +336 -0
  46. package/lib/api/mcp/install.js +424 -0
  47. package/lib/api/mcp/prompts.js +281 -281
  48. package/lib/api/mcp/resources.js +300 -241
  49. package/lib/api/mcp/sandbox.js +89 -0
  50. package/lib/api/mcp/server.js +675 -312
  51. package/lib/api/mcp/start.js +125 -0
  52. package/lib/api/mcp/transports/HttpSseTransport.js +60 -52
  53. package/lib/api/rest/server.js +105 -28
  54. package/lib/cache/CacheManager.js +9 -17
  55. package/lib/core/Analyzer.js +16 -10
  56. package/lib/core/ContextBuilder.js +19 -28
  57. package/lib/core/Reporter.js +15 -14
  58. package/lib/core/Scanner.js +81 -12
  59. package/lib/count/index.js +100 -0
  60. package/lib/formatters/format-registry.js +394 -372
  61. package/lib/formatters/gitingest-formatter.js +656 -621
  62. package/lib/formatters/toon-formatter-v1.3.js +637 -632
  63. package/lib/formatters/toon-formatter.js +239 -240
  64. package/lib/integrations/git/BlameTracker.js +4 -4
  65. package/lib/integrations/git/DiffAnalyzer.js +38 -31
  66. package/lib/integrations/git/GitClient.js +71 -53
  67. package/lib/lsp/server.js +311 -0
  68. package/lib/map/graph.js +299 -0
  69. package/lib/map/imports.js +233 -0
  70. package/lib/map/index.js +33 -0
  71. package/lib/map/rank.js +253 -0
  72. package/lib/map/render.js +346 -0
  73. package/lib/map/resolver.js +574 -0
  74. package/lib/pack/budget.js +111 -0
  75. package/lib/pack/code-lexer.js +360 -0
  76. package/lib/pack/compress.js +414 -0
  77. package/lib/pack/index.js +354 -0
  78. package/lib/pack/renderers/common.js +134 -0
  79. package/lib/pack/renderers/index.js +24 -0
  80. package/lib/pack/renderers/json.js +47 -0
  81. package/lib/pack/renderers/markdown.js +54 -0
  82. package/lib/pack/renderers/plain.js +38 -0
  83. package/lib/pack/renderers/xml.js +85 -0
  84. package/lib/pack/select.js +177 -0
  85. package/lib/pack/split.js +182 -0
  86. package/lib/pack/tree.js +35 -0
  87. package/lib/parsers/gitignore-parser.js +113 -107
  88. package/lib/parsers/method-filter-parser.js +50 -33
  89. package/lib/plugins/ExporterPlugin.js +3 -3
  90. package/lib/plugins/LanguagePlugin.js +3 -3
  91. package/lib/plugins/PluginManager.js +7 -13
  92. package/lib/policy/audit.js +294 -0
  93. package/lib/policy/enforce.js +125 -0
  94. package/lib/policy/ignore-files.js +169 -0
  95. package/lib/policy/index.js +52 -0
  96. package/lib/policy/policy.js +489 -0
  97. package/lib/pr/diff-parser.js +267 -0
  98. package/lib/pr/method-mapper.js +148 -0
  99. package/lib/pr/pr-context.js +829 -0
  100. package/lib/pr/pr-git.js +149 -0
  101. package/lib/pr/pr-renderer.js +644 -0
  102. package/lib/pr/related-tests.js +296 -0
  103. package/lib/rag/DataSourcePlugin.js +5 -5
  104. package/lib/rag/EmbeddingProvider.js +19 -8
  105. package/lib/rag/Indexer.js +14 -5
  106. package/lib/rag/VectorStoreFactory.js +3 -3
  107. package/lib/rag/sources/FileDataSource.js +6 -6
  108. package/lib/rag/stores/BaseVectorStore.js +5 -5
  109. package/lib/rag/stores/ChromaDBStore.js +10 -10
  110. package/lib/rag/stores/LanceDBStore.js +29 -19
  111. package/lib/rag/stores/PineconeStore.js +11 -9
  112. package/lib/search/bm25.js +433 -0
  113. package/lib/search/code-units.js +201 -0
  114. package/lib/search/symbols.js +189 -0
  115. package/lib/ui/dashboard.js +120 -89
  116. package/lib/ui/index.js +1 -6
  117. package/lib/ui/init-wizard-ui.js +550 -0
  118. package/lib/ui/progress-bar.js +94 -88
  119. package/lib/ui/select-input.js +40 -32
  120. package/lib/ui/template-selector.js +99 -0
  121. package/lib/ui/wizard.js +374 -329
  122. package/lib/utils/claude-token-counter.js +151 -0
  123. package/lib/utils/cli-output.js +17 -0
  124. package/lib/utils/clipboard-utils.js +49 -50
  125. package/lib/utils/config-utils.js +81 -66
  126. package/lib/utils/context-versioning.js +532 -0
  127. package/lib/utils/error-handler.js +112 -112
  128. package/lib/utils/file-utils.js +153 -69
  129. package/lib/utils/format-converter.js +356 -283
  130. package/lib/utils/git-utils.js +366 -294
  131. package/lib/utils/llm-cost-estimator.js +281 -0
  132. package/lib/utils/llm-detector.js +242 -78
  133. package/lib/utils/logger.js +243 -237
  134. package/lib/utils/messages.js +355 -0
  135. package/lib/utils/multi-repo-manager.js +364 -0
  136. package/lib/utils/profile-manager.js +380 -0
  137. package/lib/utils/safe-read.js +61 -0
  138. package/lib/utils/secret-redactor.js +61 -0
  139. package/lib/utils/snapshot-manager.js +679 -0
  140. package/lib/utils/template-manager.js +378 -0
  141. package/lib/utils/token-utils.js +201 -170
  142. package/lib/utils/tokenizer-adapter.js +421 -377
  143. package/lib/utils/updater.js +539 -506
  144. package/lib/watch/FileWatcher.js +12 -8
  145. package/lib/watch/IncrementalAnalyzer.js +7 -7
  146. package/lib/why/coupling.js +81 -0
  147. package/lib/why/errors.js +23 -0
  148. package/lib/why/git.js +169 -0
  149. package/lib/why/history.js +108 -0
  150. package/lib/why/index.js +720 -0
  151. package/lib/why/ownership.js +79 -0
  152. package/lib/why/paths.js +207 -0
  153. package/lib/why/references.js +259 -0
  154. package/lib/why/render.js +722 -0
  155. package/lib/why/search.js +358 -0
  156. package/lib/why/target.js +389 -0
  157. package/lib/wizards/config-generator.js +255 -0
  158. package/lib/wizards/project-detector.js +571 -0
  159. package/package.json +44 -10
  160. package/.ctxman/logs/ctxman-2026-07-02.log +0 -156
  161. package/.ctxman/self-knowledge/documents.lance/_transactions/0-b3dd1558-f95b-44b9-a154-18e8c330ae7a.txn +0 -0
  162. package/.ctxman/self-knowledge/documents.lance/_transactions/1-44bff6d4-e552-4010-a769-e09ad822698d.txn +0 -0
  163. package/.ctxman/self-knowledge/documents.lance/_transactions/10-37260794-5947-4ecb-b4c8-d732f58f40f1.txn +0 -0
  164. package/.ctxman/self-knowledge/documents.lance/_transactions/11-0d52a883-5518-463c-9b6d-986c3af89dfd.txn +0 -0
  165. package/.ctxman/self-knowledge/documents.lance/_transactions/12-ae354646-52f2-49c9-90fb-2e6ed8932663.txn +0 -0
  166. package/.ctxman/self-knowledge/documents.lance/_transactions/13-5d7d5e57-27da-498b-b734-091b9d38da13.txn +0 -0
  167. package/.ctxman/self-knowledge/documents.lance/_transactions/14-558ce928-810b-4252-a364-fdd1ee6f8a25.txn +0 -0
  168. package/.ctxman/self-knowledge/documents.lance/_transactions/15-27e3a58f-8b64-43b8-a9f4-e3369446968b.txn +0 -0
  169. package/.ctxman/self-knowledge/documents.lance/_transactions/16-a110c390-e5c8-4464-b976-bc7f5195fa15.txn +0 -0
  170. package/.ctxman/self-knowledge/documents.lance/_transactions/17-97513225-53de-4216-b61a-6337bd0cc4ad.txn +0 -0
  171. package/.ctxman/self-knowledge/documents.lance/_transactions/18-5ba450f0-c527-459c-bf4f-6d79b5e4285f.txn +0 -0
  172. package/.ctxman/self-knowledge/documents.lance/_transactions/19-600d3a63-5225-49e9-b8b7-30e037b564ab.txn +0 -0
  173. package/.ctxman/self-knowledge/documents.lance/_transactions/2-48a61b4e-92cf-4342-8376-914c1b99a9f9.txn +0 -0
  174. package/.ctxman/self-knowledge/documents.lance/_transactions/20-121dd912-5410-410e-84ff-82c7a23604a0.txn +0 -0
  175. package/.ctxman/self-knowledge/documents.lance/_transactions/3-bc6844ab-7ea3-4bb3-9ee1-538ce5664a9c.txn +0 -0
  176. package/.ctxman/self-knowledge/documents.lance/_transactions/4-d5e1754f-dbb5-454e-a9be-2a7da8d7bcf9.txn +0 -0
  177. package/.ctxman/self-knowledge/documents.lance/_transactions/5-4fae93df-0568-4b6f-8acf-8bef4bba6047.txn +0 -0
  178. package/.ctxman/self-knowledge/documents.lance/_transactions/6-4b1b8b87-068e-49a3-ba2b-d8dae628b252.txn +0 -0
  179. package/.ctxman/self-knowledge/documents.lance/_transactions/7-63d040fb-d2fa-4c0f-bed0-53411918a7b0.txn +0 -0
  180. package/.ctxman/self-knowledge/documents.lance/_transactions/8-c5628cf6-264c-420f-aab8-bc96a9083591.txn +0 -0
  181. package/.ctxman/self-knowledge/documents.lance/_transactions/9-c94570aa-5e53-4fcb-971f-cecc1d1d5492.txn +0 -0
  182. package/.ctxman/self-knowledge/documents.lance/_versions/1.manifest +0 -0
  183. package/.ctxman/self-knowledge/documents.lance/_versions/10.manifest +0 -0
  184. package/.ctxman/self-knowledge/documents.lance/_versions/11.manifest +0 -0
  185. package/.ctxman/self-knowledge/documents.lance/_versions/12.manifest +0 -0
  186. package/.ctxman/self-knowledge/documents.lance/_versions/13.manifest +0 -0
  187. package/.ctxman/self-knowledge/documents.lance/_versions/14.manifest +0 -0
  188. package/.ctxman/self-knowledge/documents.lance/_versions/15.manifest +0 -0
  189. package/.ctxman/self-knowledge/documents.lance/_versions/16.manifest +0 -0
  190. package/.ctxman/self-knowledge/documents.lance/_versions/17.manifest +0 -0
  191. package/.ctxman/self-knowledge/documents.lance/_versions/18.manifest +0 -0
  192. package/.ctxman/self-knowledge/documents.lance/_versions/19.manifest +0 -0
  193. package/.ctxman/self-knowledge/documents.lance/_versions/2.manifest +0 -0
  194. package/.ctxman/self-knowledge/documents.lance/_versions/20.manifest +0 -0
  195. package/.ctxman/self-knowledge/documents.lance/_versions/21.manifest +0 -0
  196. package/.ctxman/self-knowledge/documents.lance/_versions/3.manifest +0 -0
  197. package/.ctxman/self-knowledge/documents.lance/_versions/4.manifest +0 -0
  198. package/.ctxman/self-knowledge/documents.lance/_versions/5.manifest +0 -0
  199. package/.ctxman/self-knowledge/documents.lance/_versions/6.manifest +0 -0
  200. package/.ctxman/self-knowledge/documents.lance/_versions/7.manifest +0 -0
  201. package/.ctxman/self-knowledge/documents.lance/_versions/8.manifest +0 -0
  202. package/.ctxman/self-knowledge/documents.lance/_versions/9.manifest +0 -0
  203. package/.ctxman/self-knowledge/documents.lance/data/000010010011011101000010de8621405d9d5cb06540dbf02c.lance +0 -0
  204. package/.ctxman/self-knowledge/documents.lance/data/0000101001011100011000003fb62a4aa8978974075c144f1c.lance +0 -0
  205. package/.ctxman/self-knowledge/documents.lance/data/000011010000100111001011c6cc824d628f3bb1f4e04cd5d0.lance +0 -0
  206. package/.ctxman/self-knowledge/documents.lance/data/001000001000011001011100d8f74349ada090b332cd1df669.lance +0 -0
  207. package/.ctxman/self-knowledge/documents.lance/data/01000001111010001000010045ab44467bb1e7e4d8ae06bd58.lance +0 -0
  208. package/.ctxman/self-knowledge/documents.lance/data/01000101101110111110011131ef904241a89e1ffb6ddaf8e3.lance +0 -0
  209. package/.ctxman/self-knowledge/documents.lance/data/010010110000010000011010fbfcc145c6a8a827b28057a07e.lance +0 -0
  210. package/.ctxman/self-knowledge/documents.lance/data/1000011000101010001110003fb4f045c3835982efa1405324.lance +0 -0
  211. package/.ctxman/self-knowledge/documents.lance/data/100111011101110111101011f4396b410fa06af3b028d204e6.lance +0 -0
  212. package/.ctxman/self-knowledge/documents.lance/data/100111100111101000001100596b174e00995d6cc2969cc8dc.lance +0 -0
  213. package/.ctxman/self-knowledge/documents.lance/data/1010001100011000110010102ea16b484d887c7c9725feffd6.lance +0 -0
  214. package/.ctxman/self-knowledge/documents.lance/data/101101100010101111110111c5230944d9b3af7301b6392dda.lance +0 -0
  215. package/.ctxman/self-knowledge/documents.lance/data/1011011100100111111110105892bf41b5a530fedd7cf516f1.lance +0 -0
  216. package/.ctxman/self-knowledge/documents.lance/data/10111110000010001011110164e8774742bade87b483c10f36.lance +0 -0
  217. package/.ctxman/self-knowledge/documents.lance/data/1100000100010110001110103ffaca40a2a42c85067ebaf9ae.lance +0 -0
  218. package/.ctxman/self-knowledge/documents.lance/data/110000010001110110101110664bde4c76a78dfc14b05e83b8.lance +0 -0
  219. package/.ctxman/self-knowledge/documents.lance/data/11001100111000010100110050645c4281b1f35b2022234f7f.lance +0 -0
  220. package/.ctxman/self-knowledge/documents.lance/data/1101111011001100110111017214924a368f5719eab2246619.lance +0 -0
  221. package/.ctxman/self-knowledge/documents.lance/data/1110000111001010001000002150c24dcab653d39d07032e63.lance +0 -0
  222. package/.ctxman/self-knowledge/documents.lance/data/1110100000000100000011014d20534967a18b5c87721659ff.lance +0 -0
  223. package/.ctxman/self-knowledge/documents.lance/data/11110101111010000101011135a0114ef2b35dc6f1297a3381.lance +0 -0
@@ -0,0 +1,431 @@
1
+ /**
2
+ * Import Tracker - Parses source files to track package imports
3
+ * v3.0.0 - FEAT-002
4
+ */
5
+
6
+ import fs from 'fs';
7
+ import path from 'path';
8
+ import { getLogger } from '../utils/logger.js';
9
+ import FileUtils from '../utils/file-utils.js';
10
+
11
+ const logger = getLogger('ImportTracker');
12
+
13
+ /**
14
+ * ImportTracker class
15
+ * Parses source files to identify which packages are actively imported
16
+ */
17
+ export class ImportTracker {
18
+ constructor(projectRoot, options = {}) {
19
+ this.projectRoot = projectRoot;
20
+ this.options = {
21
+ excludeNodeModules: options.excludeNodeModules !== false,
22
+ includeDevImports: options.includeDevImports || false,
23
+ ...options,
24
+ };
25
+ this.imports = new Map();
26
+ }
27
+
28
+ /**
29
+ * Parse ES6 imports from content
30
+ */
31
+ parseES6Imports(content) {
32
+ const imports = [];
33
+
34
+ // Match various import patterns:
35
+ // import defaultExport from 'package'
36
+ // import { named } from 'package'
37
+ // import * as name from 'package'
38
+ // import { a, b as c } from 'package'
39
+ const es6Pattern =
40
+ /import\s+(?:(\{[^}]*\})|(\*\s+as\s+\w+)|(\w+)(?:\s*,\s*(\{[^}]*\}))?)\s+from\s+['"]([^'"]+)['"]/g;
41
+
42
+ let match;
43
+ while ((match = es6Pattern.exec(content)) !== null) {
44
+ const symbols = [];
45
+
46
+ // Named imports: { a, b, c }
47
+ if (match[1]) {
48
+ const named = match[1]
49
+ .replace(/[{}]/g, '')
50
+ .split(',')
51
+ .map((s) =>
52
+ s
53
+ .trim()
54
+ .split(/\s+as\s+/)
55
+ .pop()
56
+ )
57
+ .filter((s) => s.length > 0);
58
+ symbols.push(...named);
59
+ }
60
+
61
+ // Namespace import: * as name
62
+ if (match[2]) {
63
+ symbols.push(match[2].trim());
64
+ }
65
+
66
+ // Default import
67
+ if (match[3]) {
68
+ symbols.push(match[3].trim());
69
+ }
70
+
71
+ // Combined: default, { named }
72
+ if (match[4]) {
73
+ const named = match[4]
74
+ .replace(/[{}]/g, '')
75
+ .split(',')
76
+ .map((s) =>
77
+ s
78
+ .trim()
79
+ .split(/\s+as\s+/)
80
+ .pop()
81
+ )
82
+ .filter((s) => s.length > 0);
83
+ symbols.push(...named);
84
+ }
85
+
86
+ // Package name
87
+ const packageName = match[5];
88
+
89
+ imports.push({
90
+ package: packageName,
91
+ symbols: [...new Set(symbols)],
92
+ type: 'es6',
93
+ });
94
+ }
95
+
96
+ // Also catch dynamic imports: import('package')
97
+ const dynamicPattern = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
98
+ while ((match = dynamicPattern.exec(content)) !== null) {
99
+ imports.push({
100
+ package: match[1],
101
+ symbols: ['*'],
102
+ type: 'dynamic',
103
+ });
104
+ }
105
+
106
+ return imports;
107
+ }
108
+
109
+ /**
110
+ * Parse CommonJS requires from content
111
+ */
112
+ parseCommonJSImports(content) {
113
+ const imports = [];
114
+
115
+ // Match require('package')
116
+ // Also handle: const { a, b } = require('package')
117
+ // const x = require('package')
118
+ const cjsPattern =
119
+ /(?:const|let|var)\s+(?:(\{[^}]*\})|(\w+))\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
120
+
121
+ let match;
122
+ while ((match = cjsPattern.exec(content)) !== null) {
123
+ const symbols = [];
124
+
125
+ // Destructured: { a, b }
126
+ if (match[1]) {
127
+ const named = match[1]
128
+ .replace(/[{}]/g, '')
129
+ .split(',')
130
+ .map((s) =>
131
+ s
132
+ .trim()
133
+ .split(/\s+as\s+/)
134
+ .pop()
135
+ )
136
+ .filter((s) => s.length > 0);
137
+ symbols.push(...named);
138
+ }
139
+
140
+ // Default: identifier
141
+ if (match[2]) {
142
+ symbols.push(match[2].trim());
143
+ }
144
+
145
+ imports.push({
146
+ package: match[3],
147
+ symbols: [...new Set(symbols)],
148
+ type: 'commonjs',
149
+ });
150
+ }
151
+
152
+ // Simple require without assignment (side effects)
153
+ const simpleRequirePattern = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
154
+ while ((match = simpleRequirePattern.exec(content)) !== null) {
155
+ // Check if it's not already captured
156
+ const existing = imports.find((i) => i.package === match[1]);
157
+ if (!existing) {
158
+ imports.push({
159
+ package: match[1],
160
+ symbols: ['*'],
161
+ type: 'commonjs',
162
+ });
163
+ }
164
+ }
165
+
166
+ return imports;
167
+ }
168
+
169
+ /**
170
+ * Every module specifier a JavaScript/TypeScript file names, relative paths included:
171
+ * the ES6 and CommonJS forms above plus the ones they leave out (re-exports such as
172
+ * `export * from './a.js'`, side-effect imports `import './polyfill.js'`, type-only
173
+ * imports `import type { T } from './types'` and `import a, * as b from 'x'`).
174
+ * Used to build the file dependency graph of `ctxman map`.
175
+ * @param {string} content - Source text
176
+ * @returns {string[]} Unique specifiers in order of first appearance
177
+ */
178
+ parseModuleSpecifiers(content) {
179
+ const specifiers = [
180
+ ...this.parseES6Imports(content).map((imp) => imp.package),
181
+ ...this.parseCommonJSImports(content).map((imp) => imp.package),
182
+ ];
183
+
184
+ // The clause before `from` holds only names, braces, `*` and commas, so a match
185
+ // cannot run across a string or a statement
186
+ const fromPattern =
187
+ /(?:^|[\s;}])(?:import|export)\s+(?:type\s+)?[\w$*{}\s,]*?\s*from\s*['"]([^'"\n]+)['"]/g;
188
+ const sideEffectPattern = /(?:^|[\s;}])import\s*['"]([^'"\n]+)['"]/g;
189
+ for (const pattern of [fromPattern, sideEffectPattern]) {
190
+ let match;
191
+ while ((match = pattern.exec(content)) !== null) {
192
+ specifiers.push(match[1]);
193
+ }
194
+ }
195
+
196
+ return [...new Set(specifiers)];
197
+ }
198
+
199
+ /**
200
+ * Check if an import is from a package (not a relative path)
201
+ */
202
+ isPackageImport(importPath) {
203
+ // Relative imports start with ./ or ../
204
+ // Absolute imports start with /
205
+ // Package imports are bare identifiers
206
+ if (
207
+ importPath.startsWith('./') ||
208
+ importPath.startsWith('../') ||
209
+ importPath.startsWith('/') ||
210
+ importPath.startsWith('#')
211
+ ) {
212
+ return false;
213
+ }
214
+ return true;
215
+ }
216
+
217
+ /**
218
+ * Extract the base package name from an import path
219
+ * e.g., @babel/core -> @babel/core
220
+ * e.g., lodash/fp -> lodash
221
+ */
222
+ getBasePackageName(importPath) {
223
+ // Handle scoped packages (@org/package)
224
+ if (importPath.startsWith('@')) {
225
+ const parts = importPath.split('/');
226
+ if (parts.length >= 2) {
227
+ return `${parts[0]}/${parts[1]}`;
228
+ }
229
+ return importPath;
230
+ }
231
+
232
+ // Regular packages: package/submodule -> package
233
+ const parts = importPath.split('/');
234
+ return parts[0];
235
+ }
236
+
237
+ /**
238
+ * Parse a single file for imports
239
+ */
240
+ parseFile(filePath) {
241
+ const imports = [];
242
+
243
+ try {
244
+ const content = fs.readFileSync(filePath, 'utf-8');
245
+
246
+ // Parse ES6 imports
247
+ const es6Imports = this.parseES6Imports(content);
248
+ imports.push(...es6Imports);
249
+
250
+ // Parse CommonJS requires
251
+ const cjsImports = this.parseCommonJSImports(content);
252
+ imports.push(...cjsImports);
253
+
254
+ // Filter to package imports only and normalize package names
255
+ return imports
256
+ .filter((imp) => this.isPackageImport(imp.package))
257
+ .map((imp) => ({
258
+ ...imp,
259
+ package: this.getBasePackageName(imp.package),
260
+ }));
261
+ } catch (error) {
262
+ logger.warn(`Failed to parse file ${filePath}: ${error.message}`);
263
+ return [];
264
+ }
265
+ }
266
+
267
+ /**
268
+ * Scan a directory for source files
269
+ */
270
+ scanSourceFiles(dir, files = []) {
271
+ try {
272
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
273
+
274
+ for (const entry of entries) {
275
+ const fullPath = path.join(dir, entry.name);
276
+
277
+ // Skip node_modules
278
+ if (entry.name === 'node_modules' && this.options.excludeNodeModules) {
279
+ continue;
280
+ }
281
+
282
+ // Skip hidden directories
283
+ if (entry.name.startsWith('.') && entry.name !== '.ctxman') {
284
+ continue;
285
+ }
286
+
287
+ // Skip common non-source directories
288
+ if (['dist', 'build', 'coverage', '.git'].includes(entry.name)) {
289
+ continue;
290
+ }
291
+
292
+ if (entry.isDirectory()) {
293
+ this.scanSourceFiles(fullPath, files);
294
+ } else if (entry.isFile()) {
295
+ // Only parse text files (JS, TS, etc.)
296
+ if (FileUtils.isText(fullPath) && this.isSourceFile(fullPath)) {
297
+ files.push(fullPath);
298
+ }
299
+ }
300
+ }
301
+ } catch (error) {
302
+ logger.warn(`Failed to scan directory ${dir}: ${error.message}`);
303
+ }
304
+
305
+ return files;
306
+ }
307
+
308
+ /**
309
+ * Check if a file is a source file (JS, TS, etc.)
310
+ */
311
+ isSourceFile(filePath) {
312
+ const ext = path.extname(filePath).toLowerCase();
313
+ const sourceExtensions = [
314
+ '.js',
315
+ '.jsx',
316
+ '.ts',
317
+ '.tsx',
318
+ '.mjs',
319
+ '.cjs',
320
+ '.vue',
321
+ '.svelte',
322
+ '.astro',
323
+ ];
324
+ return sourceExtensions.includes(ext);
325
+ }
326
+
327
+ /**
328
+ * Track all imports in the project
329
+ */
330
+ trackImports(sourceFiles = null) {
331
+ logger.info('Tracking imports...');
332
+
333
+ // Get source files if not provided
334
+ if (!sourceFiles) {
335
+ sourceFiles = this.scanSourceFiles(this.projectRoot);
336
+ }
337
+
338
+ logger.debug(`Found ${sourceFiles.length} source files to analyze`);
339
+
340
+ // Parse each file
341
+ for (const file of sourceFiles) {
342
+ const fileImports = this.parseFile(file);
343
+
344
+ for (const imp of fileImports) {
345
+ const packageName = imp.package;
346
+
347
+ if (!this.imports.has(packageName)) {
348
+ this.imports.set(packageName, {
349
+ package: packageName,
350
+ imports: [],
351
+ files: [],
352
+ types: new Set(),
353
+ });
354
+ }
355
+
356
+ const record = this.imports.get(packageName);
357
+ record.imports.push(...imp.symbols);
358
+ record.files.push(path.relative(this.projectRoot, file));
359
+ record.types.add(imp.type);
360
+ }
361
+ }
362
+
363
+ // Deduplicate and finalize
364
+ const results = [];
365
+ for (const record of this.imports.values()) {
366
+ results.push({
367
+ package: record.package,
368
+ imports: [...new Set(record.imports)],
369
+ files: [...new Set(record.files)],
370
+ importTypes: [...record.types],
371
+ usageCount: record.files.length,
372
+ });
373
+ }
374
+
375
+ // Sort by usage count (descending)
376
+ results.sort((a, b) => b.usageCount - a.usageCount);
377
+
378
+ logger.info(`Found ${results.length} packages with active imports`);
379
+
380
+ return results;
381
+ }
382
+
383
+ /**
384
+ * Get packages that are actively imported
385
+ */
386
+ getActivePackages() {
387
+ return Array.from(this.imports.keys());
388
+ }
389
+
390
+ /**
391
+ * Get import details for a specific package
392
+ */
393
+ getPackageImports(packageName) {
394
+ const baseName = this.getBasePackageName(packageName);
395
+ return this.imports.get(baseName) || null;
396
+ }
397
+
398
+ /**
399
+ * Check if a package is actively imported
400
+ */
401
+ isPackageActive(packageName) {
402
+ const baseName = this.getBasePackageName(packageName);
403
+ return this.imports.has(baseName);
404
+ }
405
+
406
+ /**
407
+ * Reset the tracker
408
+ */
409
+ reset() {
410
+ this.imports = new Map();
411
+ }
412
+
413
+ /**
414
+ * Get statistics
415
+ */
416
+ getStats() {
417
+ return {
418
+ totalPackages: this.imports.size,
419
+ totalImports: Array.from(this.imports.values()).reduce(
420
+ (sum, record) => sum + record.imports.length,
421
+ 0
422
+ ),
423
+ totalFiles: Array.from(this.imports.values()).reduce(
424
+ (sum, record) => sum + record.files.length,
425
+ 0
426
+ ),
427
+ };
428
+ }
429
+ }
430
+
431
+ export default ImportTracker;