@vpxa/kb 0.1.1 → 0.1.2

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 (136) hide show
  1. package/package.json +1 -1
  2. package/packages/analyzers/dist/blast-radius-analyzer.js +13 -114
  3. package/packages/analyzers/dist/dependency-analyzer.js +11 -425
  4. package/packages/analyzers/dist/diagram-generator.js +4 -86
  5. package/packages/analyzers/dist/entry-point-analyzer.js +5 -239
  6. package/packages/analyzers/dist/index.js +1 -23
  7. package/packages/analyzers/dist/knowledge-producer.js +24 -113
  8. package/packages/analyzers/dist/pattern-analyzer.js +5 -359
  9. package/packages/analyzers/dist/regex-call-graph.js +1 -428
  10. package/packages/analyzers/dist/structure-analyzer.js +4 -258
  11. package/packages/analyzers/dist/symbol-analyzer.js +13 -442
  12. package/packages/analyzers/dist/ts-call-graph.js +1 -160
  13. package/packages/analyzers/dist/types.js +0 -1
  14. package/packages/chunker/dist/call-graph-extractor.js +1 -90
  15. package/packages/chunker/dist/chunker-factory.js +1 -36
  16. package/packages/chunker/dist/chunker.interface.js +0 -1
  17. package/packages/chunker/dist/code-chunker.js +14 -134
  18. package/packages/chunker/dist/generic-chunker.js +5 -72
  19. package/packages/chunker/dist/index.js +1 -21
  20. package/packages/chunker/dist/markdown-chunker.js +7 -119
  21. package/packages/chunker/dist/treesitter-chunker.js +8 -234
  22. package/packages/cli/dist/commands/analyze.js +3 -112
  23. package/packages/cli/dist/commands/context-cmds.js +1 -155
  24. package/packages/cli/dist/commands/environment.js +2 -204
  25. package/packages/cli/dist/commands/execution.js +1 -137
  26. package/packages/cli/dist/commands/graph.js +7 -81
  27. package/packages/cli/dist/commands/init.js +9 -87
  28. package/packages/cli/dist/commands/knowledge.js +1 -139
  29. package/packages/cli/dist/commands/search.js +8 -267
  30. package/packages/cli/dist/commands/system.js +4 -241
  31. package/packages/cli/dist/commands/workspace.js +2 -388
  32. package/packages/cli/dist/context.js +1 -14
  33. package/packages/cli/dist/helpers.js +3 -458
  34. package/packages/cli/dist/index.js +3 -69
  35. package/packages/cli/dist/kb-init.js +1 -82
  36. package/packages/cli/dist/types.js +0 -1
  37. package/packages/core/dist/constants.js +1 -43
  38. package/packages/core/dist/content-detector.js +1 -79
  39. package/packages/core/dist/errors.js +1 -40
  40. package/packages/core/dist/index.js +1 -9
  41. package/packages/core/dist/logger.js +1 -34
  42. package/packages/core/dist/types.js +0 -1
  43. package/packages/embeddings/dist/embedder.interface.js +0 -1
  44. package/packages/embeddings/dist/index.js +1 -5
  45. package/packages/embeddings/dist/onnx-embedder.js +1 -82
  46. package/packages/indexer/dist/file-hasher.js +1 -13
  47. package/packages/indexer/dist/filesystem-crawler.js +1 -125
  48. package/packages/indexer/dist/graph-extractor.js +1 -111
  49. package/packages/indexer/dist/incremental-indexer.js +1 -278
  50. package/packages/indexer/dist/index.js +1 -14
  51. package/packages/server/dist/api.js +1 -9
  52. package/packages/server/dist/config.js +1 -75
  53. package/packages/server/dist/curated-manager.js +9 -356
  54. package/packages/server/dist/index.js +1 -134
  55. package/packages/server/dist/replay-interceptor.js +1 -38
  56. package/packages/server/dist/resources/resources.js +2 -40
  57. package/packages/server/dist/server.js +1 -247
  58. package/packages/server/dist/tools/analyze.tools.js +1 -288
  59. package/packages/server/dist/tools/forge.tools.js +11 -499
  60. package/packages/server/dist/tools/forget.tool.js +3 -39
  61. package/packages/server/dist/tools/graph.tool.js +5 -110
  62. package/packages/server/dist/tools/list.tool.js +5 -53
  63. package/packages/server/dist/tools/lookup.tool.js +8 -51
  64. package/packages/server/dist/tools/onboard.tool.js +2 -112
  65. package/packages/server/dist/tools/produce.tool.js +4 -74
  66. package/packages/server/dist/tools/read.tool.js +4 -47
  67. package/packages/server/dist/tools/reindex.tool.js +2 -70
  68. package/packages/server/dist/tools/remember.tool.js +3 -42
  69. package/packages/server/dist/tools/replay.tool.js +6 -88
  70. package/packages/server/dist/tools/search.tool.js +17 -327
  71. package/packages/server/dist/tools/status.tool.js +3 -68
  72. package/packages/server/dist/tools/toolkit.tools.js +20 -1673
  73. package/packages/server/dist/tools/update.tool.js +3 -39
  74. package/packages/server/dist/tools/utility.tools.js +19 -456
  75. package/packages/store/dist/graph-store.interface.js +0 -1
  76. package/packages/store/dist/index.js +1 -9
  77. package/packages/store/dist/lance-store.js +1 -258
  78. package/packages/store/dist/sqlite-graph-store.js +8 -309
  79. package/packages/store/dist/store-factory.js +1 -14
  80. package/packages/store/dist/store.interface.js +0 -1
  81. package/packages/tools/dist/batch.js +1 -45
  82. package/packages/tools/dist/changelog.js +2 -112
  83. package/packages/tools/dist/check.js +2 -59
  84. package/packages/tools/dist/checkpoint.js +2 -43
  85. package/packages/tools/dist/codemod.js +2 -69
  86. package/packages/tools/dist/compact.js +3 -60
  87. package/packages/tools/dist/data-transform.js +1 -124
  88. package/packages/tools/dist/dead-symbols.js +2 -71
  89. package/packages/tools/dist/delegate.js +3 -128
  90. package/packages/tools/dist/diff-parse.js +3 -153
  91. package/packages/tools/dist/digest.js +7 -242
  92. package/packages/tools/dist/encode.js +1 -46
  93. package/packages/tools/dist/env-info.js +1 -58
  94. package/packages/tools/dist/eval.js +3 -79
  95. package/packages/tools/dist/evidence-map.js +3 -203
  96. package/packages/tools/dist/file-summary.js +2 -106
  97. package/packages/tools/dist/file-walk.js +1 -75
  98. package/packages/tools/dist/find-examples.js +3 -48
  99. package/packages/tools/dist/find.js +1 -120
  100. package/packages/tools/dist/forge-classify.js +2 -319
  101. package/packages/tools/dist/forge-ground.js +1 -184
  102. package/packages/tools/dist/git-context.js +3 -46
  103. package/packages/tools/dist/graph-query.js +1 -194
  104. package/packages/tools/dist/health.js +1 -118
  105. package/packages/tools/dist/http-request.js +1 -58
  106. package/packages/tools/dist/index.js +1 -273
  107. package/packages/tools/dist/lane.js +7 -227
  108. package/packages/tools/dist/measure.js +2 -119
  109. package/packages/tools/dist/onboard.js +42 -1136
  110. package/packages/tools/dist/parse-output.js +2 -158
  111. package/packages/tools/dist/process-manager.js +1 -69
  112. package/packages/tools/dist/queue.js +2 -126
  113. package/packages/tools/dist/regex-test.js +1 -39
  114. package/packages/tools/dist/rename.js +2 -70
  115. package/packages/tools/dist/replay.js +6 -108
  116. package/packages/tools/dist/schema-validate.js +1 -141
  117. package/packages/tools/dist/scope-map.js +1 -72
  118. package/packages/tools/dist/snippet.js +1 -80
  119. package/packages/tools/dist/stash.js +2 -60
  120. package/packages/tools/dist/stratum-card.js +5 -238
  121. package/packages/tools/dist/symbol.js +3 -87
  122. package/packages/tools/dist/test-run.js +2 -55
  123. package/packages/tools/dist/text-utils.js +2 -31
  124. package/packages/tools/dist/time-utils.js +1 -135
  125. package/packages/tools/dist/trace.js +2 -114
  126. package/packages/tools/dist/truncation.js +10 -41
  127. package/packages/tools/dist/watch.js +1 -61
  128. package/packages/tools/dist/web-fetch.js +9 -244
  129. package/packages/tools/dist/web-search.js +1 -46
  130. package/packages/tools/dist/workset.js +2 -77
  131. package/packages/tui/dist/App.js +260 -52468
  132. package/packages/tui/dist/index.js +286 -54551
  133. package/packages/tui/dist/panels/CuratedPanel.js +211 -34291
  134. package/packages/tui/dist/panels/LogPanel.js +259 -51703
  135. package/packages/tui/dist/panels/SearchPanel.js +212 -34824
  136. package/packages/tui/dist/panels/StatusPanel.js +211 -34304
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vpxa/kb",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "description": "Local-first AI developer toolkit — knowledge base, code analysis, context management, and developer tools for LLM agents",
6
6
  "license": "MIT",
@@ -1,114 +1,13 @@
1
- import { DependencyAnalyzer } from "./dependency-analyzer.js";
2
- class BlastRadiusAnalyzer {
3
- name = "blast-radius";
4
- depAnalyzer = new DependencyAnalyzer();
5
- async analyze(rootPath, options) {
6
- const { files, maxDepth = 5, format = "markdown" } = options;
7
- const startTime = Date.now();
8
- const depResult = await this.depAnalyzer.analyze(rootPath, { format: "json" });
9
- const depData = depResult.data;
10
- const reverseGraph = depData.reverseGraph ?? {};
11
- const testCoverage = depData.testCoverage ?? {};
12
- const affected = /* @__PURE__ */ new Map();
13
- const queue = [];
14
- for (const file of files) {
15
- affected.set(file, { path: file, reason: "changed", depth: 0 });
16
- const norm = file.replace(/\.[jt]sx?$/, "");
17
- queue.push({ path: norm, depth: 0 });
18
- }
19
- while (queue.length > 0) {
20
- const item = queue.shift();
21
- if (!item) break;
22
- const { path, depth } = item;
23
- if (depth >= maxDepth) continue;
24
- const importers = reverseGraph[path] ?? [];
25
- for (const importer of importers) {
26
- if (affected.has(importer)) continue;
27
- const reason = depth === 0 ? "direct-importer" : "transitive-importer";
28
- affected.set(importer, { path: importer, reason, depth: depth + 1 });
29
- const importerNorm = importer.replace(/\.[jt]sx?$/, "");
30
- queue.push({ path: importerNorm, depth: depth + 1 });
31
- }
32
- }
33
- const affectedTests = /* @__PURE__ */ new Set();
34
- for (const [filePath] of affected) {
35
- const norm = filePath.replace(/\.[jt]sx?$/, "");
36
- const tests = testCoverage[norm] ?? [];
37
- for (const test of tests) {
38
- if (!affected.has(test)) {
39
- affectedTests.add(test);
40
- affected.set(test, { path: test, reason: "test", depth: 999 });
41
- }
42
- }
43
- }
44
- const affectedList = [...affected.values()].sort((a, b) => a.depth - b.depth);
45
- const output = format === "json" ? JSON.stringify({ changedFiles: files, affected: affectedList }, null, 2) : this.formatMarkdown(files, affectedList, reverseGraph);
46
- return {
47
- output,
48
- data: { changedFiles: files, affected: affectedList },
49
- meta: {
50
- analyzedAt: (/* @__PURE__ */ new Date()).toISOString(),
51
- scope: rootPath,
52
- fileCount: affectedList.length,
53
- durationMs: Date.now() - startTime
54
- }
55
- };
56
- }
57
- formatMarkdown(changedFiles, affected, _reverseGraph) {
58
- const lines = [];
59
- lines.push("## Blast Radius Analysis\n");
60
- lines.push(`**Changed files:** ${changedFiles.length}`);
61
- lines.push(`**Total affected:** ${affected.length}
62
- `);
63
- const changed = affected.filter((a) => a.reason === "changed");
64
- if (changed.length > 0) {
65
- lines.push("### Changed Files\n");
66
- for (const f of changed) {
67
- lines.push(`- \`${f.path}\``);
68
- }
69
- }
70
- const direct = affected.filter((a) => a.reason === "direct-importer");
71
- if (direct.length > 0) {
72
- lines.push(`
73
- ### Direct Importers (${direct.length} files)
74
- `);
75
- for (const f of direct) {
76
- lines.push(`- \`${f.path}\``);
77
- }
78
- }
79
- const transitive = affected.filter((a) => a.reason === "transitive-importer");
80
- if (transitive.length > 0) {
81
- lines.push(`
82
- ### Transitive Importers (${transitive.length} files)
83
- `);
84
- for (const f of transitive.slice(0, 20)) {
85
- lines.push(`- \`${f.path}\` (depth ${f.depth})`);
86
- }
87
- if (transitive.length > 20) {
88
- lines.push(`- ... and ${transitive.length - 20} more`);
89
- }
90
- }
91
- const tests = affected.filter((a) => a.reason === "test");
92
- if (tests.length > 0) {
93
- lines.push(`
94
- ### Affected Tests (${tests.length} files)
95
- `);
96
- for (const f of tests) {
97
- lines.push(`- \`${f.path}\``);
98
- }
99
- }
100
- lines.push("\n### Review Summary\n");
101
- lines.push(`| Category | Count |`);
102
- lines.push(`|----------|-------|`);
103
- lines.push(`| Changed | ${changed.length} |`);
104
- lines.push(`| Direct importers | ${direct.length} |`);
105
- lines.push(`| Transitive importers | ${transitive.length} |`);
106
- lines.push(`| Affected tests | ${tests.length} |`);
107
- lines.push(`| **Total review scope** | **${affected.length}** |`);
108
- return lines.join("\n");
109
- }
110
- }
111
- export {
112
- BlastRadiusAnalyzer
113
- };
114
- //# sourceMappingURL=blast-radius-analyzer.js.map
1
+ import{DependencyAnalyzer as A}from"./dependency-analyzer.js";class j{name="blast-radius";depAnalyzer=new A;async analyze(d,n){const{files:c,maxDepth:t=5,format:f="markdown"}=n,p=Date.now(),o=(await this.depAnalyzer.analyze(d,{format:"json"})).data,e=o.reverseGraph??{},y=o.testCoverage??{},a=new Map,g=[];for(const s of c){a.set(s,{path:s,reason:"changed",depth:0});const i=s.replace(/\.[jt]sx?$/,"");g.push({path:i,depth:0})}for(;g.length>0;){const s=g.shift();if(!s)break;const{path:i,depth:h}=s;if(h>=t)continue;const l=e[i]??[];for(const m of l){if(a.has(m))continue;const v=h===0?"direct-importer":"transitive-importer";a.set(m,{path:m,reason:v,depth:h+1});const w=m.replace(/\.[jt]sx?$/,"");g.push({path:w,depth:h+1})}}const $=new Set;for(const[s]of a){const i=s.replace(/\.[jt]sx?$/,""),h=y[i]??[];for(const l of h)a.has(l)||($.add(l),a.set(l,{path:l,reason:"test",depth:999}))}const u=[...a.values()].sort((s,i)=>s.depth-i.depth);return{output:f==="json"?JSON.stringify({changedFiles:c,affected:u},null,2):this.formatMarkdown(c,u,e),data:{changedFiles:c,affected:u},meta:{analyzedAt:new Date().toISOString(),scope:d,fileCount:u.length,durationMs:Date.now()-p}}}formatMarkdown(d,n,c){const t=[];t.push(`## Blast Radius Analysis
2
+ `),t.push(`**Changed files:** ${d.length}`),t.push(`**Total affected:** ${n.length}
3
+ `);const f=n.filter(e=>e.reason==="changed");if(f.length>0){t.push(`### Changed Files
4
+ `);for(const e of f)t.push(`- \`${e.path}\``)}const p=n.filter(e=>e.reason==="direct-importer");if(p.length>0){t.push(`
5
+ ### Direct Importers (${p.length} files)
6
+ `);for(const e of p)t.push(`- \`${e.path}\``)}const r=n.filter(e=>e.reason==="transitive-importer");if(r.length>0){t.push(`
7
+ ### Transitive Importers (${r.length} files)
8
+ `);for(const e of r.slice(0,20))t.push(`- \`${e.path}\` (depth ${e.depth})`);r.length>20&&t.push(`- ... and ${r.length-20} more`)}const o=n.filter(e=>e.reason==="test");if(o.length>0){t.push(`
9
+ ### Affected Tests (${o.length} files)
10
+ `);for(const e of o)t.push(`- \`${e.path}\``)}return t.push(`
11
+ ### Review Summary
12
+ `),t.push("| Category | Count |"),t.push("|----------|-------|"),t.push(`| Changed | ${f.length} |`),t.push(`| Direct importers | ${p.length} |`),t.push(`| Transitive importers | ${r.length} |`),t.push(`| Affected tests | ${o.length} |`),t.push(`| **Total review scope** | **${n.length}** |`),t.join(`
13
+ `)}}export{j as BlastRadiusAnalyzer};
@@ -1,425 +1,11 @@
1
- import { readdir, readFile } from "node:fs/promises";
2
- import { dirname, extname, join, relative, resolve } from "node:path";
3
- const CODE_EXTENSIONS = /* @__PURE__ */ new Set([
4
- ".ts",
5
- ".tsx",
6
- ".js",
7
- ".jsx",
8
- ".mjs",
9
- ".cjs",
10
- ".py",
11
- ".java",
12
- ".go",
13
- ".cs",
14
- ".kt",
15
- ".scala",
16
- ".rb",
17
- ".rs",
18
- ".php",
19
- ".swift"
20
- ]);
21
- const IMPORT_PATTERNS = [
22
- // ── JS/TS ──
23
- // ES import: import { x } from 'module' / import x from 'module'
24
- {
25
- regex: /import\s+(?:(?:type\s+)?(?:(?:\{[^}]*\}|[\w*]+)\s+from\s+)?)['"]([^'"]+)['"]/g,
26
- confidence: "high"
27
- },
28
- // Dynamic import: import('module')
29
- {
30
- regex: /import\(\s*['"]([^'"]+)['"]\s*\)/g,
31
- confidence: "medium"
32
- },
33
- // CommonJS require: require('module')
34
- {
35
- regex: /require\(\s*['"]([^'"]+)['"]\s*\)/g,
36
- confidence: "medium"
37
- },
38
- // ── Python ──
39
- // from package import module
40
- {
41
- regex: /^from\s+([\w.]+)\s+import\b/gm,
42
- confidence: "high",
43
- lang: "python"
44
- },
45
- // import package
46
- {
47
- regex: /^import\s+([\w.]+)\s*$/gm,
48
- confidence: "high",
49
- lang: "python"
50
- },
51
- // ── Java / Kotlin / Scala ──
52
- // import com.example.Class;
53
- {
54
- regex: /^import\s+(?:static\s+)?([\w.]+(?:\.\*)?)\s*;/gm,
55
- confidence: "high",
56
- lang: "java"
57
- },
58
- // ── Go ──
59
- // import "package" or "package" inside import block
60
- {
61
- regex: /(?:^import\s+|^\s+)[""]([^""]+)[""]/gm,
62
- confidence: "high",
63
- lang: "go"
64
- },
65
- // ── C# ──
66
- // using Namespace.Something;
67
- {
68
- regex: /^using\s+(?:static\s+)?([\w.]+)\s*;/gm,
69
- confidence: "high",
70
- lang: "csharp"
71
- },
72
- // ── Rust ──
73
- // use crate::module; use std::io;
74
- {
75
- regex: /^use\s+([\w:]+(?:::\w+)*)/gm,
76
- confidence: "high",
77
- lang: "rust"
78
- },
79
- // ── PHP ──
80
- // use Namespace\Class;
81
- {
82
- regex: /^use\s+([\w\\]+)\s*;/gm,
83
- confidence: "high",
84
- lang: "php"
85
- },
86
- // ── Ruby ──
87
- // require 'gem' or require_relative 'file'
88
- {
89
- regex: /require(?:_relative)?\s+['"]([^'"]+)['"]/g,
90
- confidence: "medium",
91
- lang: "ruby"
92
- },
93
- // ── Swift ──
94
- // import Foundation
95
- {
96
- regex: /^import\s+(\w+)\s*$/gm,
97
- confidence: "high",
98
- lang: "swift"
99
- }
100
- ];
101
- const DEFAULT_EXCLUDES = /* @__PURE__ */ new Set([
102
- "node_modules",
103
- ".git",
104
- "dist",
105
- "build",
106
- "coverage",
107
- ".turbo",
108
- ".cache",
109
- "cdk.out"
110
- ]);
111
- const TEST_PATTERNS = [/\.(test|spec)\.[jt]sx?$/, /\/__tests__\//, /\/test\//, /\/tests\//];
112
- const EXT_TO_LANG = {
113
- ".ts": "js",
114
- ".tsx": "js",
115
- ".js": "js",
116
- ".jsx": "js",
117
- ".mjs": "js",
118
- ".cjs": "js",
119
- ".py": "python",
120
- ".java": "java",
121
- ".kt": "java",
122
- ".scala": "java",
123
- ".go": "go",
124
- ".cs": "csharp",
125
- ".rs": "rust",
126
- ".php": "php",
127
- ".rb": "ruby",
128
- ".swift": "swift"
129
- };
130
- function classifyExternal(source, lang) {
131
- if (!lang || lang === "js") {
132
- return !source.startsWith(".") && !source.startsWith("/");
133
- }
134
- if (lang === "python") {
135
- return !source.startsWith(".");
136
- }
137
- if (lang === "java") {
138
- return !source.startsWith("com.") || source.startsWith("com.amazonaws") || source.startsWith("com.google") || source.startsWith("com.fasterxml");
139
- }
140
- if (lang === "go") {
141
- return source.includes(".") && !source.startsWith(".");
142
- }
143
- if (lang === "csharp") {
144
- return source.startsWith("System") || source.startsWith("Microsoft") || source.startsWith("Newtonsoft") || source.startsWith("Amazon");
145
- }
146
- if (lang === "rust") {
147
- return !source.startsWith("crate::") && !source.startsWith("self::") && !source.startsWith("super::");
148
- }
149
- return true;
150
- }
151
- function isTestFile(filePath) {
152
- return TEST_PATTERNS.some((p) => p.test(filePath));
153
- }
154
- class DependencyAnalyzer {
155
- name = "dependencies";
156
- /** Map of workspace package names to their relative entry point paths */
157
- workspacePackages = /* @__PURE__ */ new Map();
158
- async analyze(rootPath, options = {}) {
159
- const { format = "markdown" } = options;
160
- const startTime = Date.now();
161
- this.workspacePackages = await this.buildWorkspaceMap(rootPath);
162
- const files = await this.collectFiles(rootPath);
163
- const imports = [];
164
- for (const filePath of files) {
165
- const content = await readFile(filePath, "utf-8");
166
- const fileImports = this.extractImports(content, filePath, rootPath);
167
- imports.push(...fileImports);
168
- }
169
- const external = this.groupExternalDeps(imports);
170
- const internal = this.groupInternalDeps(imports, rootPath);
171
- const reverseGraph = this.buildReverseGraph(imports, rootPath);
172
- const testCoverage = this.buildTestCoverage(imports, rootPath);
173
- const output = format === "json" ? JSON.stringify({ external, internal, reverseGraph, testCoverage }, null, 2) : format === "mermaid" ? this.formatMermaid(internal) : this.formatMarkdown(external, internal, rootPath, testCoverage);
174
- return {
175
- output,
176
- data: { external, internal, reverseGraph, testCoverage, totalImports: imports.length },
177
- meta: {
178
- analyzedAt: (/* @__PURE__ */ new Date()).toISOString(),
179
- scope: rootPath,
180
- fileCount: files.length,
181
- durationMs: Date.now() - startTime
182
- }
183
- };
184
- }
185
- async collectFiles(dirPath) {
186
- const files = [];
187
- const walk = async (dir) => {
188
- const entries = await readdir(dir, { withFileTypes: true });
189
- for (const entry of entries) {
190
- if (DEFAULT_EXCLUDES.has(entry.name)) continue;
191
- if (entry.name.startsWith(".")) continue;
192
- const fullPath = join(dir, entry.name);
193
- if (entry.isDirectory()) {
194
- await walk(fullPath);
195
- } else if (CODE_EXTENSIONS.has(extname(entry.name))) {
196
- files.push(fullPath);
197
- }
198
- }
199
- };
200
- await walk(dirPath);
201
- return files;
202
- }
203
- extractImports(content, filePath, rootPath) {
204
- const results = [];
205
- const ext = extname(filePath).toLowerCase();
206
- const fileLang = EXT_TO_LANG[ext];
207
- for (const pattern of IMPORT_PATTERNS) {
208
- if (pattern.lang && pattern.lang !== fileLang) continue;
209
- if (!pattern.lang && fileLang && fileLang !== "js") continue;
210
- const regex = new RegExp(pattern.regex.source, pattern.regex.flags);
211
- let match;
212
- while ((match = regex.exec(content)) !== null) {
213
- const source = match[1];
214
- const isExternal = classifyExternal(source, fileLang);
215
- results.push({
216
- source,
217
- specifiers: [],
218
- filePath: relative(rootPath, filePath).replace(/\\/g, "/"),
219
- isExternal,
220
- confidence: pattern.confidence
221
- });
222
- }
223
- }
224
- return results;
225
- }
226
- groupExternalDeps(imports) {
227
- const deps = {};
228
- for (const imp of imports) {
229
- if (!imp.isExternal) continue;
230
- const ext = extname(imp.filePath).toLowerCase();
231
- const lang = EXT_TO_LANG[ext];
232
- let pkg;
233
- if (lang === "java") {
234
- const parts = imp.source.split(".");
235
- while (parts.length > 1) {
236
- const last = parts[parts.length - 1];
237
- if (last === "*" || /^[A-Z]/.test(last)) {
238
- parts.pop();
239
- } else {
240
- break;
241
- }
242
- }
243
- if (parts.length >= 2) {
244
- pkg = parts.slice(0, 2).join(".");
245
- } else {
246
- pkg = parts.join(".");
247
- }
248
- } else if (lang === "python") {
249
- pkg = imp.source.split(".")[0];
250
- } else if (lang === "go") {
251
- pkg = imp.source;
252
- } else if (lang === "csharp") {
253
- const parts = imp.source.split(".");
254
- pkg = parts.length >= 2 ? parts.slice(0, 2).join(".") : imp.source;
255
- } else {
256
- pkg = imp.source.startsWith("@") ? imp.source.split("/").slice(0, 2).join("/") : imp.source.split("/")[0];
257
- }
258
- if (!deps[pkg]) deps[pkg] = { count: 0, confidence: imp.confidence, usedBy: /* @__PURE__ */ new Set() };
259
- deps[pkg].count++;
260
- deps[pkg].usedBy.add(imp.filePath);
261
- if (imp.confidence === "high") deps[pkg].confidence = "high";
262
- else if (imp.confidence === "medium" && deps[pkg].confidence === "low")
263
- deps[pkg].confidence = "medium";
264
- }
265
- const result = {};
266
- for (const [pkg, data] of Object.entries(deps)) {
267
- result[pkg] = { count: data.count, confidence: data.confidence, usedBy: [...data.usedBy] };
268
- }
269
- return result;
270
- }
271
- groupInternalDeps(imports, _rootPath) {
272
- const deps = {};
273
- for (const imp of imports) {
274
- if (imp.isExternal) continue;
275
- if (!deps[imp.filePath]) deps[imp.filePath] = /* @__PURE__ */ new Set();
276
- deps[imp.filePath].add(imp.source);
277
- }
278
- const result = {};
279
- for (const [file, sources] of Object.entries(deps)) {
280
- result[file] = [...sources];
281
- }
282
- return result;
283
- }
284
- /**
285
- * Build reverse graph: for each source file, who imports it?
286
- */
287
- buildReverseGraph(imports, _rootPath) {
288
- const reverse = {};
289
- for (const imp of imports) {
290
- const importerDir = dirname(imp.filePath);
291
- const resolved = this.resolveImportPath(imp.source, importerDir);
292
- if (!resolved) continue;
293
- if (!reverse[resolved]) reverse[resolved] = /* @__PURE__ */ new Set();
294
- reverse[resolved].add(imp.filePath);
295
- }
296
- const result = {};
297
- for (const [file, importers] of Object.entries(reverse)) {
298
- result[file] = [...importers];
299
- }
300
- return result;
301
- }
302
- /**
303
- * Build test coverage map: for each source file, which test files exercise it?
304
- */
305
- buildTestCoverage(imports, _rootPath) {
306
- const coverage = {};
307
- for (const imp of imports) {
308
- if (!isTestFile(imp.filePath)) continue;
309
- const importerDir = dirname(imp.filePath);
310
- const resolved = this.resolveImportPath(imp.source, importerDir);
311
- if (!resolved || isTestFile(resolved)) continue;
312
- if (!coverage[resolved]) coverage[resolved] = /* @__PURE__ */ new Set();
313
- coverage[resolved].add(imp.filePath);
314
- }
315
- const result = {};
316
- for (const [file, tests] of Object.entries(coverage)) {
317
- result[file] = [...tests];
318
- }
319
- return result;
320
- }
321
- /** Resolve a relative import path to a normalized file path (best-effort). */
322
- resolveImportPath(source, fromDir) {
323
- if (source.startsWith(".")) {
324
- const resolved = join(fromDir, source).replace(/\\/g, "/");
325
- return resolved.replace(/\.[jt]sx?$/, "");
326
- }
327
- const pkg = source.startsWith("@") ? source.split("/").slice(0, 2).join("/") : source.split("/")[0];
328
- const entryPath = this.workspacePackages.get(pkg);
329
- if (entryPath) {
330
- return entryPath.replace(/\.[jt]sx?$/, "");
331
- }
332
- return null;
333
- }
334
- /**
335
- * Build a map of workspace package names → relative entry point paths.
336
- * Scans for package.json files in common workspace dirs.
337
- */
338
- async buildWorkspaceMap(rootPath) {
339
- const map = /* @__PURE__ */ new Map();
340
- const searchDirs = ["packages", "functions", "libs", "apps", "cdk"];
341
- for (const dir of searchDirs) {
342
- const dirPath = join(rootPath, dir);
343
- try {
344
- const entries = await readdir(dirPath, { withFileTypes: true });
345
- for (const entry of entries) {
346
- if (!entry.isDirectory() || DEFAULT_EXCLUDES.has(entry.name)) continue;
347
- try {
348
- const pkgJsonPath = join(dirPath, entry.name, "package.json");
349
- const pkgJson = JSON.parse(await readFile(pkgJsonPath, "utf-8"));
350
- if (pkgJson.name) {
351
- const main = pkgJson.main ?? pkgJson.exports?.["."] ?? "src/index.ts";
352
- const entryRelative = relative(rootPath, resolve(dirPath, entry.name, main)).replace(
353
- /\\/g,
354
- "/"
355
- );
356
- map.set(pkgJson.name, entryRelative);
357
- }
358
- } catch {
359
- }
360
- }
361
- } catch {
362
- }
363
- }
364
- return map;
365
- }
366
- formatMarkdown(external, internal, rootPath, testCoverage) {
367
- const lines = [];
368
- lines.push(`## Dependencies: ${rootPath}
369
- `);
370
- const sortedExternal = Object.entries(external).sort((a, b) => b[1].count - a[1].count);
371
- const totalExternal = sortedExternal.length;
372
- const totalImports = sortedExternal.reduce((sum, [, d]) => sum + d.count, 0);
373
- const top5 = sortedExternal.slice(0, 5).map(([pkg]) => pkg);
374
- lines.push(
375
- `**${totalExternal} external packages**, **${totalImports} total imports**, **${Object.keys(internal).length} files** with internal imports.
376
- `
377
- );
378
- if (top5.length > 0) {
379
- lines.push(`**Top dependencies**: ${top5.join(", ")}
380
- `);
381
- }
382
- lines.push("### External Dependencies\n");
383
- lines.push("| Package | Imports | Used By |");
384
- lines.push("|---------|---------|---------|");
385
- for (const [pkg, data] of sortedExternal) {
386
- lines.push(
387
- `| ${pkg} | ${data.count} | ${data.usedBy.length} ${data.usedBy.length === 1 ? "file" : "files"} |`
388
- );
389
- }
390
- if (testCoverage && Object.keys(testCoverage).length > 0) {
391
- const totalTested = Object.keys(testCoverage).length;
392
- const untested = Object.keys(internal).filter(
393
- (f) => !isTestFile(f) && !testCoverage[f.replace(/\\.[jt]sx?$/, "")]
394
- );
395
- lines.push("\n### Test Coverage Summary\n");
396
- lines.push(`**${totalTested} source modules** with test coverage.`);
397
- if (untested.length > 0) {
398
- lines.push(`**${untested.length} source files** with no detected test coverage.`);
399
- }
400
- const sorted = Object.entries(testCoverage).sort((a, b) => b[1].length - a[1].length);
401
- lines.push("\n**Most-tested modules:**\n");
402
- for (const [source, tests] of sorted.slice(0, 10)) {
403
- const displayPath = source.replace(/\/dist\/[^/]*$/, "/src/index").replace(/\.mjs$/, ".ts");
404
- lines.push(`- ${displayPath} (${tests.length} ${tests.length === 1 ? "test" : "tests"})`);
405
- }
406
- }
407
- return lines.join("\n");
408
- }
409
- formatMermaid(internal) {
410
- const lines = ["graph LR"];
411
- const nodeId = (path) => path.replace(/[^a-zA-Z0-9]/g, "_");
412
- for (const [file, deps] of Object.entries(internal).slice(0, 40)) {
413
- const fromId = nodeId(file);
414
- for (const dep of deps) {
415
- const toId = nodeId(dep);
416
- lines.push(` ${fromId}["${file}"] --> ${toId}["${dep}"]`);
417
- }
418
- }
419
- return lines.join("\n");
420
- }
421
- }
422
- export {
423
- DependencyAnalyzer
424
- };
425
- //# sourceMappingURL=dependency-analyzer.js.map
1
+ import{readdir as w,readFile as j}from"node:fs/promises";import{dirname as x,extname as m,join as u,relative as v,resolve as R}from"node:path";const $=new Set([".ts",".tsx",".js",".jsx",".mjs",".cjs",".py",".java",".go",".cs",".kt",".scala",".rb",".rs",".php",".swift"]),P=[{regex:/import\s+(?:(?:type\s+)?(?:(?:\{[^}]*\}|[\w*]+)\s+from\s+)?)['"]([^'"]+)['"]/g,confidence:"high"},{regex:/import\(\s*['"]([^'"]+)['"]\s*\)/g,confidence:"medium"},{regex:/require\(\s*['"]([^'"]+)['"]\s*\)/g,confidence:"medium"},{regex:/^from\s+([\w.]+)\s+import\b/gm,confidence:"high",lang:"python"},{regex:/^import\s+([\w.]+)\s*$/gm,confidence:"high",lang:"python"},{regex:/^import\s+(?:static\s+)?([\w.]+(?:\.\*)?)\s*;/gm,confidence:"high",lang:"java"},{regex:/(?:^import\s+|^\s+)[""]([^""]+)[""]/gm,confidence:"high",lang:"go"},{regex:/^using\s+(?:static\s+)?([\w.]+)\s*;/gm,confidence:"high",lang:"csharp"},{regex:/^use\s+([\w:]+(?:::\w+)*)/gm,confidence:"high",lang:"rust"},{regex:/^use\s+([\w\\]+)\s*;/gm,confidence:"high",lang:"php"},{regex:/require(?:_relative)?\s+['"]([^'"]+)['"]/g,confidence:"medium",lang:"ruby"},{regex:/^import\s+(\w+)\s*$/gm,confidence:"high",lang:"swift"}],I=new Set(["node_modules",".git","dist","build","coverage",".turbo",".cache","cdk.out"]),E=[/\.(test|spec)\.[jt]sx?$/,/\/__tests__\//,/\/test\//,/\/tests\//],b={".ts":"js",".tsx":"js",".js":"js",".jsx":"js",".mjs":"js",".cjs":"js",".py":"python",".java":"java",".kt":"java",".scala":"java",".go":"go",".cs":"csharp",".rs":"rust",".php":"php",".rb":"ruby",".swift":"swift"};function S(a,n){return!n||n==="js"?!a.startsWith(".")&&!a.startsWith("/"):n==="python"?!a.startsWith("."):n==="java"?!a.startsWith("com.")||a.startsWith("com.amazonaws")||a.startsWith("com.google")||a.startsWith("com.fasterxml"):n==="go"?a.includes(".")&&!a.startsWith("."):n==="csharp"?a.startsWith("System")||a.startsWith("Microsoft")||a.startsWith("Newtonsoft")||a.startsWith("Amazon"):n==="rust"?!a.startsWith("crate::")&&!a.startsWith("self::")&&!a.startsWith("super::"):!0}function y(a){return E.some(n=>n.test(a))}class W{name="dependencies";workspacePackages=new Map;async analyze(n,o={}){const{format:i="markdown"}=o,t=Date.now();this.workspacePackages=await this.buildWorkspaceMap(n);const e=await this.collectFiles(n),r=[];for(const d of e){const f=await j(d,"utf-8"),h=this.extractImports(f,d,n);r.push(...h)}const s=this.groupExternalDeps(r),c=this.groupInternalDeps(r,n),g=this.buildReverseGraph(r,n),l=this.buildTestCoverage(r,n);return{output:i==="json"?JSON.stringify({external:s,internal:c,reverseGraph:g,testCoverage:l},null,2):i==="mermaid"?this.formatMermaid(c):this.formatMarkdown(s,c,n,l),data:{external:s,internal:c,reverseGraph:g,testCoverage:l,totalImports:r.length},meta:{analyzedAt:new Date().toISOString(),scope:n,fileCount:e.length,durationMs:Date.now()-t}}}async collectFiles(n){const o=[],i=async t=>{const e=await w(t,{withFileTypes:!0});for(const r of e){if(I.has(r.name)||r.name.startsWith("."))continue;const s=u(t,r.name);r.isDirectory()?await i(s):$.has(m(r.name))&&o.push(s)}};return await i(n),o}extractImports(n,o,i){const t=[],e=m(o).toLowerCase(),r=b[e];for(const s of P){if(s.lang&&s.lang!==r||!s.lang&&r&&r!=="js")continue;const c=new RegExp(s.regex.source,s.regex.flags);let g;for(;(g=c.exec(n))!==null;){const l=g[1],p=S(l,r);t.push({source:l,specifiers:[],filePath:v(i,o).replace(/\\/g,"/"),isExternal:p,confidence:s.confidence})}}return t}groupExternalDeps(n){const o={};for(const t of n){if(!t.isExternal)continue;const e=m(t.filePath).toLowerCase(),r=b[e];let s;if(r==="java"){const c=t.source.split(".");for(;c.length>1;){const g=c[c.length-1];if(g==="*"||/^[A-Z]/.test(g))c.pop();else break}c.length>=2?s=c.slice(0,2).join("."):s=c.join(".")}else if(r==="python")s=t.source.split(".")[0];else if(r==="go")s=t.source;else if(r==="csharp"){const c=t.source.split(".");s=c.length>=2?c.slice(0,2).join("."):t.source}else s=t.source.startsWith("@")?t.source.split("/").slice(0,2).join("/"):t.source.split("/")[0];o[s]||(o[s]={count:0,confidence:t.confidence,usedBy:new Set}),o[s].count++,o[s].usedBy.add(t.filePath),t.confidence==="high"?o[s].confidence="high":t.confidence==="medium"&&o[s].confidence==="low"&&(o[s].confidence="medium")}const i={};for(const[t,e]of Object.entries(o))i[t]={count:e.count,confidence:e.confidence,usedBy:[...e.usedBy]};return i}groupInternalDeps(n,o){const i={};for(const e of n)e.isExternal||(i[e.filePath]||(i[e.filePath]=new Set),i[e.filePath].add(e.source));const t={};for(const[e,r]of Object.entries(i))t[e]=[...r];return t}buildReverseGraph(n,o){const i={};for(const e of n){const r=x(e.filePath),s=this.resolveImportPath(e.source,r);s&&(i[s]||(i[s]=new Set),i[s].add(e.filePath))}const t={};for(const[e,r]of Object.entries(i))t[e]=[...r];return t}buildTestCoverage(n,o){const i={};for(const e of n){if(!y(e.filePath))continue;const r=x(e.filePath),s=this.resolveImportPath(e.source,r);!s||y(s)||(i[s]||(i[s]=new Set),i[s].add(e.filePath))}const t={};for(const[e,r]of Object.entries(i))t[e]=[...r];return t}resolveImportPath(n,o){if(n.startsWith("."))return u(o,n).replace(/\\/g,"/").replace(/\.[jt]sx?$/,"");const i=n.startsWith("@")?n.split("/").slice(0,2).join("/"):n.split("/")[0],t=this.workspacePackages.get(i);return t?t.replace(/\.[jt]sx?$/,""):null}async buildWorkspaceMap(n){const o=new Map,i=["packages","functions","libs","apps","cdk"];for(const t of i){const e=u(n,t);try{const r=await w(e,{withFileTypes:!0});for(const s of r)if(!(!s.isDirectory()||I.has(s.name)))try{const c=u(e,s.name,"package.json"),g=JSON.parse(await j(c,"utf-8"));if(g.name){const l=g.main??g.exports?.["."]??"src/index.ts",p=v(n,R(e,s.name,l)).replace(/\\/g,"/");o.set(g.name,p)}}catch{}}catch{}}return o}formatMarkdown(n,o,i,t){const e=[];e.push(`## Dependencies: ${i}
2
+ `);const r=Object.entries(n).sort((l,p)=>p[1].count-l[1].count),s=r.length,c=r.reduce((l,[,p])=>l+p.count,0),g=r.slice(0,5).map(([l])=>l);e.push(`**${s} external packages**, **${c} total imports**, **${Object.keys(o).length} files** with internal imports.
3
+ `),g.length>0&&e.push(`**Top dependencies**: ${g.join(", ")}
4
+ `),e.push(`### External Dependencies
5
+ `),e.push("| Package | Imports | Used By |"),e.push("|---------|---------|---------|");for(const[l,p]of r)e.push(`| ${l} | ${p.count} | ${p.usedBy.length} ${p.usedBy.length===1?"file":"files"} |`);if(t&&Object.keys(t).length>0){const l=Object.keys(t).length,p=Object.keys(o).filter(f=>!y(f)&&!t[f.replace(/\\.[jt]sx?$/,"")]);e.push(`
6
+ ### Test Coverage Summary
7
+ `),e.push(`**${l} source modules** with test coverage.`),p.length>0&&e.push(`**${p.length} source files** with no detected test coverage.`);const d=Object.entries(t).sort((f,h)=>h[1].length-f[1].length);e.push(`
8
+ **Most-tested modules:**
9
+ `);for(const[f,h]of d.slice(0,10)){const k=f.replace(/\/dist\/[^/]*$/,"/src/index").replace(/\.mjs$/,".ts");e.push(`- ${k} (${h.length} ${h.length===1?"test":"tests"})`)}}return e.join(`
10
+ `)}formatMermaid(n){const o=["graph LR"],i=t=>t.replace(/[^a-zA-Z0-9]/g,"_");for(const[t,e]of Object.entries(n).slice(0,40)){const r=i(t);for(const s of e){const c=i(s);o.push(` ${r}["${t}"] --> ${c}["${s}"]`)}}return o.join(`
11
+ `)}}export{W as DependencyAnalyzer};