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
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency Scanner - Analyzes node_modules for token impact
|
|
3
|
+
* v3.0.0 - FEAT-002
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { execSync } from 'child_process';
|
|
9
|
+
import TokenUtils from '../utils/token-utils.js';
|
|
10
|
+
import FileUtils from '../utils/file-utils.js';
|
|
11
|
+
import { getLogger } from '../utils/logger.js';
|
|
12
|
+
|
|
13
|
+
const logger = getLogger('DependencyScanner');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* DependencyScanner class
|
|
17
|
+
* Analyzes node_modules for token impact, tracks imports, and checks security
|
|
18
|
+
*/
|
|
19
|
+
export class DependencyScanner {
|
|
20
|
+
constructor(projectRoot, options = {}) {
|
|
21
|
+
this.projectRoot = projectRoot;
|
|
22
|
+
this.options = {
|
|
23
|
+
depth: options.depth || 1,
|
|
24
|
+
includeTypes: options.includeTypes || false,
|
|
25
|
+
checkSecurity: options.checkSecurity !== false,
|
|
26
|
+
...options,
|
|
27
|
+
};
|
|
28
|
+
this.packageJson = null;
|
|
29
|
+
this.stats = {
|
|
30
|
+
totalDependencies: 0,
|
|
31
|
+
activeDependencies: 0,
|
|
32
|
+
totalTokens: 0,
|
|
33
|
+
securityIssues: 0,
|
|
34
|
+
typesExtracted: 0,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Load and parse package.json
|
|
40
|
+
*/
|
|
41
|
+
loadPackageJson() {
|
|
42
|
+
// Return cached value if already loaded
|
|
43
|
+
if (this.packageJson) {
|
|
44
|
+
return this.packageJson;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const packagePath = path.join(this.projectRoot, 'package.json');
|
|
48
|
+
|
|
49
|
+
if (!fs.existsSync(packagePath)) {
|
|
50
|
+
throw new Error('No package.json found in project root');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const content = fs.readFileSync(packagePath, 'utf-8');
|
|
55
|
+
this.packageJson = JSON.parse(content);
|
|
56
|
+
return this.packageJson;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
throw new Error(`Failed to parse package.json: ${error.message}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get all dependencies (dependencies + devDependencies)
|
|
64
|
+
*/
|
|
65
|
+
getAllDependencies() {
|
|
66
|
+
if (!this.packageJson) {
|
|
67
|
+
this.loadPackageJson();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const deps = {
|
|
71
|
+
...this.packageJson.dependencies,
|
|
72
|
+
...this.packageJson.devDependencies,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
return deps;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Find the path to a dependency in node_modules
|
|
80
|
+
* Supports both npm and pnpm directory structures
|
|
81
|
+
*/
|
|
82
|
+
findDependencyPath(packageName) {
|
|
83
|
+
const possiblePaths = [
|
|
84
|
+
// Standard npm structure
|
|
85
|
+
path.join(this.projectRoot, 'node_modules', packageName),
|
|
86
|
+
// pnpm structure (node_modules/.pnpm/package@version/node_modules/package)
|
|
87
|
+
path.join(this.projectRoot, 'node_modules', '.pnpm'),
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
// Check standard npm path first
|
|
91
|
+
if (fs.existsSync(possiblePaths[0])) {
|
|
92
|
+
return possiblePaths[0];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// For pnpm, we need to search in .pnpm directory
|
|
96
|
+
const pnpmDir = possiblePaths[1];
|
|
97
|
+
if (fs.existsSync(pnpmDir)) {
|
|
98
|
+
// Look for package directories matching the pattern package@version
|
|
99
|
+
const entries = fs.readdirSync(pnpmDir);
|
|
100
|
+
for (const entry of entries) {
|
|
101
|
+
if (entry.startsWith(packageName + '@') || entry.includes('/' + packageName + '@')) {
|
|
102
|
+
// Handle scoped packages (e.g., @babel/core -> @babel+core@version)
|
|
103
|
+
const pnpmPkgPath = path.join(pnpmDir, entry, 'node_modules', packageName);
|
|
104
|
+
if (fs.existsSync(pnpmPkgPath)) {
|
|
105
|
+
return pnpmPkgPath;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get the installed version of a package
|
|
116
|
+
*/
|
|
117
|
+
getInstalledVersion(packageName, depPath) {
|
|
118
|
+
try {
|
|
119
|
+
const pkgJsonPath = path.join(depPath, 'package.json');
|
|
120
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
121
|
+
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
|
|
122
|
+
return pkgJson.version || 'unknown';
|
|
123
|
+
}
|
|
124
|
+
} catch (error) {
|
|
125
|
+
logger.warn(`Could not read version for ${packageName}: ${error.message}`);
|
|
126
|
+
}
|
|
127
|
+
return 'unknown';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Scan a dependency directory and count tokens
|
|
132
|
+
*/
|
|
133
|
+
scanDependencyTokens(depPath) {
|
|
134
|
+
let totalTokens = 0;
|
|
135
|
+
let fileCount = 0;
|
|
136
|
+
const startTime = Date.now();
|
|
137
|
+
const maxTime = 5000; // 5 second timeout per dependency
|
|
138
|
+
|
|
139
|
+
const scanDir = (dir) => {
|
|
140
|
+
// Check timeout
|
|
141
|
+
if (Date.now() - startTime > maxTime) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
147
|
+
|
|
148
|
+
for (const entry of entries) {
|
|
149
|
+
// Check timeout in loop
|
|
150
|
+
if (Date.now() - startTime > maxTime) {
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const fullPath = path.join(dir, entry.name);
|
|
155
|
+
|
|
156
|
+
// Skip common non-essential directories
|
|
157
|
+
if (entry.isDirectory()) {
|
|
158
|
+
if (
|
|
159
|
+
[
|
|
160
|
+
'test',
|
|
161
|
+
'tests',
|
|
162
|
+
'__tests__',
|
|
163
|
+
'examples',
|
|
164
|
+
'docs',
|
|
165
|
+
'.github',
|
|
166
|
+
'coverage',
|
|
167
|
+
'node_modules',
|
|
168
|
+
].includes(entry.name)
|
|
169
|
+
) {
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
scanDir(fullPath);
|
|
173
|
+
} else if (entry.isFile()) {
|
|
174
|
+
// Only count text files
|
|
175
|
+
if (FileUtils.isText(fullPath)) {
|
|
176
|
+
try {
|
|
177
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
178
|
+
const tokens = TokenUtils.calculate(content, fullPath);
|
|
179
|
+
totalTokens += tokens;
|
|
180
|
+
fileCount++;
|
|
181
|
+
} catch (_error) {
|
|
182
|
+
// Skip files that can't be read
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
} catch (_error) {
|
|
188
|
+
// Skip directories that can't be read
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
scanDir(depPath);
|
|
193
|
+
|
|
194
|
+
return { tokens: totalTokens, files: fileCount };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Extract TypeScript definitions from a dependency
|
|
199
|
+
*/
|
|
200
|
+
extractTypeDefinitions(depPath, packageName) {
|
|
201
|
+
const types = [];
|
|
202
|
+
const maxFiles = 10; // Limit to avoid excessive output
|
|
203
|
+
|
|
204
|
+
const findDtsFiles = (dir) => {
|
|
205
|
+
if (types.length >= maxFiles) return;
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
209
|
+
|
|
210
|
+
for (const entry of entries) {
|
|
211
|
+
if (types.length >= maxFiles) break;
|
|
212
|
+
|
|
213
|
+
const fullPath = path.join(dir, entry.name);
|
|
214
|
+
|
|
215
|
+
if (entry.isDirectory()) {
|
|
216
|
+
// Skip test directories
|
|
217
|
+
if (['test', 'tests', '__tests__', 'node_modules'].includes(entry.name)) {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
findDtsFiles(fullPath);
|
|
221
|
+
} else if (entry.isFile() && entry.name.endsWith('.d.ts')) {
|
|
222
|
+
try {
|
|
223
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
224
|
+
// Truncate large files
|
|
225
|
+
const truncatedContent =
|
|
226
|
+
content.length > 5000 ? content.substring(0, 5000) + '\n// ... truncated' : content;
|
|
227
|
+
|
|
228
|
+
types.push({
|
|
229
|
+
file: path.relative(depPath, fullPath),
|
|
230
|
+
content: truncatedContent,
|
|
231
|
+
tokens: TokenUtils.calculate(truncatedContent, fullPath),
|
|
232
|
+
});
|
|
233
|
+
} catch (_error) {
|
|
234
|
+
// Skip files that can't be read
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
} catch (_error) {
|
|
239
|
+
// Skip directories that can't be read
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
// Check for bundled types
|
|
244
|
+
findDtsFiles(depPath);
|
|
245
|
+
|
|
246
|
+
// Also check for @types/package
|
|
247
|
+
if (types.length === 0) {
|
|
248
|
+
const typesPackagePath = this.findDependencyPath(`@types/${packageName}`);
|
|
249
|
+
if (typesPackagePath) {
|
|
250
|
+
findDtsFiles(typesPackagePath);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return types;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Check for security vulnerabilities using npm audit
|
|
259
|
+
*/
|
|
260
|
+
async checkSecurity(packageName, _version) {
|
|
261
|
+
if (!this.options.checkSecurity) {
|
|
262
|
+
return { status: 'skipped', vulnerabilities: [] };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
try {
|
|
266
|
+
// Run npm audit for specific package
|
|
267
|
+
const auditResult = this.runNpmAudit(packageName);
|
|
268
|
+
return auditResult;
|
|
269
|
+
} catch (error) {
|
|
270
|
+
logger.warn(`Security check failed for ${packageName}: ${error.message}`);
|
|
271
|
+
return { status: 'unknown', vulnerabilities: [] };
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Run npm audit for security vulnerabilities
|
|
277
|
+
*/
|
|
278
|
+
runNpmAudit(packageName) {
|
|
279
|
+
try {
|
|
280
|
+
// Run npm audit --json to get structured output
|
|
281
|
+
const result = execSync('npm audit --json --omit=dev 2>/dev/null', {
|
|
282
|
+
cwd: this.projectRoot,
|
|
283
|
+
encoding: 'utf-8',
|
|
284
|
+
timeout: 30000,
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
const audit = JSON.parse(result);
|
|
288
|
+
return this.parseAuditResult(audit, packageName);
|
|
289
|
+
} catch (error) {
|
|
290
|
+
// npm audit returns non-zero exit code when vulnerabilities are found
|
|
291
|
+
if (error.stdout) {
|
|
292
|
+
try {
|
|
293
|
+
const audit = JSON.parse(error.stdout);
|
|
294
|
+
return this.parseAuditResult(audit, packageName);
|
|
295
|
+
} catch (_parseError) {
|
|
296
|
+
// Ignore parse errors
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return { status: 'unknown', vulnerabilities: [] };
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Parse npm audit result for a specific package
|
|
305
|
+
*/
|
|
306
|
+
parseAuditResult(audit, packageName) {
|
|
307
|
+
const vulnerabilities = [];
|
|
308
|
+
|
|
309
|
+
if (audit.vulnerabilities) {
|
|
310
|
+
for (const [name, vuln] of Object.entries(audit.vulnerabilities)) {
|
|
311
|
+
if (name === packageName || (vuln.via && vuln.via.some((v) => v.name === packageName))) {
|
|
312
|
+
vulnerabilities.push({
|
|
313
|
+
name: name,
|
|
314
|
+
severity: vuln.severity,
|
|
315
|
+
title: vuln.name || name,
|
|
316
|
+
fixAvailable: vuln.fixAvailable || false,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return {
|
|
323
|
+
status: vulnerabilities.length > 0 ? 'review' : 'ok',
|
|
324
|
+
vulnerabilities,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Analyze a single dependency
|
|
330
|
+
*/
|
|
331
|
+
async analyzeDependency(packageName, version) {
|
|
332
|
+
const depPath = this.findDependencyPath(packageName);
|
|
333
|
+
|
|
334
|
+
if (!depPath) {
|
|
335
|
+
return {
|
|
336
|
+
name: packageName,
|
|
337
|
+
version: version,
|
|
338
|
+
status: 'missing',
|
|
339
|
+
tokens: 0,
|
|
340
|
+
files: 0,
|
|
341
|
+
security: { status: 'unknown', vulnerabilities: [] },
|
|
342
|
+
types: [],
|
|
343
|
+
active: false,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const installedVersion = this.getInstalledVersion(packageName, depPath);
|
|
348
|
+
const tokenInfo = this.scanDependencyTokens(depPath);
|
|
349
|
+
|
|
350
|
+
// Extract type definitions if requested
|
|
351
|
+
const types = this.options.includeTypes
|
|
352
|
+
? this.extractTypeDefinitions(depPath, packageName)
|
|
353
|
+
: [];
|
|
354
|
+
|
|
355
|
+
// Check security
|
|
356
|
+
const security = await this.checkSecurity(packageName, installedVersion);
|
|
357
|
+
|
|
358
|
+
return {
|
|
359
|
+
name: packageName,
|
|
360
|
+
version: installedVersion,
|
|
361
|
+
declaredVersion: version,
|
|
362
|
+
status: 'installed',
|
|
363
|
+
tokens: tokenInfo.tokens,
|
|
364
|
+
files: tokenInfo.files,
|
|
365
|
+
path: depPath,
|
|
366
|
+
security,
|
|
367
|
+
types,
|
|
368
|
+
active: false, // Will be set by import tracker
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Main analysis method
|
|
374
|
+
*/
|
|
375
|
+
async analyze(activeImports = []) {
|
|
376
|
+
logger.info('Starting dependency analysis...');
|
|
377
|
+
|
|
378
|
+
// Load package.json
|
|
379
|
+
this.loadPackageJson();
|
|
380
|
+
|
|
381
|
+
// Get all dependencies
|
|
382
|
+
const dependencies = this.getAllDependencies();
|
|
383
|
+
const results = [];
|
|
384
|
+
|
|
385
|
+
// Analyze each dependency
|
|
386
|
+
for (const [name, version] of Object.entries(dependencies)) {
|
|
387
|
+
logger.debug(`Analyzing ${name}@${version}`);
|
|
388
|
+
|
|
389
|
+
const analysis = await this.analyzeDependency(name, version);
|
|
390
|
+
|
|
391
|
+
// Mark as active if in activeImports
|
|
392
|
+
if (activeImports.some((imp) => imp.package === name || imp.package.startsWith(name + '/'))) {
|
|
393
|
+
analysis.active = true;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
results.push(analysis);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Update stats
|
|
400
|
+
this.stats.totalDependencies = results.length;
|
|
401
|
+
this.stats.activeDependencies = results.filter((r) => r.active).length;
|
|
402
|
+
this.stats.totalTokens = results.reduce((sum, r) => sum + r.tokens, 0);
|
|
403
|
+
this.stats.securityIssues = results.reduce(
|
|
404
|
+
(sum, r) => sum + r.security.vulnerabilities.length,
|
|
405
|
+
0
|
|
406
|
+
);
|
|
407
|
+
this.stats.typesExtracted = results.reduce((sum, r) => sum + r.types.length, 0);
|
|
408
|
+
|
|
409
|
+
// Sort by token count (descending)
|
|
410
|
+
results.sort((a, b) => b.tokens - a.tokens);
|
|
411
|
+
|
|
412
|
+
return {
|
|
413
|
+
dependencies: results,
|
|
414
|
+
summary: this.generateSummary(results),
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Generate summary of dependency analysis
|
|
420
|
+
*/
|
|
421
|
+
generateSummary(results) {
|
|
422
|
+
return {
|
|
423
|
+
totalDependencies: results.length,
|
|
424
|
+
installedDependencies: results.filter((r) => r.status === 'installed').length,
|
|
425
|
+
missingDependencies: results.filter((r) => r.status === 'missing').length,
|
|
426
|
+
activeDependencies: results.filter((r) => r.active).length,
|
|
427
|
+
totalTokens: this.stats.totalTokens,
|
|
428
|
+
securityIssues: this.stats.securityIssues,
|
|
429
|
+
typesExtracted: this.stats.typesExtracted,
|
|
430
|
+
largestDependencies: results.slice(0, 5).map((r) => ({
|
|
431
|
+
name: r.name,
|
|
432
|
+
tokens: r.tokens,
|
|
433
|
+
})),
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Generate dependency-context.json
|
|
439
|
+
*/
|
|
440
|
+
generateContext(analysis, activeImports) {
|
|
441
|
+
const context = {
|
|
442
|
+
generated: new Date().toISOString(),
|
|
443
|
+
projectRoot: this.projectRoot,
|
|
444
|
+
summary: {
|
|
445
|
+
totalDependencies: analysis.summary.totalDependencies,
|
|
446
|
+
activeDependencies: analysis.summary.activeDependencies,
|
|
447
|
+
totalTokens: analysis.summary.totalTokens,
|
|
448
|
+
securityIssues: analysis.summary.securityIssues,
|
|
449
|
+
},
|
|
450
|
+
packages: [],
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
// Focus on actively used packages first
|
|
454
|
+
const sortedDeps = [...analysis.dependencies].sort((a, b) => {
|
|
455
|
+
if (a.active && !b.active) return -1;
|
|
456
|
+
if (!a.active && b.active) return 1;
|
|
457
|
+
return b.tokens - a.tokens;
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
for (const dep of sortedDeps) {
|
|
461
|
+
const pkgContext = {
|
|
462
|
+
name: dep.name,
|
|
463
|
+
version: dep.version,
|
|
464
|
+
active: dep.active,
|
|
465
|
+
tokens: dep.tokens,
|
|
466
|
+
security: dep.security.status,
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
// Add imports if active
|
|
470
|
+
if (dep.active) {
|
|
471
|
+
const imports = activeImports.find((imp) => imp.package === dep.name);
|
|
472
|
+
if (imports) {
|
|
473
|
+
pkgContext.imports = imports.imports;
|
|
474
|
+
pkgContext.usedIn = imports.files.slice(0, 5);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Add type definitions if available and requested
|
|
479
|
+
if (this.options.includeTypes && dep.types.length > 0) {
|
|
480
|
+
pkgContext.types = dep.types.slice(0, 3).map((t) => ({
|
|
481
|
+
file: t.file,
|
|
482
|
+
tokens: t.tokens,
|
|
483
|
+
}));
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Add security details if issues found
|
|
487
|
+
if (dep.security?.vulnerabilities?.length > 0) {
|
|
488
|
+
pkgContext.vulnerabilities = dep.security.vulnerabilities;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
context.packages.push(pkgContext);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return context;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Get statistics
|
|
499
|
+
*/
|
|
500
|
+
getStats() {
|
|
501
|
+
return { ...this.stats };
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
export default DependencyScanner;
|