ctxman 3.0.0 → 3.1.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 (224) 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 +381 -136
  16. package/bin/cli.js +1880 -529
  17. package/bin/cm-ask.js +18 -67
  18. package/bin/cm-audit.js +121 -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 +687 -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 +772 -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 +116 -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 +312 -0
  93. package/lib/policy/enforce.js +125 -0
  94. package/lib/policy/ignore-files.js +169 -0
  95. package/lib/policy/index.js +53 -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/ask.js +122 -0
  108. package/lib/rag/sources/FileDataSource.js +12 -9
  109. package/lib/rag/stores/BaseVectorStore.js +5 -5
  110. package/lib/rag/stores/ChromaDBStore.js +10 -10
  111. package/lib/rag/stores/LanceDBStore.js +29 -19
  112. package/lib/rag/stores/PineconeStore.js +11 -9
  113. package/lib/search/bm25.js +433 -0
  114. package/lib/search/code-units.js +201 -0
  115. package/lib/search/symbols.js +189 -0
  116. package/lib/ui/dashboard.js +120 -89
  117. package/lib/ui/index.js +1 -6
  118. package/lib/ui/init-wizard-ui.js +550 -0
  119. package/lib/ui/progress-bar.js +94 -88
  120. package/lib/ui/select-input.js +40 -32
  121. package/lib/ui/template-selector.js +99 -0
  122. package/lib/ui/wizard.js +374 -329
  123. package/lib/utils/claude-token-counter.js +151 -0
  124. package/lib/utils/cli-output.js +17 -0
  125. package/lib/utils/clipboard-utils.js +49 -50
  126. package/lib/utils/config-utils.js +81 -66
  127. package/lib/utils/context-versioning.js +532 -0
  128. package/lib/utils/error-handler.js +112 -112
  129. package/lib/utils/file-utils.js +153 -69
  130. package/lib/utils/format-converter.js +356 -283
  131. package/lib/utils/git-utils.js +366 -294
  132. package/lib/utils/llm-cost-estimator.js +281 -0
  133. package/lib/utils/llm-detector.js +242 -78
  134. package/lib/utils/logger.js +246 -237
  135. package/lib/utils/messages.js +355 -0
  136. package/lib/utils/multi-repo-manager.js +364 -0
  137. package/lib/utils/profile-manager.js +380 -0
  138. package/lib/utils/safe-read.js +61 -0
  139. package/lib/utils/secret-redactor.js +61 -0
  140. package/lib/utils/snapshot-manager.js +679 -0
  141. package/lib/utils/template-manager.js +378 -0
  142. package/lib/utils/token-utils.js +201 -170
  143. package/lib/utils/tokenizer-adapter.js +421 -377
  144. package/lib/utils/updater.js +539 -506
  145. package/lib/watch/FileWatcher.js +12 -8
  146. package/lib/watch/IncrementalAnalyzer.js +7 -7
  147. package/lib/why/coupling.js +81 -0
  148. package/lib/why/errors.js +23 -0
  149. package/lib/why/git.js +169 -0
  150. package/lib/why/history.js +108 -0
  151. package/lib/why/index.js +720 -0
  152. package/lib/why/ownership.js +79 -0
  153. package/lib/why/paths.js +207 -0
  154. package/lib/why/references.js +259 -0
  155. package/lib/why/render.js +722 -0
  156. package/lib/why/search.js +358 -0
  157. package/lib/why/target.js +389 -0
  158. package/lib/wizards/config-generator.js +257 -0
  159. package/lib/wizards/project-detector.js +572 -0
  160. package/package.json +44 -10
  161. package/.ctxman/logs/ctxman-2026-07-02.log +0 -156
  162. package/.ctxman/self-knowledge/documents.lance/_transactions/0-b3dd1558-f95b-44b9-a154-18e8c330ae7a.txn +0 -0
  163. package/.ctxman/self-knowledge/documents.lance/_transactions/1-44bff6d4-e552-4010-a769-e09ad822698d.txn +0 -0
  164. package/.ctxman/self-knowledge/documents.lance/_transactions/10-37260794-5947-4ecb-b4c8-d732f58f40f1.txn +0 -0
  165. package/.ctxman/self-knowledge/documents.lance/_transactions/11-0d52a883-5518-463c-9b6d-986c3af89dfd.txn +0 -0
  166. package/.ctxman/self-knowledge/documents.lance/_transactions/12-ae354646-52f2-49c9-90fb-2e6ed8932663.txn +0 -0
  167. package/.ctxman/self-knowledge/documents.lance/_transactions/13-5d7d5e57-27da-498b-b734-091b9d38da13.txn +0 -0
  168. package/.ctxman/self-knowledge/documents.lance/_transactions/14-558ce928-810b-4252-a364-fdd1ee6f8a25.txn +0 -0
  169. package/.ctxman/self-knowledge/documents.lance/_transactions/15-27e3a58f-8b64-43b8-a9f4-e3369446968b.txn +0 -0
  170. package/.ctxman/self-knowledge/documents.lance/_transactions/16-a110c390-e5c8-4464-b976-bc7f5195fa15.txn +0 -0
  171. package/.ctxman/self-knowledge/documents.lance/_transactions/17-97513225-53de-4216-b61a-6337bd0cc4ad.txn +0 -0
  172. package/.ctxman/self-knowledge/documents.lance/_transactions/18-5ba450f0-c527-459c-bf4f-6d79b5e4285f.txn +0 -0
  173. package/.ctxman/self-knowledge/documents.lance/_transactions/19-600d3a63-5225-49e9-b8b7-30e037b564ab.txn +0 -0
  174. package/.ctxman/self-knowledge/documents.lance/_transactions/2-48a61b4e-92cf-4342-8376-914c1b99a9f9.txn +0 -0
  175. package/.ctxman/self-knowledge/documents.lance/_transactions/20-121dd912-5410-410e-84ff-82c7a23604a0.txn +0 -0
  176. package/.ctxman/self-knowledge/documents.lance/_transactions/3-bc6844ab-7ea3-4bb3-9ee1-538ce5664a9c.txn +0 -0
  177. package/.ctxman/self-knowledge/documents.lance/_transactions/4-d5e1754f-dbb5-454e-a9be-2a7da8d7bcf9.txn +0 -0
  178. package/.ctxman/self-knowledge/documents.lance/_transactions/5-4fae93df-0568-4b6f-8acf-8bef4bba6047.txn +0 -0
  179. package/.ctxman/self-knowledge/documents.lance/_transactions/6-4b1b8b87-068e-49a3-ba2b-d8dae628b252.txn +0 -0
  180. package/.ctxman/self-knowledge/documents.lance/_transactions/7-63d040fb-d2fa-4c0f-bed0-53411918a7b0.txn +0 -0
  181. package/.ctxman/self-knowledge/documents.lance/_transactions/8-c5628cf6-264c-420f-aab8-bc96a9083591.txn +0 -0
  182. package/.ctxman/self-knowledge/documents.lance/_transactions/9-c94570aa-5e53-4fcb-971f-cecc1d1d5492.txn +0 -0
  183. package/.ctxman/self-knowledge/documents.lance/_versions/1.manifest +0 -0
  184. package/.ctxman/self-knowledge/documents.lance/_versions/10.manifest +0 -0
  185. package/.ctxman/self-knowledge/documents.lance/_versions/11.manifest +0 -0
  186. package/.ctxman/self-knowledge/documents.lance/_versions/12.manifest +0 -0
  187. package/.ctxman/self-knowledge/documents.lance/_versions/13.manifest +0 -0
  188. package/.ctxman/self-knowledge/documents.lance/_versions/14.manifest +0 -0
  189. package/.ctxman/self-knowledge/documents.lance/_versions/15.manifest +0 -0
  190. package/.ctxman/self-knowledge/documents.lance/_versions/16.manifest +0 -0
  191. package/.ctxman/self-knowledge/documents.lance/_versions/17.manifest +0 -0
  192. package/.ctxman/self-knowledge/documents.lance/_versions/18.manifest +0 -0
  193. package/.ctxman/self-knowledge/documents.lance/_versions/19.manifest +0 -0
  194. package/.ctxman/self-knowledge/documents.lance/_versions/2.manifest +0 -0
  195. package/.ctxman/self-knowledge/documents.lance/_versions/20.manifest +0 -0
  196. package/.ctxman/self-knowledge/documents.lance/_versions/21.manifest +0 -0
  197. package/.ctxman/self-knowledge/documents.lance/_versions/3.manifest +0 -0
  198. package/.ctxman/self-knowledge/documents.lance/_versions/4.manifest +0 -0
  199. package/.ctxman/self-knowledge/documents.lance/_versions/5.manifest +0 -0
  200. package/.ctxman/self-knowledge/documents.lance/_versions/6.manifest +0 -0
  201. package/.ctxman/self-knowledge/documents.lance/_versions/7.manifest +0 -0
  202. package/.ctxman/self-knowledge/documents.lance/_versions/8.manifest +0 -0
  203. package/.ctxman/self-knowledge/documents.lance/_versions/9.manifest +0 -0
  204. package/.ctxman/self-knowledge/documents.lance/data/000010010011011101000010de8621405d9d5cb06540dbf02c.lance +0 -0
  205. package/.ctxman/self-knowledge/documents.lance/data/0000101001011100011000003fb62a4aa8978974075c144f1c.lance +0 -0
  206. package/.ctxman/self-knowledge/documents.lance/data/000011010000100111001011c6cc824d628f3bb1f4e04cd5d0.lance +0 -0
  207. package/.ctxman/self-knowledge/documents.lance/data/001000001000011001011100d8f74349ada090b332cd1df669.lance +0 -0
  208. package/.ctxman/self-knowledge/documents.lance/data/01000001111010001000010045ab44467bb1e7e4d8ae06bd58.lance +0 -0
  209. package/.ctxman/self-knowledge/documents.lance/data/01000101101110111110011131ef904241a89e1ffb6ddaf8e3.lance +0 -0
  210. package/.ctxman/self-knowledge/documents.lance/data/010010110000010000011010fbfcc145c6a8a827b28057a07e.lance +0 -0
  211. package/.ctxman/self-knowledge/documents.lance/data/1000011000101010001110003fb4f045c3835982efa1405324.lance +0 -0
  212. package/.ctxman/self-knowledge/documents.lance/data/100111011101110111101011f4396b410fa06af3b028d204e6.lance +0 -0
  213. package/.ctxman/self-knowledge/documents.lance/data/100111100111101000001100596b174e00995d6cc2969cc8dc.lance +0 -0
  214. package/.ctxman/self-knowledge/documents.lance/data/1010001100011000110010102ea16b484d887c7c9725feffd6.lance +0 -0
  215. package/.ctxman/self-knowledge/documents.lance/data/101101100010101111110111c5230944d9b3af7301b6392dda.lance +0 -0
  216. package/.ctxman/self-knowledge/documents.lance/data/1011011100100111111110105892bf41b5a530fedd7cf516f1.lance +0 -0
  217. package/.ctxman/self-knowledge/documents.lance/data/10111110000010001011110164e8774742bade87b483c10f36.lance +0 -0
  218. package/.ctxman/self-knowledge/documents.lance/data/1100000100010110001110103ffaca40a2a42c85067ebaf9ae.lance +0 -0
  219. package/.ctxman/self-knowledge/documents.lance/data/110000010001110110101110664bde4c76a78dfc14b05e83b8.lance +0 -0
  220. package/.ctxman/self-knowledge/documents.lance/data/11001100111000010100110050645c4281b1f35b2022234f7f.lance +0 -0
  221. package/.ctxman/self-knowledge/documents.lance/data/1101111011001100110111017214924a368f5719eab2246619.lance +0 -0
  222. package/.ctxman/self-knowledge/documents.lance/data/1110000111001010001000002150c24dcab653d39d07032e63.lance +0 -0
  223. package/.ctxman/self-knowledge/documents.lance/data/1110100000000100000011014d20534967a18b5c87721659ff.lance +0 -0
  224. package/.ctxman/self-knowledge/documents.lance/data/11110101111010000101011135a0114ef2b35dc6f1297a3381.lance +0 -0
@@ -0,0 +1,336 @@
1
+ /**
2
+ * Unused Exports Detector
3
+ * Detects exported functions/variables that are never imported
4
+ * Part of FEAT-005: AI-Powered Context Suggestions
5
+ */
6
+
7
+ import path from 'path';
8
+ import { getLogger } from '../utils/logger.js';
9
+
10
+ const logger = getLogger('UnusedExportsDetector');
11
+
12
+ export class UnusedExportsDetector {
13
+ constructor(options = {}) {
14
+ this.options = {
15
+ excludePatterns: options.excludePatterns || ['index.js', 'index.ts', '*.d.ts'],
16
+ ...options,
17
+ };
18
+ this.unusedExports = [];
19
+ }
20
+
21
+ /**
22
+ * Detect unused exports in files
23
+ * @param {Array} files - Array of file objects with content
24
+ * @returns {Array} Array of unused export objects
25
+ */
26
+ detect(files) {
27
+ logger.debug(`Analyzing ${files.length} files for unused exports`);
28
+
29
+ // Build export and import maps
30
+ const exportsMap = this.buildExportsMap(files);
31
+ const importsMap = this.buildImportsMap(files);
32
+
33
+ // Find unused exports
34
+ const unused = this.findUnusedExports(exportsMap, importsMap);
35
+
36
+ this.unusedExports = unused;
37
+ logger.info(`Found ${unused.length} potentially unused exports`);
38
+
39
+ return unused;
40
+ }
41
+
42
+ /**
43
+ * Build a map of all exports from files
44
+ */
45
+ buildExportsMap(files) {
46
+ const exportsMap = new Map();
47
+
48
+ for (const file of files) {
49
+ if (!file.content || !this.isCodeFile(file.path)) {
50
+ continue;
51
+ }
52
+
53
+ const exports = this.extractExports(file.content, file.path);
54
+ if (exports.length > 0) {
55
+ exportsMap.set(file.relativePath || file.path, exports);
56
+ }
57
+ }
58
+
59
+ return exportsMap;
60
+ }
61
+
62
+ /**
63
+ * Build a map of all imports in files
64
+ */
65
+ buildImportsMap(files) {
66
+ const importsMap = new Map();
67
+
68
+ for (const file of files) {
69
+ if (!file.content || !this.isCodeFile(file.path)) {
70
+ continue;
71
+ }
72
+
73
+ const imports = this.extractImports(file.content);
74
+ for (const imp of imports) {
75
+ const key = imp.name;
76
+ if (!importsMap.has(key)) {
77
+ importsMap.set(key, []);
78
+ }
79
+ importsMap.get(key).push({
80
+ file: file.relativePath || file.path,
81
+ source: imp.source,
82
+ });
83
+ }
84
+ }
85
+
86
+ return importsMap;
87
+ }
88
+
89
+ /**
90
+ * Extract exports from JavaScript/TypeScript content
91
+ */
92
+ extractExports(content, _filePath) {
93
+ const exports = [];
94
+ const lines = content.split('\n');
95
+
96
+ for (let i = 0; i < lines.length; i++) {
97
+ const line = lines[i];
98
+
99
+ // export function name
100
+ const funcMatch = line.match(/export\s+(?:async\s+)?function\s+(\w+)/);
101
+ if (funcMatch) {
102
+ exports.push({
103
+ name: funcMatch[1],
104
+ type: 'function',
105
+ line: i + 1,
106
+ });
107
+ continue;
108
+ }
109
+
110
+ // export const/let/var name
111
+ const constMatch = line.match(/export\s+(?:const|let|var)\s+(\w+)/);
112
+ if (constMatch) {
113
+ exports.push({
114
+ name: constMatch[1],
115
+ type: 'variable',
116
+ line: i + 1,
117
+ });
118
+ continue;
119
+ }
120
+
121
+ // export class Name
122
+ const classMatch = line.match(/export\s+class\s+(\w+)/);
123
+ if (classMatch) {
124
+ exports.push({
125
+ name: classMatch[1],
126
+ type: 'class',
127
+ line: i + 1,
128
+ });
129
+ continue;
130
+ }
131
+
132
+ // export { name1, name2 }
133
+ const namedMatch = line.match(/export\s+\{([^}]+)\}/);
134
+ if (namedMatch) {
135
+ const names = namedMatch[1]
136
+ .split(',')
137
+ .map((n) => {
138
+ const parts = n.trim().split(/\s+as\s+/);
139
+ return parts[parts.length - 1].trim();
140
+ })
141
+ .filter((n) => n);
142
+
143
+ for (const name of names) {
144
+ exports.push({
145
+ name,
146
+ type: 'named',
147
+ line: i + 1,
148
+ });
149
+ }
150
+ continue;
151
+ }
152
+
153
+ // export default name
154
+ const defaultMatch = line.match(/export\s+default\s+(\w+)/);
155
+ if (defaultMatch) {
156
+ exports.push({
157
+ name: defaultMatch[1],
158
+ type: 'default',
159
+ line: i + 1,
160
+ isDefault: true,
161
+ });
162
+ }
163
+ }
164
+
165
+ return exports;
166
+ }
167
+
168
+ /**
169
+ * Extract imports from JavaScript/TypeScript content
170
+ */
171
+ extractImports(content) {
172
+ const imports = [];
173
+ const lines = content.split('\n');
174
+
175
+ for (let i = 0; i < lines.length; i++) {
176
+ const line = lines[i];
177
+
178
+ // import { name } from 'source'
179
+ const namedImport = line.match(/import\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/);
180
+ if (namedImport) {
181
+ const names = namedImport[1]
182
+ .split(',')
183
+ .map((n) => {
184
+ const parts = n.trim().split(/\s+as\s+/);
185
+ return parts[0].trim();
186
+ })
187
+ .filter((n) => n);
188
+
189
+ for (const name of names) {
190
+ imports.push({ name, source: namedImport[2], line: i + 1 });
191
+ }
192
+ continue;
193
+ }
194
+
195
+ // import name from 'source'
196
+ const defaultImport = line.match(/import\s+(\w+)\s+from\s+['"]([^'"]+)['"]/);
197
+ if (defaultImport) {
198
+ imports.push({ name: defaultImport[1], source: defaultImport[2], line: i + 1 });
199
+ continue;
200
+ }
201
+
202
+ // import * as name from 'source'
203
+ const namespaceImport = line.match(/import\s+\*\s+as\s+(\w+)\s+from\s+['"]([^'"]+)['"]/);
204
+ if (namespaceImport) {
205
+ imports.push({
206
+ name: namespaceImport[1],
207
+ source: namespaceImport[2],
208
+ line: i + 1,
209
+ isNamespace: true,
210
+ });
211
+ continue;
212
+ }
213
+
214
+ // require('source')
215
+ const requireMatch = line.match(
216
+ /(?:const|let|var)\s+\{([^}]+)\}\s*=\s*require\(['"]([^'"]+)['"]\)/
217
+ );
218
+ if (requireMatch) {
219
+ const names = requireMatch[1]
220
+ .split(',')
221
+ .map((n) => n.trim())
222
+ .filter((n) => n);
223
+ for (const name of names) {
224
+ imports.push({ name, source: requireMatch[2], line: i + 1 });
225
+ }
226
+ }
227
+ }
228
+
229
+ return imports;
230
+ }
231
+
232
+ /**
233
+ * Find exports that are never imported
234
+ */
235
+ findUnusedExports(exportsMap, importsMap) {
236
+ const unused = [];
237
+
238
+ for (const [filePath, exports] of exportsMap) {
239
+ // Skip index files (typically re-export)
240
+ if (this.shouldExclude(filePath)) {
241
+ continue;
242
+ }
243
+
244
+ for (const exp of exports) {
245
+ // Skip default exports (often used differently)
246
+ if (exp.isDefault) {
247
+ continue;
248
+ }
249
+
250
+ // Check if this export is imported anywhere
251
+ const imports = importsMap.get(exp.name);
252
+
253
+ if (!imports || imports.length === 0) {
254
+ unused.push({
255
+ file: filePath,
256
+ name: exp.name,
257
+ type: exp.type,
258
+ line: exp.line,
259
+ tokens: this.estimateExportTokens(exp),
260
+ });
261
+ }
262
+ }
263
+ }
264
+
265
+ // Sort by file, then by line
266
+ unused.sort((a, b) => {
267
+ if (a.file !== b.file) return a.file.localeCompare(b.file);
268
+ return a.line - b.line;
269
+ });
270
+
271
+ return unused;
272
+ }
273
+
274
+ /**
275
+ * Check if file should be excluded from unused export detection
276
+ */
277
+ shouldExclude(filePath) {
278
+ const basename = path.basename(filePath);
279
+
280
+ for (const pattern of this.options.excludePatterns) {
281
+ if (pattern.startsWith('*')) {
282
+ const ext = pattern.substring(1);
283
+ if (basename.endsWith(ext)) return true;
284
+ } else if (basename === pattern) {
285
+ return true;
286
+ }
287
+ }
288
+
289
+ return false;
290
+ }
291
+
292
+ /**
293
+ * Check if file is a code file
294
+ */
295
+ isCodeFile(filePath) {
296
+ const codeExtensions = ['.js', '.ts', '.jsx', '.tsx', '.mjs'];
297
+ const ext = path.extname(filePath).toLowerCase();
298
+ return codeExtensions.includes(ext);
299
+ }
300
+
301
+ /**
302
+ * Estimate tokens for an export
303
+ */
304
+ estimateExportTokens(exp) {
305
+ // Rough estimate based on type
306
+ const baseTokens = { function: 30, class: 50, variable: 10, named: 15 };
307
+ return baseTokens[exp.type] || 10;
308
+ }
309
+
310
+ /**
311
+ * Get statistics about unused exports
312
+ */
313
+ getStats() {
314
+ const byFile = {};
315
+
316
+ for (const exp of this.unusedExports) {
317
+ if (!byFile[exp.file]) {
318
+ byFile[exp.file] = [];
319
+ }
320
+ byFile[exp.file].push(exp);
321
+ }
322
+
323
+ return {
324
+ totalUnused: this.unusedExports.length,
325
+ filesAffected: Object.keys(byFile).length,
326
+ byType: {
327
+ functions: this.unusedExports.filter((e) => e.type === 'function').length,
328
+ classes: this.unusedExports.filter((e) => e.type === 'class').length,
329
+ variables: this.unusedExports.filter((e) => e.type === 'variable').length,
330
+ named: this.unusedExports.filter((e) => e.type === 'named').length,
331
+ },
332
+ };
333
+ }
334
+ }
335
+
336
+ export default UnusedExportsDetector;