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,451 @@
1
+ /**
2
+ * AI Context Suggester
3
+ * Provides smart context optimization suggestions without external AI APIs
4
+ * Part of FEAT-005: AI-Powered Context Suggestions
5
+ */
6
+
7
+ import path from 'path';
8
+ import fs from 'fs';
9
+ import { getLogger } from '../utils/logger.js';
10
+ import FileUtils from '../utils/file-utils.js';
11
+ import { DuplicateDetector } from './duplicate-detector.js';
12
+ import { UnusedExportsDetector } from './unused-exports-detector.js';
13
+
14
+ const logger = getLogger('AISuggester');
15
+
16
+ // Token thresholds for suggestions
17
+ const TOKEN_THRESHOLDS = {
18
+ CRITICAL_LARGE_FILE: 50000, // 50K tokens
19
+ WARNING_LARGE_FILE: 20000, // 20K tokens
20
+ SUGGESTION_LARGE_FILE: 10000, // 10K tokens
21
+ HIGH_TEST_RATIO: 0.4, // 40% test files
22
+ HIGH_CONFIG_RATIO: 0.1, // 10% config files
23
+ };
24
+
25
+ // Category icons
26
+ const ICONS = {
27
+ critical: '🔴',
28
+ warning: '🟡',
29
+ suggestion: '🟢',
30
+ info: 'ℹ️',
31
+ };
32
+
33
+ export class AISuggester {
34
+ constructor(options = {}) {
35
+ this.options = {
36
+ projectRoot: options.projectRoot || process.cwd(),
37
+ json: options.json || false,
38
+ verbose: options.verbose || false,
39
+ ...options,
40
+ };
41
+
42
+ this.duplicateDetector = new DuplicateDetector(options);
43
+ this.unusedExportsDetector = new UnusedExportsDetector(options);
44
+
45
+ this.suggestions = {
46
+ critical: [],
47
+ warning: [],
48
+ suggestion: [],
49
+ };
50
+
51
+ this.metrics = {
52
+ totalFiles: 0,
53
+ totalTokens: 0,
54
+ testFiles: 0,
55
+ testTokens: 0,
56
+ configFiles: 0,
57
+ configTokens: 0,
58
+ largeFiles: [],
59
+ files: [],
60
+ };
61
+ }
62
+
63
+ /**
64
+ * Analyze project and generate suggestions
65
+ * @param {Object} stats - Token analysis stats from TokenCalculator
66
+ * @param {Array} files - Array of analyzed file info
67
+ * @returns {Object} Suggestions and efficiency score
68
+ */
69
+ async analyze(stats, files = []) {
70
+ logger.info('Starting AI context analysis');
71
+
72
+ this.metrics.totalFiles = stats.totalFiles || 0;
73
+ this.metrics.totalTokens = stats.totalTokens || 0;
74
+ this.metrics.files = files;
75
+
76
+ // Run all analyses
77
+ this.analyzeLargeFiles(files);
78
+ this.analyzeTestFiles(files);
79
+ this.analyzeConfigFiles(files);
80
+
81
+ // Run code quality analyses
82
+ await this.analyzeDuplicates(files);
83
+ await this.analyzeUnusedExports(files);
84
+
85
+ // Calculate efficiency score
86
+ const efficiencyScore = this.calculateEfficiencyScore();
87
+
88
+ // Generate output
89
+ const result = {
90
+ score: efficiencyScore,
91
+ suggestions: this.suggestions,
92
+ metrics: {
93
+ totalFiles: this.metrics.totalFiles,
94
+ totalTokens: this.metrics.totalTokens,
95
+ testFiles: this.metrics.testFiles,
96
+ testTokens: this.metrics.testTokens,
97
+ configFiles: this.metrics.configFiles,
98
+ configTokens: this.metrics.configTokens,
99
+ largeFiles: this.metrics.largeFiles.length,
100
+ },
101
+ };
102
+
103
+ logger.info(`Analysis complete. Efficiency score: ${efficiencyScore}/100`);
104
+
105
+ return result;
106
+ }
107
+
108
+ /**
109
+ * Analyze large files for exclusion suggestions
110
+ */
111
+ analyzeLargeFiles(files) {
112
+ logger.debug('Analyzing large files');
113
+
114
+ const largeFiles = files
115
+ .filter((f) => f.tokens >= TOKEN_THRESHOLDS.SUGGESTION_LARGE_FILE)
116
+ .sort((a, b) => b.tokens - a.tokens);
117
+
118
+ this.metrics.largeFiles = largeFiles;
119
+
120
+ for (const file of largeFiles) {
121
+ const percentage = ((file.tokens / this.metrics.totalTokens) * 100).toFixed(1);
122
+ const tokens = file.tokens.toLocaleString();
123
+
124
+ if (file.tokens >= TOKEN_THRESHOLDS.CRITICAL_LARGE_FILE) {
125
+ this.suggestions.critical.push({
126
+ type: 'large_file',
127
+ message: `Exclude large file: ${file.relativePath} (${tokens} tokens, ${percentage}%)`,
128
+ file: file.relativePath,
129
+ tokens: file.tokens,
130
+ percentage: parseFloat(percentage),
131
+ action: `Add ${file.relativePath} to .contextignore`,
132
+ });
133
+ } else if (file.tokens >= TOKEN_THRESHOLDS.WARNING_LARGE_FILE) {
134
+ this.suggestions.warning.push({
135
+ type: 'large_file',
136
+ message: `Consider splitting or excluding: ${file.relativePath} (${tokens} tokens, ${percentage}%)`,
137
+ file: file.relativePath,
138
+ tokens: file.tokens,
139
+ percentage: parseFloat(percentage),
140
+ action: `Add ${file.relativePath} to .contextignore or refactor into smaller modules`,
141
+ });
142
+ } else {
143
+ this.suggestions.suggestion.push({
144
+ type: 'large_file',
145
+ message: `Large file detected: ${file.relativePath} (${tokens} tokens)`,
146
+ file: file.relativePath,
147
+ tokens: file.tokens,
148
+ percentage: parseFloat(percentage),
149
+ });
150
+ }
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Analyze test files for exclusion suggestions
156
+ */
157
+ analyzeTestFiles(files) {
158
+ logger.debug('Analyzing test files');
159
+
160
+ const testPatterns = [
161
+ '.test.',
162
+ '.spec.',
163
+ '_test.',
164
+ '_spec.',
165
+ '/test/',
166
+ '/tests/',
167
+ '/__tests__/',
168
+ ];
169
+ const testFiles = files.filter((f) => {
170
+ // Normalize path with leading slash for pattern matching
171
+ const normalizedPath = '/' + f.relativePath;
172
+ return testPatterns.some((p) => f.relativePath.includes(p) || normalizedPath.includes(p));
173
+ });
174
+
175
+ this.metrics.testFiles = testFiles.length;
176
+ this.metrics.testTokens = testFiles.reduce((sum, f) => sum + f.tokens, 0);
177
+
178
+ const testRatio =
179
+ this.metrics.totalTokens > 0 ? this.metrics.testTokens / this.metrics.totalTokens : 0;
180
+
181
+ if (testFiles.length > 0 && testRatio > TOKEN_THRESHOLDS.HIGH_TEST_RATIO) {
182
+ const tokens = this.metrics.testTokens.toLocaleString();
183
+
184
+ this.suggestions.warning.push({
185
+ type: 'test_files',
186
+ message: `${testFiles.length} test files included (${tokens} tokens)`,
187
+ count: testFiles.length,
188
+ tokens: this.metrics.testTokens,
189
+ percentage: (testRatio * 100).toFixed(1),
190
+ action: 'Add **/*.test.js, **/*.spec.js to .contextignore',
191
+ });
192
+ } else if (testFiles.length > 5) {
193
+ this.suggestions.suggestion.push({
194
+ type: 'test_files',
195
+ message: `${testFiles.length} test files included`,
196
+ count: testFiles.length,
197
+ tokens: this.metrics.testTokens,
198
+ percentage: (testRatio * 100).toFixed(1),
199
+ action: 'Consider excluding test files for smaller context',
200
+ });
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Analyze configuration files for exclusion suggestions
206
+ */
207
+ analyzeConfigFiles(files) {
208
+ logger.debug('Analyzing config files');
209
+
210
+ const configPatterns = ['.json', '.yaml', '.yml', '.toml', '.ini', '.conf'];
211
+ const excludePatterns = ['package.json', 'package-lock.json', 'tsconfig.json'];
212
+
213
+ const configFiles = files.filter((f) => {
214
+ const ext = path.extname(f.relativePath).toLowerCase();
215
+ const basename = path.basename(f.relativePath);
216
+ return configPatterns.includes(ext) && !excludePatterns.includes(basename);
217
+ });
218
+
219
+ this.metrics.configFiles = configFiles.length;
220
+ this.metrics.configTokens = configFiles.reduce((sum, f) => sum + f.tokens, 0);
221
+
222
+ const configRatio =
223
+ this.metrics.totalTokens > 0 ? this.metrics.configTokens / this.metrics.totalTokens : 0;
224
+
225
+ if (configFiles.length > 0 && configRatio > TOKEN_THRESHOLDS.HIGH_CONFIG_RATIO) {
226
+ const tokens = this.metrics.configTokens.toLocaleString();
227
+
228
+ this.suggestions.warning.push({
229
+ type: 'config_files',
230
+ message: `${configFiles.length} configuration files included (${tokens} tokens)`,
231
+ count: configFiles.length,
232
+ tokens: this.metrics.configTokens,
233
+ percentage: (configRatio * 100).toFixed(1),
234
+ action: 'Add **/*.json, **/*.yaml to .contextignore',
235
+ });
236
+ }
237
+ }
238
+
239
+ /**
240
+ * Analyze for duplicate code
241
+ */
242
+ async analyzeDuplicates(files) {
243
+ logger.debug('Analyzing duplicate code');
244
+
245
+ // Get file contents for analysis
246
+ const filesWithContent = this.getFilesWithContent(files);
247
+
248
+ const duplicates = this.duplicateDetector.detect(filesWithContent);
249
+
250
+ if (duplicates.length > 0) {
251
+ const stats = this.duplicateDetector.getStats();
252
+
253
+ this.suggestions.suggestion.push({
254
+ type: 'duplicate_code',
255
+ message: `${duplicates.length} potential duplicate code blocks detected`,
256
+ count: duplicates.length,
257
+ wastedTokens: stats.wastedTokens,
258
+ locations: duplicates.slice(0, 3).map((d) => ({
259
+ preview: d.preview,
260
+ files: d.files.slice(0, 3),
261
+ })),
262
+ });
263
+ }
264
+ }
265
+
266
+ /**
267
+ * Analyze for unused exports
268
+ */
269
+ async analyzeUnusedExports(files) {
270
+ logger.debug('Analyzing unused exports');
271
+
272
+ // Get file contents for analysis
273
+ const filesWithContent = this.getFilesWithContent(files);
274
+
275
+ const unused = this.unusedExportsDetector.detect(filesWithContent);
276
+
277
+ if (unused.length > 0) {
278
+ const stats = this.unusedExportsDetector.getStats();
279
+
280
+ // Group by file
281
+ const byFile = {};
282
+ for (const exp of unused) {
283
+ if (!byFile[exp.file]) {
284
+ byFile[exp.file] = [];
285
+ }
286
+ byFile[exp.file].push(exp.name);
287
+ }
288
+
289
+ this.suggestions.suggestion.push({
290
+ type: 'unused_exports',
291
+ message: `${unused.length} unused exports in ${stats.filesAffected} files`,
292
+ count: unused.length,
293
+ filesAffected: stats.filesAffected,
294
+ examples: Object.entries(byFile)
295
+ .slice(0, 3)
296
+ .map(([file, names]) => ({
297
+ file,
298
+ exports: names.slice(0, 5),
299
+ })),
300
+ });
301
+ }
302
+ }
303
+
304
+ /**
305
+ * Read file contents for analysis
306
+ */
307
+ getFilesWithContent(files) {
308
+ const filesWithContent = [];
309
+
310
+ for (const file of files) {
311
+ try {
312
+ // Only read code files
313
+ if (!FileUtils.isCode(file.path)) {
314
+ continue;
315
+ }
316
+
317
+ const content = fs.readFileSync(file.path, 'utf8');
318
+ filesWithContent.push({
319
+ path: file.path,
320
+ relativePath: file.relativePath,
321
+ content,
322
+ });
323
+ } catch (_error) {
324
+ // Skip files that can't be read
325
+ logger.debug(`Could not read file: ${file.relativePath}`);
326
+ }
327
+ }
328
+
329
+ return filesWithContent;
330
+ }
331
+
332
+ /**
333
+ * Calculate context efficiency score (0-100)
334
+ */
335
+ calculateEfficiencyScore() {
336
+ let score = 100;
337
+
338
+ // Penalize for large files
339
+ for (const file of this.metrics.largeFiles) {
340
+ if (file.tokens >= TOKEN_THRESHOLDS.CRITICAL_LARGE_FILE) {
341
+ score -= 15;
342
+ } else if (file.tokens >= TOKEN_THRESHOLDS.WARNING_LARGE_FILE) {
343
+ score -= 8;
344
+ } else if (file.tokens >= TOKEN_THRESHOLDS.SUGGESTION_LARGE_FILE) {
345
+ score -= 3;
346
+ }
347
+ }
348
+
349
+ // Penalize for test files included
350
+ const testRatio =
351
+ this.metrics.totalTokens > 0 ? this.metrics.testTokens / this.metrics.totalTokens : 0;
352
+ if (testRatio > 0.3) {
353
+ score -= 10;
354
+ } else if (testRatio > 0.2) {
355
+ score -= 5;
356
+ }
357
+
358
+ // Penalize for config files
359
+ const configRatio =
360
+ this.metrics.totalTokens > 0 ? this.metrics.configTokens / this.metrics.totalTokens : 0;
361
+ if (configRatio > 0.05) {
362
+ score -= 5;
363
+ }
364
+
365
+ // Penalize for duplicate code
366
+ const duplicateStats = this.duplicateDetector.getStats();
367
+ if (duplicateStats.totalGroups > 5) {
368
+ score -= 5;
369
+ } else if (duplicateStats.totalGroups > 2) {
370
+ score -= 2;
371
+ }
372
+
373
+ // Penalize for unused exports
374
+ const unusedStats = this.unusedExportsDetector.getStats();
375
+ if (unusedStats.totalUnused > 10) {
376
+ score -= 5;
377
+ } else if (unusedStats.totalUnused > 5) {
378
+ score -= 2;
379
+ }
380
+
381
+ return Math.max(0, Math.min(100, score));
382
+ }
383
+
384
+ /**
385
+ * Format suggestions for display
386
+ */
387
+ formatOutput(result) {
388
+ if (this.options.json) {
389
+ return JSON.stringify(result, null, 2);
390
+ }
391
+
392
+ const lines = [];
393
+
394
+ lines.push('');
395
+ lines.push('🤖 AI Context Suggestions:');
396
+ lines.push('═'.repeat(50));
397
+ lines.push(`📊 Context Efficiency Score: ${result.score}/100`);
398
+ lines.push('');
399
+
400
+ // Critical suggestions
401
+ if (result.suggestions.critical.length > 0) {
402
+ lines.push(`${ICONS.critical} Critical Suggestions:`);
403
+ for (const s of result.suggestions.critical) {
404
+ lines.push(` • ${s.message}`);
405
+ if (s.action && this.options.verbose) {
406
+ lines.push(` → ${s.action}`);
407
+ }
408
+ }
409
+ lines.push('');
410
+ }
411
+
412
+ // Warnings
413
+ if (result.suggestions.warning.length > 0) {
414
+ lines.push(`${ICONS.warning} Warnings:`);
415
+ for (const s of result.suggestions.warning) {
416
+ lines.push(` • ${s.message}`);
417
+ if (s.action) {
418
+ lines.push(` → ${s.action}`);
419
+ }
420
+ }
421
+ lines.push('');
422
+ }
423
+
424
+ // Suggestions
425
+ if (result.suggestions.suggestion.length > 0) {
426
+ lines.push(`${ICONS.suggestion} Suggestions:`);
427
+ for (const s of result.suggestions.suggestion) {
428
+ lines.push(` • ${s.message}`);
429
+ if (s.action && this.options.verbose) {
430
+ lines.push(` → ${s.action}`);
431
+ }
432
+ }
433
+ lines.push('');
434
+ }
435
+
436
+ // If no suggestions
437
+ if (
438
+ result.suggestions.critical.length === 0 &&
439
+ result.suggestions.warning.length === 0 &&
440
+ result.suggestions.suggestion.length === 0
441
+ ) {
442
+ lines.push(`${ICONS.info} No optimization suggestions found.`);
443
+ lines.push('Your context appears to be well-optimized!');
444
+ lines.push('');
445
+ }
446
+
447
+ return lines.join('\n');
448
+ }
449
+ }
450
+
451
+ export default AISuggester;