ctxman 3.0.0 ā 3.1.1
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 +381 -136
- package/bin/cli.js +1880 -529
- package/bin/cm-ask.js +18 -67
- package/bin/cm-audit.js +121 -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 +687 -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 +772 -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 +116 -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 +312 -0
- package/lib/policy/enforce.js +125 -0
- package/lib/policy/ignore-files.js +169 -0
- package/lib/policy/index.js +53 -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/ask.js +122 -0
- package/lib/rag/sources/FileDataSource.js +12 -9
- 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 +246 -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 +257 -0
- package/lib/wizards/project-detector.js +572 -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
package/bin/cli.js
CHANGED
|
@@ -3,15 +3,29 @@
|
|
|
3
3
|
import { TokenAnalyzer } from '../index.js';
|
|
4
4
|
import FormatRegistry from '../lib/formatters/format-registry.js';
|
|
5
5
|
import FormatConverter from '../lib/utils/format-converter.js';
|
|
6
|
-
import { LLMDetector } from '../lib/utils/llm-detector.js';
|
|
6
|
+
import { LLMDetector, DEFAULT_TARGET_MODEL } from '../lib/utils/llm-detector.js';
|
|
7
|
+
import { LLMCostEstimator } from '../lib/utils/llm-cost-estimator.js';
|
|
7
8
|
import APIServer from '../lib/api/rest/server.js';
|
|
8
9
|
import FileWatcher from '../lib/watch/FileWatcher.js';
|
|
9
10
|
import IncrementalAnalyzer from '../lib/watch/IncrementalAnalyzer.js';
|
|
10
11
|
import DiffAnalyzer from '../lib/integrations/git/DiffAnalyzer.js';
|
|
11
|
-
import
|
|
12
|
+
import TemplateManager from '../lib/utils/template-manager.js';
|
|
13
|
+
import ProfileManager from '../lib/utils/profile-manager.js';
|
|
14
|
+
import { AISuggester } from '../lib/analyzers/ai-suggester.js';
|
|
15
|
+
import { DependencyScanner } from '../lib/analyzers/dependency-scanner.js';
|
|
16
|
+
import { ImportTracker } from '../lib/analyzers/import-tracker.js';
|
|
17
|
+
import MultiRepoManager from '../lib/utils/multi-repo-manager.js';
|
|
18
|
+
import SnapshotManager from '../lib/utils/snapshot-manager.js';
|
|
19
|
+
import ContextVersioning from '../lib/utils/context-versioning.js';
|
|
20
|
+
import { loadPolicy } from '../lib/policy/policy.js';
|
|
21
|
+
import { spawnSync } from 'child_process';
|
|
12
22
|
import { fileURLToPath } from 'url';
|
|
13
|
-
import { dirname, resolve } from 'path';
|
|
23
|
+
import { dirname, resolve, sep } from 'path';
|
|
14
24
|
import { readFileSync } from 'fs';
|
|
25
|
+
import { exitOnBrokenPipe } from '../lib/utils/cli-output.js';
|
|
26
|
+
|
|
27
|
+
// `ctxman --cli | head` closes stdout early; stop quietly instead of throwing EPIPE
|
|
28
|
+
exitOnBrokenPipe();
|
|
15
29
|
|
|
16
30
|
// ESM equivalents for __dirname and __filename
|
|
17
31
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -20,631 +34,1968 @@ const __dirname = dirname(__filename);
|
|
|
20
34
|
// Load package.json
|
|
21
35
|
const pkg = JSON.parse(readFileSync(resolve(__dirname, '../package.json'), 'utf-8'));
|
|
22
36
|
|
|
23
|
-
|
|
24
|
-
|
|
37
|
+
// Run one of the bin/cm-*.js subcommand scripts with the remaining arguments
|
|
38
|
+
function runSubcommand(script, scriptArgs) {
|
|
39
|
+
const result = spawnSync(process.execPath, [resolve(__dirname, script), ...scriptArgs], {
|
|
40
|
+
stdio: 'inherit',
|
|
41
|
+
});
|
|
42
|
+
process.exitCode = result.status ?? 1;
|
|
43
|
+
}
|
|
25
44
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
45
|
+
async function main() {
|
|
46
|
+
const args = process.argv.slice(2);
|
|
47
|
+
// Subcommands count only in first position, so a word inside an unquoted `ask` query or
|
|
48
|
+
// an option value (`--profile serve`) does not switch commands
|
|
49
|
+
const command = args[0]?.startsWith('-') ? null : args[0];
|
|
50
|
+
|
|
51
|
+
// Before --help: the github command prints its own help
|
|
52
|
+
if (command === 'github' || command === 'git') {
|
|
53
|
+
runSubcommand('./cm-gitingest.js', args.slice(1));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (command === 'count') {
|
|
58
|
+
runSubcommand('./cm-count.js', args.slice(1));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (command === 'map') {
|
|
63
|
+
runSubcommand('./cm-map.js', args.slice(1));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (command === 'pr') {
|
|
68
|
+
runSubcommand('./cm-pr.js', args.slice(1));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (command === 'mcp') {
|
|
73
|
+
runSubcommand('./cm-mcp.js', args.slice(1));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (command === 'search') {
|
|
78
|
+
runSubcommand('./cm-search.js', args.slice(1));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (command === 'policy') {
|
|
83
|
+
runSubcommand('./cm-policy.js', args.slice(1));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (command === 'audit') {
|
|
88
|
+
runSubcommand('./cm-audit.js', args.slice(1));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (command === 'why') {
|
|
93
|
+
runSubcommand('./cm-why.js', args.slice(1));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (command === 'pack') {
|
|
98
|
+
runSubcommand('./cm-pack.js', args.slice(1));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
103
|
+
printHelp();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Check for version flag
|
|
108
|
+
if (args.includes('--version')) {
|
|
109
|
+
console.log(`Ctxman v${pkg.version}`);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Check for RAG 'ask' mode (v3.1.0); the rest of the arguments are the query
|
|
114
|
+
if (command === 'ask') {
|
|
115
|
+
runSubcommand('./cm-ask.js', args.slice(1));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (command === 'update') {
|
|
120
|
+
runSubcommand('./cm-update.js', args.slice(1));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Digest from an existing report/context file, handled by the legacy entry point's code
|
|
125
|
+
if (args.includes('--gitingest-from-report') || args.includes('--gitingest-from-context')) {
|
|
126
|
+
const { main: runLegacy } = await import('../ctxman.js');
|
|
127
|
+
await runLegacy(args);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Check for profile listing (FEAT-004)
|
|
132
|
+
if (args.includes('--list-profiles')) {
|
|
133
|
+
listProfiles();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Check for profile creation (FEAT-004)
|
|
138
|
+
if (args.includes('--create-profile')) {
|
|
139
|
+
createProfile(args);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Check for profile export (FEAT-004)
|
|
144
|
+
if (args.includes('--export-profile')) {
|
|
145
|
+
exportProfile(args);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Check for template listing (FEAT-009)
|
|
150
|
+
if (args.includes('--list-templates')) {
|
|
151
|
+
listTemplates();
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Check for format listing
|
|
156
|
+
if (args.includes('--list-formats')) {
|
|
157
|
+
listFormats();
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Check for LLM model listing (v2.3.7)
|
|
162
|
+
if (args.includes('--list-llms')) {
|
|
163
|
+
listLLMs(args);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Check for cost estimation (FEAT-010)
|
|
168
|
+
if (args.includes('--estimate-cost')) {
|
|
169
|
+
runCostEstimation(args);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Check for multi-repo commands (FEAT-006)
|
|
174
|
+
if (args.includes('--add-repo')) {
|
|
175
|
+
addRepo(args);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (args.includes('--remove-repo')) {
|
|
180
|
+
removeRepo(args);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (args.includes('--list-repos')) {
|
|
185
|
+
listRepos();
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Check for AI suggestions (FEAT-005)
|
|
190
|
+
if (args.includes('--ai-suggest')) {
|
|
191
|
+
await runAISuggest(args);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Check for snapshot commands (FEAT-003)
|
|
196
|
+
if (args.includes('--snapshot')) {
|
|
197
|
+
await runSnapshot(args);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (args.includes('--list-snapshots')) {
|
|
202
|
+
await listSnapshots();
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (args.includes('--diff-last')) {
|
|
207
|
+
await runDiffLast(args);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (args.includes('--diff-snapshot')) {
|
|
212
|
+
await runDiffSnapshot(args);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (args.includes('--snapshot-trend')) {
|
|
217
|
+
await runSnapshotTrend(args);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Check for context versioning commands (FEAT-012)
|
|
222
|
+
if (args.includes('--list-versions')) {
|
|
223
|
+
await listVersions();
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (args.includes('--restore-version')) {
|
|
228
|
+
await restoreVersion(args);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (args.includes('--compare-versions')) {
|
|
233
|
+
await compareVersions(args);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Check for performance dashboard (FEAT-001)
|
|
238
|
+
if (args.includes('--perf-dashboard')) {
|
|
239
|
+
await runPerfDashboard(args);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Check for dependency scanning (FEAT-002)
|
|
244
|
+
if (args.includes('--scan-dependencies')) {
|
|
245
|
+
await runDependencyScan(args);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Check for format conversion mode (v2.3.2)
|
|
250
|
+
if (command === 'convert') {
|
|
251
|
+
runFormatConversion(args);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Check for init command (FEAT-001: Configuration Wizard)
|
|
256
|
+
if (command === 'init') {
|
|
257
|
+
await runInitWizard(args);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Check for API server mode (v3.0.0)
|
|
262
|
+
if (command === 'serve') {
|
|
263
|
+
await runAPIServer(args);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Check for watch mode (v3.0.0)
|
|
268
|
+
if (command === 'watch') {
|
|
269
|
+
await runWatchMode(args);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Check for explicit dashboard mode
|
|
274
|
+
if (args.includes('--dashboard')) {
|
|
275
|
+
try {
|
|
276
|
+
await runDashboard();
|
|
277
|
+
return;
|
|
278
|
+
} catch (error) {
|
|
279
|
+
console.error('ā ļø Live dashboard failed.');
|
|
280
|
+
console.error(' Error:', error.message);
|
|
281
|
+
console.error(' Falling back to standard mode...\n');
|
|
29
282
|
}
|
|
283
|
+
}
|
|
30
284
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
285
|
+
// Check if CLI mode is explicitly requested OR any analysis flags are present
|
|
286
|
+
const hasAnalysisFlags = args.some(
|
|
287
|
+
(arg) => arg.startsWith('-') && arg !== '--cli' && arg !== '--wizard' && arg !== '--dashboard'
|
|
288
|
+
);
|
|
36
289
|
|
|
37
|
-
|
|
38
|
-
if (args.includes('--list-formats')) {
|
|
39
|
-
listFormats();
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
290
|
+
const cliMode = args.includes('--cli') || hasAnalysisFlags;
|
|
42
291
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
292
|
+
// DEFAULT: Run wizard mode unless --cli flag or other flags are present
|
|
293
|
+
if (!cliMode) {
|
|
294
|
+
try {
|
|
295
|
+
await runWizard();
|
|
296
|
+
return;
|
|
297
|
+
} catch (error) {
|
|
298
|
+
// If wizard fails, fall through to normal mode
|
|
299
|
+
console.error('ā ļø Interactive wizard mode failed.');
|
|
300
|
+
console.error(' Error:', error.message);
|
|
301
|
+
console.error(' Falling back to CLI mode...\n');
|
|
302
|
+
console.error(' Tip: Use --cli flag to skip wizard mode\n');
|
|
53
303
|
}
|
|
304
|
+
}
|
|
54
305
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const commandPath = resolve(__dirname, './cm-gitingest.js');
|
|
58
|
-
const gitArgs = args.filter(arg => arg !== 'github' && arg !== 'git');
|
|
59
|
-
execSync(`node "${commandPath}" ${gitArgs.join(' ')}`, { stdio: 'inherit' });
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
306
|
+
// CLI Mode: Run traditional command-line analysis
|
|
307
|
+
let options = parseArguments(args);
|
|
62
308
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
309
|
+
// Apply template if specified (FEAT-009)
|
|
310
|
+
if (options.template) {
|
|
311
|
+
try {
|
|
312
|
+
const manager = new TemplateManager(process.cwd());
|
|
313
|
+
const templateConfig = manager.apply(options.template, {
|
|
314
|
+
targetModel: options.targetModel,
|
|
315
|
+
});
|
|
71
316
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
317
|
+
// Merge template config with options (options take precedence)
|
|
318
|
+
options = {
|
|
319
|
+
...options,
|
|
320
|
+
templateConfig,
|
|
321
|
+
// Use template's target model if not explicitly set
|
|
322
|
+
targetModel: options.targetModel || templateConfig.targetModel,
|
|
323
|
+
};
|
|
324
|
+
} catch (error) {
|
|
325
|
+
console.error(`ā Template error: ${error.message}`);
|
|
326
|
+
process.exit(1);
|
|
76
327
|
}
|
|
328
|
+
}
|
|
77
329
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
330
|
+
// Apply profile if specified (FEAT-004)
|
|
331
|
+
if (options.profile) {
|
|
332
|
+
try {
|
|
333
|
+
const manager = new ProfileManager(process.cwd());
|
|
334
|
+
const profileConfig = manager.apply(options.profile, {
|
|
335
|
+
targetModel: options.targetModel,
|
|
336
|
+
});
|
|
83
337
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
338
|
+
// Merge profile config with options (options take precedence)
|
|
339
|
+
options = {
|
|
340
|
+
...options,
|
|
341
|
+
profileConfig,
|
|
342
|
+
// Use profile's target model if not explicitly set
|
|
343
|
+
targetModel: options.targetModel || profileConfig.targetModel,
|
|
344
|
+
methodLevel: options.methodLevel || profileConfig.methodLevel,
|
|
345
|
+
};
|
|
346
|
+
} catch (error) {
|
|
347
|
+
console.error(`ā Profile error: ${error.message}`);
|
|
348
|
+
process.exit(1);
|
|
94
349
|
}
|
|
350
|
+
}
|
|
95
351
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
arg.startsWith('-') &&
|
|
99
|
-
arg !== '--cli' &&
|
|
100
|
-
arg !== '--wizard' &&
|
|
101
|
-
arg !== '--dashboard'
|
|
102
|
-
);
|
|
352
|
+
// Export policy (.ctxman/policy.json, CTXMAN_POLICY): deny rules, export limits, audit log
|
|
353
|
+
const policy = loadCliPolicy(options.projectRoot);
|
|
103
354
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
await runWizard();
|
|
110
|
-
return;
|
|
111
|
-
} catch (error) {
|
|
112
|
-
// If wizard fails, fall through to normal mode
|
|
113
|
-
console.error('ā ļø Interactive wizard mode failed.');
|
|
114
|
-
console.error(' Error:', error.message);
|
|
115
|
-
console.error(' Falling back to CLI mode...\n');
|
|
116
|
-
console.error(' Tip: Use --cli flag to skip wizard mode\n');
|
|
117
|
-
}
|
|
118
|
-
}
|
|
355
|
+
// Git integration: Filter to changed files only (v3.0.0)
|
|
356
|
+
if (options.changedOnly || options.changedSince) {
|
|
357
|
+
await runChangedFilesAnalysis({ ...options, policy });
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
119
360
|
|
|
120
|
-
|
|
121
|
-
|
|
361
|
+
// Multi-repository analysis (FEAT-006)
|
|
362
|
+
if (options.multiRepo) {
|
|
363
|
+
await runMultiRepoAnalysis(options);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
122
366
|
|
|
123
|
-
|
|
124
|
-
if (options.changedOnly || options.changedSince) {
|
|
125
|
-
await runChangedFilesAnalysis(options);
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
367
|
+
printStartupInfo(options);
|
|
128
368
|
|
|
129
|
-
|
|
369
|
+
const analyzer = new TokenAnalyzer(options.projectRoot, { ...options, policy });
|
|
370
|
+
const stats = analyzer.run();
|
|
130
371
|
|
|
131
|
-
|
|
132
|
-
|
|
372
|
+
// Auto-create context version when using --cli (FEAT-012)
|
|
373
|
+
if (args.includes('--cli') && stats) {
|
|
374
|
+
await autoCreateVersion(stats, options);
|
|
375
|
+
}
|
|
133
376
|
}
|
|
134
377
|
|
|
135
378
|
function parseArguments(args) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
379
|
+
return {
|
|
380
|
+
// Output options
|
|
381
|
+
saveReport: args.includes('--save-report') || args.includes('-s'),
|
|
382
|
+
verbose: args.includes('--verbose') || args.includes('-v'),
|
|
383
|
+
contextExport: args.includes('--context-export'),
|
|
384
|
+
contextToClipboard: args.includes('--context-clipboard'),
|
|
385
|
+
json: args.includes('--json'), // NEW: JSON output mode
|
|
386
|
+
|
|
387
|
+
// Analysis options
|
|
388
|
+
methodLevel: args.includes('--method-level') || args.includes('-m'),
|
|
389
|
+
gitingest: args.includes('--gitingest') || args.includes('-g'),
|
|
390
|
+
redactSecrets: !args.includes('--no-redact'),
|
|
391
|
+
respectIgnoreFiles: getRespectIgnoreFiles(args),
|
|
392
|
+
aiSuggest: args.includes('--ai-suggest'), // FEAT-005: AI suggestions
|
|
393
|
+
|
|
394
|
+
// Profile options (FEAT-004)
|
|
395
|
+
profile: getProfile(args),
|
|
396
|
+
|
|
397
|
+
// Template options (FEAT-009)
|
|
398
|
+
template: getTemplate(args),
|
|
399
|
+
|
|
400
|
+
// Format options (v2.3.0)
|
|
401
|
+
outputFormat: getOutputFormat(args),
|
|
402
|
+
|
|
403
|
+
// LLM options (v2.3.7)
|
|
404
|
+
targetModel: getTargetModel(args),
|
|
405
|
+
autoDetectLLM: args.includes('--auto-detect-llm'),
|
|
406
|
+
|
|
407
|
+
// Git options (v3.0.0)
|
|
408
|
+
changedOnly: args.includes('--changed-only'),
|
|
409
|
+
changedSince: getChangedSince(args),
|
|
410
|
+
|
|
411
|
+
// Multi-repo options (FEAT-006)
|
|
412
|
+
multiRepo: args.includes('--multi-repo'),
|
|
413
|
+
|
|
414
|
+
// UI options (v2.3.0)
|
|
415
|
+
simple: args.includes('--simple'),
|
|
416
|
+
dashboard: args.includes('--dashboard'),
|
|
417
|
+
|
|
418
|
+
// Chunking options (v2.3.0)
|
|
419
|
+
chunking: {
|
|
420
|
+
enabled: args.includes('--chunk'),
|
|
421
|
+
strategy: getChunkStrategy(args),
|
|
422
|
+
maxTokensPerChunk: getChunkSize(args),
|
|
423
|
+
},
|
|
424
|
+
|
|
425
|
+
projectRoot: process.cwd(),
|
|
426
|
+
};
|
|
174
427
|
}
|
|
175
428
|
|
|
176
429
|
function getOutputFormat(args) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
430
|
+
const formatIndex = args.findIndex((arg) => arg === '--output' || arg === '-o');
|
|
431
|
+
if (formatIndex !== -1 && args[formatIndex + 1]) {
|
|
432
|
+
return args[formatIndex + 1];
|
|
433
|
+
}
|
|
434
|
+
return null; // Not set: context exports stay JSON (llm-context.json)
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// --respect-ignore-files <list|all>: also honour other AI tools' ignore files
|
|
438
|
+
function getRespectIgnoreFiles(args) {
|
|
439
|
+
const index = args.indexOf('--respect-ignore-files');
|
|
440
|
+
if (index === -1) return null;
|
|
441
|
+
const value = args[index + 1];
|
|
442
|
+
return value && !value.startsWith('-') ? value : 'all';
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// An invalid policy file stops the command instead of being ignored
|
|
446
|
+
function loadCliPolicy(projectRoot) {
|
|
447
|
+
try {
|
|
448
|
+
return loadPolicy(projectRoot);
|
|
449
|
+
} catch (error) {
|
|
450
|
+
console.error(`ā ${error.message}`);
|
|
451
|
+
process.exit(error.exitCode ?? 2);
|
|
452
|
+
}
|
|
182
453
|
}
|
|
183
454
|
|
|
184
455
|
function getChunkStrategy(args) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
456
|
+
const strategyIndex = args.findIndex((arg) => arg === '--chunk-strategy');
|
|
457
|
+
if (strategyIndex !== -1 && args[strategyIndex + 1]) {
|
|
458
|
+
return args[strategyIndex + 1];
|
|
459
|
+
}
|
|
460
|
+
return 'smart'; // Default strategy
|
|
190
461
|
}
|
|
191
462
|
|
|
192
463
|
function getChunkSize(args) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
464
|
+
const sizeIndex = args.findIndex((arg) => arg === '--chunk-size');
|
|
465
|
+
if (sizeIndex !== -1 && args[sizeIndex + 1]) {
|
|
466
|
+
return parseInt(args[sizeIndex + 1], 10);
|
|
467
|
+
}
|
|
468
|
+
return 100000; // Default 100k tokens
|
|
198
469
|
}
|
|
199
470
|
|
|
200
471
|
function getTargetModel(args) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
472
|
+
// Check for explicit model flag
|
|
473
|
+
const modelIndex = args.findIndex((arg) => arg === '--target-model');
|
|
474
|
+
if (modelIndex !== -1 && args[modelIndex + 1]) {
|
|
475
|
+
return args[modelIndex + 1];
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Check for auto-detect flag
|
|
479
|
+
if (args.includes('--auto-detect-llm')) {
|
|
480
|
+
const detected = LLMDetector.detect();
|
|
481
|
+
if (detected !== 'unknown') {
|
|
482
|
+
console.log(`ā
Auto-detected LLM: ${LLMDetector.getProfile(detected).name}`);
|
|
483
|
+
return detected;
|
|
214
484
|
}
|
|
485
|
+
}
|
|
215
486
|
|
|
216
|
-
|
|
487
|
+
return null; // No model specified
|
|
217
488
|
}
|
|
218
489
|
|
|
219
|
-
function listLLMs() {
|
|
220
|
-
|
|
221
|
-
|
|
490
|
+
function listLLMs(args = []) {
|
|
491
|
+
const includeRetired = args.includes('--all');
|
|
492
|
+
const models = LLMDetector.getModelList({ includeRetired });
|
|
493
|
+
const checked = LLMDetector.getDataDate();
|
|
222
494
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
// Group by vendor
|
|
226
|
-
const byVendor = {};
|
|
227
|
-
models.forEach(model => {
|
|
228
|
-
if (!byVendor[model.vendor]) {
|
|
229
|
-
byVendor[model.vendor] = [];
|
|
230
|
-
}
|
|
231
|
-
byVendor[model.vendor].push(model);
|
|
232
|
-
});
|
|
495
|
+
console.log(`\nš Supported LLM Models${checked ? ` (checked ${checked})` : ''}:\n`);
|
|
496
|
+
console.log('ā'.repeat(78));
|
|
233
497
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
498
|
+
// Group by vendor
|
|
499
|
+
const byVendor = {};
|
|
500
|
+
models.forEach((model) => {
|
|
501
|
+
if (!byVendor[model.vendor]) {
|
|
502
|
+
byVendor[model.vendor] = [];
|
|
503
|
+
}
|
|
504
|
+
byVendor[model.vendor].push(model);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
const size = (tokens) =>
|
|
508
|
+
!tokens
|
|
509
|
+
? '-'
|
|
510
|
+
: tokens >= 1000000
|
|
511
|
+
? `${+(tokens / 1000000).toFixed(2)}M`
|
|
512
|
+
: `${Math.floor(tokens / 1000)}K`;
|
|
513
|
+
const price = (pricing) => (pricing ? `$${pricing.input}/$${pricing.output}` : '-');
|
|
514
|
+
|
|
515
|
+
// Display by vendor
|
|
516
|
+
Object.entries(byVendor).forEach(([vendor, models]) => {
|
|
517
|
+
console.log(`\n${vendor}:`);
|
|
518
|
+
console.log(
|
|
519
|
+
` ${'ID'.padEnd(26)} ${'Context'.padStart(7)} ${'Output'.padStart(7)} ${'$ in/out per 1M'.padEnd(16)} Status`
|
|
520
|
+
);
|
|
521
|
+
models.forEach((model) => {
|
|
522
|
+
const status = model.status === 'active' ? '' : model.status;
|
|
523
|
+
console.log(
|
|
524
|
+
` ${model.id.padEnd(26)} ${size(model.contextWindow).padStart(7)} ${size(model.outputWindow).padStart(7)} ${price(model.pricing).padEnd(16)} ${status}`
|
|
525
|
+
);
|
|
243
526
|
});
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
console.log('\n' + 'ā'.repeat(78));
|
|
530
|
+
console.log('\nUsage:');
|
|
531
|
+
console.log(' ctxman --target-model <MODEL_ID>');
|
|
532
|
+
console.log(' ctxman --auto-detect-llm');
|
|
533
|
+
console.log(' ctxman --list-llms --all Include models the provider has retired');
|
|
534
|
+
console.log('\nExample:');
|
|
535
|
+
console.log(` ctxman --target-model ${DEFAULT_TARGET_MODEL}`);
|
|
536
|
+
console.log(' ctxman --auto-detect-llm --cli\n');
|
|
252
537
|
}
|
|
253
538
|
|
|
254
539
|
function printStartupInfo(options) {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
540
|
+
console.log(`š Ctxman v${pkg.version}`);
|
|
541
|
+
console.log('='.repeat(50));
|
|
542
|
+
|
|
543
|
+
// Show profile info if used
|
|
544
|
+
if (options.profile) {
|
|
545
|
+
const manager = new ProfileManager(process.cwd());
|
|
546
|
+
const profile = manager.get(options.profile);
|
|
547
|
+
if (profile) {
|
|
548
|
+
console.log(`š Using profile: ${profile.name}`);
|
|
549
|
+
console.log(` Description: ${profile.description}`);
|
|
550
|
+
console.log();
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Show template info if used
|
|
555
|
+
if (options.template) {
|
|
556
|
+
const manager = new TemplateManager(process.cwd());
|
|
557
|
+
const template = manager.get(options.template);
|
|
558
|
+
if (template) {
|
|
559
|
+
console.log(`š Using template: ${template.name}`);
|
|
560
|
+
console.log(` Description: ${template.description}`);
|
|
561
|
+
console.log();
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// Only show active options if any are set
|
|
566
|
+
const hasOptions =
|
|
567
|
+
options.outputFormat ||
|
|
568
|
+
options.methodLevel ||
|
|
569
|
+
options.chunking?.enabled ||
|
|
570
|
+
options.saveReport ||
|
|
571
|
+
options.verbose ||
|
|
572
|
+
options.contextExport ||
|
|
573
|
+
options.contextToClipboard ||
|
|
574
|
+
options.gitingest;
|
|
575
|
+
|
|
576
|
+
if (hasOptions) {
|
|
577
|
+
console.log('š Active options:');
|
|
578
|
+
if (options.outputFormat) {
|
|
579
|
+
console.log(` Output format: ${options.outputFormat}`);
|
|
580
|
+
}
|
|
581
|
+
if (options.methodLevel) {
|
|
582
|
+
console.log(' Method-level analysis: enabled');
|
|
583
|
+
}
|
|
584
|
+
if (options.saveReport) {
|
|
585
|
+
console.log(' Save report: enabled');
|
|
586
|
+
}
|
|
587
|
+
if (options.gitingest) {
|
|
588
|
+
console.log(' GitIngest format: enabled');
|
|
287
589
|
}
|
|
590
|
+
if (options.contextExport) {
|
|
591
|
+
console.log(' Context export: enabled');
|
|
592
|
+
}
|
|
593
|
+
if (options.contextToClipboard) {
|
|
594
|
+
console.log(' Copy to clipboard: enabled');
|
|
595
|
+
}
|
|
596
|
+
if (options.chunking?.enabled) {
|
|
597
|
+
console.log(
|
|
598
|
+
` Chunking: ${options.chunking.strategy} (${options.chunking.maxTokensPerChunk.toLocaleString()} tokens/chunk)`
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
console.log();
|
|
602
|
+
}
|
|
288
603
|
}
|
|
289
604
|
|
|
290
605
|
function printHelp() {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
606
|
+
console.log(
|
|
607
|
+
`Ctxman v${pkg.version} - AI Development Platform with Plugin Architecture and Git Integration`
|
|
608
|
+
);
|
|
609
|
+
console.log();
|
|
610
|
+
console.log('Usage: ctxman [options]');
|
|
611
|
+
console.log();
|
|
612
|
+
console.log('Initialization (FEAT-001):');
|
|
613
|
+
console.log(' init Initialize ctxman configuration with interactive wizard');
|
|
614
|
+
console.log(' --force, -f Overwrite existing configuration files');
|
|
615
|
+
console.log(' --minimal Create minimal configuration only');
|
|
616
|
+
console.log(' --yes, -y Skip prompts and use defaults');
|
|
617
|
+
console.log();
|
|
618
|
+
console.log('Default Mode:');
|
|
619
|
+
console.log(' ctxman Launch interactive wizard (DEFAULT)');
|
|
620
|
+
console.log(' --cli Use CLI mode instead of wizard');
|
|
621
|
+
console.log();
|
|
622
|
+
console.log('Profile Options (FEAT-004):');
|
|
623
|
+
console.log(' --profile <name> Use a team configuration profile');
|
|
624
|
+
console.log(' --list-profiles List available profiles');
|
|
625
|
+
console.log(' --create-profile <name> Create a new custom profile');
|
|
626
|
+
console.log(' --export-profile <name> Export a profile to JSON');
|
|
627
|
+
console.log();
|
|
628
|
+
console.log('Template Options (FEAT-009):');
|
|
629
|
+
console.log(' -t, --template <name> Use a pre-built context template');
|
|
630
|
+
console.log(' --list-templates List available templates');
|
|
631
|
+
console.log();
|
|
632
|
+
console.log('Analysis Options:');
|
|
633
|
+
console.log(' -s, --save-report Save detailed JSON report');
|
|
634
|
+
console.log(' -v, --verbose Show all included files');
|
|
635
|
+
console.log(' -m, --method-level Enable method-level analysis');
|
|
636
|
+
console.log(' -g, --gitingest Generate GitIngest-style digest');
|
|
637
|
+
console.log(' --no-redact Keep API keys/tokens/private keys in the digest');
|
|
638
|
+
console.log(' --respect-ignore-files LIST');
|
|
639
|
+
console.log(' Also honour AI-tool ignore files: "all" or a list such');
|
|
640
|
+
console.log(' as .cursorignore,.aiexclude (see ctxman policy)');
|
|
641
|
+
console.log(' --ai-suggest Get AI-powered context optimization suggestions');
|
|
642
|
+
console.log();
|
|
643
|
+
console.log('Output Options (v2.3.0):');
|
|
644
|
+
console.log(
|
|
645
|
+
' -o, --output FORMAT Format of --context-export/--context-clipboard (default: json)'
|
|
646
|
+
);
|
|
647
|
+
console.log(
|
|
648
|
+
' Formats: toon, json, yaml, csv, xml, markdown, gitingest'
|
|
649
|
+
);
|
|
650
|
+
console.log(' --context-export Generate LLM context file');
|
|
651
|
+
console.log(' --context-clipboard Copy context to clipboard');
|
|
652
|
+
console.log(' --list-formats List all available output formats');
|
|
653
|
+
console.log();
|
|
654
|
+
console.log('UI Options (v2.3.0):');
|
|
655
|
+
console.log(' --simple Simple text-based output (no fancy UI)');
|
|
656
|
+
console.log(' --dashboard Live dashboard mode');
|
|
657
|
+
console.log(' --wizard Force interactive wizard mode');
|
|
658
|
+
console.log();
|
|
659
|
+
console.log('Chunking Options (v2.3.0):');
|
|
660
|
+
console.log(' --chunk Enable smart chunking for large repos');
|
|
661
|
+
console.log(' --chunk-strategy TYPE Chunking strategy (smart, size, file, directory)');
|
|
662
|
+
console.log(' --chunk-size TOKENS Max tokens per chunk (default: 100000)');
|
|
663
|
+
console.log();
|
|
664
|
+
console.log('LLM Optimization (v2.3.7):');
|
|
665
|
+
console.log(
|
|
666
|
+
' --target-model MODEL Optimize for specific LLM (e.g., claude-sonnet-5-5, gpt-5)'
|
|
667
|
+
);
|
|
668
|
+
console.log(' --auto-detect-llm Auto-detect LLM from environment variables');
|
|
669
|
+
console.log(' --list-llms List all supported LLM models');
|
|
670
|
+
console.log();
|
|
671
|
+
console.log('Cost Estimation (FEAT-010):');
|
|
672
|
+
console.log(' --estimate-cost Show cost estimates for all LLM providers');
|
|
673
|
+
console.log();
|
|
674
|
+
console.log('Snapshot & Diff (FEAT-003):');
|
|
675
|
+
console.log(' --snapshot [message] Create a snapshot of current token state');
|
|
676
|
+
console.log(' --list-snapshots List all saved snapshots');
|
|
677
|
+
console.log(' --diff-last Compare current state with last snapshot');
|
|
678
|
+
console.log(' --diff-snapshot <id1> <id2> Compare two specific snapshots');
|
|
679
|
+
console.log(' --snapshot-trend Show token growth trend across snapshots');
|
|
680
|
+
console.log(' --limit N Limit snapshots to analyze (default: 10)');
|
|
681
|
+
console.log(' --json Output in JSON format');
|
|
682
|
+
console.log();
|
|
683
|
+
console.log('Context Versioning (FEAT-012):');
|
|
684
|
+
console.log(' --cli Run analysis and auto-create context version');
|
|
685
|
+
console.log(' --list-versions List all saved context versions');
|
|
686
|
+
console.log(' --restore-version <id> Restore a previous context version');
|
|
687
|
+
console.log(' --compare-versions <id1> <id2> Compare two context versions');
|
|
688
|
+
console.log(' --json Output in JSON format (for compare)');
|
|
689
|
+
console.log();
|
|
690
|
+
console.log('Performance Dashboard (FEAT-001):');
|
|
691
|
+
console.log(' --perf-dashboard Display text-based performance dashboard');
|
|
692
|
+
console.log(' --period <period> Time period: 7d (default), 30d, all');
|
|
693
|
+
console.log(' --save-report Save dashboard output to file');
|
|
694
|
+
console.log();
|
|
695
|
+
console.log('Dependency Scanner (FEAT-002):');
|
|
696
|
+
console.log(' --scan-dependencies Analyze node_modules for token impact');
|
|
697
|
+
console.log(' --dependency-depth N How deep to scan (default: 1)');
|
|
698
|
+
console.log(' --include-types Include TypeScript definitions');
|
|
699
|
+
console.log(' --check-security Check for security vulnerabilities (npm audit)');
|
|
700
|
+
console.log();
|
|
701
|
+
console.log('Multi-Repository (FEAT-006):');
|
|
702
|
+
console.log(' --multi-repo Analyze all configured repositories');
|
|
703
|
+
console.log(' --add-repo <path> Add a repository to configuration');
|
|
704
|
+
console.log(' --remove-repo <path> Remove a repository from configuration');
|
|
705
|
+
console.log(' --list-repos List all configured repositories');
|
|
706
|
+
console.log();
|
|
707
|
+
console.log('Git Integration (v3.0.0):');
|
|
708
|
+
console.log(' --changed-only Analyze only files with uncommitted changes');
|
|
709
|
+
console.log(' --changed-since REF Analyze files changed since commit/branch');
|
|
710
|
+
console.log();
|
|
711
|
+
console.log('Platform Features (v3.0.0):');
|
|
712
|
+
console.log(' serve [options] Start REST API server');
|
|
713
|
+
console.log(' --port PORT Server port (default: 3000)');
|
|
714
|
+
console.log(' --auth-token TOKEN API authentication token');
|
|
715
|
+
console.log(' --host HOST Address to bind (default: localhost)');
|
|
716
|
+
console.log(' --cors Send CORS headers (off by default)');
|
|
717
|
+
console.log(' watch [options] Watch mode with auto-analysis');
|
|
718
|
+
console.log(' --debounce MS Debounce delay (default: 1000ms)');
|
|
719
|
+
console.log();
|
|
720
|
+
console.log('General Options:');
|
|
721
|
+
console.log(' -h, --help Show this help');
|
|
722
|
+
console.log(' --version Show version number');
|
|
723
|
+
console.log();
|
|
724
|
+
console.log('Template Examples:');
|
|
725
|
+
console.log(' ctxman --template bug-fix Use bug-fix template');
|
|
726
|
+
console.log(' ctxman -t feature --cli Feature template in CLI mode');
|
|
727
|
+
console.log(' ctxman --list-templates Show all templates');
|
|
728
|
+
console.log();
|
|
729
|
+
console.log('Configuration Files:');
|
|
730
|
+
console.log(' .contextinclude Include only specified files');
|
|
731
|
+
console.log(' .contextignore Exclude specified files');
|
|
732
|
+
console.log(' .methodinclude Include only specified methods');
|
|
733
|
+
console.log(' .methodignore Exclude specified methods');
|
|
734
|
+
console.log(' .ctxman/templates/*.json Custom template files');
|
|
735
|
+
console.log(' .ctxman/profiles/*.json Custom team profile files');
|
|
736
|
+
console.log();
|
|
737
|
+
console.log('Format Conversion (v2.3.2):');
|
|
738
|
+
console.log(' convert INPUT --from FORMAT --to FORMAT');
|
|
739
|
+
console.log(' Convert between formats');
|
|
740
|
+
console.log(' Examples:');
|
|
741
|
+
console.log(' ctxman convert report.json --from json --to toon');
|
|
742
|
+
console.log(' ctxman convert data.toon --from toon --to yaml');
|
|
743
|
+
console.log(' ctxman convert context.yaml --from yaml --to json');
|
|
744
|
+
console.log();
|
|
745
|
+
console.log('GitHub Integration (v2.3.6+):');
|
|
746
|
+
console.log(' github URL [options] Generate GitIngest from GitHub repository');
|
|
747
|
+
console.log(' git URL [options] Alias for github command');
|
|
748
|
+
console.log(' Examples:');
|
|
749
|
+
console.log(' ctxman github facebook/react');
|
|
750
|
+
console.log(' ctxman github https://github.com/vercel/next.js --branch canary');
|
|
751
|
+
console.log(' ctxman git angular/angular -o docs/angular.txt');
|
|
752
|
+
console.log();
|
|
753
|
+
console.log('Other Commands:');
|
|
754
|
+
console.log(' pack [path] [options] File contents in one LLM-ready document (xml, markdown,');
|
|
755
|
+
console.log(' plain, json); --compress, --budget, --split');
|
|
756
|
+
console.log(' (ctxman pack --help for options)');
|
|
757
|
+
console.log(' map [path] [options] Ranked repository map within a token budget');
|
|
758
|
+
console.log(' (ctxman map --help for options)');
|
|
759
|
+
console.log(' pr [--base REF] [--head REF] [path]');
|
|
760
|
+
console.log(' Review context for a branch: log, changed methods,');
|
|
761
|
+
console.log(' diff, related tests (ctxman pr --help)');
|
|
762
|
+
console.log(' why FILE[:SYMBOL|#L10-L40] [options]');
|
|
763
|
+
console.log(' Why the code looks like this: commits, references,');
|
|
764
|
+
console.log(' owners, co-changes, docs and tests (ctxman why --help)');
|
|
765
|
+
console.log(' search QUERY [path] Ranked (BM25) search over methods and code chunks');
|
|
766
|
+
console.log(' count [path|-] [--model ID] [--api]');
|
|
767
|
+
console.log(' Count tokens for a model (exact for Claude with --api)');
|
|
768
|
+
console.log(' mcp [install|uninstall|config]');
|
|
769
|
+
console.log(' Start the MCP server or add it to an MCP client');
|
|
770
|
+
console.log(' policy [check|init] Show or create the export policy (.ctxman/policy.json)');
|
|
771
|
+
console.log(' audit [--since DATE] Show the export audit log written under a policy');
|
|
772
|
+
console.log(' ask QUESTION Answer a question from the indexed codebase');
|
|
773
|
+
console.log(' update [check|install|rollback|channel NAME|info]');
|
|
774
|
+
console.log(' Check for and manage ctxman updates');
|
|
775
|
+
console.log();
|
|
776
|
+
console.log('Examples:');
|
|
777
|
+
console.log(' ctxman # Launch interactive wizard (DEFAULT)');
|
|
778
|
+
console.log(' ctxman --cli # Use CLI mode');
|
|
779
|
+
console.log(' ctxman --cli -o json --save-report # CLI: JSON format + save report');
|
|
780
|
+
console.log(' ctxman --cli -o toon --context-clipboard # CLI: TOON to clipboard');
|
|
781
|
+
console.log(' ctxman --cli --gitingest --chunk # CLI: GitIngest with chunking');
|
|
782
|
+
console.log(' ctxman --cli -m -o yaml # CLI: Method-level + YAML format');
|
|
783
|
+
console.log(' ctxman --cli --chunk --chunk-strategy smart # CLI: Smart chunking');
|
|
784
|
+
console.log(' ctxman convert data.json --from json --to toon # Convert formats');
|
|
785
|
+
console.log();
|
|
786
|
+
console.log('Format Comparison (token efficiency):');
|
|
787
|
+
console.log(' TOON: 40-50% reduction (most efficient)');
|
|
788
|
+
console.log(' JSON: Standard format (baseline)');
|
|
789
|
+
console.log(' YAML: Human-readable (5-10% larger than JSON)');
|
|
790
|
+
console.log(' Markdown: Documentation-friendly (20-30% larger)');
|
|
791
|
+
console.log();
|
|
792
|
+
console.log('For more information: https://github.com/hakkisagdic/ctxman');
|
|
383
793
|
}
|
|
384
794
|
|
|
385
795
|
function listFormats() {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
console.log('š Available Output Formats:\n');
|
|
390
|
-
console.log('Format'.padEnd(15) + 'Description'.padEnd(50) + 'Extension');
|
|
391
|
-
console.log('='.repeat(80));
|
|
392
|
-
|
|
393
|
-
for (const [name, info] of Object.entries(formats)) {
|
|
394
|
-
console.log(
|
|
395
|
-
name.padEnd(15) +
|
|
396
|
-
info.description.substring(0, 48).padEnd(50) +
|
|
397
|
-
info.extension
|
|
398
|
-
);
|
|
399
|
-
}
|
|
796
|
+
const registry = new FormatRegistry();
|
|
797
|
+
const formats = registry.getAllInfo();
|
|
400
798
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
799
|
+
console.log('š Available Output Formats:\n');
|
|
800
|
+
console.log('Format'.padEnd(15) + 'Description'.padEnd(50) + 'Extension');
|
|
801
|
+
console.log('='.repeat(80));
|
|
802
|
+
|
|
803
|
+
for (const [name, info] of Object.entries(formats)) {
|
|
804
|
+
console.log(name.padEnd(15) + info.description.substring(0, 48).padEnd(50) + info.extension);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
console.log();
|
|
808
|
+
console.log('Usage: ctxman --output <format>');
|
|
809
|
+
console.log('Example: ctxman --output toon --context-clipboard');
|
|
404
810
|
}
|
|
405
811
|
|
|
406
812
|
function getChangedSince(args) {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
813
|
+
const sinceIndex = args.findIndex((arg) => arg === '--changed-since');
|
|
814
|
+
if (sinceIndex !== -1 && args[sinceIndex + 1]) {
|
|
815
|
+
return args[sinceIndex + 1];
|
|
816
|
+
}
|
|
817
|
+
return null;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
function getTemplate(args) {
|
|
821
|
+
const templateIndex = args.findIndex((arg) => arg === '--template' || arg === '-t');
|
|
822
|
+
if (templateIndex !== -1 && args[templateIndex + 1]) {
|
|
823
|
+
return args[templateIndex + 1];
|
|
824
|
+
}
|
|
825
|
+
return null;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
function getProfile(args) {
|
|
829
|
+
const profileIndex = args.findIndex((arg) => arg === '--profile');
|
|
830
|
+
if (profileIndex !== -1 && args[profileIndex + 1]) {
|
|
831
|
+
return args[profileIndex + 1];
|
|
832
|
+
}
|
|
833
|
+
return null;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
function listTemplates() {
|
|
837
|
+
const manager = new TemplateManager(process.cwd());
|
|
838
|
+
console.log(manager.formatList());
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
function listProfiles() {
|
|
842
|
+
const manager = new ProfileManager(process.cwd());
|
|
843
|
+
console.log(manager.formatList());
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
function createProfile(args) {
|
|
847
|
+
const profileIndex = args.findIndex((arg) => arg === '--create-profile');
|
|
848
|
+
const profileName = profileIndex !== -1 && args[profileIndex + 1] ? args[profileIndex + 1] : null;
|
|
849
|
+
|
|
850
|
+
if (!profileName) {
|
|
851
|
+
console.error('ā Profile name required');
|
|
852
|
+
console.error(' Usage: ctxman --create-profile <name>');
|
|
853
|
+
process.exit(1);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
const manager = new ProfileManager(process.cwd());
|
|
857
|
+
|
|
858
|
+
// Create a basic profile template
|
|
859
|
+
const profile = {
|
|
860
|
+
name: `${profileName} Profile`,
|
|
861
|
+
description: `Configuration for ${profileName}`,
|
|
862
|
+
createdBy: process.env.USER || 'unknown',
|
|
863
|
+
config: {
|
|
864
|
+
exclude: ['**/*.test.js', '**/*.spec.js', 'node_modules/**'],
|
|
865
|
+
include: ['src/**', 'lib/**'],
|
|
866
|
+
targetModel: DEFAULT_TARGET_MODEL,
|
|
867
|
+
methodLevel: false,
|
|
868
|
+
},
|
|
869
|
+
};
|
|
870
|
+
|
|
871
|
+
try {
|
|
872
|
+
const filePath = manager.createCustom(profileName, profile);
|
|
873
|
+
console.log(`\nā
Created profile '${profileName}'`);
|
|
874
|
+
console.log(` Location: ${filePath}\n`);
|
|
875
|
+
console.log(' Edit the file to customize your team configuration.\n');
|
|
876
|
+
} catch (error) {
|
|
877
|
+
console.error(`ā Failed to create profile: ${error.message}`);
|
|
878
|
+
process.exit(1);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
function exportProfile(args) {
|
|
883
|
+
const profileIndex = args.findIndex((arg) => arg === '--export-profile');
|
|
884
|
+
const profileName = profileIndex !== -1 && args[profileIndex + 1] ? args[profileIndex + 1] : null;
|
|
885
|
+
|
|
886
|
+
if (!profileName) {
|
|
887
|
+
console.error('ā Profile name required');
|
|
888
|
+
console.error(' Usage: ctxman --export-profile <name>');
|
|
889
|
+
process.exit(1);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
const manager = new ProfileManager(process.cwd());
|
|
893
|
+
|
|
894
|
+
try {
|
|
895
|
+
const profile = manager.export(profileName);
|
|
896
|
+
console.log(JSON.stringify(profile, null, 2));
|
|
897
|
+
} catch (error) {
|
|
898
|
+
console.error(`ā Failed to export profile: ${error.message}`);
|
|
899
|
+
process.exit(1);
|
|
900
|
+
}
|
|
412
901
|
}
|
|
413
902
|
|
|
414
903
|
async function runAPIServer(args) {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
904
|
+
const portIndex = args.findIndex((arg) => arg === '--port');
|
|
905
|
+
const port = portIndex !== -1 && args[portIndex + 1] ? parseInt(args[portIndex + 1], 10) : 3000;
|
|
906
|
+
|
|
907
|
+
const authTokenIndex = args.findIndex((arg) => arg === '--auth-token');
|
|
908
|
+
const authToken =
|
|
909
|
+
authTokenIndex !== -1 && args[authTokenIndex + 1] ? args[authTokenIndex + 1] : null;
|
|
910
|
+
|
|
911
|
+
const hostIndex = args.findIndex((arg) => arg === '--host');
|
|
912
|
+
const host = hostIndex !== -1 && args[hostIndex + 1] ? args[hostIndex + 1] : 'localhost';
|
|
913
|
+
const cors = args.includes('--cors');
|
|
914
|
+
|
|
915
|
+
if (!authToken && cors) {
|
|
916
|
+
console.warn('ā ļø --cors without --auth-token: any web page you open can read the responses.');
|
|
917
|
+
}
|
|
918
|
+
if (!authToken && !['localhost', '127.0.0.1', '::1'].includes(host)) {
|
|
919
|
+
console.warn(
|
|
920
|
+
`ā ļø Listening on ${host} without --auth-token: anyone on the network can query it.`
|
|
921
|
+
);
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
const server = new APIServer({ port, host, authToken, cors });
|
|
925
|
+
|
|
926
|
+
// Handle shutdown
|
|
927
|
+
process.on('SIGINT', () => {
|
|
928
|
+
console.log('\n\nš Shutting down server...');
|
|
929
|
+
server.stop();
|
|
930
|
+
process.exit(0);
|
|
931
|
+
});
|
|
433
932
|
|
|
434
|
-
|
|
933
|
+
server.start();
|
|
435
934
|
}
|
|
436
935
|
|
|
437
936
|
async function runChangedFilesAnalysis(options) {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
937
|
+
console.log('š Git Integration - Analyzing Changed Files');
|
|
938
|
+
console.log('ā'.repeat(60));
|
|
939
|
+
console.log();
|
|
940
|
+
|
|
941
|
+
const diffAnalyzer = new DiffAnalyzer(options.projectRoot);
|
|
942
|
+
const changes = diffAnalyzer.analyzeChanges(options.changedSince);
|
|
943
|
+
|
|
944
|
+
console.log(`š Found ${changes.totalChangedFiles} changed files`);
|
|
945
|
+
if (options.changedSince) {
|
|
946
|
+
console.log(` Since: ${options.changedSince}`);
|
|
947
|
+
}
|
|
948
|
+
console.log(` Impact: ${changes.impact.level.toUpperCase()} (score: ${changes.impact.score})`);
|
|
949
|
+
console.log();
|
|
950
|
+
|
|
951
|
+
if (changes.totalChangedFiles === 0) {
|
|
952
|
+
console.log('ā
No changed files to analyze');
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
// Analyze only changed files; git reports them relative to the repository root
|
|
957
|
+
const changedFiles = new Set(changes.changedFiles);
|
|
958
|
+
const analyzer = new TokenAnalyzer(options.projectRoot, {
|
|
959
|
+
...options,
|
|
960
|
+
fileFilter: (relativePath) => changedFiles.has(relativePath.split(sep).join('/')),
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
analyzer.run();
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
async function runWatchMode(args) {
|
|
967
|
+
console.log('šļø Starting watch mode...\n');
|
|
968
|
+
|
|
969
|
+
const projectRoot = process.cwd();
|
|
970
|
+
const debounce = args.includes('--debounce')
|
|
971
|
+
? parseInt(args[args.indexOf('--debounce') + 1], 10) || 1000
|
|
972
|
+
: 1000;
|
|
973
|
+
|
|
974
|
+
const watcher = new FileWatcher(projectRoot, { debounce });
|
|
975
|
+
const analyzer = new IncrementalAnalyzer({ methodLevel: args.includes('-m') });
|
|
976
|
+
|
|
977
|
+
// Handle file changes
|
|
978
|
+
watcher.on('file:changed', async (event) => {
|
|
979
|
+
console.log(`\nš File ${event.type}: ${event.relativePath}`);
|
|
980
|
+
await analyzer.analyzeChange(event);
|
|
981
|
+
});
|
|
982
|
+
|
|
983
|
+
// Handle analysis complete
|
|
984
|
+
analyzer.on('analysis:complete', (event) => {
|
|
985
|
+
console.log(` ā
Analysis complete: ${event.analysis.tokens} tokens (${event.elapsed}ms)`);
|
|
986
|
+
|
|
987
|
+
const stats = analyzer.getStats();
|
|
988
|
+
console.log(
|
|
989
|
+
` š Total: ${stats.totalFiles} files, ${stats.totalTokens.toLocaleString()} tokens`
|
|
990
|
+
);
|
|
991
|
+
});
|
|
992
|
+
|
|
993
|
+
// Handle file deletion
|
|
994
|
+
analyzer.on('file:deleted', (event) => {
|
|
995
|
+
console.log(` šļø File deleted: ${event.file} (-${event.oldTokens} tokens)`);
|
|
996
|
+
});
|
|
997
|
+
|
|
998
|
+
// Start watching
|
|
999
|
+
watcher.start();
|
|
1000
|
+
|
|
1001
|
+
console.log('ā
Watch mode active');
|
|
1002
|
+
console.log(' Press Ctrl+C to stop\n');
|
|
1003
|
+
|
|
1004
|
+
// Keep process alive
|
|
1005
|
+
process.on('SIGINT', () => {
|
|
1006
|
+
console.log('\n\nš Stopping watch mode...');
|
|
1007
|
+
watcher.stop();
|
|
1008
|
+
process.exit(0);
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// Ink needs raw mode on a terminal; without one it prints an error and exits 0 having done
|
|
1013
|
+
// nothing, so the callers' fallbacks never ran
|
|
1014
|
+
function assertInteractiveTerminal() {
|
|
1015
|
+
if (!process.stdin.isTTY) {
|
|
1016
|
+
throw new Error('stdin is not an interactive terminal');
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
async function runWizard() {
|
|
1021
|
+
try {
|
|
1022
|
+
assertInteractiveTerminal();
|
|
1023
|
+
|
|
1024
|
+
// Dynamic imports for ESM modules
|
|
1025
|
+
const ReactModule = await import('react');
|
|
1026
|
+
const React = ReactModule.default || ReactModule;
|
|
1027
|
+
const { render } = await import('ink');
|
|
1028
|
+
const Wizard = (await import('../lib/ui/wizard.js')).default;
|
|
1029
|
+
|
|
1030
|
+
// Clear screen for clean wizard display
|
|
1031
|
+
console.clear();
|
|
1032
|
+
console.log('š§ Starting interactive wizard...\n');
|
|
1033
|
+
|
|
1034
|
+
const instance = render(
|
|
1035
|
+
React.createElement(Wizard, {
|
|
1036
|
+
onComplete: (answers) => {
|
|
1037
|
+
instance.unmount();
|
|
1038
|
+
|
|
1039
|
+
console.log('\n⨠Wizard complete! Running analysis with your configuration...\n');
|
|
1040
|
+
|
|
1041
|
+
// Show template info if profile is used
|
|
1042
|
+
if (answers.profile && answers.profile !== 'custom') {
|
|
1043
|
+
console.log(`š Using template: ${answers.profile}`);
|
|
1044
|
+
if (answers.profileMetadata) {
|
|
1045
|
+
console.log(` Description: ${answers.profileMetadata.description}`);
|
|
1046
|
+
}
|
|
1047
|
+
console.log();
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
// Run analyzer with wizard configuration
|
|
1051
|
+
const options = {
|
|
1052
|
+
outputFormat: answers.outputFormat,
|
|
1053
|
+
useCase: answers.useCase,
|
|
1054
|
+
targetModel: answers.targetModel,
|
|
1055
|
+
projectRoot: process.cwd(),
|
|
1056
|
+
simple: true, // No export menu
|
|
1057
|
+
contextExport: true, // Auto-export to file
|
|
1058
|
+
template: answers.profile !== 'custom' ? answers.profile : null,
|
|
1059
|
+
policy: loadCliPolicy(process.cwd()),
|
|
1060
|
+
};
|
|
1061
|
+
|
|
1062
|
+
const analyzer = new TokenAnalyzer(options.projectRoot, options);
|
|
1063
|
+
analyzer.run();
|
|
1064
|
+
|
|
1065
|
+
console.log('\nā
Analysis complete! Context exported to llm-context.json\n');
|
|
1066
|
+
},
|
|
1067
|
+
})
|
|
1068
|
+
);
|
|
1069
|
+
} catch (error) {
|
|
1070
|
+
throw error; // Re-throw to be caught by main()
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
async function runDashboard() {
|
|
1075
|
+
try {
|
|
1076
|
+
assertInteractiveTerminal();
|
|
1077
|
+
|
|
1078
|
+
// Dynamic imports for ESM modules
|
|
1079
|
+
const ReactModule = await import('react');
|
|
1080
|
+
const React = ReactModule.default || ReactModule;
|
|
1081
|
+
const { render } = await import('ink');
|
|
1082
|
+
const Dashboard = (await import('../lib/ui/dashboard.js')).default;
|
|
1083
|
+
|
|
1084
|
+
// Clear console and show loading message
|
|
1085
|
+
console.clear();
|
|
1086
|
+
console.log('š Analyzing project for dashboard...\n');
|
|
1087
|
+
|
|
1088
|
+
// Run analyzer silently (no console output)
|
|
1089
|
+
const originalLog = console.log;
|
|
1090
|
+
const logs = [];
|
|
1091
|
+
console.log = (...args) => logs.push(args); // Capture logs
|
|
1092
|
+
|
|
1093
|
+
const analyzer = new TokenAnalyzer(process.cwd(), {
|
|
1094
|
+
simple: true,
|
|
1095
|
+
verbose: false,
|
|
1096
|
+
dashboard: true, // Skip export handling
|
|
1097
|
+
});
|
|
1098
|
+
const stats = analyzer.run();
|
|
1099
|
+
|
|
1100
|
+
console.log = originalLog; // Restore console.log
|
|
441
1101
|
|
|
442
|
-
|
|
443
|
-
|
|
1102
|
+
// Clear and render dashboard
|
|
1103
|
+
console.clear();
|
|
1104
|
+
|
|
1105
|
+
const instance = render(
|
|
1106
|
+
React.createElement(Dashboard, {
|
|
1107
|
+
stats,
|
|
1108
|
+
topFiles: stats.largestFiles || [],
|
|
1109
|
+
status: 'complete',
|
|
1110
|
+
onExit: () => {
|
|
1111
|
+
instance.unmount();
|
|
1112
|
+
process.exit(0);
|
|
1113
|
+
},
|
|
1114
|
+
})
|
|
1115
|
+
);
|
|
1116
|
+
} catch (error) {
|
|
1117
|
+
throw error; // Re-throw to be caught by main()
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
/**
|
|
1122
|
+
* Run init wizard for configuration setup (FEAT-001)
|
|
1123
|
+
* @param {string[]} args - Command line arguments
|
|
1124
|
+
*/
|
|
1125
|
+
async function runInitWizard(args) {
|
|
1126
|
+
try {
|
|
1127
|
+
const options = {
|
|
1128
|
+
force: args.includes('--force') || args.includes('-f'),
|
|
1129
|
+
minimal: args.includes('--minimal'),
|
|
1130
|
+
yes: args.includes('--yes') || args.includes('-y'),
|
|
1131
|
+
};
|
|
444
1132
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
1133
|
+
if (!options.yes && !options.minimal && !process.stdin.isTTY) {
|
|
1134
|
+
console.error('ā The init wizard needs an interactive terminal.');
|
|
1135
|
+
console.error(' Use --yes for the detected defaults or --minimal for a minimal config.');
|
|
1136
|
+
process.exitCode = 1;
|
|
1137
|
+
return;
|
|
448
1138
|
}
|
|
449
|
-
console.log(` Impact: ${changes.impact.level.toUpperCase()} (score: ${changes.impact.score})`);
|
|
450
|
-
console.log();
|
|
451
1139
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
1140
|
+
// Dynamic imports for ESM modules
|
|
1141
|
+
const ReactModule = await import('react');
|
|
1142
|
+
const React = ReactModule.default || ReactModule;
|
|
1143
|
+
const { render } = await import('ink');
|
|
1144
|
+
const InitWizard = (await import('../lib/ui/init-wizard-ui.js')).default;
|
|
1145
|
+
|
|
1146
|
+
// Clear screen for clean wizard display
|
|
1147
|
+
console.clear();
|
|
1148
|
+
|
|
1149
|
+
// If --yes flag, skip interactive UI and use defaults
|
|
1150
|
+
if (options.yes || options.minimal) {
|
|
1151
|
+
console.log('š§ Initializing ctxman configuration...\n');
|
|
1152
|
+
|
|
1153
|
+
const ProjectDetector = (await import('../lib/wizards/project-detector.js')).default;
|
|
1154
|
+
const ConfigGenerator = (await import('../lib/wizards/config-generator.js')).default;
|
|
1155
|
+
|
|
1156
|
+
const detector = new ProjectDetector(process.cwd());
|
|
1157
|
+
const generator = new ConfigGenerator(process.cwd());
|
|
1158
|
+
|
|
1159
|
+
// Detect project type
|
|
1160
|
+
const projects = await detector.detect();
|
|
1161
|
+
|
|
1162
|
+
let config;
|
|
1163
|
+
if (options.minimal) {
|
|
1164
|
+
config = {
|
|
1165
|
+
contextignore: ['node_modules/', '.git/', 'dist/', 'build/', 'coverage/'],
|
|
1166
|
+
contextinclude: ['src/**', 'lib/**'],
|
|
1167
|
+
methodinclude: [],
|
|
1168
|
+
};
|
|
1169
|
+
} else {
|
|
1170
|
+
const types = projects.map((p) => p.type);
|
|
1171
|
+
config = detector.getMergedConfig(types);
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
// Generate config files
|
|
1175
|
+
const result = generator.generate(config, {
|
|
1176
|
+
force: options.force,
|
|
1177
|
+
minimal: options.minimal,
|
|
1178
|
+
});
|
|
1179
|
+
|
|
1180
|
+
// Display results
|
|
1181
|
+
console.log('Configuration generated:\n');
|
|
1182
|
+
result.created.forEach((f) => console.log(` ā Created ${f.filename}`));
|
|
1183
|
+
result.overwritten.forEach((f) => console.log(` ā Updated ${f.filename}`));
|
|
1184
|
+
result.skipped.forEach((f) => console.log(` ā Skipped ${f.filename} (${f.reason})`));
|
|
1185
|
+
|
|
1186
|
+
console.log('\nā
Configuration complete! Try: ctxman --cli\n');
|
|
1187
|
+
return;
|
|
455
1188
|
}
|
|
456
1189
|
|
|
457
|
-
//
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
1190
|
+
// Interactive wizard
|
|
1191
|
+
const instance = render(
|
|
1192
|
+
React.createElement(InitWizard, {
|
|
1193
|
+
options,
|
|
1194
|
+
onComplete: (result) => {
|
|
1195
|
+
instance.unmount();
|
|
462
1196
|
|
|
463
|
-
|
|
1197
|
+
if (result) {
|
|
1198
|
+
console.log('\nā
Configuration complete!\n');
|
|
1199
|
+
}
|
|
1200
|
+
},
|
|
1201
|
+
})
|
|
1202
|
+
);
|
|
1203
|
+
} catch (error) {
|
|
1204
|
+
console.error('ā Init wizard failed:', error.message);
|
|
1205
|
+
console.error(error.stack);
|
|
1206
|
+
process.exit(1);
|
|
1207
|
+
}
|
|
464
1208
|
}
|
|
465
1209
|
|
|
466
|
-
|
|
467
|
-
|
|
1210
|
+
/**
|
|
1211
|
+
* Run cost estimation for all LLM providers (FEAT-010)
|
|
1212
|
+
* @param {string[]} args - Command line arguments
|
|
1213
|
+
*/
|
|
1214
|
+
function runCostEstimation(_args) {
|
|
1215
|
+
console.log('š° LLM Cost Estimator');
|
|
1216
|
+
console.log('ā'.repeat(60));
|
|
1217
|
+
console.log();
|
|
1218
|
+
console.log('š Analyzing repository...');
|
|
1219
|
+
console.log();
|
|
1220
|
+
|
|
1221
|
+
// Run analyzer silently (no console output)
|
|
1222
|
+
const originalLog = console.log;
|
|
1223
|
+
const logs = [];
|
|
1224
|
+
console.log = (...args) => logs.push(args); // Capture logs
|
|
1225
|
+
|
|
1226
|
+
const analyzer = new TokenAnalyzer(process.cwd(), {
|
|
1227
|
+
simple: true,
|
|
1228
|
+
verbose: false,
|
|
1229
|
+
dashboard: true, // Skip export handling
|
|
1230
|
+
});
|
|
1231
|
+
const stats = analyzer.run();
|
|
1232
|
+
|
|
1233
|
+
console.log = originalLog; // Restore console.log
|
|
1234
|
+
|
|
1235
|
+
if (!stats || !stats.totalTokens) {
|
|
1236
|
+
console.error('ā Failed to analyze repository');
|
|
1237
|
+
process.exit(1);
|
|
1238
|
+
}
|
|
468
1239
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
? parseInt(args[args.indexOf('--debounce') + 1], 10) || 1000
|
|
472
|
-
: 1000;
|
|
1240
|
+
// Create cost estimator
|
|
1241
|
+
const estimator = new LLMCostEstimator(stats.totalTokens);
|
|
473
1242
|
|
|
474
|
-
|
|
475
|
-
|
|
1243
|
+
// Get comparisons and recommendation
|
|
1244
|
+
const comparisons = estimator.compareAll();
|
|
1245
|
+
const recommendation = estimator.getRecommendation(comparisons);
|
|
476
1246
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
await analyzer.analyzeChange(event);
|
|
481
|
-
});
|
|
1247
|
+
// Display formatted output
|
|
1248
|
+
console.log(estimator.formatEstimates(comparisons, recommendation));
|
|
1249
|
+
}
|
|
482
1250
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
1251
|
+
/**
|
|
1252
|
+
* Add a repository to multi-repo configuration (FEAT-006)
|
|
1253
|
+
* @param {string[]} args - Command line arguments
|
|
1254
|
+
*/
|
|
1255
|
+
function addRepo(args) {
|
|
1256
|
+
const repoIndex = args.findIndex((arg) => arg === '--add-repo');
|
|
1257
|
+
const repoPath = repoIndex !== -1 && args[repoIndex + 1] ? args[repoIndex + 1] : null;
|
|
1258
|
+
|
|
1259
|
+
if (!repoPath) {
|
|
1260
|
+
console.error('ā Repository path required');
|
|
1261
|
+
console.error(' Usage: ctxman --add-repo <path>');
|
|
1262
|
+
console.error(' Example: ctxman --add-repo ../frontend-app');
|
|
1263
|
+
process.exit(1);
|
|
1264
|
+
}
|
|
486
1265
|
|
|
487
|
-
|
|
488
|
-
console.log(` š Total: ${stats.totalFiles} files, ${stats.totalTokens.toLocaleString()} tokens`);
|
|
489
|
-
});
|
|
1266
|
+
const manager = new MultiRepoManager(process.cwd());
|
|
490
1267
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
1268
|
+
// Parse optional alias from --alias flag
|
|
1269
|
+
const aliasIndex = args.findIndex((arg) => arg === '--alias');
|
|
1270
|
+
const alias = aliasIndex !== -1 && args[aliasIndex + 1] ? args[aliasIndex + 1] : null;
|
|
1271
|
+
|
|
1272
|
+
try {
|
|
1273
|
+
const repo = manager.addRepo(repoPath, { alias });
|
|
1274
|
+
console.log(`\nā
Added repository: ${repo.alias}`);
|
|
1275
|
+
console.log(` Path: ${repo.path}`);
|
|
1276
|
+
console.log(` ID: ${repo.id}\n`);
|
|
1277
|
+
} catch (error) {
|
|
1278
|
+
console.error(`ā Failed to add repository: ${error.message}`);
|
|
1279
|
+
process.exit(1);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
495
1282
|
|
|
496
|
-
|
|
497
|
-
|
|
1283
|
+
/**
|
|
1284
|
+
* Remove a repository from multi-repo configuration (FEAT-006)
|
|
1285
|
+
* @param {string[]} args - Command line arguments
|
|
1286
|
+
*/
|
|
1287
|
+
function removeRepo(args) {
|
|
1288
|
+
const repoIndex = args.findIndex((arg) => arg === '--remove-repo');
|
|
1289
|
+
const repoPath = repoIndex !== -1 && args[repoIndex + 1] ? args[repoIndex + 1] : null;
|
|
1290
|
+
|
|
1291
|
+
if (!repoPath) {
|
|
1292
|
+
console.error('ā Repository path required');
|
|
1293
|
+
console.error(' Usage: ctxman --remove-repo <path>');
|
|
1294
|
+
console.error(' Example: ctxman --remove-repo ../frontend-app');
|
|
1295
|
+
process.exit(1);
|
|
1296
|
+
}
|
|
498
1297
|
|
|
499
|
-
|
|
500
|
-
console.log(' Press Ctrl+C to stop\n');
|
|
1298
|
+
const manager = new MultiRepoManager(process.cwd());
|
|
501
1299
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
1300
|
+
if (manager.removeRepo(repoPath)) {
|
|
1301
|
+
console.log(`\nā
Removed repository: ${repoPath}\n`);
|
|
1302
|
+
} else {
|
|
1303
|
+
console.error(`ā Repository not found: ${repoPath}`);
|
|
1304
|
+
console.error(' Use --list-repos to see configured repositories.');
|
|
1305
|
+
process.exit(1);
|
|
1306
|
+
}
|
|
508
1307
|
}
|
|
509
1308
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
const Wizard = (await import('../lib/ui/wizard.js')).default;
|
|
517
|
-
|
|
518
|
-
// Clear screen for clean wizard display
|
|
519
|
-
console.clear();
|
|
520
|
-
console.log('š§ Starting interactive wizard...\n');
|
|
521
|
-
|
|
522
|
-
const instance = render(
|
|
523
|
-
React.createElement(Wizard, {
|
|
524
|
-
onComplete: (answers) => {
|
|
525
|
-
instance.unmount();
|
|
526
|
-
|
|
527
|
-
console.log('\n⨠Wizard complete! Running analysis with your configuration...\n');
|
|
528
|
-
|
|
529
|
-
// Run analyzer with wizard configuration
|
|
530
|
-
const options = {
|
|
531
|
-
outputFormat: answers.outputFormat,
|
|
532
|
-
useCase: answers.useCase,
|
|
533
|
-
targetModel: answers.targetModel,
|
|
534
|
-
projectRoot: process.cwd(),
|
|
535
|
-
simple: true, // No export menu
|
|
536
|
-
contextExport: true // Auto-export to file
|
|
537
|
-
};
|
|
538
|
-
|
|
539
|
-
const analyzer = new TokenAnalyzer(options.projectRoot, options);
|
|
540
|
-
analyzer.run();
|
|
541
|
-
|
|
542
|
-
console.log('\nā
Analysis complete! Context exported to llm-context.json\n');
|
|
543
|
-
}
|
|
544
|
-
})
|
|
545
|
-
);
|
|
546
|
-
} catch (error) {
|
|
547
|
-
throw error; // Re-throw to be caught by main()
|
|
548
|
-
}
|
|
1309
|
+
/**
|
|
1310
|
+
* List all configured repositories (FEAT-006)
|
|
1311
|
+
*/
|
|
1312
|
+
function listRepos() {
|
|
1313
|
+
const manager = new MultiRepoManager(process.cwd());
|
|
1314
|
+
console.log(manager.formatList());
|
|
549
1315
|
}
|
|
550
1316
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
1317
|
+
/**
|
|
1318
|
+
* Run multi-repository analysis (FEAT-006)
|
|
1319
|
+
* @param {object} options - Analysis options
|
|
1320
|
+
*/
|
|
1321
|
+
async function runMultiRepoAnalysis(options) {
|
|
1322
|
+
console.log('š Multi-Repository Context Analysis');
|
|
1323
|
+
console.log('ā'.repeat(60));
|
|
1324
|
+
console.log();
|
|
1325
|
+
|
|
1326
|
+
const manager = new MultiRepoManager(process.cwd());
|
|
1327
|
+
const repos = manager.listRepos();
|
|
1328
|
+
|
|
1329
|
+
if (repos.length === 0) {
|
|
1330
|
+
console.log('ā ļø No repositories configured.');
|
|
1331
|
+
console.log();
|
|
1332
|
+
console.log(' Add repositories with:');
|
|
1333
|
+
console.log(' ctxman --add-repo <path>');
|
|
1334
|
+
console.log();
|
|
1335
|
+
console.log(' Example:');
|
|
1336
|
+
console.log(' ctxman --add-repo ../frontend');
|
|
1337
|
+
console.log(' ctxman --add-repo ../api --alias "API Service"');
|
|
1338
|
+
console.log();
|
|
1339
|
+
return;
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
console.log(`š Analyzing ${repos.length} repositories...\n`);
|
|
1343
|
+
|
|
1344
|
+
// Run analysis
|
|
1345
|
+
const results = manager.analyzeAll(options);
|
|
1346
|
+
|
|
1347
|
+
// Display results
|
|
1348
|
+
console.log(manager.formatResults(results));
|
|
1349
|
+
|
|
1350
|
+
// Generate GitIngest digest if requested
|
|
1351
|
+
if (options.gitingest) {
|
|
1352
|
+
console.log('\nš Generating GitIngest digest...\n');
|
|
1353
|
+
await generateMultiRepoDigest(results, options);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
/**
|
|
1358
|
+
* Generate GitIngest digest for multi-repo results
|
|
1359
|
+
* @param {object} results - Multi-repo analysis results
|
|
1360
|
+
* @param {object} options - Generation options
|
|
1361
|
+
*/
|
|
1362
|
+
async function generateMultiRepoDigest(results, _options) {
|
|
1363
|
+
const fs = await import('fs');
|
|
1364
|
+
const path = await import('path');
|
|
1365
|
+
|
|
1366
|
+
let digest = `# Multi-Repository Context\n\n`;
|
|
1367
|
+
digest += `Generated: ${new Date().toISOString()}\n\n`;
|
|
1368
|
+
|
|
1369
|
+
// Repository summary
|
|
1370
|
+
digest += `## Repository Summary\n\n`;
|
|
1371
|
+
digest += `| Repository | Files | Tokens | % of Total |\n`;
|
|
1372
|
+
digest += `|------------|-------|--------|------------|\n`;
|
|
1373
|
+
|
|
1374
|
+
for (const repo of results.repos) {
|
|
1375
|
+
if (!repo.error) {
|
|
1376
|
+
digest += `| ${repo.alias} | ${repo.files.toLocaleString()} | ${repo.tokens.toLocaleString()} | ${repo.percentage}% |\n`;
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
digest += `| **Total** | **${results.combined.totalFiles.toLocaleString()}** | **${results.combined.totalTokens.toLocaleString()}** | **100%** |\n\n`;
|
|
1381
|
+
|
|
1382
|
+
// File contents per repository
|
|
1383
|
+
digest += `## Repository Contents\n\n`;
|
|
1384
|
+
|
|
1385
|
+
for (const repo of results.repos) {
|
|
1386
|
+
if (repo.error) continue;
|
|
1387
|
+
|
|
1388
|
+
digest += `### ${repo.alias}\n\n`;
|
|
1389
|
+
|
|
1390
|
+
// Read and include file contents
|
|
1391
|
+
const repoPath = repo.resolvedPath;
|
|
1392
|
+
if (fs.existsSync(repoPath)) {
|
|
1393
|
+
digest += `Path: ${repo.path}\n\n`;
|
|
1394
|
+
digest += `Files: ${repo.files}, Tokens: ${repo.tokens.toLocaleString()}\n\n`;
|
|
593
1395
|
}
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
// Write digest
|
|
1399
|
+
const outputPath = path.join(process.cwd(), 'digest.txt');
|
|
1400
|
+
fs.writeFileSync(outputPath, digest);
|
|
1401
|
+
console.log(`š Generated digest: ${outputPath}\n`);
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
/**
|
|
1405
|
+
* Run AI-powered context suggestions (FEAT-005)
|
|
1406
|
+
* @param {string[]} args - Command line arguments
|
|
1407
|
+
*/
|
|
1408
|
+
async function runAISuggest(args) {
|
|
1409
|
+
const json = args.includes('--json');
|
|
1410
|
+
// With --json, stdout carries only the JSON document
|
|
1411
|
+
const info = json ? console.error : console.log;
|
|
1412
|
+
|
|
1413
|
+
info('š¤ AI Context Suggestions');
|
|
1414
|
+
info('ā'.repeat(60));
|
|
1415
|
+
info();
|
|
1416
|
+
info('š Analyzing repository for optimization opportunities...');
|
|
1417
|
+
info();
|
|
1418
|
+
|
|
1419
|
+
const verbose = args.includes('--verbose') || args.includes('-v');
|
|
1420
|
+
|
|
1421
|
+
// Run analyzer silently (no console output)
|
|
1422
|
+
const originalLog = console.log;
|
|
1423
|
+
const logs = [];
|
|
1424
|
+
console.log = (...args) => logs.push(args); // Capture logs
|
|
1425
|
+
|
|
1426
|
+
const analyzer = new TokenAnalyzer(process.cwd(), {
|
|
1427
|
+
simple: true,
|
|
1428
|
+
verbose: false,
|
|
1429
|
+
dashboard: true, // Skip export handling
|
|
1430
|
+
});
|
|
1431
|
+
const stats = analyzer.run();
|
|
1432
|
+
|
|
1433
|
+
console.log = originalLog; // Restore console.log
|
|
1434
|
+
|
|
1435
|
+
if (!stats || !stats.totalTokens) {
|
|
1436
|
+
console.error('ā Failed to analyze repository');
|
|
1437
|
+
process.exit(1);
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
// Get the files array from stats
|
|
1441
|
+
const files = stats.largestFiles || [];
|
|
1442
|
+
|
|
1443
|
+
// Create AI suggester
|
|
1444
|
+
const suggester = new AISuggester({
|
|
1445
|
+
projectRoot: process.cwd(),
|
|
1446
|
+
verbose,
|
|
1447
|
+
json,
|
|
1448
|
+
});
|
|
1449
|
+
|
|
1450
|
+
// Run analysis and get suggestions; the suggester's logger writes through console.log
|
|
1451
|
+
console.log = info;
|
|
1452
|
+
const result = await suggester.analyze(stats, files);
|
|
1453
|
+
console.log = originalLog;
|
|
1454
|
+
|
|
1455
|
+
// Display formatted output
|
|
1456
|
+
console.log(suggester.formatOutput(result));
|
|
1457
|
+
|
|
1458
|
+
// Save report if requested
|
|
1459
|
+
if (args.includes('--save-report') || args.includes('-s')) {
|
|
1460
|
+
const reportPath = resolve(process.cwd(), 'ai-suggestions-report.json');
|
|
1461
|
+
const fs = await import('fs');
|
|
1462
|
+
fs.writeFileSync(reportPath, JSON.stringify(result, null, 2));
|
|
1463
|
+
info(`š¾ Report saved to: ai-suggestions-report.json`);
|
|
1464
|
+
}
|
|
594
1465
|
}
|
|
595
1466
|
|
|
596
1467
|
function runFormatConversion(args) {
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
1468
|
+
// v2.3.2: Format conversion utility
|
|
1469
|
+
const converter = new FormatConverter();
|
|
1470
|
+
|
|
1471
|
+
// Parse arguments
|
|
1472
|
+
const fromIndex = args.findIndex((arg) => arg === '--from');
|
|
1473
|
+
const toIndex = args.findIndex((arg) => arg === '--to');
|
|
1474
|
+
const inputIndex = args.findIndex((arg) => arg === 'convert') + 1;
|
|
1475
|
+
|
|
1476
|
+
if (fromIndex === -1 || toIndex === -1) {
|
|
1477
|
+
console.error('ā Format conversion requires --from and --to flags');
|
|
1478
|
+
console.error(' Usage: ctxman convert input.json --from json --to toon');
|
|
1479
|
+
process.exit(1);
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
const inputFile = args[inputIndex];
|
|
1483
|
+
const fromFormat = args[fromIndex + 1];
|
|
1484
|
+
const toFormat = args[toIndex + 1];
|
|
1485
|
+
|
|
1486
|
+
if (!inputFile) {
|
|
1487
|
+
console.error('ā No input file specified');
|
|
1488
|
+
process.exit(1);
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
// Generate output filename
|
|
1492
|
+
const outputFile = inputFile.replace(/\.[^.]+$/, `.${toFormat}`);
|
|
1493
|
+
|
|
1494
|
+
console.log('š Converting formats...');
|
|
1495
|
+
console.log(` Input: ${inputFile} (${fromFormat})`);
|
|
1496
|
+
console.log(` Output: ${outputFile} (${toFormat})`);
|
|
1497
|
+
console.log();
|
|
1498
|
+
|
|
1499
|
+
try {
|
|
1500
|
+
const result = converter.convertFile(inputFile, outputFile, fromFormat, toFormat);
|
|
1501
|
+
|
|
1502
|
+
console.log('ā
Conversion successful!');
|
|
1503
|
+
console.log(` Input size: ${result.inputSize.toLocaleString()} chars`);
|
|
1504
|
+
console.log(` Output size: ${result.outputSize.toLocaleString()} chars`);
|
|
1505
|
+
console.log(` Savings: ${result.savingsPercent} (${result.savings} chars)`);
|
|
1506
|
+
console.log(` Output file: ${result.outputFile}`);
|
|
1507
|
+
} catch (error) {
|
|
1508
|
+
console.error('ā Conversion failed:', error.message);
|
|
1509
|
+
process.exit(1);
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
/**
|
|
1514
|
+
* Create a snapshot (FEAT-003)
|
|
1515
|
+
* @param {string[]} args - Command line arguments
|
|
1516
|
+
*/
|
|
1517
|
+
async function runSnapshot(args) {
|
|
1518
|
+
console.log('šø Creating Snapshot');
|
|
1519
|
+
console.log('ā'.repeat(60));
|
|
1520
|
+
console.log();
|
|
1521
|
+
|
|
1522
|
+
// Get message from arguments
|
|
1523
|
+
const snapshotIndex = args.findIndex((arg) => arg === '--snapshot');
|
|
1524
|
+
let message = '';
|
|
1525
|
+
if (snapshotIndex !== -1 && args[snapshotIndex + 1] && !args[snapshotIndex + 1].startsWith('-')) {
|
|
1526
|
+
message = args[snapshotIndex + 1];
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
// Run analyzer silently
|
|
1530
|
+
const originalLog = console.log;
|
|
1531
|
+
const logs = [];
|
|
1532
|
+
console.log = (...args) => logs.push(args);
|
|
1533
|
+
|
|
1534
|
+
const analyzer = new TokenAnalyzer(process.cwd(), {
|
|
1535
|
+
simple: true,
|
|
1536
|
+
verbose: false,
|
|
1537
|
+
dashboard: true,
|
|
1538
|
+
});
|
|
1539
|
+
const stats = analyzer.run();
|
|
1540
|
+
|
|
1541
|
+
console.log = originalLog;
|
|
1542
|
+
|
|
1543
|
+
if (!stats || !stats.totalTokens) {
|
|
1544
|
+
console.error('ā Failed to analyze repository');
|
|
1545
|
+
process.exit(1);
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
// Create snapshot
|
|
1549
|
+
const manager = new SnapshotManager(process.cwd());
|
|
1550
|
+
const { id, snapshot } = await manager.createSnapshot(stats, message);
|
|
1551
|
+
|
|
1552
|
+
// Format output
|
|
1553
|
+
console.log('\nšø Snapshot created:', id);
|
|
1554
|
+
console.log(' Files:', snapshot.summary.totalFiles.toLocaleString());
|
|
1555
|
+
console.log(' Tokens:', snapshot.summary.totalTokens.toLocaleString());
|
|
1556
|
+
if (message) {
|
|
1557
|
+
console.log(' Message:', message);
|
|
1558
|
+
}
|
|
1559
|
+
console.log();
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
/**
|
|
1563
|
+
* List all snapshots (FEAT-003)
|
|
1564
|
+
*/
|
|
1565
|
+
async function listSnapshots() {
|
|
1566
|
+
const manager = new SnapshotManager(process.cwd());
|
|
1567
|
+
const output = await manager.listSnapshots();
|
|
1568
|
+
console.log(output);
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
/**
|
|
1572
|
+
* Compare with last snapshot (FEAT-003)
|
|
1573
|
+
* @param {string[]} args - Command line arguments
|
|
1574
|
+
*/
|
|
1575
|
+
async function runDiffLast(args) {
|
|
1576
|
+
const json = args.includes('--json');
|
|
1577
|
+
// With --json, stdout carries only the JSON document
|
|
1578
|
+
const info = json ? console.error : console.log;
|
|
1579
|
+
|
|
1580
|
+
info('š Comparing with Last Snapshot');
|
|
1581
|
+
info('ā'.repeat(60));
|
|
1582
|
+
info();
|
|
1583
|
+
|
|
1584
|
+
// Run current analysis
|
|
1585
|
+
const originalLog = console.log;
|
|
1586
|
+
const logs = [];
|
|
1587
|
+
console.log = (...args) => logs.push(args);
|
|
1588
|
+
|
|
1589
|
+
const analyzer = new TokenAnalyzer(process.cwd(), {
|
|
1590
|
+
simple: true,
|
|
1591
|
+
verbose: false,
|
|
1592
|
+
dashboard: true,
|
|
1593
|
+
});
|
|
1594
|
+
const stats = analyzer.run();
|
|
1595
|
+
|
|
1596
|
+
console.log = originalLog;
|
|
1597
|
+
|
|
1598
|
+
if (!stats || !stats.totalTokens) {
|
|
1599
|
+
console.error('ā Failed to analyze repository');
|
|
1600
|
+
process.exit(1);
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
// Compare with last snapshot
|
|
1604
|
+
const manager = new SnapshotManager(process.cwd());
|
|
1605
|
+
const comparison = await manager.compareWithLast(stats);
|
|
1606
|
+
|
|
1607
|
+
if (!comparison) {
|
|
1608
|
+
info('\nā ļø No previous snapshots found.');
|
|
1609
|
+
info(' Create one with: ctxman --snapshot "message"\n');
|
|
1610
|
+
return;
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
if (json) {
|
|
1614
|
+
console.log(JSON.stringify(comparison, null, 2));
|
|
1615
|
+
} else {
|
|
1616
|
+
console.log(manager.formatDiff(comparison));
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
/**
|
|
1621
|
+
* Compare two specific snapshots (FEAT-003)
|
|
1622
|
+
* @param {string[]} args - Command line arguments
|
|
1623
|
+
*/
|
|
1624
|
+
async function runDiffSnapshot(args) {
|
|
1625
|
+
const diffIndex = args.findIndex((arg) => arg === '--diff-snapshot');
|
|
1626
|
+
const id1 = diffIndex !== -1 && args[diffIndex + 1] ? args[diffIndex + 1] : null;
|
|
1627
|
+
const id2 = diffIndex !== -1 && args[diffIndex + 2] ? args[diffIndex + 2] : null;
|
|
1628
|
+
|
|
1629
|
+
const json = args.includes('--json');
|
|
1630
|
+
|
|
1631
|
+
if (!id1 || !id2) {
|
|
1632
|
+
console.error('ā Two snapshot IDs required');
|
|
1633
|
+
console.error(' Usage: ctxman --diff-snapshot <id1> <id2>');
|
|
1634
|
+
console.error(' Example: ctxman --diff-snapshot snap-001 snap-002');
|
|
1635
|
+
process.exit(1);
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
const manager = new SnapshotManager(process.cwd());
|
|
1639
|
+
const comparison = await manager.compareSnapshots(id1, id2);
|
|
1640
|
+
|
|
1641
|
+
if (!comparison) {
|
|
1642
|
+
console.error('ā One or both snapshots not found');
|
|
1643
|
+
console.error(` ID1: ${id1}`);
|
|
1644
|
+
console.error(` ID2: ${id2}`);
|
|
1645
|
+
process.exit(1);
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
if (json) {
|
|
1649
|
+
console.log(JSON.stringify(comparison, null, 2));
|
|
1650
|
+
} else {
|
|
1651
|
+
console.log(manager.formatDiff(comparison));
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
/**
|
|
1656
|
+
* Show snapshot trend (FEAT-003)
|
|
1657
|
+
* @param {string[]} args - Command line arguments
|
|
1658
|
+
*/
|
|
1659
|
+
async function runSnapshotTrend(args) {
|
|
1660
|
+
const json = args.includes('--json');
|
|
1661
|
+
// With --json, stdout carries only the JSON document
|
|
1662
|
+
const info = json ? console.error : console.log;
|
|
1663
|
+
|
|
1664
|
+
info('š Snapshot Trend Analysis');
|
|
1665
|
+
info('ā'.repeat(60));
|
|
1666
|
+
info();
|
|
1667
|
+
|
|
1668
|
+
const limitIndex = args.findIndex((arg) => arg === '--limit');
|
|
1669
|
+
const limit = limitIndex !== -1 && args[limitIndex + 1] ? parseInt(args[limitIndex + 1], 10) : 10;
|
|
1670
|
+
|
|
1671
|
+
const manager = new SnapshotManager(process.cwd());
|
|
1672
|
+
const trend = await manager.getTrend(limit);
|
|
1673
|
+
|
|
1674
|
+
if (!trend) {
|
|
1675
|
+
info('\nā ļø Need at least 2 snapshots for trend analysis.');
|
|
1676
|
+
info(' Create snapshots with: ctxman --snapshot "message"\n');
|
|
1677
|
+
return;
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
if (json) {
|
|
1681
|
+
console.log(JSON.stringify(trend, null, 2));
|
|
1682
|
+
} else {
|
|
1683
|
+
console.log(manager.formatTrend(trend));
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
/**
|
|
1688
|
+
* List all context versions (FEAT-012)
|
|
1689
|
+
*/
|
|
1690
|
+
async function listVersions() {
|
|
1691
|
+
const manager = new ContextVersioning(process.cwd());
|
|
1692
|
+
console.log(await manager.listVersions());
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
/**
|
|
1696
|
+
* Restore a context version (FEAT-012)
|
|
1697
|
+
* @param {string[]} args - Command line arguments
|
|
1698
|
+
*/
|
|
1699
|
+
async function restoreVersion(args) {
|
|
1700
|
+
const restoreIndex = args.findIndex((arg) => arg === '--restore-version');
|
|
1701
|
+
const versionId = restoreIndex !== -1 && args[restoreIndex + 1] ? args[restoreIndex + 1] : null;
|
|
1702
|
+
|
|
1703
|
+
if (!versionId) {
|
|
1704
|
+
console.error('ā Version ID required');
|
|
1705
|
+
console.error(' Usage: ctxman --restore-version <id>');
|
|
1706
|
+
console.error(' Example: ctxman --restore-version ctx-v001');
|
|
1707
|
+
process.exit(1);
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
const manager = new ContextVersioning(process.cwd());
|
|
1711
|
+
const version = await manager.restoreVersion(versionId);
|
|
1712
|
+
|
|
1713
|
+
if (!version) {
|
|
1714
|
+
console.error(`ā Version not found: ${versionId}`);
|
|
1715
|
+
console.error(' Use --list-versions to see available versions.');
|
|
1716
|
+
process.exit(1);
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
console.log(`\nā
Restored context version: ${versionId}`);
|
|
1720
|
+
console.log(` Created: ${version.timestamp}`);
|
|
1721
|
+
console.log(` Files: ${version.summary.totalFiles.toLocaleString()}`);
|
|
1722
|
+
console.log(` Tokens: ${version.summary.totalTokens.toLocaleString()}`);
|
|
1723
|
+
if (version.config.targetModel) {
|
|
1724
|
+
console.log(` Model: ${version.config.targetModel}`);
|
|
1725
|
+
}
|
|
1726
|
+
if (version.gitCommit) {
|
|
1727
|
+
console.log(` Git: ${version.gitCommit}`);
|
|
1728
|
+
}
|
|
1729
|
+
console.log();
|
|
1730
|
+
|
|
1731
|
+
// Export restored context to file
|
|
1732
|
+
const fs = await import('fs');
|
|
1733
|
+
const outputPath = 'restored-context.json';
|
|
1734
|
+
fs.writeFileSync(outputPath, JSON.stringify(version, null, 2));
|
|
1735
|
+
console.log(`š Saved to: ${outputPath}\n`);
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
/**
|
|
1739
|
+
* Compare two context versions (FEAT-012)
|
|
1740
|
+
* @param {string[]} args - Command line arguments
|
|
1741
|
+
*/
|
|
1742
|
+
async function compareVersions(args) {
|
|
1743
|
+
const compareIndex = args.findIndex((arg) => arg === '--compare-versions');
|
|
1744
|
+
const id1 = compareIndex !== -1 && args[compareIndex + 1] ? args[compareIndex + 1] : null;
|
|
1745
|
+
const id2 = compareIndex !== -1 && args[compareIndex + 2] ? args[compareIndex + 2] : null;
|
|
1746
|
+
|
|
1747
|
+
if (!id1 || !id2) {
|
|
1748
|
+
console.error('ā Two version IDs required');
|
|
1749
|
+
console.error(' Usage: ctxman --compare-versions <id1> <id2>');
|
|
1750
|
+
console.error(' Example: ctxman --compare-versions ctx-v001 ctx-v002');
|
|
1751
|
+
process.exit(1);
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
const manager = new ContextVersioning(process.cwd());
|
|
1755
|
+
const comparison = await manager.compareVersions(id1, id2);
|
|
1756
|
+
|
|
1757
|
+
if (!comparison) {
|
|
1758
|
+
console.error('ā One or both versions not found');
|
|
1759
|
+
console.error(` ID1: ${id1}`);
|
|
1760
|
+
console.error(` ID2: ${id2}`);
|
|
1761
|
+
process.exit(1);
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
const json = args.includes('--json');
|
|
1765
|
+
if (json) {
|
|
1766
|
+
console.log(JSON.stringify(comparison, null, 2));
|
|
1767
|
+
} else {
|
|
1768
|
+
console.log(manager.formatComparison(comparison));
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
/**
|
|
1773
|
+
* Run dependency scan (FEAT-002)
|
|
1774
|
+
* @param {string[]} args - Command line arguments
|
|
1775
|
+
*/
|
|
1776
|
+
async function runDependencyScan(args) {
|
|
1777
|
+
console.log('š¦ Dependency Context Scanner');
|
|
1778
|
+
console.log('ā'.repeat(60));
|
|
1779
|
+
console.log();
|
|
1780
|
+
|
|
1781
|
+
// Parse options
|
|
1782
|
+
const depthIndex = args.findIndex((arg) => arg === '--dependency-depth');
|
|
1783
|
+
const depth = depthIndex !== -1 && args[depthIndex + 1] ? parseInt(args[depthIndex + 1], 10) : 1;
|
|
1784
|
+
|
|
1785
|
+
const includeTypes = args.includes('--include-types');
|
|
1786
|
+
const checkSecurity = args.includes('--check-security');
|
|
1787
|
+
|
|
1788
|
+
try {
|
|
1789
|
+
// Initialize scanner
|
|
1790
|
+
const scanner = new DependencyScanner(process.cwd(), {
|
|
1791
|
+
depth,
|
|
1792
|
+
includeTypes,
|
|
1793
|
+
checkSecurity,
|
|
1794
|
+
});
|
|
1795
|
+
|
|
1796
|
+
// Initialize import tracker
|
|
1797
|
+
const tracker = new ImportTracker(process.cwd());
|
|
1798
|
+
|
|
1799
|
+
console.log('š Scanning source files for imports...');
|
|
1800
|
+
const activeImports = tracker.trackImports();
|
|
1801
|
+
|
|
1802
|
+
console.log(`š Found ${activeImports.length} actively imported packages`);
|
|
1803
|
+
console.log();
|
|
1804
|
+
|
|
1805
|
+
console.log('š Analyzing dependencies...');
|
|
1806
|
+
const analysis = await scanner.analyze(activeImports);
|
|
1807
|
+
|
|
1808
|
+
// Display results
|
|
1809
|
+
console.log('ā'.repeat(60));
|
|
1810
|
+
console.log('š DEPENDENCY ANALYSIS REPORT');
|
|
1811
|
+
console.log('ā'.repeat(60));
|
|
1812
|
+
console.log();
|
|
1813
|
+
|
|
1814
|
+
console.log(`Total dependencies: ${analysis.summary.totalDependencies}`);
|
|
1815
|
+
console.log(`Installed: ${analysis.summary.installedDependencies}`);
|
|
1816
|
+
console.log(`Missing: ${analysis.summary.missingDependencies}`);
|
|
1817
|
+
console.log(`Active in codebase: ${analysis.summary.activeDependencies}`);
|
|
1818
|
+
console.log(`Total tokens: ${analysis.summary.totalTokens.toLocaleString()}`);
|
|
1819
|
+
|
|
1820
|
+
if (analysis.summary.securityIssues > 0) {
|
|
1821
|
+
console.log(`ā ļø Security issues: ${analysis.summary.securityIssues}`);
|
|
1822
|
+
}
|
|
1823
|
+
console.log();
|
|
1824
|
+
|
|
1825
|
+
// Display table
|
|
1826
|
+
console.log('ā'.repeat(80));
|
|
1827
|
+
console.log(
|
|
1828
|
+
'Package'.padEnd(30) +
|
|
1829
|
+
'Version'.padEnd(12) +
|
|
1830
|
+
'Tokens'.padStart(12) +
|
|
1831
|
+
'Status'.padStart(10) +
|
|
1832
|
+
'Security'.padStart(10)
|
|
1833
|
+
);
|
|
1834
|
+
console.log('ā'.repeat(80));
|
|
1835
|
+
|
|
1836
|
+
for (const dep of analysis.dependencies.slice(0, 20)) {
|
|
1837
|
+
const active = dep.active ? 'ā' : ' ';
|
|
1838
|
+
const security = dep.security?.status || 'unknown';
|
|
1839
|
+
const securityIcon = security === 'ok' ? 'ā
' : security === 'review' ? 'ā ļø' : 'ā';
|
|
1840
|
+
|
|
1841
|
+
console.log(
|
|
1842
|
+
`${active} ${dep.name}`.substring(0, 30).padEnd(30) +
|
|
1843
|
+
dep.version.substring(0, 10).padEnd(12) +
|
|
1844
|
+
dep.tokens.toLocaleString().padStart(12) +
|
|
1845
|
+
dep.status.padStart(10) +
|
|
1846
|
+
securityIcon.padStart(8)
|
|
1847
|
+
);
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
if (analysis.dependencies.length > 20) {
|
|
1851
|
+
console.log(`... and ${analysis.dependencies.length - 20} more dependencies`);
|
|
609
1852
|
}
|
|
1853
|
+
console.log();
|
|
610
1854
|
|
|
611
|
-
|
|
612
|
-
const
|
|
613
|
-
|
|
1855
|
+
// Display security alerts
|
|
1856
|
+
const securityIssues = analysis.dependencies.filter(
|
|
1857
|
+
(d) => d.security?.vulnerabilities?.length > 0
|
|
1858
|
+
);
|
|
614
1859
|
|
|
615
|
-
if (
|
|
616
|
-
|
|
617
|
-
|
|
1860
|
+
if (securityIssues.length > 0) {
|
|
1861
|
+
console.log('ā ļø SECURITY ALERTS:');
|
|
1862
|
+
console.log('ā'.repeat(60));
|
|
1863
|
+
|
|
1864
|
+
for (const dep of securityIssues) {
|
|
1865
|
+
for (const vuln of dep.security.vulnerabilities) {
|
|
1866
|
+
console.log(
|
|
1867
|
+
` - ${dep.name}@${dep.version}: ${vuln.severity.toUpperCase()} - ${vuln.title}`
|
|
1868
|
+
);
|
|
1869
|
+
if (vuln.fixAvailable) {
|
|
1870
|
+
console.log(` š” Fix available - consider upgrading`);
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
console.log();
|
|
618
1875
|
}
|
|
619
1876
|
|
|
620
|
-
// Generate
|
|
621
|
-
|
|
1877
|
+
// Generate dependency context
|
|
1878
|
+
console.log('š Generating dependency context...');
|
|
1879
|
+
const context = scanner.generateContext(analysis, activeImports);
|
|
1880
|
+
|
|
1881
|
+
// Write to file
|
|
1882
|
+
const outputPath = resolve(process.cwd(), 'dependency-context.json');
|
|
1883
|
+
const fs = await import('fs');
|
|
1884
|
+
fs.writeFileSync(outputPath, JSON.stringify(context, null, 2));
|
|
622
1885
|
|
|
623
|
-
|
|
624
|
-
console.log(
|
|
625
|
-
|
|
1886
|
+
const contextTokens = JSON.stringify(context).length / 4; // Rough estimate
|
|
1887
|
+
console.log(
|
|
1888
|
+
`ā
dependency-context.json created (~${Math.round(contextTokens).toLocaleString()} tokens)`
|
|
1889
|
+
);
|
|
1890
|
+
console.log(` Active dependencies: ${analysis.summary.activeDependencies}`);
|
|
1891
|
+
console.log(
|
|
1892
|
+
` Total tokens in dependencies: ${analysis.summary.totalTokens.toLocaleString()}`
|
|
1893
|
+
);
|
|
626
1894
|
console.log();
|
|
627
1895
|
|
|
628
|
-
|
|
629
|
-
|
|
1896
|
+
// Display type definitions if extracted
|
|
1897
|
+
if (includeTypes && analysis.summary.typesExtracted > 0) {
|
|
1898
|
+
console.log(`š TypeScript definitions extracted: ${analysis.summary.typesExtracted} files`);
|
|
1899
|
+
console.log();
|
|
1900
|
+
}
|
|
630
1901
|
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
1902
|
+
// Display largest dependencies
|
|
1903
|
+
if (analysis.summary.largestDependencies.length > 0) {
|
|
1904
|
+
console.log('š LARGEST DEPENDENCIES BY TOKEN COUNT:');
|
|
1905
|
+
console.log('ā'.repeat(40));
|
|
1906
|
+
for (const dep of analysis.summary.largestDependencies.slice(0, 5)) {
|
|
1907
|
+
console.log(` ${dep.name.padEnd(25)} ${dep.tokens.toLocaleString().padStart(10)} tokens`);
|
|
1908
|
+
}
|
|
1909
|
+
console.log();
|
|
639
1910
|
}
|
|
1911
|
+
} catch (error) {
|
|
1912
|
+
console.error('ā Dependency scan failed:', error.message);
|
|
1913
|
+
console.error();
|
|
1914
|
+
console.error('Make sure you are in a Node.js project with package.json and node_modules.');
|
|
1915
|
+
process.exit(1);
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
/**
|
|
1920
|
+
* Run performance dashboard (FEAT-001)
|
|
1921
|
+
* @param {string[]} args - Command line arguments
|
|
1922
|
+
*/
|
|
1923
|
+
async function runPerfDashboard(args) {
|
|
1924
|
+
console.log('š Ctxman Performance Dashboard');
|
|
1925
|
+
console.log('ā'.repeat(60));
|
|
1926
|
+
console.log();
|
|
1927
|
+
|
|
1928
|
+
// Parse period argument
|
|
1929
|
+
const periodIndex = args.findIndex((arg) => arg === '--period');
|
|
1930
|
+
const period = periodIndex !== -1 && args[periodIndex + 1] ? args[periodIndex + 1] : '7d';
|
|
1931
|
+
|
|
1932
|
+
// Dynamic import for PerformanceDashboard
|
|
1933
|
+
const { PerformanceDashboard } = await import('../lib/analyzers/performance-dashboard.js');
|
|
1934
|
+
|
|
1935
|
+
const dashboard = new PerformanceDashboard({
|
|
1936
|
+
projectRoot: process.cwd(),
|
|
1937
|
+
});
|
|
1938
|
+
|
|
1939
|
+
// Run current analysis first to collect fresh metrics
|
|
1940
|
+
console.log('š Running analysis to collect current metrics...');
|
|
1941
|
+
console.log();
|
|
1942
|
+
|
|
1943
|
+
const originalLog = console.log;
|
|
1944
|
+
const logs = [];
|
|
1945
|
+
console.log = (...args) => logs.push(args);
|
|
1946
|
+
|
|
1947
|
+
try {
|
|
1948
|
+
await dashboard.runAnalysis();
|
|
1949
|
+
} catch (_error) {
|
|
1950
|
+
// Continue even if analysis fails - we may have historical data
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
console.log = originalLog;
|
|
1954
|
+
|
|
1955
|
+
// Display the dashboard
|
|
1956
|
+
dashboard.display({ period });
|
|
1957
|
+
|
|
1958
|
+
// Save report if requested
|
|
1959
|
+
if (args.includes('--save-report')) {
|
|
1960
|
+
const reportPath = resolve(process.cwd(), 'performance-report.txt');
|
|
1961
|
+
const fs = await import('fs');
|
|
1962
|
+
fs.writeFileSync(reportPath, dashboard.render({ period }));
|
|
1963
|
+
console.log(`š¾ Report saved to: performance-report.txt`);
|
|
1964
|
+
console.log();
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
/**
|
|
1969
|
+
* Create a context version automatically after CLI analysis (FEAT-012)
|
|
1970
|
+
* @param {object} stats - Analysis stats
|
|
1971
|
+
* @param {object} options - CLI options
|
|
1972
|
+
*/
|
|
1973
|
+
async function autoCreateVersion(stats, options) {
|
|
1974
|
+
const manager = new ContextVersioning(process.cwd());
|
|
1975
|
+
|
|
1976
|
+
const config = {
|
|
1977
|
+
exclude: options.exclude || [],
|
|
1978
|
+
include: options.include || [],
|
|
1979
|
+
targetModel: options.targetModel || null,
|
|
1980
|
+
};
|
|
1981
|
+
|
|
1982
|
+
const { id, version } = await manager.createVersion(stats, config, '');
|
|
1983
|
+
|
|
1984
|
+
console.log(`\nš Context version created: ${id}`);
|
|
1985
|
+
console.log(` Files: ${version.summary.totalFiles.toLocaleString()}`);
|
|
1986
|
+
console.log(` Tokens: ${version.summary.totalTokens.toLocaleString()}`);
|
|
1987
|
+
if (version.gitCommit) {
|
|
1988
|
+
console.log(` Git: ${version.gitCommit}`);
|
|
1989
|
+
}
|
|
1990
|
+
console.log();
|
|
640
1991
|
}
|
|
641
1992
|
|
|
642
1993
|
// ESM entry point
|
|
643
|
-
main().catch(error => {
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
1994
|
+
main().catch((error) => {
|
|
1995
|
+
console.error('ā Error:', error.message);
|
|
1996
|
+
// Exit codes:
|
|
1997
|
+
// 0: Success
|
|
1998
|
+
// 1: General error
|
|
1999
|
+
// 2: Validation/argument error
|
|
2000
|
+
process.exit(1);
|
|
650
2001
|
});
|