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,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bilingual Messages Utility
|
|
3
|
+
* v3.0.0 - Internationalization support for CLI messages
|
|
4
|
+
*
|
|
5
|
+
* Supports English (en) and Turkish (tr) messages.
|
|
6
|
+
* Language is detected from LANG environment variable or can be set explicitly.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Message codes and their translations
|
|
10
|
+
const messages = {
|
|
11
|
+
// CLI Help Messages
|
|
12
|
+
'cli.help.usage': {
|
|
13
|
+
en: 'Usage',
|
|
14
|
+
tr: 'Kullanım',
|
|
15
|
+
},
|
|
16
|
+
'cli.help.options': {
|
|
17
|
+
en: 'Options',
|
|
18
|
+
tr: 'Seçenekler',
|
|
19
|
+
},
|
|
20
|
+
'cli.help.examples': {
|
|
21
|
+
en: 'Examples',
|
|
22
|
+
tr: 'Örnekler',
|
|
23
|
+
},
|
|
24
|
+
'cli.help.description': {
|
|
25
|
+
en: 'Description',
|
|
26
|
+
tr: 'Açıklama',
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
// Analysis Messages
|
|
30
|
+
'analysis.start': {
|
|
31
|
+
en: 'Starting analysis...',
|
|
32
|
+
tr: 'Analiz başlatılıyor...',
|
|
33
|
+
},
|
|
34
|
+
'analysis.complete': {
|
|
35
|
+
en: 'Analysis complete',
|
|
36
|
+
tr: 'Analiz tamamlandı',
|
|
37
|
+
},
|
|
38
|
+
'analysis.files_found': {
|
|
39
|
+
en: 'Files found',
|
|
40
|
+
tr: 'Dosya bulundu',
|
|
41
|
+
},
|
|
42
|
+
'analysis.total_tokens': {
|
|
43
|
+
en: 'Total tokens',
|
|
44
|
+
tr: 'Toplam token',
|
|
45
|
+
},
|
|
46
|
+
'analysis.method_count': {
|
|
47
|
+
en: 'Methods found',
|
|
48
|
+
tr: 'Method bulundu',
|
|
49
|
+
},
|
|
50
|
+
'analysis.no_files': {
|
|
51
|
+
en: 'No files found to analyze',
|
|
52
|
+
tr: 'Analiz edilecek dosya bulunamadı',
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
// Export Messages
|
|
56
|
+
'export.success': {
|
|
57
|
+
en: 'Export successful',
|
|
58
|
+
tr: 'Dışa aktarma başarılı',
|
|
59
|
+
},
|
|
60
|
+
'export.copied': {
|
|
61
|
+
en: 'Copied to clipboard',
|
|
62
|
+
tr: 'Panoya kopyalandı',
|
|
63
|
+
},
|
|
64
|
+
'export.saved': {
|
|
65
|
+
en: 'Saved to file',
|
|
66
|
+
tr: 'Dosyaya kaydedildi',
|
|
67
|
+
},
|
|
68
|
+
'export.failed': {
|
|
69
|
+
en: 'Export failed',
|
|
70
|
+
tr: 'Dışa aktarma başarısız',
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
// Error Messages
|
|
74
|
+
'error.file_not_found': {
|
|
75
|
+
en: 'File not found',
|
|
76
|
+
tr: 'Dosya bulunamadı',
|
|
77
|
+
},
|
|
78
|
+
'error.permission_denied': {
|
|
79
|
+
en: 'Permission denied',
|
|
80
|
+
tr: 'İzin reddedildi',
|
|
81
|
+
},
|
|
82
|
+
'error.invalid_format': {
|
|
83
|
+
en: 'Invalid format',
|
|
84
|
+
tr: 'Geçersiz format',
|
|
85
|
+
},
|
|
86
|
+
'error.missing_param': {
|
|
87
|
+
en: 'Missing required parameter',
|
|
88
|
+
tr: 'Gerekli parametre eksik',
|
|
89
|
+
},
|
|
90
|
+
'error.unsupported_lang': {
|
|
91
|
+
en: 'Unsupported language',
|
|
92
|
+
tr: 'Desteklenmeyen dil',
|
|
93
|
+
},
|
|
94
|
+
'error.git_not_found': {
|
|
95
|
+
en: 'Git repository not found',
|
|
96
|
+
tr: 'Git deposu bulunamadı',
|
|
97
|
+
},
|
|
98
|
+
'error.tiktoken_unavailable': {
|
|
99
|
+
en: 'tiktoken not available, using estimation mode',
|
|
100
|
+
tr: 'tiktoken mevcut değil, tahmin modu kullanılıyor',
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
// Success Messages
|
|
104
|
+
'success.server_started': {
|
|
105
|
+
en: 'API server started',
|
|
106
|
+
tr: 'API sunucusu başlatıldı',
|
|
107
|
+
},
|
|
108
|
+
'success.server_stopped': {
|
|
109
|
+
en: 'API server stopped',
|
|
110
|
+
tr: 'API sunucusu durduruldu',
|
|
111
|
+
},
|
|
112
|
+
'success.cache_cleared': {
|
|
113
|
+
en: 'Cache cleared successfully',
|
|
114
|
+
tr: 'Önbellek başarıyla temizlendi',
|
|
115
|
+
},
|
|
116
|
+
'success.config_saved': {
|
|
117
|
+
en: 'Configuration saved',
|
|
118
|
+
tr: 'Yapılandırma kaydedildi',
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
// Warning Messages
|
|
122
|
+
'warning.large_project': {
|
|
123
|
+
en: 'Large project detected, analysis may take longer',
|
|
124
|
+
tr: 'Büyük proje tespit edildi, analiz daha uzun sürebilir',
|
|
125
|
+
},
|
|
126
|
+
'warning.token_limit': {
|
|
127
|
+
en: 'Token limit exceeded, some files may be excluded',
|
|
128
|
+
tr: 'Token limiti aşıldı, bazı dosyalar hariç tutulabilir',
|
|
129
|
+
},
|
|
130
|
+
'warning.no_methods': {
|
|
131
|
+
en: 'No methods found in the specified files',
|
|
132
|
+
tr: 'Belirtilen dosyalarda method bulunamadı',
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
// Info Messages
|
|
136
|
+
'info.watch_mode': {
|
|
137
|
+
en: 'Watching for file changes...',
|
|
138
|
+
tr: 'Dosya değişiklikleri izleniyor...',
|
|
139
|
+
},
|
|
140
|
+
'info.press_exit': {
|
|
141
|
+
en: 'Press Ctrl+C to exit',
|
|
142
|
+
tr: 'Çıkmak için Ctrl+C tuşlarına basın',
|
|
143
|
+
},
|
|
144
|
+
'info.using_cache': {
|
|
145
|
+
en: 'Using cached results',
|
|
146
|
+
tr: 'Önbelleğe alınmış sonuçlar kullanılıyor',
|
|
147
|
+
},
|
|
148
|
+
'info.checking_updates': {
|
|
149
|
+
en: 'Checking for updates...',
|
|
150
|
+
tr: 'Güncellemeler kontrol ediliyor...',
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
// Git Integration Messages
|
|
154
|
+
'git.no_changes': {
|
|
155
|
+
en: 'No changes detected',
|
|
156
|
+
tr: 'Değişiklik tespit edilmedi',
|
|
157
|
+
},
|
|
158
|
+
'git.changes_found': {
|
|
159
|
+
en: 'Changes found in {count} files',
|
|
160
|
+
tr: '{count} dosyada değişiklik bulundu',
|
|
161
|
+
},
|
|
162
|
+
'git.branch_switch': {
|
|
163
|
+
en: 'Switched to branch',
|
|
164
|
+
tr: 'Branch değiştirildi',
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
// API Messages
|
|
168
|
+
'api.listening': {
|
|
169
|
+
en: 'Listening on',
|
|
170
|
+
tr: 'Dinleniyor',
|
|
171
|
+
},
|
|
172
|
+
'api.docs_available': {
|
|
173
|
+
en: 'API documentation available at',
|
|
174
|
+
tr: 'API dokümantasyonu şu adreste mevcut',
|
|
175
|
+
},
|
|
176
|
+
'api.auth_required': {
|
|
177
|
+
en: 'Authentication required',
|
|
178
|
+
tr: 'Kimlik doğrulama gerekli',
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
// Progress Messages
|
|
182
|
+
'progress.scanning': {
|
|
183
|
+
en: 'Scanning files...',
|
|
184
|
+
tr: 'Dosyalar taranıyor...',
|
|
185
|
+
},
|
|
186
|
+
'progress.analyzing': {
|
|
187
|
+
en: 'Analyzing tokens...',
|
|
188
|
+
tr: "Token'lar analiz ediliyor...",
|
|
189
|
+
},
|
|
190
|
+
'progress.generating': {
|
|
191
|
+
en: 'Generating report...',
|
|
192
|
+
tr: 'Rapor oluşturuluyor...',
|
|
193
|
+
},
|
|
194
|
+
'progress.uploading': {
|
|
195
|
+
en: 'Uploading...',
|
|
196
|
+
tr: 'Yükleniyor...',
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
// Configuration Messages
|
|
200
|
+
'config.created': {
|
|
201
|
+
en: 'Configuration file created',
|
|
202
|
+
tr: 'Yapılandırma dosyası oluşturuldu',
|
|
203
|
+
},
|
|
204
|
+
'config.updated': {
|
|
205
|
+
en: 'Configuration updated',
|
|
206
|
+
tr: 'Yapılandırma güncellendi',
|
|
207
|
+
},
|
|
208
|
+
'config.not_found': {
|
|
209
|
+
en: 'Configuration file not found, using defaults',
|
|
210
|
+
tr: 'Yapılandırma dosyası bulunamadı, varsayılanlar kullanılıyor',
|
|
211
|
+
},
|
|
212
|
+
|
|
213
|
+
// Plugin Messages
|
|
214
|
+
'plugin.loaded': {
|
|
215
|
+
en: 'Plugin loaded',
|
|
216
|
+
tr: 'Plugin yüklendi',
|
|
217
|
+
},
|
|
218
|
+
'plugin.not_found': {
|
|
219
|
+
en: 'Plugin not found',
|
|
220
|
+
tr: 'Plugin bulunamadı',
|
|
221
|
+
},
|
|
222
|
+
'plugin.error': {
|
|
223
|
+
en: 'Plugin error',
|
|
224
|
+
tr: 'Plugin hatası',
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
// Default language (can be overridden)
|
|
229
|
+
let currentLanguage = 'en';
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Detect language from environment
|
|
233
|
+
* @returns {string} Language code ('en' or 'tr')
|
|
234
|
+
*/
|
|
235
|
+
function detectLanguage() {
|
|
236
|
+
const lang = process.env.LANG || process.env.LANGUAGE || process.env.LC_ALL || '';
|
|
237
|
+
|
|
238
|
+
// Check for Turkish
|
|
239
|
+
if (lang.toLowerCase().includes('tr') || lang.toLowerCase().includes('tur')) {
|
|
240
|
+
return 'tr';
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Default to English
|
|
244
|
+
return 'en';
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Set the current language
|
|
249
|
+
* @param {string} lang - Language code ('en' or 'tr')
|
|
250
|
+
*/
|
|
251
|
+
export function setLanguage(lang) {
|
|
252
|
+
if (lang === 'en' || lang === 'tr') {
|
|
253
|
+
currentLanguage = lang;
|
|
254
|
+
} else {
|
|
255
|
+
console.warn(`Unsupported language: ${lang}, defaulting to English`);
|
|
256
|
+
currentLanguage = 'en';
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Get the current language
|
|
262
|
+
* @returns {string} Current language code
|
|
263
|
+
*/
|
|
264
|
+
export function getLanguage() {
|
|
265
|
+
return currentLanguage;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Get a message by code
|
|
270
|
+
* @param {string} code - Message code (e.g., 'cli.help.usage')
|
|
271
|
+
* @param {object} params - Optional parameters for message interpolation
|
|
272
|
+
* @returns {string} Translated message
|
|
273
|
+
*/
|
|
274
|
+
export function getMessage(code, params = {}) {
|
|
275
|
+
const message = messages[code];
|
|
276
|
+
|
|
277
|
+
if (!message) {
|
|
278
|
+
console.warn(`Message code not found: ${code}`);
|
|
279
|
+
return code;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
let text = message[currentLanguage] || message.en;
|
|
283
|
+
|
|
284
|
+
// Interpolate parameters
|
|
285
|
+
for (const [key, value] of Object.entries(params)) {
|
|
286
|
+
text = text.replace(new RegExp(`\\{${key}\\}`, 'g'), value);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return text;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Get all messages for a language
|
|
294
|
+
* @param {string} lang - Language code
|
|
295
|
+
* @returns {object} All messages in the specified language
|
|
296
|
+
*/
|
|
297
|
+
export function getAllMessages(lang = currentLanguage) {
|
|
298
|
+
const result = {};
|
|
299
|
+
|
|
300
|
+
for (const [code, translations] of Object.entries(messages)) {
|
|
301
|
+
result[code] = translations[lang] || translations.en;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return result;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Check if a message code exists
|
|
309
|
+
* @param {string} code - Message code
|
|
310
|
+
* @returns {boolean} True if code exists
|
|
311
|
+
*/
|
|
312
|
+
export function hasMessage(code) {
|
|
313
|
+
return code in messages;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Get message codes by prefix
|
|
318
|
+
* @param {string} prefix - Message code prefix (e.g., 'error')
|
|
319
|
+
* @returns {string[]} Array of matching message codes
|
|
320
|
+
*/
|
|
321
|
+
export function getMessagesByPrefix(prefix) {
|
|
322
|
+
return Object.keys(messages).filter((code) => code.startsWith(prefix));
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Format a message with emoji prefix
|
|
327
|
+
* @param {string} code - Message code
|
|
328
|
+
* @param {string} emoji - Emoji to prepend
|
|
329
|
+
* @param {object} params - Optional parameters
|
|
330
|
+
* @returns {string} Formatted message
|
|
331
|
+
*/
|
|
332
|
+
export function formatMessage(code, emoji = '', params = {}) {
|
|
333
|
+
const message = getMessage(code, params);
|
|
334
|
+
return emoji ? `${emoji} ${message}` : message;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Initialize language on module load
|
|
338
|
+
currentLanguage = detectLanguage();
|
|
339
|
+
|
|
340
|
+
// Export message codes for external use
|
|
341
|
+
export const MessageCodes = Object.keys(messages).reduce((acc, code) => {
|
|
342
|
+
acc[code.toUpperCase().replace(/\./g, '_')] = code;
|
|
343
|
+
return acc;
|
|
344
|
+
}, {});
|
|
345
|
+
|
|
346
|
+
export default {
|
|
347
|
+
getMessage,
|
|
348
|
+
getAllMessages,
|
|
349
|
+
setLanguage,
|
|
350
|
+
getLanguage,
|
|
351
|
+
hasMessage,
|
|
352
|
+
getMessagesByPrefix,
|
|
353
|
+
formatMessage,
|
|
354
|
+
MessageCodes,
|
|
355
|
+
};
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-Repository Manager
|
|
3
|
+
* FEAT-006: Multi-Repository Context
|
|
4
|
+
*
|
|
5
|
+
* Manages multiple repositories for monorepo or microservices context analysis
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import TokenCalculator from '../analyzers/token-calculator.js';
|
|
11
|
+
import { getLogger } from './logger.js';
|
|
12
|
+
|
|
13
|
+
const logger = getLogger('MultiRepoManager');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Default configuration structure for repos.json
|
|
17
|
+
*/
|
|
18
|
+
const DEFAULT_CONFIG = {
|
|
19
|
+
version: '1.0',
|
|
20
|
+
repos: [],
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* MultiRepoManager - Handles multi-repository context analysis
|
|
25
|
+
*
|
|
26
|
+
* Features:
|
|
27
|
+
* - Add/remove repositories
|
|
28
|
+
* - Per-repo token analysis
|
|
29
|
+
* - Combined context generation
|
|
30
|
+
* - Repository configuration persistence
|
|
31
|
+
*/
|
|
32
|
+
export class MultiRepoManager {
|
|
33
|
+
/**
|
|
34
|
+
* @param {string} projectRoot - Root directory for configuration
|
|
35
|
+
* @param {object} options - Configuration options
|
|
36
|
+
*/
|
|
37
|
+
constructor(projectRoot, options = {}) {
|
|
38
|
+
this.projectRoot = projectRoot;
|
|
39
|
+
this.configPath = options.configPath || path.join(projectRoot, '.ctxman', 'repos.json');
|
|
40
|
+
this.config = null;
|
|
41
|
+
this.options = {
|
|
42
|
+
autoSave: true,
|
|
43
|
+
...options,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Load repository configuration from file
|
|
49
|
+
* @returns {object} Configuration object
|
|
50
|
+
*/
|
|
51
|
+
load() {
|
|
52
|
+
try {
|
|
53
|
+
if (fs.existsSync(this.configPath)) {
|
|
54
|
+
const content = fs.readFileSync(this.configPath, 'utf-8');
|
|
55
|
+
const parsed = JSON.parse(content);
|
|
56
|
+
// Deep clone to avoid reference issues
|
|
57
|
+
this.config = {
|
|
58
|
+
version: parsed.version || '1.0',
|
|
59
|
+
repos: Array.isArray(parsed.repos) ? [...parsed.repos] : [],
|
|
60
|
+
};
|
|
61
|
+
logger.info(`Loaded ${this.config.repos.length} repositories from config`);
|
|
62
|
+
return this.config;
|
|
63
|
+
}
|
|
64
|
+
} catch (error) {
|
|
65
|
+
logger.warn(`Failed to load config: ${error.message}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Return default config if file doesn't exist
|
|
69
|
+
this.config = { ...DEFAULT_CONFIG, repos: [] };
|
|
70
|
+
return this.config;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Save repository configuration to file
|
|
75
|
+
*/
|
|
76
|
+
save() {
|
|
77
|
+
// Ensure directory exists
|
|
78
|
+
const configDir = path.dirname(this.configPath);
|
|
79
|
+
if (!fs.existsSync(configDir)) {
|
|
80
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
fs.writeFileSync(this.configPath, JSON.stringify(this.config, null, 2));
|
|
84
|
+
logger.info(`Saved configuration to ${this.configPath}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Add a repository to configuration
|
|
89
|
+
* @param {string} repoPath - Path to repository (relative or absolute)
|
|
90
|
+
* @param {object} options - Repository options
|
|
91
|
+
* @returns {object} Added repository configuration
|
|
92
|
+
*/
|
|
93
|
+
addRepo(repoPath, options = {}) {
|
|
94
|
+
if (!this.config) {
|
|
95
|
+
this.load();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Resolve path
|
|
99
|
+
const resolvedPath = this.resolvePath(repoPath);
|
|
100
|
+
|
|
101
|
+
// Check if repo already exists
|
|
102
|
+
const existingIndex = this.config.repos.findIndex(
|
|
103
|
+
(r) => this.resolvePath(r.path) === resolvedPath
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
if (existingIndex !== -1) {
|
|
107
|
+
logger.warn(`Repository already exists: ${repoPath}`);
|
|
108
|
+
return this.config.repos[existingIndex];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Create repo entry
|
|
112
|
+
const repoId = options.id || this.generateRepoId(repoPath);
|
|
113
|
+
const repo = {
|
|
114
|
+
id: repoId,
|
|
115
|
+
path: repoPath,
|
|
116
|
+
alias: options.alias || path.basename(resolvedPath),
|
|
117
|
+
exclude: options.exclude || ['node_modules/**', 'dist/**', 'build/**', 'coverage/**'],
|
|
118
|
+
weight: options.weight || 1.0,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
this.config.repos.push(repo);
|
|
122
|
+
|
|
123
|
+
if (this.options.autoSave) {
|
|
124
|
+
this.save();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
logger.info(`Added repository: ${repo.alias} (${repoPath})`);
|
|
128
|
+
return repo;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Remove a repository from configuration
|
|
133
|
+
* @param {string} repoPath - Path to repository
|
|
134
|
+
* @returns {boolean} True if removed, false if not found
|
|
135
|
+
*/
|
|
136
|
+
removeRepo(repoPath) {
|
|
137
|
+
if (!this.config) {
|
|
138
|
+
this.load();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const resolvedPath = this.resolvePath(repoPath);
|
|
142
|
+
const initialLength = this.config.repos.length;
|
|
143
|
+
|
|
144
|
+
this.config.repos = this.config.repos.filter((r) => this.resolvePath(r.path) !== resolvedPath);
|
|
145
|
+
|
|
146
|
+
if (this.config.repos.length < initialLength) {
|
|
147
|
+
if (this.options.autoSave) {
|
|
148
|
+
this.save();
|
|
149
|
+
}
|
|
150
|
+
logger.info(`Removed repository: ${repoPath}`);
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
logger.warn(`Repository not found: ${repoPath}`);
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* List all configured repositories
|
|
160
|
+
* @returns {Array} List of repository configurations
|
|
161
|
+
*/
|
|
162
|
+
listRepos() {
|
|
163
|
+
if (!this.config) {
|
|
164
|
+
this.load();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return this.config.repos || [];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Analyze all repositories and return combined stats
|
|
172
|
+
* @param {object} options - Analysis options
|
|
173
|
+
* @returns {object} Combined analysis results
|
|
174
|
+
*/
|
|
175
|
+
analyzeAll(options = {}) {
|
|
176
|
+
if (!this.config) {
|
|
177
|
+
this.load();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const results = {
|
|
181
|
+
repos: [],
|
|
182
|
+
combined: {
|
|
183
|
+
totalFiles: 0,
|
|
184
|
+
totalTokens: 0,
|
|
185
|
+
totalLines: 0,
|
|
186
|
+
totalBytes: 0,
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
for (const repo of this.config.repos) {
|
|
191
|
+
const repoResult = this.analyzeRepo(repo, options);
|
|
192
|
+
results.repos.push(repoResult);
|
|
193
|
+
|
|
194
|
+
results.combined.totalFiles += repoResult.files;
|
|
195
|
+
results.combined.totalTokens += repoResult.tokens;
|
|
196
|
+
results.combined.totalLines += repoResult.lines;
|
|
197
|
+
results.combined.totalBytes += repoResult.bytes;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Calculate percentages
|
|
201
|
+
for (const repo of results.repos) {
|
|
202
|
+
repo.percentage =
|
|
203
|
+
results.combined.totalTokens > 0
|
|
204
|
+
? ((repo.tokens / results.combined.totalTokens) * 100).toFixed(1)
|
|
205
|
+
: 0;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
logger.info(`Analyzed ${results.repos.length} repositories`);
|
|
209
|
+
return results;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Analyze a single repository
|
|
214
|
+
* @param {object} repo - Repository configuration
|
|
215
|
+
* @param {object} options - Analysis options
|
|
216
|
+
* @returns {object} Repository analysis result
|
|
217
|
+
*/
|
|
218
|
+
analyzeRepo(repo, options = {}) {
|
|
219
|
+
const resolvedPath = this.resolvePath(repo.path);
|
|
220
|
+
const repoResult = {
|
|
221
|
+
id: repo.id,
|
|
222
|
+
alias: repo.alias,
|
|
223
|
+
path: repo.path,
|
|
224
|
+
resolvedPath,
|
|
225
|
+
files: 0,
|
|
226
|
+
tokens: 0,
|
|
227
|
+
lines: 0,
|
|
228
|
+
bytes: 0,
|
|
229
|
+
byExtension: {},
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
234
|
+
logger.warn(`Repository path does not exist: ${resolvedPath}`);
|
|
235
|
+
repoResult.error = 'Path does not exist';
|
|
236
|
+
return repoResult;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Create analyzer with repo-specific excludes
|
|
240
|
+
const analyzer = new TokenCalculator(resolvedPath, {
|
|
241
|
+
verbose: false,
|
|
242
|
+
simple: true,
|
|
243
|
+
...options,
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Run analysis
|
|
247
|
+
const stats = analyzer.run();
|
|
248
|
+
|
|
249
|
+
if (stats) {
|
|
250
|
+
repoResult.files = stats.totalFiles || 0;
|
|
251
|
+
repoResult.tokens = stats.totalTokens || 0;
|
|
252
|
+
repoResult.lines = stats.totalLines || 0;
|
|
253
|
+
repoResult.bytes = stats.totalBytes || 0;
|
|
254
|
+
repoResult.byExtension = stats.byExtension || {};
|
|
255
|
+
repoResult.largestFiles = stats.largestFiles || [];
|
|
256
|
+
}
|
|
257
|
+
} catch (error) {
|
|
258
|
+
logger.error(`Failed to analyze repo ${repo.alias}: ${error.message}`);
|
|
259
|
+
repoResult.error = error.message;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return repoResult;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Resolve repository path (relative or absolute)
|
|
267
|
+
* @param {string} repoPath - Repository path
|
|
268
|
+
* @returns {string} Resolved absolute path
|
|
269
|
+
*/
|
|
270
|
+
resolvePath(repoPath) {
|
|
271
|
+
if (path.isAbsolute(repoPath)) {
|
|
272
|
+
return repoPath;
|
|
273
|
+
}
|
|
274
|
+
return path.resolve(this.projectRoot, repoPath);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Generate a unique repository ID from path
|
|
279
|
+
* @param {string} repoPath - Repository path
|
|
280
|
+
* @returns {string} Generated ID
|
|
281
|
+
*/
|
|
282
|
+
generateRepoId(repoPath) {
|
|
283
|
+
const basename = path.basename(repoPath);
|
|
284
|
+
const timestamp = Date.now().toString(36);
|
|
285
|
+
return `${basename}-${timestamp}`;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Format analysis results for display
|
|
290
|
+
* @param {object} results - Analysis results
|
|
291
|
+
* @returns {string} Formatted output
|
|
292
|
+
*/
|
|
293
|
+
formatResults(results) {
|
|
294
|
+
const lines = [];
|
|
295
|
+
|
|
296
|
+
lines.push('🔀 Multi-Repository Context Analysis:');
|
|
297
|
+
lines.push('═'.repeat(50));
|
|
298
|
+
|
|
299
|
+
if (results.repos.length === 0) {
|
|
300
|
+
lines.push(' No repositories configured.');
|
|
301
|
+
lines.push(' Use --add-repo <path> to add repositories.');
|
|
302
|
+
return lines.join('\n');
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
lines.push(` Repositories: ${results.repos.length}`);
|
|
306
|
+
lines.push('');
|
|
307
|
+
|
|
308
|
+
results.repos.forEach((repo, index) => {
|
|
309
|
+
const num = `${index + 1}.`;
|
|
310
|
+
lines.push(` ${num} ${repo.alias} (${repo.path})`);
|
|
311
|
+
|
|
312
|
+
if (repo.error) {
|
|
313
|
+
lines.push(` ⚠️ Error: ${repo.error}`);
|
|
314
|
+
} else {
|
|
315
|
+
lines.push(` Files: ${repo.files.toLocaleString()}`);
|
|
316
|
+
lines.push(` Tokens: ${repo.tokens.toLocaleString()} (${repo.percentage}%)`);
|
|
317
|
+
}
|
|
318
|
+
lines.push('');
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
lines.push(' ' + '─'.repeat(46));
|
|
322
|
+
lines.push(
|
|
323
|
+
` Combined Total: ${results.combined.totalFiles.toLocaleString()} files, ${results.combined.totalTokens.toLocaleString()} tokens`
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
return lines.join('\n');
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Format repository list for display
|
|
331
|
+
* @returns {string} Formatted output
|
|
332
|
+
*/
|
|
333
|
+
formatList() {
|
|
334
|
+
const repos = this.listRepos();
|
|
335
|
+
|
|
336
|
+
if (repos.length === 0) {
|
|
337
|
+
return 'No repositories configured.\nUse --add-repo <path> to add a repository.';
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const lines = [];
|
|
341
|
+
lines.push('📋 Configured Repositories:');
|
|
342
|
+
lines.push('═'.repeat(50));
|
|
343
|
+
|
|
344
|
+
repos.forEach((repo, index) => {
|
|
345
|
+
const resolvedPath = this.resolvePath(repo.path);
|
|
346
|
+
const exists = fs.existsSync(resolvedPath);
|
|
347
|
+
const status = exists ? '✓' : '⚠';
|
|
348
|
+
|
|
349
|
+
lines.push(`${status} ${index + 1}. ${repo.alias}`);
|
|
350
|
+
lines.push(` Path: ${repo.path}`);
|
|
351
|
+
lines.push(` ID: ${repo.id}`);
|
|
352
|
+
|
|
353
|
+
if (!exists) {
|
|
354
|
+
lines.push(` ⚠️ Path does not exist`);
|
|
355
|
+
}
|
|
356
|
+
lines.push('');
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
lines.push(`Total: ${repos.length} repositories`);
|
|
360
|
+
return lines.join('\n');
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export default MultiRepoManager;
|