ctxman 3.0.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 (190) hide show
  1. package/.ctxman/custom-profiles.example.json +20 -0
  2. package/.ctxman/llm-profiles.json +131 -0
  3. package/.ctxman/logs/ctxman-2026-07-02.log +156 -0
  4. package/.ctxman/self-knowledge/documents.lance/_transactions/0-b3dd1558-f95b-44b9-a154-18e8c330ae7a.txn +0 -0
  5. package/.ctxman/self-knowledge/documents.lance/_transactions/1-44bff6d4-e552-4010-a769-e09ad822698d.txn +0 -0
  6. package/.ctxman/self-knowledge/documents.lance/_transactions/10-37260794-5947-4ecb-b4c8-d732f58f40f1.txn +0 -0
  7. package/.ctxman/self-knowledge/documents.lance/_transactions/11-0d52a883-5518-463c-9b6d-986c3af89dfd.txn +0 -0
  8. package/.ctxman/self-knowledge/documents.lance/_transactions/12-ae354646-52f2-49c9-90fb-2e6ed8932663.txn +0 -0
  9. package/.ctxman/self-knowledge/documents.lance/_transactions/13-5d7d5e57-27da-498b-b734-091b9d38da13.txn +0 -0
  10. package/.ctxman/self-knowledge/documents.lance/_transactions/14-558ce928-810b-4252-a364-fdd1ee6f8a25.txn +0 -0
  11. package/.ctxman/self-knowledge/documents.lance/_transactions/15-27e3a58f-8b64-43b8-a9f4-e3369446968b.txn +0 -0
  12. package/.ctxman/self-knowledge/documents.lance/_transactions/16-a110c390-e5c8-4464-b976-bc7f5195fa15.txn +0 -0
  13. package/.ctxman/self-knowledge/documents.lance/_transactions/17-97513225-53de-4216-b61a-6337bd0cc4ad.txn +0 -0
  14. package/.ctxman/self-knowledge/documents.lance/_transactions/18-5ba450f0-c527-459c-bf4f-6d79b5e4285f.txn +0 -0
  15. package/.ctxman/self-knowledge/documents.lance/_transactions/19-600d3a63-5225-49e9-b8b7-30e037b564ab.txn +0 -0
  16. package/.ctxman/self-knowledge/documents.lance/_transactions/2-48a61b4e-92cf-4342-8376-914c1b99a9f9.txn +0 -0
  17. package/.ctxman/self-knowledge/documents.lance/_transactions/20-121dd912-5410-410e-84ff-82c7a23604a0.txn +0 -0
  18. package/.ctxman/self-knowledge/documents.lance/_transactions/3-bc6844ab-7ea3-4bb3-9ee1-538ce5664a9c.txn +0 -0
  19. package/.ctxman/self-knowledge/documents.lance/_transactions/4-d5e1754f-dbb5-454e-a9be-2a7da8d7bcf9.txn +0 -0
  20. package/.ctxman/self-knowledge/documents.lance/_transactions/5-4fae93df-0568-4b6f-8acf-8bef4bba6047.txn +0 -0
  21. package/.ctxman/self-knowledge/documents.lance/_transactions/6-4b1b8b87-068e-49a3-ba2b-d8dae628b252.txn +0 -0
  22. package/.ctxman/self-knowledge/documents.lance/_transactions/7-63d040fb-d2fa-4c0f-bed0-53411918a7b0.txn +0 -0
  23. package/.ctxman/self-knowledge/documents.lance/_transactions/8-c5628cf6-264c-420f-aab8-bc96a9083591.txn +0 -0
  24. package/.ctxman/self-knowledge/documents.lance/_transactions/9-c94570aa-5e53-4fcb-971f-cecc1d1d5492.txn +0 -0
  25. package/.ctxman/self-knowledge/documents.lance/_versions/1.manifest +0 -0
  26. package/.ctxman/self-knowledge/documents.lance/_versions/10.manifest +0 -0
  27. package/.ctxman/self-knowledge/documents.lance/_versions/11.manifest +0 -0
  28. package/.ctxman/self-knowledge/documents.lance/_versions/12.manifest +0 -0
  29. package/.ctxman/self-knowledge/documents.lance/_versions/13.manifest +0 -0
  30. package/.ctxman/self-knowledge/documents.lance/_versions/14.manifest +0 -0
  31. package/.ctxman/self-knowledge/documents.lance/_versions/15.manifest +0 -0
  32. package/.ctxman/self-knowledge/documents.lance/_versions/16.manifest +0 -0
  33. package/.ctxman/self-knowledge/documents.lance/_versions/17.manifest +0 -0
  34. package/.ctxman/self-knowledge/documents.lance/_versions/18.manifest +0 -0
  35. package/.ctxman/self-knowledge/documents.lance/_versions/19.manifest +0 -0
  36. package/.ctxman/self-knowledge/documents.lance/_versions/2.manifest +0 -0
  37. package/.ctxman/self-knowledge/documents.lance/_versions/20.manifest +0 -0
  38. package/.ctxman/self-knowledge/documents.lance/_versions/21.manifest +0 -0
  39. package/.ctxman/self-knowledge/documents.lance/_versions/3.manifest +0 -0
  40. package/.ctxman/self-knowledge/documents.lance/_versions/4.manifest +0 -0
  41. package/.ctxman/self-knowledge/documents.lance/_versions/5.manifest +0 -0
  42. package/.ctxman/self-knowledge/documents.lance/_versions/6.manifest +0 -0
  43. package/.ctxman/self-knowledge/documents.lance/_versions/7.manifest +0 -0
  44. package/.ctxman/self-knowledge/documents.lance/_versions/8.manifest +0 -0
  45. package/.ctxman/self-knowledge/documents.lance/_versions/9.manifest +0 -0
  46. package/.ctxman/self-knowledge/documents.lance/data/000010010011011101000010de8621405d9d5cb06540dbf02c.lance +0 -0
  47. package/.ctxman/self-knowledge/documents.lance/data/0000101001011100011000003fb62a4aa8978974075c144f1c.lance +0 -0
  48. package/.ctxman/self-knowledge/documents.lance/data/000011010000100111001011c6cc824d628f3bb1f4e04cd5d0.lance +0 -0
  49. package/.ctxman/self-knowledge/documents.lance/data/001000001000011001011100d8f74349ada090b332cd1df669.lance +0 -0
  50. package/.ctxman/self-knowledge/documents.lance/data/01000001111010001000010045ab44467bb1e7e4d8ae06bd58.lance +0 -0
  51. package/.ctxman/self-knowledge/documents.lance/data/01000101101110111110011131ef904241a89e1ffb6ddaf8e3.lance +0 -0
  52. package/.ctxman/self-knowledge/documents.lance/data/010010110000010000011010fbfcc145c6a8a827b28057a07e.lance +0 -0
  53. package/.ctxman/self-knowledge/documents.lance/data/1000011000101010001110003fb4f045c3835982efa1405324.lance +0 -0
  54. package/.ctxman/self-knowledge/documents.lance/data/100111011101110111101011f4396b410fa06af3b028d204e6.lance +0 -0
  55. package/.ctxman/self-knowledge/documents.lance/data/100111100111101000001100596b174e00995d6cc2969cc8dc.lance +0 -0
  56. package/.ctxman/self-knowledge/documents.lance/data/1010001100011000110010102ea16b484d887c7c9725feffd6.lance +0 -0
  57. package/.ctxman/self-knowledge/documents.lance/data/101101100010101111110111c5230944d9b3af7301b6392dda.lance +0 -0
  58. package/.ctxman/self-knowledge/documents.lance/data/1011011100100111111110105892bf41b5a530fedd7cf516f1.lance +0 -0
  59. package/.ctxman/self-knowledge/documents.lance/data/10111110000010001011110164e8774742bade87b483c10f36.lance +0 -0
  60. package/.ctxman/self-knowledge/documents.lance/data/1100000100010110001110103ffaca40a2a42c85067ebaf9ae.lance +0 -0
  61. package/.ctxman/self-knowledge/documents.lance/data/110000010001110110101110664bde4c76a78dfc14b05e83b8.lance +0 -0
  62. package/.ctxman/self-knowledge/documents.lance/data/11001100111000010100110050645c4281b1f35b2022234f7f.lance +0 -0
  63. package/.ctxman/self-knowledge/documents.lance/data/1101111011001100110111017214924a368f5719eab2246619.lance +0 -0
  64. package/.ctxman/self-knowledge/documents.lance/data/1110000111001010001000002150c24dcab653d39d07032e63.lance +0 -0
  65. package/.ctxman/self-knowledge/documents.lance/data/1110100000000100000011014d20534967a18b5c87721659ff.lance +0 -0
  66. package/.ctxman/self-knowledge/documents.lance/data/11110101111010000101011135a0114ef2b35dc6f1297a3381.lance +0 -0
  67. package/.ctxman/wizard-profiles/code-review/.contextignore +41 -0
  68. package/.ctxman/wizard-profiles/code-review/.contextinclude +30 -0
  69. package/.ctxman/wizard-profiles/code-review/.methodignore +26 -0
  70. package/.ctxman/wizard-profiles/code-review/.methodinclude +47 -0
  71. package/.ctxman/wizard-profiles/code-review/profile.json +26 -0
  72. package/.ctxman/wizard-profiles/documentation/.contextignore +21 -0
  73. package/.ctxman/wizard-profiles/documentation/.contextinclude +24 -0
  74. package/.ctxman/wizard-profiles/documentation/.methodignore +19 -0
  75. package/.ctxman/wizard-profiles/documentation/.methodinclude +44 -0
  76. package/.ctxman/wizard-profiles/documentation/profile.json +27 -0
  77. package/.ctxman/wizard-profiles/full/.contextignore +23 -0
  78. package/.ctxman/wizard-profiles/full/.contextinclude +38 -0
  79. package/.ctxman/wizard-profiles/full/.methodignore +5 -0
  80. package/.ctxman/wizard-profiles/full/.methodinclude +5 -0
  81. package/.ctxman/wizard-profiles/full/profile.json +28 -0
  82. package/.ctxman/wizard-profiles/llm-explain/.contextignore +25 -0
  83. package/.ctxman/wizard-profiles/llm-explain/.contextinclude +33 -0
  84. package/.ctxman/wizard-profiles/llm-explain/.methodignore +19 -0
  85. package/.ctxman/wizard-profiles/llm-explain/.methodinclude +48 -0
  86. package/.ctxman/wizard-profiles/llm-explain/profile.json +27 -0
  87. package/.ctxman/wizard-profiles/minimal/.contextignore +27 -0
  88. package/.ctxman/wizard-profiles/minimal/.contextinclude +14 -0
  89. package/.ctxman/wizard-profiles/minimal/.methodignore +22 -0
  90. package/.ctxman/wizard-profiles/minimal/.methodinclude +18 -0
  91. package/.ctxman/wizard-profiles/minimal/profile.json +26 -0
  92. package/.ctxman/wizard-profiles/security-audit/.contextignore +36 -0
  93. package/.ctxman/wizard-profiles/security-audit/.contextinclude +37 -0
  94. package/.ctxman/wizard-profiles/security-audit/.methodignore +29 -0
  95. package/.ctxman/wizard-profiles/security-audit/.methodinclude +60 -0
  96. package/.ctxman/wizard-profiles/security-audit/profile.json +34 -0
  97. package/README.md +1167 -0
  98. package/bin/cli.js +650 -0
  99. package/bin/cm-ask.js +95 -0
  100. package/bin/cm-gitingest.js +134 -0
  101. package/bin/cm-update.js +175 -0
  102. package/bin/mcp-server.js +31 -0
  103. package/ctxman.js +236 -0
  104. package/examples/README.md +298 -0
  105. package/examples/custom-llm-profiles.example.json +19 -0
  106. package/examples/wizard-profiles/code-review/.contextignore +41 -0
  107. package/examples/wizard-profiles/code-review/.contextinclude +30 -0
  108. package/examples/wizard-profiles/code-review/.methodignore +26 -0
  109. package/examples/wizard-profiles/code-review/.methodinclude +47 -0
  110. package/examples/wizard-profiles/code-review/profile.json +26 -0
  111. package/examples/wizard-profiles/documentation/.contextignore +21 -0
  112. package/examples/wizard-profiles/documentation/.contextinclude +24 -0
  113. package/examples/wizard-profiles/documentation/.methodignore +19 -0
  114. package/examples/wizard-profiles/documentation/.methodinclude +44 -0
  115. package/examples/wizard-profiles/documentation/profile.json +27 -0
  116. package/examples/wizard-profiles/full/.contextignore +23 -0
  117. package/examples/wizard-profiles/full/.contextinclude +38 -0
  118. package/examples/wizard-profiles/full/.methodignore +5 -0
  119. package/examples/wizard-profiles/full/.methodinclude +5 -0
  120. package/examples/wizard-profiles/full/profile.json +28 -0
  121. package/examples/wizard-profiles/llm-explain/.contextignore +25 -0
  122. package/examples/wizard-profiles/llm-explain/.contextinclude +33 -0
  123. package/examples/wizard-profiles/llm-explain/.methodignore +19 -0
  124. package/examples/wizard-profiles/llm-explain/.methodinclude +48 -0
  125. package/examples/wizard-profiles/llm-explain/profile.json +27 -0
  126. package/examples/wizard-profiles/minimal/.contextignore +27 -0
  127. package/examples/wizard-profiles/minimal/.contextinclude +14 -0
  128. package/examples/wizard-profiles/minimal/.methodignore +22 -0
  129. package/examples/wizard-profiles/minimal/.methodinclude +18 -0
  130. package/examples/wizard-profiles/minimal/profile.json +26 -0
  131. package/examples/wizard-profiles/security-audit/.contextignore +36 -0
  132. package/examples/wizard-profiles/security-audit/.contextinclude +37 -0
  133. package/examples/wizard-profiles/security-audit/.methodignore +29 -0
  134. package/examples/wizard-profiles/security-audit/.methodinclude +60 -0
  135. package/examples/wizard-profiles/security-audit/profile.json +34 -0
  136. package/index.js +68 -0
  137. package/lib/agents/pr-failure-agent.js +201 -0
  138. package/lib/analyzers/go-method-analyzer.js +126 -0
  139. package/lib/analyzers/method-analyzer.js +457 -0
  140. package/lib/analyzers/token-calculator.js +577 -0
  141. package/lib/api/mcp/prompts.js +319 -0
  142. package/lib/api/mcp/resources.js +269 -0
  143. package/lib/api/mcp/server.js +339 -0
  144. package/lib/api/mcp/transports/HttpSseTransport.js +63 -0
  145. package/lib/api/rest/server.js +405 -0
  146. package/lib/cache/CacheManager.js +388 -0
  147. package/lib/core/Analyzer.js +180 -0
  148. package/lib/core/ContextBuilder.js +325 -0
  149. package/lib/core/Reporter.js +270 -0
  150. package/lib/core/Scanner.js +129 -0
  151. package/lib/formatters/format-registry.js +419 -0
  152. package/lib/formatters/gitingest-formatter.js +746 -0
  153. package/lib/formatters/toon-formatter-v1.3.js +731 -0
  154. package/lib/formatters/toon-formatter.js +291 -0
  155. package/lib/integrations/git/BlameTracker.js +142 -0
  156. package/lib/integrations/git/DiffAnalyzer.js +260 -0
  157. package/lib/integrations/git/GitClient.js +336 -0
  158. package/lib/parsers/gitignore-parser.js +127 -0
  159. package/lib/parsers/method-filter-parser.js +50 -0
  160. package/lib/plugins/ExporterPlugin.js +102 -0
  161. package/lib/plugins/LanguagePlugin.js +100 -0
  162. package/lib/plugins/PluginManager.js +302 -0
  163. package/lib/rag/DataSourcePlugin.js +31 -0
  164. package/lib/rag/EmbeddingProvider.js +75 -0
  165. package/lib/rag/Indexer.js +83 -0
  166. package/lib/rag/VectorStoreFactory.js +24 -0
  167. package/lib/rag/sources/FileDataSource.js +70 -0
  168. package/lib/rag/stores/BaseVectorStore.js +43 -0
  169. package/lib/rag/stores/ChromaDBStore.js +85 -0
  170. package/lib/rag/stores/LanceDBStore.js +109 -0
  171. package/lib/rag/stores/PineconeStore.js +74 -0
  172. package/lib/ui/dashboard.js +109 -0
  173. package/lib/ui/index.js +15 -0
  174. package/lib/ui/progress-bar.js +110 -0
  175. package/lib/ui/select-input.js +51 -0
  176. package/lib/ui/wizard.js +359 -0
  177. package/lib/utils/clipboard-utils.js +66 -0
  178. package/lib/utils/config-utils.js +94 -0
  179. package/lib/utils/error-handler.js +145 -0
  180. package/lib/utils/file-utils.js +89 -0
  181. package/lib/utils/format-converter.js +325 -0
  182. package/lib/utils/git-utils.js +337 -0
  183. package/lib/utils/llm-detector.js +409 -0
  184. package/lib/utils/logger.js +278 -0
  185. package/lib/utils/token-utils.js +200 -0
  186. package/lib/utils/tokenizer-adapter.js +454 -0
  187. package/lib/utils/updater.js +565 -0
  188. package/lib/watch/FileWatcher.js +208 -0
  189. package/lib/watch/IncrementalAnalyzer.js +206 -0
  190. package/package.json +118 -0
@@ -0,0 +1,325 @@
1
+ /**
2
+ * ContextBuilder - Smart Context Generation Module
3
+ * v3.0.0 - Modular architecture
4
+ *
5
+ * Responsibilities:
6
+ * - Build optimized context for LLMs
7
+ * - Apply smart filtering strategies
8
+ * - Generate context in multiple formats
9
+ * - Support use-case templates
10
+ * - Optimize for target LLM models
11
+ */
12
+
13
+ import { LLMDetector } from '../utils/llm-detector.js';
14
+ import { getLogger } from '../utils/logger.js';
15
+
16
+ const logger = getLogger('ContextBuilder');
17
+
18
+ export class ContextBuilder {
19
+ constructor(options = {}) {
20
+ this.options = {
21
+ targetModel: null,
22
+ targetTokens: null,
23
+ useCase: 'custom',
24
+ includeTests: true,
25
+ includeDocumentation: true,
26
+ priorityStrategy: 'balanced', // balanced, changed-first, core-first
27
+ ...options
28
+ };
29
+ }
30
+
31
+ /**
32
+ * Build context from analysis results
33
+ * @param {AnalysisResult} analysisResult
34
+ * @returns {Context}
35
+ */
36
+ build(analysisResult) {
37
+ logger.info('Building context...');
38
+
39
+ const { files, stats } = analysisResult;
40
+
41
+ // Apply smart filtering if target tokens specified
42
+ let filteredFiles = files;
43
+ if (this.options.targetTokens) {
44
+ filteredFiles = this.applySmartFiltering(files, this.options.targetTokens);
45
+ }
46
+
47
+ // Build context structure
48
+ const context = {
49
+ metadata: this.buildMetadata(stats),
50
+ files: this.buildFileList(filteredFiles),
51
+ methods: this.options.methodLevel ? this.buildMethodList(filteredFiles) : null,
52
+ statistics: stats
53
+ };
54
+
55
+ // Optimize for target LLM if specified
56
+ if (this.options.targetModel) {
57
+ return this.optimizeForLLM(context);
58
+ }
59
+
60
+ return context;
61
+ }
62
+
63
+ /**
64
+ * Build context metadata
65
+ * @param {object} stats
66
+ * @returns {object}
67
+ */
68
+ buildMetadata(stats) {
69
+ return {
70
+ generatedAt: new Date().toISOString(),
71
+ totalFiles: stats.totalFiles,
72
+ totalTokens: stats.totalTokens,
73
+ totalSize: stats.totalSize,
74
+ targetModel: this.options.targetModel,
75
+ useCase: this.options.useCase,
76
+ configuration: {
77
+ includeTests: this.options.includeTests,
78
+ includeDocumentation: this.options.includeDocumentation,
79
+ methodLevel: this.options.methodLevel
80
+ }
81
+ };
82
+ }
83
+
84
+ /**
85
+ * Build file list structure
86
+ * @param {Array<FileAnalysis>} files
87
+ * @returns {object}
88
+ */
89
+ buildFileList(files) {
90
+ const filesByDirectory = {};
91
+
92
+ for (const file of files) {
93
+ const dirPath = file.relativePath.includes('/')
94
+ ? file.relativePath.substring(0, file.relativePath.lastIndexOf('/'))
95
+ : '/';
96
+
97
+ if (!filesByDirectory[dirPath]) {
98
+ filesByDirectory[dirPath] = [];
99
+ }
100
+
101
+ filesByDirectory[dirPath].push({
102
+ name: file.name,
103
+ path: file.relativePath,
104
+ tokens: file.tokens,
105
+ size: file.size,
106
+ language: file.language
107
+ });
108
+ }
109
+
110
+ return filesByDirectory;
111
+ }
112
+
113
+ /**
114
+ * Build method list structure
115
+ * @param {Array<FileAnalysis>} files
116
+ * @returns {object}
117
+ */
118
+ buildMethodList(files) {
119
+ const methods = {};
120
+
121
+ for (const file of files) {
122
+ if (file.methods && file.methods.length > 0) {
123
+ methods[file.relativePath] = file.methods.map(method => ({
124
+ name: method.name,
125
+ line: method.line,
126
+ tokens: method.tokens || 0,
127
+ type: method.type || 'function'
128
+ }));
129
+ }
130
+ }
131
+
132
+ return methods;
133
+ }
134
+
135
+ /**
136
+ * Apply smart filtering to fit target token budget
137
+ * @param {Array<FileAnalysis>} files
138
+ * @param {number} targetTokens
139
+ * @returns {Array<FileAnalysis>}
140
+ */
141
+ applySmartFiltering(files, targetTokens) {
142
+ logger.info(`Applying smart filtering: target ${targetTokens} tokens`);
143
+
144
+ // Calculate current total
145
+ const currentTotal = files.reduce((sum, f) => sum + f.tokens, 0);
146
+
147
+ if (currentTotal <= targetTokens) {
148
+ logger.info('All files fit within target');
149
+ return files;
150
+ }
151
+
152
+ // Apply priority strategy
153
+ const prioritized = this.prioritizeFiles(files);
154
+
155
+ // Select files until token budget reached
156
+ const selected = [];
157
+ let runningTotal = 0;
158
+
159
+ for (const file of prioritized) {
160
+ if (runningTotal + file.tokens <= targetTokens) {
161
+ selected.push(file);
162
+ runningTotal += file.tokens;
163
+ } else {
164
+ // Check if we can include part of the file
165
+ const remainingBudget = targetTokens - runningTotal;
166
+ if (remainingBudget > 1000) { // Minimum 1k tokens
167
+ logger.info(`Partial file inclusion: ${file.relativePath} (${remainingBudget} tokens)`);
168
+ // Note: Actual implementation would truncate file content
169
+ selected.push(file);
170
+ }
171
+ break;
172
+ }
173
+ }
174
+
175
+ logger.info(`Filtered ${files.length} → ${selected.length} files (${runningTotal} tokens)`);
176
+ return selected;
177
+ }
178
+
179
+ /**
180
+ * Prioritize files based on strategy
181
+ * @param {Array<FileAnalysis>} files
182
+ * @returns {Array<FileAnalysis>}
183
+ */
184
+ prioritizeFiles(files) {
185
+ const strategy = this.options.priorityStrategy;
186
+
187
+ switch (strategy) {
188
+ case 'changed-first':
189
+ // Prioritize recently changed files (requires git metadata)
190
+ return files.sort((a, b) => {
191
+ const aModified = a.modified || 0;
192
+ const bModified = b.modified || 0;
193
+ return bModified - aModified;
194
+ });
195
+
196
+ case 'core-first':
197
+ // Prioritize core application files (heuristic-based)
198
+ return files.sort((a, b) => {
199
+ const aCoreScore = this.calculateCoreScore(a);
200
+ const bCoreScore = this.calculateCoreScore(b);
201
+ return bCoreScore - aCoreScore;
202
+ });
203
+
204
+ case 'balanced':
205
+ default:
206
+ // Balance between size and importance
207
+ return files.sort((a, b) => {
208
+ const aScore = this.calculateBalancedScore(a);
209
+ const bScore = this.calculateBalancedScore(b);
210
+ return bScore - aScore;
211
+ });
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Calculate core importance score (heuristic)
217
+ * @param {FileAnalysis} file
218
+ * @returns {number}
219
+ */
220
+ calculateCoreScore(file) {
221
+ let score = 0;
222
+
223
+ const path = file.relativePath.toLowerCase();
224
+
225
+ // High priority paths
226
+ if (path.includes('src/') || path.includes('lib/')) score += 10;
227
+ if (path.includes('core/') || path.includes('main')) score += 8;
228
+ if (path.includes('index.') || path.includes('app.')) score += 7;
229
+
230
+ // Medium priority
231
+ if (path.includes('utils/') || path.includes('helpers/')) score += 5;
232
+ if (path.includes('components/')) score += 4;
233
+
234
+ // Lower priority
235
+ if (path.includes('test')) score -= 5;
236
+ if (path.includes('docs/') || path.includes('examples/')) score -= 3;
237
+
238
+ return score;
239
+ }
240
+
241
+ /**
242
+ * Calculate balanced score
243
+ * @param {FileAnalysis} file
244
+ * @returns {number}
245
+ */
246
+ calculateBalancedScore(file) {
247
+ const coreScore = this.calculateCoreScore(file);
248
+ const sizeScore = Math.log(file.tokens + 1); // Logarithmic to avoid dominating
249
+ return coreScore + sizeScore;
250
+ }
251
+
252
+ /**
253
+ * Optimize context for target LLM
254
+ * @param {Context} context
255
+ * @returns {Context}
256
+ */
257
+ optimizeForLLM(context) {
258
+ const profile = LLMDetector.getProfile(this.options.targetModel);
259
+
260
+ logger.info(`Optimizing for ${profile.name} (${profile.contextWindow} tokens)`);
261
+
262
+ // Apply LLM-specific optimizations
263
+ const optimized = { ...context };
264
+
265
+ // Recommend format
266
+ optimized.metadata.recommendedFormat = profile.preferredFormat;
267
+
268
+ // Check if chunking needed
269
+ const totalTokens = context.statistics.totalTokens;
270
+ optimized.metadata.fitsInContext = totalTokens <= profile.maxRecommendedInput;
271
+ optimized.metadata.chunksNeeded = optimized.metadata.fitsInContext
272
+ ? 1
273
+ : Math.ceil(totalTokens / profile.maxRecommendedInput);
274
+
275
+ // Add LLM-specific metadata
276
+ optimized.metadata.targetLLM = {
277
+ name: profile.name,
278
+ vendor: profile.vendor,
279
+ contextWindow: profile.contextWindow,
280
+ recommendedInput: profile.maxRecommendedInput
281
+ };
282
+
283
+ return optimized;
284
+ }
285
+
286
+ /**
287
+ * Generate compact file paths (for TOON format)
288
+ * @param {Context} context
289
+ * @returns {object}
290
+ */
291
+ generateCompactPaths(context) {
292
+ const compact = {};
293
+
294
+ for (const [dir, files] of Object.entries(context.files)) {
295
+ const fileNames = files.map(f => f.name);
296
+ compact[dir] = fileNames;
297
+ }
298
+
299
+ return compact;
300
+ }
301
+
302
+ /**
303
+ * Get context summary
304
+ * @param {Context} context
305
+ * @returns {string}
306
+ */
307
+ getSummary(context) {
308
+ const meta = context.metadata;
309
+ const stats = context.statistics;
310
+
311
+ let summary = `Context Summary:\n`;
312
+ summary += ` Files: ${meta.totalFiles}\n`;
313
+ summary += ` Tokens: ${meta.totalTokens.toLocaleString()}\n`;
314
+ summary += ` Size: ${(meta.totalSize / 1024).toFixed(1)} KB\n`;
315
+
316
+ if (meta.targetLLM) {
317
+ summary += ` Target: ${meta.targetLLM.name}\n`;
318
+ summary += ` Fits in context: ${meta.fitsInContext ? 'Yes' : `No (${meta.chunksNeeded} chunks)`}\n`;
319
+ }
320
+
321
+ return summary;
322
+ }
323
+ }
324
+
325
+ export default ContextBuilder;
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Reporter - Report Generation Module
3
+ * v3.0.0 - Modular architecture
4
+ *
5
+ * Responsibilities:
6
+ * - Generate reports in multiple formats
7
+ * - Display statistics and summaries
8
+ * - Export to files or clipboard
9
+ * - Support custom report templates
10
+ */
11
+
12
+ import fs from 'fs';
13
+ import path from 'path';
14
+ import FormatRegistry from '../formatters/format-registry.js';
15
+ import ClipboardUtils from '../utils/clipboard-utils.js';
16
+ import { getLogger } from '../utils/logger.js';
17
+
18
+ const logger = getLogger('Reporter');
19
+
20
+ export class Reporter {
21
+ constructor(options = {}) {
22
+ this.options = {
23
+ format: 'toon',
24
+ outputPath: null,
25
+ clipboard: false,
26
+ verbose: true,
27
+ ...options
28
+ };
29
+
30
+ this.formatRegistry = new FormatRegistry();
31
+ }
32
+
33
+ /**
34
+ * Generate and output report
35
+ * @param {Context} context
36
+ * @param {object} stats
37
+ */
38
+ async report(context, stats) {
39
+ logger.info('Generating report...');
40
+
41
+ // Console output
42
+ if (this.options.verbose) {
43
+ this.printConsoleReport(stats);
44
+ }
45
+
46
+ // File export
47
+ if (this.options.outputPath) {
48
+ await this.exportToFile(context, this.options.outputPath);
49
+ }
50
+
51
+ // Clipboard
52
+ if (this.options.clipboard) {
53
+ await this.exportToClipboard(context);
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Print console report
59
+ * @param {object} stats
60
+ */
61
+ printConsoleReport(stats) {
62
+ console.log('\n📊 ANALYSIS RESULTS');
63
+ console.log('═'.repeat(60));
64
+ console.log();
65
+
66
+ // Summary
67
+ console.log('📁 FILES & TOKENS:');
68
+ console.log(` Total Files: ${stats.totalFiles}`);
69
+ console.log(` Total Tokens: ${stats.totalTokens.toLocaleString()}`);
70
+ console.log(` Total Size: ${(stats.totalSize / 1024).toFixed(1)} KB`);
71
+ console.log(` Avg Tokens/File: ${this.getAverageTokens(stats)}`);
72
+ console.log();
73
+
74
+ // Language distribution
75
+ if (Object.keys(stats.byLanguage).length > 0) {
76
+ console.log('🌍 LANGUAGE DISTRIBUTION:');
77
+ const languages = Object.entries(stats.byLanguage)
78
+ .sort((a, b) => b[1].tokens - a[1].tokens)
79
+ .slice(0, 5);
80
+
81
+ for (const [lang, langStats] of languages) {
82
+ const percentage = (langStats.tokens / stats.totalTokens * 100).toFixed(1);
83
+ const bar = '▓'.repeat(Math.round(percentage / 2));
84
+ console.log(` ${lang.padEnd(15)} ${percentage.padStart(5)}% ${bar}`);
85
+ }
86
+ console.log();
87
+ }
88
+
89
+ // Largest files
90
+ if (stats.largestFiles && stats.largestFiles.length > 0) {
91
+ console.log('🏆 LARGEST FILES (by tokens):');
92
+ for (let i = 0; i < Math.min(5, stats.largestFiles.length); i++) {
93
+ const file = stats.largestFiles[i];
94
+ const percentage = (file.tokens / stats.totalTokens * 100).toFixed(1);
95
+ console.log(` ${(i + 1)}. ${file.path.padEnd(40)} ${file.tokens.toLocaleString().padStart(8)} tokens (${percentage}%)`);
96
+ }
97
+ console.log();
98
+ }
99
+
100
+ // Method stats
101
+ if (stats.totalMethods > 0) {
102
+ console.log('🔧 METHOD ANALYSIS:');
103
+ console.log(` Total Methods: ${stats.totalMethods}`);
104
+ if (stats.includedMethods !== undefined) {
105
+ console.log(` Included Methods: ${stats.includedMethods}`);
106
+ console.log(` Filtered: ${stats.totalMethods - stats.includedMethods}`);
107
+ }
108
+ console.log();
109
+ }
110
+
111
+ // Performance stats
112
+ if (stats.analysisTime) {
113
+ console.log('⚡ PERFORMANCE:');
114
+ console.log(` Analysis Time: ${stats.analysisTime}ms`);
115
+ if (stats.cacheHits !== undefined) {
116
+ const totalCache = stats.cacheHits + stats.cacheMisses;
117
+ const hitRate = totalCache > 0 ? (stats.cacheHits / totalCache * 100).toFixed(1) : 0;
118
+ console.log(` Cache Hit Rate: ${hitRate}% (${stats.cacheHits}/${totalCache})`);
119
+ }
120
+ console.log();
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Export context to file
126
+ * @param {Context} context
127
+ * @param {string} outputPath
128
+ */
129
+ async exportToFile(context, outputPath) {
130
+ try {
131
+ const format = this.detectFormatFromPath(outputPath);
132
+ const content = this.formatRegistry.encode(format, context);
133
+
134
+ fs.writeFileSync(outputPath, content, 'utf-8');
135
+
136
+ const size = (Buffer.byteLength(content, 'utf-8') / 1024).toFixed(1);
137
+ logger.info(`Exported to ${outputPath} (${size} KB, ${format} format)`);
138
+
139
+ console.log(`💾 Report saved: ${outputPath}`);
140
+ console.log(` Format: ${format.toUpperCase()}`);
141
+ console.log(` Size: ${size} KB`);
142
+
143
+ } catch (error) {
144
+ logger.error(`Failed to export to file: ${error.message}`);
145
+ throw error;
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Export context to clipboard
151
+ * @param {Context} context
152
+ */
153
+ async exportToClipboard(context) {
154
+ try {
155
+ const format = this.options.format || 'toon';
156
+ const content = this.formatRegistry.encode(format, context);
157
+
158
+ const success = ClipboardUtils.copy(content);
159
+
160
+ if (success) {
161
+ const size = (Buffer.byteLength(content, 'utf-8') / 1024).toFixed(1);
162
+ logger.info(`Copied to clipboard (${size} KB, ${format} format)`);
163
+
164
+ console.log(`📋 Context copied to clipboard!`);
165
+ console.log(` Format: ${format.toUpperCase()}`);
166
+ console.log(` Size: ${size} KB`);
167
+ } else {
168
+ logger.warn('Clipboard copy failed, falling back to file export');
169
+ const fallbackPath = path.join(process.cwd(), `context-${Date.now()}.${format}`);
170
+ await this.exportToFile(context, fallbackPath);
171
+ }
172
+
173
+ } catch (error) {
174
+ logger.error(`Failed to export to clipboard: ${error.message}`);
175
+ throw error;
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Detect output format from file path
181
+ * @param {string} outputPath
182
+ * @returns {string}
183
+ */
184
+ detectFormatFromPath(outputPath) {
185
+ const ext = path.extname(outputPath).toLowerCase();
186
+
187
+ const formatMap = {
188
+ '.toon': 'toon',
189
+ '.json': 'json',
190
+ '.yaml': 'yaml',
191
+ '.yml': 'yaml',
192
+ '.md': 'markdown',
193
+ '.csv': 'csv',
194
+ '.xml': 'xml',
195
+ '.txt': 'gitingest'
196
+ };
197
+
198
+ return formatMap[ext] || this.options.format || 'toon';
199
+ }
200
+
201
+ /**
202
+ * Get average tokens per file
203
+ * @param {object} stats
204
+ * @returns {string}
205
+ */
206
+ getAverageTokens(stats) {
207
+ if (stats.totalFiles === 0) return 'N/A';
208
+ const avg = Math.round(stats.totalTokens / stats.totalFiles);
209
+ return avg.toLocaleString();
210
+ }
211
+
212
+ /**
213
+ * Generate summary text
214
+ * @param {Context} context
215
+ * @returns {string}
216
+ */
217
+ generateSummary(context) {
218
+ const meta = context.metadata;
219
+ const stats = context.statistics;
220
+
221
+ let summary = 'Ctxman Analysis Summary\n';
222
+ summary += '='.repeat(50) + '\n\n';
223
+
224
+ summary += `Generated: ${new Date(meta.generatedAt).toLocaleString()}\n`;
225
+ summary += `Files: ${meta.totalFiles}\n`;
226
+ summary += `Tokens: ${meta.totalTokens.toLocaleString()}\n`;
227
+ summary += `Size: ${(meta.totalSize / 1024).toFixed(1)} KB\n\n`;
228
+
229
+ if (meta.targetLLM) {
230
+ summary += `Target LLM: ${meta.targetLLM.name}\n`;
231
+ summary += `Fits in context: ${meta.fitsInContext ? 'Yes' : 'No'}\n`;
232
+ if (!meta.fitsInContext) {
233
+ summary += `Chunks needed: ${meta.chunksNeeded}\n`;
234
+ }
235
+ summary += '\n';
236
+ }
237
+
238
+ summary += `Use Case: ${meta.useCase}\n`;
239
+ summary += `Method Analysis: ${meta.configuration.methodLevel ? 'Enabled' : 'Disabled'}\n`;
240
+
241
+ return summary;
242
+ }
243
+
244
+ /**
245
+ * Export in multiple formats
246
+ * @param {Context} context
247
+ * @param {Array<string>} formats
248
+ */
249
+ async exportMultiple(context, formats) {
250
+ const results = [];
251
+
252
+ for (const format of formats) {
253
+ const outputPath = path.join(
254
+ process.cwd(),
255
+ `context-${Date.now()}.${format}`
256
+ );
257
+
258
+ try {
259
+ await this.exportToFile(context, outputPath);
260
+ results.push({ format, path: outputPath, success: true });
261
+ } catch (error) {
262
+ results.push({ format, error: error.message, success: false });
263
+ }
264
+ }
265
+
266
+ return results;
267
+ }
268
+ }
269
+
270
+ export default Reporter;