ctxman 3.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.ctxman/llm-profiles.json +1067 -39
- package/.ctxman/profiles/backend-team.json +13 -0
- package/.ctxman/profiles/default.json +12 -0
- package/.ctxman/profiles/devops-team.json +21 -0
- package/.ctxman/profiles/frontend-team.json +20 -0
- package/.ctxman/templates/bug-fix.json +17 -0
- package/.ctxman/templates/code-review.json +9 -0
- package/.ctxman/templates/documentation.json +9 -0
- package/.ctxman/templates/feature.json +9 -0
- package/.ctxman/templates/full-context.json +9 -0
- package/.ctxman/templates/refactoring.json +18 -0
- package/.ctxman/templates/testing.json +9 -0
- package/.ctxman/wizard-profiles/security-audit/profile.json +7 -1
- package/LICENSE +21 -0
- package/README.md +380 -136
- package/bin/cli.js +1880 -529
- package/bin/cm-ask.js +18 -13
- package/bin/cm-audit.js +124 -0
- package/bin/cm-count.js +151 -0
- package/bin/cm-gitingest.js +128 -115
- package/bin/cm-map.js +242 -0
- package/bin/cm-mcp.js +293 -0
- package/bin/cm-pack.js +405 -0
- package/bin/cm-policy.js +170 -0
- package/bin/cm-pr.js +248 -0
- package/bin/cm-search.js +178 -0
- package/bin/cm-update.js +128 -128
- package/bin/cm-why.js +197 -0
- package/bin/mcp-server.js +8 -24
- package/ctxman.js +237 -183
- package/examples/README.md +19 -4
- package/examples/github-actions/ctxman-check.yml +49 -0
- package/examples/wizard-profiles/security-audit/profile.json +7 -1
- package/index.d.ts +1422 -0
- package/index.js +55 -31
- package/lib/agents/pr-failure-agent.js +48 -45
- package/lib/analyzers/ai-suggester.js +451 -0
- package/lib/analyzers/dependency-scanner.js +505 -0
- package/lib/analyzers/duplicate-detector.js +264 -0
- package/lib/analyzers/go-method-analyzer.js +151 -110
- package/lib/analyzers/import-tracker.js +431 -0
- package/lib/analyzers/method-analyzer.js +1055 -434
- package/lib/analyzers/performance-dashboard.js +683 -0
- package/lib/analyzers/token-calculator.js +895 -504
- package/lib/analyzers/unused-exports-detector.js +336 -0
- package/lib/api/mcp/install.js +424 -0
- package/lib/api/mcp/prompts.js +281 -281
- package/lib/api/mcp/resources.js +300 -241
- package/lib/api/mcp/sandbox.js +89 -0
- package/lib/api/mcp/server.js +675 -312
- package/lib/api/mcp/start.js +125 -0
- package/lib/api/mcp/transports/HttpSseTransport.js +60 -52
- package/lib/api/rest/server.js +105 -28
- package/lib/cache/CacheManager.js +9 -17
- package/lib/core/Analyzer.js +16 -10
- package/lib/core/ContextBuilder.js +19 -28
- package/lib/core/Reporter.js +15 -14
- package/lib/core/Scanner.js +81 -12
- package/lib/count/index.js +100 -0
- package/lib/formatters/format-registry.js +394 -372
- package/lib/formatters/gitingest-formatter.js +656 -621
- package/lib/formatters/toon-formatter-v1.3.js +637 -632
- package/lib/formatters/toon-formatter.js +239 -240
- package/lib/integrations/git/BlameTracker.js +4 -4
- package/lib/integrations/git/DiffAnalyzer.js +38 -31
- package/lib/integrations/git/GitClient.js +71 -53
- package/lib/lsp/server.js +311 -0
- package/lib/map/graph.js +299 -0
- package/lib/map/imports.js +233 -0
- package/lib/map/index.js +33 -0
- package/lib/map/rank.js +253 -0
- package/lib/map/render.js +346 -0
- package/lib/map/resolver.js +574 -0
- package/lib/pack/budget.js +111 -0
- package/lib/pack/code-lexer.js +360 -0
- package/lib/pack/compress.js +414 -0
- package/lib/pack/index.js +354 -0
- package/lib/pack/renderers/common.js +134 -0
- package/lib/pack/renderers/index.js +24 -0
- package/lib/pack/renderers/json.js +47 -0
- package/lib/pack/renderers/markdown.js +54 -0
- package/lib/pack/renderers/plain.js +38 -0
- package/lib/pack/renderers/xml.js +85 -0
- package/lib/pack/select.js +177 -0
- package/lib/pack/split.js +182 -0
- package/lib/pack/tree.js +35 -0
- package/lib/parsers/gitignore-parser.js +113 -107
- package/lib/parsers/method-filter-parser.js +50 -33
- package/lib/plugins/ExporterPlugin.js +3 -3
- package/lib/plugins/LanguagePlugin.js +3 -3
- package/lib/plugins/PluginManager.js +7 -13
- package/lib/policy/audit.js +294 -0
- package/lib/policy/enforce.js +125 -0
- package/lib/policy/ignore-files.js +169 -0
- package/lib/policy/index.js +52 -0
- package/lib/policy/policy.js +489 -0
- package/lib/pr/diff-parser.js +267 -0
- package/lib/pr/method-mapper.js +148 -0
- package/lib/pr/pr-context.js +829 -0
- package/lib/pr/pr-git.js +149 -0
- package/lib/pr/pr-renderer.js +644 -0
- package/lib/pr/related-tests.js +296 -0
- package/lib/rag/DataSourcePlugin.js +5 -5
- package/lib/rag/EmbeddingProvider.js +19 -8
- package/lib/rag/Indexer.js +14 -5
- package/lib/rag/VectorStoreFactory.js +3 -3
- package/lib/rag/sources/FileDataSource.js +6 -6
- package/lib/rag/stores/BaseVectorStore.js +5 -5
- package/lib/rag/stores/ChromaDBStore.js +10 -10
- package/lib/rag/stores/LanceDBStore.js +29 -19
- package/lib/rag/stores/PineconeStore.js +11 -9
- package/lib/search/bm25.js +433 -0
- package/lib/search/code-units.js +201 -0
- package/lib/search/symbols.js +189 -0
- package/lib/ui/dashboard.js +120 -89
- package/lib/ui/index.js +1 -6
- package/lib/ui/init-wizard-ui.js +550 -0
- package/lib/ui/progress-bar.js +94 -88
- package/lib/ui/select-input.js +40 -32
- package/lib/ui/template-selector.js +99 -0
- package/lib/ui/wizard.js +374 -329
- package/lib/utils/claude-token-counter.js +151 -0
- package/lib/utils/cli-output.js +17 -0
- package/lib/utils/clipboard-utils.js +49 -50
- package/lib/utils/config-utils.js +81 -66
- package/lib/utils/context-versioning.js +532 -0
- package/lib/utils/error-handler.js +112 -112
- package/lib/utils/file-utils.js +153 -69
- package/lib/utils/format-converter.js +356 -283
- package/lib/utils/git-utils.js +366 -294
- package/lib/utils/llm-cost-estimator.js +281 -0
- package/lib/utils/llm-detector.js +242 -78
- package/lib/utils/logger.js +243 -237
- package/lib/utils/messages.js +355 -0
- package/lib/utils/multi-repo-manager.js +364 -0
- package/lib/utils/profile-manager.js +380 -0
- package/lib/utils/safe-read.js +61 -0
- package/lib/utils/secret-redactor.js +61 -0
- package/lib/utils/snapshot-manager.js +679 -0
- package/lib/utils/template-manager.js +378 -0
- package/lib/utils/token-utils.js +201 -170
- package/lib/utils/tokenizer-adapter.js +421 -377
- package/lib/utils/updater.js +539 -506
- package/lib/watch/FileWatcher.js +12 -8
- package/lib/watch/IncrementalAnalyzer.js +7 -7
- package/lib/why/coupling.js +81 -0
- package/lib/why/errors.js +23 -0
- package/lib/why/git.js +169 -0
- package/lib/why/history.js +108 -0
- package/lib/why/index.js +720 -0
- package/lib/why/ownership.js +79 -0
- package/lib/why/paths.js +207 -0
- package/lib/why/references.js +259 -0
- package/lib/why/render.js +722 -0
- package/lib/why/search.js +358 -0
- package/lib/why/target.js +389 -0
- package/lib/wizards/config-generator.js +255 -0
- package/lib/wizards/project-detector.js +571 -0
- package/package.json +44 -10
- package/.ctxman/logs/ctxman-2026-07-02.log +0 -156
- package/.ctxman/self-knowledge/documents.lance/_transactions/0-b3dd1558-f95b-44b9-a154-18e8c330ae7a.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/1-44bff6d4-e552-4010-a769-e09ad822698d.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/10-37260794-5947-4ecb-b4c8-d732f58f40f1.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/11-0d52a883-5518-463c-9b6d-986c3af89dfd.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/12-ae354646-52f2-49c9-90fb-2e6ed8932663.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/13-5d7d5e57-27da-498b-b734-091b9d38da13.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/14-558ce928-810b-4252-a364-fdd1ee6f8a25.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/15-27e3a58f-8b64-43b8-a9f4-e3369446968b.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/16-a110c390-e5c8-4464-b976-bc7f5195fa15.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/17-97513225-53de-4216-b61a-6337bd0cc4ad.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/18-5ba450f0-c527-459c-bf4f-6d79b5e4285f.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/19-600d3a63-5225-49e9-b8b7-30e037b564ab.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/2-48a61b4e-92cf-4342-8376-914c1b99a9f9.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/20-121dd912-5410-410e-84ff-82c7a23604a0.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/3-bc6844ab-7ea3-4bb3-9ee1-538ce5664a9c.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/4-d5e1754f-dbb5-454e-a9be-2a7da8d7bcf9.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/5-4fae93df-0568-4b6f-8acf-8bef4bba6047.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/6-4b1b8b87-068e-49a3-ba2b-d8dae628b252.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/7-63d040fb-d2fa-4c0f-bed0-53411918a7b0.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/8-c5628cf6-264c-420f-aab8-bc96a9083591.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/9-c94570aa-5e53-4fcb-971f-cecc1d1d5492.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/1.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/10.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/11.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/12.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/13.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/14.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/15.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/16.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/17.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/18.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/19.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/2.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/20.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/21.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/3.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/4.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/5.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/6.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/7.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/8.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/9.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/000010010011011101000010de8621405d9d5cb06540dbf02c.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/0000101001011100011000003fb62a4aa8978974075c144f1c.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/000011010000100111001011c6cc824d628f3bb1f4e04cd5d0.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/001000001000011001011100d8f74349ada090b332cd1df669.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/01000001111010001000010045ab44467bb1e7e4d8ae06bd58.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/01000101101110111110011131ef904241a89e1ffb6ddaf8e3.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/010010110000010000011010fbfcc145c6a8a827b28057a07e.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1000011000101010001110003fb4f045c3835982efa1405324.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/100111011101110111101011f4396b410fa06af3b028d204e6.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/100111100111101000001100596b174e00995d6cc2969cc8dc.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1010001100011000110010102ea16b484d887c7c9725feffd6.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/101101100010101111110111c5230944d9b3af7301b6392dda.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1011011100100111111110105892bf41b5a530fedd7cf516f1.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/10111110000010001011110164e8774742bade87b483c10f36.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1100000100010110001110103ffaca40a2a42c85067ebaf9ae.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/110000010001110110101110664bde4c76a78dfc14b05e83b8.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/11001100111000010100110050645c4281b1f35b2022234f7f.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1101111011001100110111017214924a368f5719eab2246619.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1110000111001010001000002150c24dcab653d39d07032e63.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1110100000000100000011014d20534967a18b5c87721659ff.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/11110101111010000101011135a0114ef2b35dc6f1297a3381.lance +0 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Import Tracker - Parses source files to track package imports
|
|
3
|
+
* v3.0.0 - FEAT-002
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { getLogger } from '../utils/logger.js';
|
|
9
|
+
import FileUtils from '../utils/file-utils.js';
|
|
10
|
+
|
|
11
|
+
const logger = getLogger('ImportTracker');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* ImportTracker class
|
|
15
|
+
* Parses source files to identify which packages are actively imported
|
|
16
|
+
*/
|
|
17
|
+
export class ImportTracker {
|
|
18
|
+
constructor(projectRoot, options = {}) {
|
|
19
|
+
this.projectRoot = projectRoot;
|
|
20
|
+
this.options = {
|
|
21
|
+
excludeNodeModules: options.excludeNodeModules !== false,
|
|
22
|
+
includeDevImports: options.includeDevImports || false,
|
|
23
|
+
...options,
|
|
24
|
+
};
|
|
25
|
+
this.imports = new Map();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Parse ES6 imports from content
|
|
30
|
+
*/
|
|
31
|
+
parseES6Imports(content) {
|
|
32
|
+
const imports = [];
|
|
33
|
+
|
|
34
|
+
// Match various import patterns:
|
|
35
|
+
// import defaultExport from 'package'
|
|
36
|
+
// import { named } from 'package'
|
|
37
|
+
// import * as name from 'package'
|
|
38
|
+
// import { a, b as c } from 'package'
|
|
39
|
+
const es6Pattern =
|
|
40
|
+
/import\s+(?:(\{[^}]*\})|(\*\s+as\s+\w+)|(\w+)(?:\s*,\s*(\{[^}]*\}))?)\s+from\s+['"]([^'"]+)['"]/g;
|
|
41
|
+
|
|
42
|
+
let match;
|
|
43
|
+
while ((match = es6Pattern.exec(content)) !== null) {
|
|
44
|
+
const symbols = [];
|
|
45
|
+
|
|
46
|
+
// Named imports: { a, b, c }
|
|
47
|
+
if (match[1]) {
|
|
48
|
+
const named = match[1]
|
|
49
|
+
.replace(/[{}]/g, '')
|
|
50
|
+
.split(',')
|
|
51
|
+
.map((s) =>
|
|
52
|
+
s
|
|
53
|
+
.trim()
|
|
54
|
+
.split(/\s+as\s+/)
|
|
55
|
+
.pop()
|
|
56
|
+
)
|
|
57
|
+
.filter((s) => s.length > 0);
|
|
58
|
+
symbols.push(...named);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Namespace import: * as name
|
|
62
|
+
if (match[2]) {
|
|
63
|
+
symbols.push(match[2].trim());
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Default import
|
|
67
|
+
if (match[3]) {
|
|
68
|
+
symbols.push(match[3].trim());
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Combined: default, { named }
|
|
72
|
+
if (match[4]) {
|
|
73
|
+
const named = match[4]
|
|
74
|
+
.replace(/[{}]/g, '')
|
|
75
|
+
.split(',')
|
|
76
|
+
.map((s) =>
|
|
77
|
+
s
|
|
78
|
+
.trim()
|
|
79
|
+
.split(/\s+as\s+/)
|
|
80
|
+
.pop()
|
|
81
|
+
)
|
|
82
|
+
.filter((s) => s.length > 0);
|
|
83
|
+
symbols.push(...named);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Package name
|
|
87
|
+
const packageName = match[5];
|
|
88
|
+
|
|
89
|
+
imports.push({
|
|
90
|
+
package: packageName,
|
|
91
|
+
symbols: [...new Set(symbols)],
|
|
92
|
+
type: 'es6',
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Also catch dynamic imports: import('package')
|
|
97
|
+
const dynamicPattern = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
98
|
+
while ((match = dynamicPattern.exec(content)) !== null) {
|
|
99
|
+
imports.push({
|
|
100
|
+
package: match[1],
|
|
101
|
+
symbols: ['*'],
|
|
102
|
+
type: 'dynamic',
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return imports;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Parse CommonJS requires from content
|
|
111
|
+
*/
|
|
112
|
+
parseCommonJSImports(content) {
|
|
113
|
+
const imports = [];
|
|
114
|
+
|
|
115
|
+
// Match require('package')
|
|
116
|
+
// Also handle: const { a, b } = require('package')
|
|
117
|
+
// const x = require('package')
|
|
118
|
+
const cjsPattern =
|
|
119
|
+
/(?:const|let|var)\s+(?:(\{[^}]*\})|(\w+))\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
120
|
+
|
|
121
|
+
let match;
|
|
122
|
+
while ((match = cjsPattern.exec(content)) !== null) {
|
|
123
|
+
const symbols = [];
|
|
124
|
+
|
|
125
|
+
// Destructured: { a, b }
|
|
126
|
+
if (match[1]) {
|
|
127
|
+
const named = match[1]
|
|
128
|
+
.replace(/[{}]/g, '')
|
|
129
|
+
.split(',')
|
|
130
|
+
.map((s) =>
|
|
131
|
+
s
|
|
132
|
+
.trim()
|
|
133
|
+
.split(/\s+as\s+/)
|
|
134
|
+
.pop()
|
|
135
|
+
)
|
|
136
|
+
.filter((s) => s.length > 0);
|
|
137
|
+
symbols.push(...named);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Default: identifier
|
|
141
|
+
if (match[2]) {
|
|
142
|
+
symbols.push(match[2].trim());
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
imports.push({
|
|
146
|
+
package: match[3],
|
|
147
|
+
symbols: [...new Set(symbols)],
|
|
148
|
+
type: 'commonjs',
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Simple require without assignment (side effects)
|
|
153
|
+
const simpleRequirePattern = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
154
|
+
while ((match = simpleRequirePattern.exec(content)) !== null) {
|
|
155
|
+
// Check if it's not already captured
|
|
156
|
+
const existing = imports.find((i) => i.package === match[1]);
|
|
157
|
+
if (!existing) {
|
|
158
|
+
imports.push({
|
|
159
|
+
package: match[1],
|
|
160
|
+
symbols: ['*'],
|
|
161
|
+
type: 'commonjs',
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return imports;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Every module specifier a JavaScript/TypeScript file names, relative paths included:
|
|
171
|
+
* the ES6 and CommonJS forms above plus the ones they leave out (re-exports such as
|
|
172
|
+
* `export * from './a.js'`, side-effect imports `import './polyfill.js'`, type-only
|
|
173
|
+
* imports `import type { T } from './types'` and `import a, * as b from 'x'`).
|
|
174
|
+
* Used to build the file dependency graph of `ctxman map`.
|
|
175
|
+
* @param {string} content - Source text
|
|
176
|
+
* @returns {string[]} Unique specifiers in order of first appearance
|
|
177
|
+
*/
|
|
178
|
+
parseModuleSpecifiers(content) {
|
|
179
|
+
const specifiers = [
|
|
180
|
+
...this.parseES6Imports(content).map((imp) => imp.package),
|
|
181
|
+
...this.parseCommonJSImports(content).map((imp) => imp.package),
|
|
182
|
+
];
|
|
183
|
+
|
|
184
|
+
// The clause before `from` holds only names, braces, `*` and commas, so a match
|
|
185
|
+
// cannot run across a string or a statement
|
|
186
|
+
const fromPattern =
|
|
187
|
+
/(?:^|[\s;}])(?:import|export)\s+(?:type\s+)?[\w$*{}\s,]*?\s*from\s*['"]([^'"\n]+)['"]/g;
|
|
188
|
+
const sideEffectPattern = /(?:^|[\s;}])import\s*['"]([^'"\n]+)['"]/g;
|
|
189
|
+
for (const pattern of [fromPattern, sideEffectPattern]) {
|
|
190
|
+
let match;
|
|
191
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
192
|
+
specifiers.push(match[1]);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return [...new Set(specifiers)];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Check if an import is from a package (not a relative path)
|
|
201
|
+
*/
|
|
202
|
+
isPackageImport(importPath) {
|
|
203
|
+
// Relative imports start with ./ or ../
|
|
204
|
+
// Absolute imports start with /
|
|
205
|
+
// Package imports are bare identifiers
|
|
206
|
+
if (
|
|
207
|
+
importPath.startsWith('./') ||
|
|
208
|
+
importPath.startsWith('../') ||
|
|
209
|
+
importPath.startsWith('/') ||
|
|
210
|
+
importPath.startsWith('#')
|
|
211
|
+
) {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Extract the base package name from an import path
|
|
219
|
+
* e.g., @babel/core -> @babel/core
|
|
220
|
+
* e.g., lodash/fp -> lodash
|
|
221
|
+
*/
|
|
222
|
+
getBasePackageName(importPath) {
|
|
223
|
+
// Handle scoped packages (@org/package)
|
|
224
|
+
if (importPath.startsWith('@')) {
|
|
225
|
+
const parts = importPath.split('/');
|
|
226
|
+
if (parts.length >= 2) {
|
|
227
|
+
return `${parts[0]}/${parts[1]}`;
|
|
228
|
+
}
|
|
229
|
+
return importPath;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Regular packages: package/submodule -> package
|
|
233
|
+
const parts = importPath.split('/');
|
|
234
|
+
return parts[0];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Parse a single file for imports
|
|
239
|
+
*/
|
|
240
|
+
parseFile(filePath) {
|
|
241
|
+
const imports = [];
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
245
|
+
|
|
246
|
+
// Parse ES6 imports
|
|
247
|
+
const es6Imports = this.parseES6Imports(content);
|
|
248
|
+
imports.push(...es6Imports);
|
|
249
|
+
|
|
250
|
+
// Parse CommonJS requires
|
|
251
|
+
const cjsImports = this.parseCommonJSImports(content);
|
|
252
|
+
imports.push(...cjsImports);
|
|
253
|
+
|
|
254
|
+
// Filter to package imports only and normalize package names
|
|
255
|
+
return imports
|
|
256
|
+
.filter((imp) => this.isPackageImport(imp.package))
|
|
257
|
+
.map((imp) => ({
|
|
258
|
+
...imp,
|
|
259
|
+
package: this.getBasePackageName(imp.package),
|
|
260
|
+
}));
|
|
261
|
+
} catch (error) {
|
|
262
|
+
logger.warn(`Failed to parse file ${filePath}: ${error.message}`);
|
|
263
|
+
return [];
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Scan a directory for source files
|
|
269
|
+
*/
|
|
270
|
+
scanSourceFiles(dir, files = []) {
|
|
271
|
+
try {
|
|
272
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
273
|
+
|
|
274
|
+
for (const entry of entries) {
|
|
275
|
+
const fullPath = path.join(dir, entry.name);
|
|
276
|
+
|
|
277
|
+
// Skip node_modules
|
|
278
|
+
if (entry.name === 'node_modules' && this.options.excludeNodeModules) {
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Skip hidden directories
|
|
283
|
+
if (entry.name.startsWith('.') && entry.name !== '.ctxman') {
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Skip common non-source directories
|
|
288
|
+
if (['dist', 'build', 'coverage', '.git'].includes(entry.name)) {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (entry.isDirectory()) {
|
|
293
|
+
this.scanSourceFiles(fullPath, files);
|
|
294
|
+
} else if (entry.isFile()) {
|
|
295
|
+
// Only parse text files (JS, TS, etc.)
|
|
296
|
+
if (FileUtils.isText(fullPath) && this.isSourceFile(fullPath)) {
|
|
297
|
+
files.push(fullPath);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
} catch (error) {
|
|
302
|
+
logger.warn(`Failed to scan directory ${dir}: ${error.message}`);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return files;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Check if a file is a source file (JS, TS, etc.)
|
|
310
|
+
*/
|
|
311
|
+
isSourceFile(filePath) {
|
|
312
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
313
|
+
const sourceExtensions = [
|
|
314
|
+
'.js',
|
|
315
|
+
'.jsx',
|
|
316
|
+
'.ts',
|
|
317
|
+
'.tsx',
|
|
318
|
+
'.mjs',
|
|
319
|
+
'.cjs',
|
|
320
|
+
'.vue',
|
|
321
|
+
'.svelte',
|
|
322
|
+
'.astro',
|
|
323
|
+
];
|
|
324
|
+
return sourceExtensions.includes(ext);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Track all imports in the project
|
|
329
|
+
*/
|
|
330
|
+
trackImports(sourceFiles = null) {
|
|
331
|
+
logger.info('Tracking imports...');
|
|
332
|
+
|
|
333
|
+
// Get source files if not provided
|
|
334
|
+
if (!sourceFiles) {
|
|
335
|
+
sourceFiles = this.scanSourceFiles(this.projectRoot);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
logger.debug(`Found ${sourceFiles.length} source files to analyze`);
|
|
339
|
+
|
|
340
|
+
// Parse each file
|
|
341
|
+
for (const file of sourceFiles) {
|
|
342
|
+
const fileImports = this.parseFile(file);
|
|
343
|
+
|
|
344
|
+
for (const imp of fileImports) {
|
|
345
|
+
const packageName = imp.package;
|
|
346
|
+
|
|
347
|
+
if (!this.imports.has(packageName)) {
|
|
348
|
+
this.imports.set(packageName, {
|
|
349
|
+
package: packageName,
|
|
350
|
+
imports: [],
|
|
351
|
+
files: [],
|
|
352
|
+
types: new Set(),
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const record = this.imports.get(packageName);
|
|
357
|
+
record.imports.push(...imp.symbols);
|
|
358
|
+
record.files.push(path.relative(this.projectRoot, file));
|
|
359
|
+
record.types.add(imp.type);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Deduplicate and finalize
|
|
364
|
+
const results = [];
|
|
365
|
+
for (const record of this.imports.values()) {
|
|
366
|
+
results.push({
|
|
367
|
+
package: record.package,
|
|
368
|
+
imports: [...new Set(record.imports)],
|
|
369
|
+
files: [...new Set(record.files)],
|
|
370
|
+
importTypes: [...record.types],
|
|
371
|
+
usageCount: record.files.length,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Sort by usage count (descending)
|
|
376
|
+
results.sort((a, b) => b.usageCount - a.usageCount);
|
|
377
|
+
|
|
378
|
+
logger.info(`Found ${results.length} packages with active imports`);
|
|
379
|
+
|
|
380
|
+
return results;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Get packages that are actively imported
|
|
385
|
+
*/
|
|
386
|
+
getActivePackages() {
|
|
387
|
+
return Array.from(this.imports.keys());
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Get import details for a specific package
|
|
392
|
+
*/
|
|
393
|
+
getPackageImports(packageName) {
|
|
394
|
+
const baseName = this.getBasePackageName(packageName);
|
|
395
|
+
return this.imports.get(baseName) || null;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Check if a package is actively imported
|
|
400
|
+
*/
|
|
401
|
+
isPackageActive(packageName) {
|
|
402
|
+
const baseName = this.getBasePackageName(packageName);
|
|
403
|
+
return this.imports.has(baseName);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Reset the tracker
|
|
408
|
+
*/
|
|
409
|
+
reset() {
|
|
410
|
+
this.imports = new Map();
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Get statistics
|
|
415
|
+
*/
|
|
416
|
+
getStats() {
|
|
417
|
+
return {
|
|
418
|
+
totalPackages: this.imports.size,
|
|
419
|
+
totalImports: Array.from(this.imports.values()).reduce(
|
|
420
|
+
(sum, record) => sum + record.imports.length,
|
|
421
|
+
0
|
|
422
|
+
),
|
|
423
|
+
totalFiles: Array.from(this.imports.values()).reduce(
|
|
424
|
+
(sum, record) => sum + record.files.length,
|
|
425
|
+
0
|
|
426
|
+
),
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export default ImportTracker;
|