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.
Files changed (224) hide show
  1. package/.ctxman/llm-profiles.json +1067 -39
  2. package/.ctxman/profiles/backend-team.json +13 -0
  3. package/.ctxman/profiles/default.json +12 -0
  4. package/.ctxman/profiles/devops-team.json +21 -0
  5. package/.ctxman/profiles/frontend-team.json +20 -0
  6. package/.ctxman/templates/bug-fix.json +17 -0
  7. package/.ctxman/templates/code-review.json +9 -0
  8. package/.ctxman/templates/documentation.json +9 -0
  9. package/.ctxman/templates/feature.json +9 -0
  10. package/.ctxman/templates/full-context.json +9 -0
  11. package/.ctxman/templates/refactoring.json +18 -0
  12. package/.ctxman/templates/testing.json +9 -0
  13. package/.ctxman/wizard-profiles/security-audit/profile.json +7 -1
  14. package/LICENSE +21 -0
  15. package/README.md +381 -136
  16. package/bin/cli.js +1880 -529
  17. package/bin/cm-ask.js +18 -67
  18. package/bin/cm-audit.js +121 -0
  19. package/bin/cm-count.js +151 -0
  20. package/bin/cm-gitingest.js +128 -115
  21. package/bin/cm-map.js +242 -0
  22. package/bin/cm-mcp.js +293 -0
  23. package/bin/cm-pack.js +405 -0
  24. package/bin/cm-policy.js +170 -0
  25. package/bin/cm-pr.js +248 -0
  26. package/bin/cm-search.js +178 -0
  27. package/bin/cm-update.js +128 -128
  28. package/bin/cm-why.js +197 -0
  29. package/bin/mcp-server.js +8 -24
  30. package/ctxman.js +237 -183
  31. package/examples/README.md +19 -4
  32. package/examples/github-actions/ctxman-check.yml +49 -0
  33. package/examples/wizard-profiles/security-audit/profile.json +7 -1
  34. package/index.d.ts +1422 -0
  35. package/index.js +55 -31
  36. package/lib/agents/pr-failure-agent.js +48 -45
  37. package/lib/analyzers/ai-suggester.js +451 -0
  38. package/lib/analyzers/dependency-scanner.js +505 -0
  39. package/lib/analyzers/duplicate-detector.js +264 -0
  40. package/lib/analyzers/go-method-analyzer.js +151 -110
  41. package/lib/analyzers/import-tracker.js +431 -0
  42. package/lib/analyzers/method-analyzer.js +1055 -434
  43. package/lib/analyzers/performance-dashboard.js +687 -0
  44. package/lib/analyzers/token-calculator.js +895 -504
  45. package/lib/analyzers/unused-exports-detector.js +336 -0
  46. package/lib/api/mcp/install.js +424 -0
  47. package/lib/api/mcp/prompts.js +281 -281
  48. package/lib/api/mcp/resources.js +300 -241
  49. package/lib/api/mcp/sandbox.js +89 -0
  50. package/lib/api/mcp/server.js +772 -312
  51. package/lib/api/mcp/start.js +125 -0
  52. package/lib/api/mcp/transports/HttpSseTransport.js +60 -52
  53. package/lib/api/rest/server.js +105 -28
  54. package/lib/cache/CacheManager.js +9 -17
  55. package/lib/core/Analyzer.js +16 -10
  56. package/lib/core/ContextBuilder.js +19 -28
  57. package/lib/core/Reporter.js +15 -14
  58. package/lib/core/Scanner.js +81 -12
  59. package/lib/count/index.js +100 -0
  60. package/lib/formatters/format-registry.js +394 -372
  61. package/lib/formatters/gitingest-formatter.js +656 -621
  62. package/lib/formatters/toon-formatter-v1.3.js +637 -632
  63. package/lib/formatters/toon-formatter.js +239 -240
  64. package/lib/integrations/git/BlameTracker.js +4 -4
  65. package/lib/integrations/git/DiffAnalyzer.js +38 -31
  66. package/lib/integrations/git/GitClient.js +71 -53
  67. package/lib/lsp/server.js +311 -0
  68. package/lib/map/graph.js +299 -0
  69. package/lib/map/imports.js +233 -0
  70. package/lib/map/index.js +33 -0
  71. package/lib/map/rank.js +253 -0
  72. package/lib/map/render.js +346 -0
  73. package/lib/map/resolver.js +574 -0
  74. package/lib/pack/budget.js +111 -0
  75. package/lib/pack/code-lexer.js +360 -0
  76. package/lib/pack/compress.js +414 -0
  77. package/lib/pack/index.js +354 -0
  78. package/lib/pack/renderers/common.js +134 -0
  79. package/lib/pack/renderers/index.js +24 -0
  80. package/lib/pack/renderers/json.js +47 -0
  81. package/lib/pack/renderers/markdown.js +54 -0
  82. package/lib/pack/renderers/plain.js +38 -0
  83. package/lib/pack/renderers/xml.js +85 -0
  84. package/lib/pack/select.js +177 -0
  85. package/lib/pack/split.js +182 -0
  86. package/lib/pack/tree.js +35 -0
  87. package/lib/parsers/gitignore-parser.js +116 -107
  88. package/lib/parsers/method-filter-parser.js +50 -33
  89. package/lib/plugins/ExporterPlugin.js +3 -3
  90. package/lib/plugins/LanguagePlugin.js +3 -3
  91. package/lib/plugins/PluginManager.js +7 -13
  92. package/lib/policy/audit.js +312 -0
  93. package/lib/policy/enforce.js +125 -0
  94. package/lib/policy/ignore-files.js +169 -0
  95. package/lib/policy/index.js +53 -0
  96. package/lib/policy/policy.js +489 -0
  97. package/lib/pr/diff-parser.js +267 -0
  98. package/lib/pr/method-mapper.js +148 -0
  99. package/lib/pr/pr-context.js +829 -0
  100. package/lib/pr/pr-git.js +149 -0
  101. package/lib/pr/pr-renderer.js +644 -0
  102. package/lib/pr/related-tests.js +296 -0
  103. package/lib/rag/DataSourcePlugin.js +5 -5
  104. package/lib/rag/EmbeddingProvider.js +19 -8
  105. package/lib/rag/Indexer.js +14 -5
  106. package/lib/rag/VectorStoreFactory.js +3 -3
  107. package/lib/rag/ask.js +122 -0
  108. package/lib/rag/sources/FileDataSource.js +12 -9
  109. package/lib/rag/stores/BaseVectorStore.js +5 -5
  110. package/lib/rag/stores/ChromaDBStore.js +10 -10
  111. package/lib/rag/stores/LanceDBStore.js +29 -19
  112. package/lib/rag/stores/PineconeStore.js +11 -9
  113. package/lib/search/bm25.js +433 -0
  114. package/lib/search/code-units.js +201 -0
  115. package/lib/search/symbols.js +189 -0
  116. package/lib/ui/dashboard.js +120 -89
  117. package/lib/ui/index.js +1 -6
  118. package/lib/ui/init-wizard-ui.js +550 -0
  119. package/lib/ui/progress-bar.js +94 -88
  120. package/lib/ui/select-input.js +40 -32
  121. package/lib/ui/template-selector.js +99 -0
  122. package/lib/ui/wizard.js +374 -329
  123. package/lib/utils/claude-token-counter.js +151 -0
  124. package/lib/utils/cli-output.js +17 -0
  125. package/lib/utils/clipboard-utils.js +49 -50
  126. package/lib/utils/config-utils.js +81 -66
  127. package/lib/utils/context-versioning.js +532 -0
  128. package/lib/utils/error-handler.js +112 -112
  129. package/lib/utils/file-utils.js +153 -69
  130. package/lib/utils/format-converter.js +356 -283
  131. package/lib/utils/git-utils.js +366 -294
  132. package/lib/utils/llm-cost-estimator.js +281 -0
  133. package/lib/utils/llm-detector.js +242 -78
  134. package/lib/utils/logger.js +246 -237
  135. package/lib/utils/messages.js +355 -0
  136. package/lib/utils/multi-repo-manager.js +364 -0
  137. package/lib/utils/profile-manager.js +380 -0
  138. package/lib/utils/safe-read.js +61 -0
  139. package/lib/utils/secret-redactor.js +61 -0
  140. package/lib/utils/snapshot-manager.js +679 -0
  141. package/lib/utils/template-manager.js +378 -0
  142. package/lib/utils/token-utils.js +201 -170
  143. package/lib/utils/tokenizer-adapter.js +421 -377
  144. package/lib/utils/updater.js +539 -506
  145. package/lib/watch/FileWatcher.js +12 -8
  146. package/lib/watch/IncrementalAnalyzer.js +7 -7
  147. package/lib/why/coupling.js +81 -0
  148. package/lib/why/errors.js +23 -0
  149. package/lib/why/git.js +169 -0
  150. package/lib/why/history.js +108 -0
  151. package/lib/why/index.js +720 -0
  152. package/lib/why/ownership.js +79 -0
  153. package/lib/why/paths.js +207 -0
  154. package/lib/why/references.js +259 -0
  155. package/lib/why/render.js +722 -0
  156. package/lib/why/search.js +358 -0
  157. package/lib/why/target.js +389 -0
  158. package/lib/wizards/config-generator.js +257 -0
  159. package/lib/wizards/project-detector.js +572 -0
  160. package/package.json +44 -10
  161. package/.ctxman/logs/ctxman-2026-07-02.log +0 -156
  162. package/.ctxman/self-knowledge/documents.lance/_transactions/0-b3dd1558-f95b-44b9-a154-18e8c330ae7a.txn +0 -0
  163. package/.ctxman/self-knowledge/documents.lance/_transactions/1-44bff6d4-e552-4010-a769-e09ad822698d.txn +0 -0
  164. package/.ctxman/self-knowledge/documents.lance/_transactions/10-37260794-5947-4ecb-b4c8-d732f58f40f1.txn +0 -0
  165. package/.ctxman/self-knowledge/documents.lance/_transactions/11-0d52a883-5518-463c-9b6d-986c3af89dfd.txn +0 -0
  166. package/.ctxman/self-knowledge/documents.lance/_transactions/12-ae354646-52f2-49c9-90fb-2e6ed8932663.txn +0 -0
  167. package/.ctxman/self-knowledge/documents.lance/_transactions/13-5d7d5e57-27da-498b-b734-091b9d38da13.txn +0 -0
  168. package/.ctxman/self-knowledge/documents.lance/_transactions/14-558ce928-810b-4252-a364-fdd1ee6f8a25.txn +0 -0
  169. package/.ctxman/self-knowledge/documents.lance/_transactions/15-27e3a58f-8b64-43b8-a9f4-e3369446968b.txn +0 -0
  170. package/.ctxman/self-knowledge/documents.lance/_transactions/16-a110c390-e5c8-4464-b976-bc7f5195fa15.txn +0 -0
  171. package/.ctxman/self-knowledge/documents.lance/_transactions/17-97513225-53de-4216-b61a-6337bd0cc4ad.txn +0 -0
  172. package/.ctxman/self-knowledge/documents.lance/_transactions/18-5ba450f0-c527-459c-bf4f-6d79b5e4285f.txn +0 -0
  173. package/.ctxman/self-knowledge/documents.lance/_transactions/19-600d3a63-5225-49e9-b8b7-30e037b564ab.txn +0 -0
  174. package/.ctxman/self-knowledge/documents.lance/_transactions/2-48a61b4e-92cf-4342-8376-914c1b99a9f9.txn +0 -0
  175. package/.ctxman/self-knowledge/documents.lance/_transactions/20-121dd912-5410-410e-84ff-82c7a23604a0.txn +0 -0
  176. package/.ctxman/self-knowledge/documents.lance/_transactions/3-bc6844ab-7ea3-4bb3-9ee1-538ce5664a9c.txn +0 -0
  177. package/.ctxman/self-knowledge/documents.lance/_transactions/4-d5e1754f-dbb5-454e-a9be-2a7da8d7bcf9.txn +0 -0
  178. package/.ctxman/self-knowledge/documents.lance/_transactions/5-4fae93df-0568-4b6f-8acf-8bef4bba6047.txn +0 -0
  179. package/.ctxman/self-knowledge/documents.lance/_transactions/6-4b1b8b87-068e-49a3-ba2b-d8dae628b252.txn +0 -0
  180. package/.ctxman/self-knowledge/documents.lance/_transactions/7-63d040fb-d2fa-4c0f-bed0-53411918a7b0.txn +0 -0
  181. package/.ctxman/self-knowledge/documents.lance/_transactions/8-c5628cf6-264c-420f-aab8-bc96a9083591.txn +0 -0
  182. package/.ctxman/self-knowledge/documents.lance/_transactions/9-c94570aa-5e53-4fcb-971f-cecc1d1d5492.txn +0 -0
  183. package/.ctxman/self-knowledge/documents.lance/_versions/1.manifest +0 -0
  184. package/.ctxman/self-knowledge/documents.lance/_versions/10.manifest +0 -0
  185. package/.ctxman/self-knowledge/documents.lance/_versions/11.manifest +0 -0
  186. package/.ctxman/self-knowledge/documents.lance/_versions/12.manifest +0 -0
  187. package/.ctxman/self-knowledge/documents.lance/_versions/13.manifest +0 -0
  188. package/.ctxman/self-knowledge/documents.lance/_versions/14.manifest +0 -0
  189. package/.ctxman/self-knowledge/documents.lance/_versions/15.manifest +0 -0
  190. package/.ctxman/self-knowledge/documents.lance/_versions/16.manifest +0 -0
  191. package/.ctxman/self-knowledge/documents.lance/_versions/17.manifest +0 -0
  192. package/.ctxman/self-knowledge/documents.lance/_versions/18.manifest +0 -0
  193. package/.ctxman/self-knowledge/documents.lance/_versions/19.manifest +0 -0
  194. package/.ctxman/self-knowledge/documents.lance/_versions/2.manifest +0 -0
  195. package/.ctxman/self-knowledge/documents.lance/_versions/20.manifest +0 -0
  196. package/.ctxman/self-knowledge/documents.lance/_versions/21.manifest +0 -0
  197. package/.ctxman/self-knowledge/documents.lance/_versions/3.manifest +0 -0
  198. package/.ctxman/self-knowledge/documents.lance/_versions/4.manifest +0 -0
  199. package/.ctxman/self-knowledge/documents.lance/_versions/5.manifest +0 -0
  200. package/.ctxman/self-knowledge/documents.lance/_versions/6.manifest +0 -0
  201. package/.ctxman/self-knowledge/documents.lance/_versions/7.manifest +0 -0
  202. package/.ctxman/self-knowledge/documents.lance/_versions/8.manifest +0 -0
  203. package/.ctxman/self-knowledge/documents.lance/_versions/9.manifest +0 -0
  204. package/.ctxman/self-knowledge/documents.lance/data/000010010011011101000010de8621405d9d5cb06540dbf02c.lance +0 -0
  205. package/.ctxman/self-knowledge/documents.lance/data/0000101001011100011000003fb62a4aa8978974075c144f1c.lance +0 -0
  206. package/.ctxman/self-knowledge/documents.lance/data/000011010000100111001011c6cc824d628f3bb1f4e04cd5d0.lance +0 -0
  207. package/.ctxman/self-knowledge/documents.lance/data/001000001000011001011100d8f74349ada090b332cd1df669.lance +0 -0
  208. package/.ctxman/self-knowledge/documents.lance/data/01000001111010001000010045ab44467bb1e7e4d8ae06bd58.lance +0 -0
  209. package/.ctxman/self-knowledge/documents.lance/data/01000101101110111110011131ef904241a89e1ffb6ddaf8e3.lance +0 -0
  210. package/.ctxman/self-knowledge/documents.lance/data/010010110000010000011010fbfcc145c6a8a827b28057a07e.lance +0 -0
  211. package/.ctxman/self-knowledge/documents.lance/data/1000011000101010001110003fb4f045c3835982efa1405324.lance +0 -0
  212. package/.ctxman/self-knowledge/documents.lance/data/100111011101110111101011f4396b410fa06af3b028d204e6.lance +0 -0
  213. package/.ctxman/self-knowledge/documents.lance/data/100111100111101000001100596b174e00995d6cc2969cc8dc.lance +0 -0
  214. package/.ctxman/self-knowledge/documents.lance/data/1010001100011000110010102ea16b484d887c7c9725feffd6.lance +0 -0
  215. package/.ctxman/self-knowledge/documents.lance/data/101101100010101111110111c5230944d9b3af7301b6392dda.lance +0 -0
  216. package/.ctxman/self-knowledge/documents.lance/data/1011011100100111111110105892bf41b5a530fedd7cf516f1.lance +0 -0
  217. package/.ctxman/self-knowledge/documents.lance/data/10111110000010001011110164e8774742bade87b483c10f36.lance +0 -0
  218. package/.ctxman/self-knowledge/documents.lance/data/1100000100010110001110103ffaca40a2a42c85067ebaf9ae.lance +0 -0
  219. package/.ctxman/self-knowledge/documents.lance/data/110000010001110110101110664bde4c76a78dfc14b05e83b8.lance +0 -0
  220. package/.ctxman/self-knowledge/documents.lance/data/11001100111000010100110050645c4281b1f35b2022234f7f.lance +0 -0
  221. package/.ctxman/self-knowledge/documents.lance/data/1101111011001100110111017214924a368f5719eab2246619.lance +0 -0
  222. package/.ctxman/self-knowledge/documents.lance/data/1110000111001010001000002150c24dcab653d39d07032e63.lance +0 -0
  223. package/.ctxman/self-knowledge/documents.lance/data/1110100000000100000011014d20534967a18b5c87721659ff.lance +0 -0
  224. 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;