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.
- package/.ctxman/custom-profiles.example.json +20 -0
- package/.ctxman/llm-profiles.json +131 -0
- package/.ctxman/logs/ctxman-2026-07-02.log +156 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/0-b3dd1558-f95b-44b9-a154-18e8c330ae7a.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/1-44bff6d4-e552-4010-a769-e09ad822698d.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/10-37260794-5947-4ecb-b4c8-d732f58f40f1.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/11-0d52a883-5518-463c-9b6d-986c3af89dfd.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/12-ae354646-52f2-49c9-90fb-2e6ed8932663.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/13-5d7d5e57-27da-498b-b734-091b9d38da13.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/14-558ce928-810b-4252-a364-fdd1ee6f8a25.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/15-27e3a58f-8b64-43b8-a9f4-e3369446968b.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/16-a110c390-e5c8-4464-b976-bc7f5195fa15.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/17-97513225-53de-4216-b61a-6337bd0cc4ad.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/18-5ba450f0-c527-459c-bf4f-6d79b5e4285f.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/19-600d3a63-5225-49e9-b8b7-30e037b564ab.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/2-48a61b4e-92cf-4342-8376-914c1b99a9f9.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/20-121dd912-5410-410e-84ff-82c7a23604a0.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/3-bc6844ab-7ea3-4bb3-9ee1-538ce5664a9c.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/4-d5e1754f-dbb5-454e-a9be-2a7da8d7bcf9.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/5-4fae93df-0568-4b6f-8acf-8bef4bba6047.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/6-4b1b8b87-068e-49a3-ba2b-d8dae628b252.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/7-63d040fb-d2fa-4c0f-bed0-53411918a7b0.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/8-c5628cf6-264c-420f-aab8-bc96a9083591.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/9-c94570aa-5e53-4fcb-971f-cecc1d1d5492.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/1.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/10.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/11.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/12.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/13.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/14.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/15.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/16.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/17.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/18.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/19.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/2.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/20.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/21.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/3.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/4.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/5.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/6.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/7.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/8.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/9.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/000010010011011101000010de8621405d9d5cb06540dbf02c.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/0000101001011100011000003fb62a4aa8978974075c144f1c.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/000011010000100111001011c6cc824d628f3bb1f4e04cd5d0.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/001000001000011001011100d8f74349ada090b332cd1df669.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/01000001111010001000010045ab44467bb1e7e4d8ae06bd58.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/01000101101110111110011131ef904241a89e1ffb6ddaf8e3.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/010010110000010000011010fbfcc145c6a8a827b28057a07e.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1000011000101010001110003fb4f045c3835982efa1405324.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/100111011101110111101011f4396b410fa06af3b028d204e6.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/100111100111101000001100596b174e00995d6cc2969cc8dc.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1010001100011000110010102ea16b484d887c7c9725feffd6.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/101101100010101111110111c5230944d9b3af7301b6392dda.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1011011100100111111110105892bf41b5a530fedd7cf516f1.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/10111110000010001011110164e8774742bade87b483c10f36.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1100000100010110001110103ffaca40a2a42c85067ebaf9ae.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/110000010001110110101110664bde4c76a78dfc14b05e83b8.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/11001100111000010100110050645c4281b1f35b2022234f7f.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1101111011001100110111017214924a368f5719eab2246619.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1110000111001010001000002150c24dcab653d39d07032e63.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1110100000000100000011014d20534967a18b5c87721659ff.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/11110101111010000101011135a0114ef2b35dc6f1297a3381.lance +0 -0
- package/.ctxman/wizard-profiles/code-review/.contextignore +41 -0
- package/.ctxman/wizard-profiles/code-review/.contextinclude +30 -0
- package/.ctxman/wizard-profiles/code-review/.methodignore +26 -0
- package/.ctxman/wizard-profiles/code-review/.methodinclude +47 -0
- package/.ctxman/wizard-profiles/code-review/profile.json +26 -0
- package/.ctxman/wizard-profiles/documentation/.contextignore +21 -0
- package/.ctxman/wizard-profiles/documentation/.contextinclude +24 -0
- package/.ctxman/wizard-profiles/documentation/.methodignore +19 -0
- package/.ctxman/wizard-profiles/documentation/.methodinclude +44 -0
- package/.ctxman/wizard-profiles/documentation/profile.json +27 -0
- package/.ctxman/wizard-profiles/full/.contextignore +23 -0
- package/.ctxman/wizard-profiles/full/.contextinclude +38 -0
- package/.ctxman/wizard-profiles/full/.methodignore +5 -0
- package/.ctxman/wizard-profiles/full/.methodinclude +5 -0
- package/.ctxman/wizard-profiles/full/profile.json +28 -0
- package/.ctxman/wizard-profiles/llm-explain/.contextignore +25 -0
- package/.ctxman/wizard-profiles/llm-explain/.contextinclude +33 -0
- package/.ctxman/wizard-profiles/llm-explain/.methodignore +19 -0
- package/.ctxman/wizard-profiles/llm-explain/.methodinclude +48 -0
- package/.ctxman/wizard-profiles/llm-explain/profile.json +27 -0
- package/.ctxman/wizard-profiles/minimal/.contextignore +27 -0
- package/.ctxman/wizard-profiles/minimal/.contextinclude +14 -0
- package/.ctxman/wizard-profiles/minimal/.methodignore +22 -0
- package/.ctxman/wizard-profiles/minimal/.methodinclude +18 -0
- package/.ctxman/wizard-profiles/minimal/profile.json +26 -0
- package/.ctxman/wizard-profiles/security-audit/.contextignore +36 -0
- package/.ctxman/wizard-profiles/security-audit/.contextinclude +37 -0
- package/.ctxman/wizard-profiles/security-audit/.methodignore +29 -0
- package/.ctxman/wizard-profiles/security-audit/.methodinclude +60 -0
- package/.ctxman/wizard-profiles/security-audit/profile.json +34 -0
- package/README.md +1167 -0
- package/bin/cli.js +650 -0
- package/bin/cm-ask.js +95 -0
- package/bin/cm-gitingest.js +134 -0
- package/bin/cm-update.js +175 -0
- package/bin/mcp-server.js +31 -0
- package/ctxman.js +236 -0
- package/examples/README.md +298 -0
- package/examples/custom-llm-profiles.example.json +19 -0
- package/examples/wizard-profiles/code-review/.contextignore +41 -0
- package/examples/wizard-profiles/code-review/.contextinclude +30 -0
- package/examples/wizard-profiles/code-review/.methodignore +26 -0
- package/examples/wizard-profiles/code-review/.methodinclude +47 -0
- package/examples/wizard-profiles/code-review/profile.json +26 -0
- package/examples/wizard-profiles/documentation/.contextignore +21 -0
- package/examples/wizard-profiles/documentation/.contextinclude +24 -0
- package/examples/wizard-profiles/documentation/.methodignore +19 -0
- package/examples/wizard-profiles/documentation/.methodinclude +44 -0
- package/examples/wizard-profiles/documentation/profile.json +27 -0
- package/examples/wizard-profiles/full/.contextignore +23 -0
- package/examples/wizard-profiles/full/.contextinclude +38 -0
- package/examples/wizard-profiles/full/.methodignore +5 -0
- package/examples/wizard-profiles/full/.methodinclude +5 -0
- package/examples/wizard-profiles/full/profile.json +28 -0
- package/examples/wizard-profiles/llm-explain/.contextignore +25 -0
- package/examples/wizard-profiles/llm-explain/.contextinclude +33 -0
- package/examples/wizard-profiles/llm-explain/.methodignore +19 -0
- package/examples/wizard-profiles/llm-explain/.methodinclude +48 -0
- package/examples/wizard-profiles/llm-explain/profile.json +27 -0
- package/examples/wizard-profiles/minimal/.contextignore +27 -0
- package/examples/wizard-profiles/minimal/.contextinclude +14 -0
- package/examples/wizard-profiles/minimal/.methodignore +22 -0
- package/examples/wizard-profiles/minimal/.methodinclude +18 -0
- package/examples/wizard-profiles/minimal/profile.json +26 -0
- package/examples/wizard-profiles/security-audit/.contextignore +36 -0
- package/examples/wizard-profiles/security-audit/.contextinclude +37 -0
- package/examples/wizard-profiles/security-audit/.methodignore +29 -0
- package/examples/wizard-profiles/security-audit/.methodinclude +60 -0
- package/examples/wizard-profiles/security-audit/profile.json +34 -0
- package/index.js +68 -0
- package/lib/agents/pr-failure-agent.js +201 -0
- package/lib/analyzers/go-method-analyzer.js +126 -0
- package/lib/analyzers/method-analyzer.js +457 -0
- package/lib/analyzers/token-calculator.js +577 -0
- package/lib/api/mcp/prompts.js +319 -0
- package/lib/api/mcp/resources.js +269 -0
- package/lib/api/mcp/server.js +339 -0
- package/lib/api/mcp/transports/HttpSseTransport.js +63 -0
- package/lib/api/rest/server.js +405 -0
- package/lib/cache/CacheManager.js +388 -0
- package/lib/core/Analyzer.js +180 -0
- package/lib/core/ContextBuilder.js +325 -0
- package/lib/core/Reporter.js +270 -0
- package/lib/core/Scanner.js +129 -0
- package/lib/formatters/format-registry.js +419 -0
- package/lib/formatters/gitingest-formatter.js +746 -0
- package/lib/formatters/toon-formatter-v1.3.js +731 -0
- package/lib/formatters/toon-formatter.js +291 -0
- package/lib/integrations/git/BlameTracker.js +142 -0
- package/lib/integrations/git/DiffAnalyzer.js +260 -0
- package/lib/integrations/git/GitClient.js +336 -0
- package/lib/parsers/gitignore-parser.js +127 -0
- package/lib/parsers/method-filter-parser.js +50 -0
- package/lib/plugins/ExporterPlugin.js +102 -0
- package/lib/plugins/LanguagePlugin.js +100 -0
- package/lib/plugins/PluginManager.js +302 -0
- package/lib/rag/DataSourcePlugin.js +31 -0
- package/lib/rag/EmbeddingProvider.js +75 -0
- package/lib/rag/Indexer.js +83 -0
- package/lib/rag/VectorStoreFactory.js +24 -0
- package/lib/rag/sources/FileDataSource.js +70 -0
- package/lib/rag/stores/BaseVectorStore.js +43 -0
- package/lib/rag/stores/ChromaDBStore.js +85 -0
- package/lib/rag/stores/LanceDBStore.js +109 -0
- package/lib/rag/stores/PineconeStore.js +74 -0
- package/lib/ui/dashboard.js +109 -0
- package/lib/ui/index.js +15 -0
- package/lib/ui/progress-bar.js +110 -0
- package/lib/ui/select-input.js +51 -0
- package/lib/ui/wizard.js +359 -0
- package/lib/utils/clipboard-utils.js +66 -0
- package/lib/utils/config-utils.js +94 -0
- package/lib/utils/error-handler.js +145 -0
- package/lib/utils/file-utils.js +89 -0
- package/lib/utils/format-converter.js +325 -0
- package/lib/utils/git-utils.js +337 -0
- package/lib/utils/llm-detector.js +409 -0
- package/lib/utils/logger.js +278 -0
- package/lib/utils/token-utils.js +200 -0
- package/lib/utils/tokenizer-adapter.js +454 -0
- package/lib/utils/updater.js +565 -0
- package/lib/watch/FileWatcher.js +208 -0
- package/lib/watch/IncrementalAnalyzer.js +206 -0
- 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;
|