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.
- package/.ctxman/llm-profiles.json +1067 -39
- package/.ctxman/profiles/backend-team.json +13 -0
- package/.ctxman/profiles/default.json +12 -0
- package/.ctxman/profiles/devops-team.json +21 -0
- package/.ctxman/profiles/frontend-team.json +20 -0
- package/.ctxman/templates/bug-fix.json +17 -0
- package/.ctxman/templates/code-review.json +9 -0
- package/.ctxman/templates/documentation.json +9 -0
- package/.ctxman/templates/feature.json +9 -0
- package/.ctxman/templates/full-context.json +9 -0
- package/.ctxman/templates/refactoring.json +18 -0
- package/.ctxman/templates/testing.json +9 -0
- package/.ctxman/wizard-profiles/security-audit/profile.json +7 -1
- package/LICENSE +21 -0
- package/README.md +380 -136
- package/bin/cli.js +1880 -529
- package/bin/cm-ask.js +18 -13
- package/bin/cm-audit.js +124 -0
- package/bin/cm-count.js +151 -0
- package/bin/cm-gitingest.js +128 -115
- package/bin/cm-map.js +242 -0
- package/bin/cm-mcp.js +293 -0
- package/bin/cm-pack.js +405 -0
- package/bin/cm-policy.js +170 -0
- package/bin/cm-pr.js +248 -0
- package/bin/cm-search.js +178 -0
- package/bin/cm-update.js +128 -128
- package/bin/cm-why.js +197 -0
- package/bin/mcp-server.js +8 -24
- package/ctxman.js +237 -183
- package/examples/README.md +19 -4
- package/examples/github-actions/ctxman-check.yml +49 -0
- package/examples/wizard-profiles/security-audit/profile.json +7 -1
- package/index.d.ts +1422 -0
- package/index.js +55 -31
- package/lib/agents/pr-failure-agent.js +48 -45
- package/lib/analyzers/ai-suggester.js +451 -0
- package/lib/analyzers/dependency-scanner.js +505 -0
- package/lib/analyzers/duplicate-detector.js +264 -0
- package/lib/analyzers/go-method-analyzer.js +151 -110
- package/lib/analyzers/import-tracker.js +431 -0
- package/lib/analyzers/method-analyzer.js +1055 -434
- package/lib/analyzers/performance-dashboard.js +683 -0
- package/lib/analyzers/token-calculator.js +895 -504
- package/lib/analyzers/unused-exports-detector.js +336 -0
- package/lib/api/mcp/install.js +424 -0
- package/lib/api/mcp/prompts.js +281 -281
- package/lib/api/mcp/resources.js +300 -241
- package/lib/api/mcp/sandbox.js +89 -0
- package/lib/api/mcp/server.js +675 -312
- package/lib/api/mcp/start.js +125 -0
- package/lib/api/mcp/transports/HttpSseTransport.js +60 -52
- package/lib/api/rest/server.js +105 -28
- package/lib/cache/CacheManager.js +9 -17
- package/lib/core/Analyzer.js +16 -10
- package/lib/core/ContextBuilder.js +19 -28
- package/lib/core/Reporter.js +15 -14
- package/lib/core/Scanner.js +81 -12
- package/lib/count/index.js +100 -0
- package/lib/formatters/format-registry.js +394 -372
- package/lib/formatters/gitingest-formatter.js +656 -621
- package/lib/formatters/toon-formatter-v1.3.js +637 -632
- package/lib/formatters/toon-formatter.js +239 -240
- package/lib/integrations/git/BlameTracker.js +4 -4
- package/lib/integrations/git/DiffAnalyzer.js +38 -31
- package/lib/integrations/git/GitClient.js +71 -53
- package/lib/lsp/server.js +311 -0
- package/lib/map/graph.js +299 -0
- package/lib/map/imports.js +233 -0
- package/lib/map/index.js +33 -0
- package/lib/map/rank.js +253 -0
- package/lib/map/render.js +346 -0
- package/lib/map/resolver.js +574 -0
- package/lib/pack/budget.js +111 -0
- package/lib/pack/code-lexer.js +360 -0
- package/lib/pack/compress.js +414 -0
- package/lib/pack/index.js +354 -0
- package/lib/pack/renderers/common.js +134 -0
- package/lib/pack/renderers/index.js +24 -0
- package/lib/pack/renderers/json.js +47 -0
- package/lib/pack/renderers/markdown.js +54 -0
- package/lib/pack/renderers/plain.js +38 -0
- package/lib/pack/renderers/xml.js +85 -0
- package/lib/pack/select.js +177 -0
- package/lib/pack/split.js +182 -0
- package/lib/pack/tree.js +35 -0
- package/lib/parsers/gitignore-parser.js +113 -107
- package/lib/parsers/method-filter-parser.js +50 -33
- package/lib/plugins/ExporterPlugin.js +3 -3
- package/lib/plugins/LanguagePlugin.js +3 -3
- package/lib/plugins/PluginManager.js +7 -13
- package/lib/policy/audit.js +294 -0
- package/lib/policy/enforce.js +125 -0
- package/lib/policy/ignore-files.js +169 -0
- package/lib/policy/index.js +52 -0
- package/lib/policy/policy.js +489 -0
- package/lib/pr/diff-parser.js +267 -0
- package/lib/pr/method-mapper.js +148 -0
- package/lib/pr/pr-context.js +829 -0
- package/lib/pr/pr-git.js +149 -0
- package/lib/pr/pr-renderer.js +644 -0
- package/lib/pr/related-tests.js +296 -0
- package/lib/rag/DataSourcePlugin.js +5 -5
- package/lib/rag/EmbeddingProvider.js +19 -8
- package/lib/rag/Indexer.js +14 -5
- package/lib/rag/VectorStoreFactory.js +3 -3
- package/lib/rag/sources/FileDataSource.js +6 -6
- package/lib/rag/stores/BaseVectorStore.js +5 -5
- package/lib/rag/stores/ChromaDBStore.js +10 -10
- package/lib/rag/stores/LanceDBStore.js +29 -19
- package/lib/rag/stores/PineconeStore.js +11 -9
- package/lib/search/bm25.js +433 -0
- package/lib/search/code-units.js +201 -0
- package/lib/search/symbols.js +189 -0
- package/lib/ui/dashboard.js +120 -89
- package/lib/ui/index.js +1 -6
- package/lib/ui/init-wizard-ui.js +550 -0
- package/lib/ui/progress-bar.js +94 -88
- package/lib/ui/select-input.js +40 -32
- package/lib/ui/template-selector.js +99 -0
- package/lib/ui/wizard.js +374 -329
- package/lib/utils/claude-token-counter.js +151 -0
- package/lib/utils/cli-output.js +17 -0
- package/lib/utils/clipboard-utils.js +49 -50
- package/lib/utils/config-utils.js +81 -66
- package/lib/utils/context-versioning.js +532 -0
- package/lib/utils/error-handler.js +112 -112
- package/lib/utils/file-utils.js +153 -69
- package/lib/utils/format-converter.js +356 -283
- package/lib/utils/git-utils.js +366 -294
- package/lib/utils/llm-cost-estimator.js +281 -0
- package/lib/utils/llm-detector.js +242 -78
- package/lib/utils/logger.js +243 -237
- package/lib/utils/messages.js +355 -0
- package/lib/utils/multi-repo-manager.js +364 -0
- package/lib/utils/profile-manager.js +380 -0
- package/lib/utils/safe-read.js +61 -0
- package/lib/utils/secret-redactor.js +61 -0
- package/lib/utils/snapshot-manager.js +679 -0
- package/lib/utils/template-manager.js +378 -0
- package/lib/utils/token-utils.js +201 -170
- package/lib/utils/tokenizer-adapter.js +421 -377
- package/lib/utils/updater.js +539 -506
- package/lib/watch/FileWatcher.js +12 -8
- package/lib/watch/IncrementalAnalyzer.js +7 -7
- package/lib/why/coupling.js +81 -0
- package/lib/why/errors.js +23 -0
- package/lib/why/git.js +169 -0
- package/lib/why/history.js +108 -0
- package/lib/why/index.js +720 -0
- package/lib/why/ownership.js +79 -0
- package/lib/why/paths.js +207 -0
- package/lib/why/references.js +259 -0
- package/lib/why/render.js +722 -0
- package/lib/why/search.js +358 -0
- package/lib/why/target.js +389 -0
- package/lib/wizards/config-generator.js +255 -0
- package/lib/wizards/project-detector.js +571 -0
- package/package.json +44 -10
- package/.ctxman/logs/ctxman-2026-07-02.log +0 -156
- 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
|
@@ -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;
|