@vpxa/kb 0.1.1 → 0.1.3

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 (138) hide show
  1. package/README.md +3 -3
  2. package/package.json +1 -1
  3. package/packages/analyzers/dist/blast-radius-analyzer.js +13 -114
  4. package/packages/analyzers/dist/dependency-analyzer.js +11 -425
  5. package/packages/analyzers/dist/diagram-generator.js +4 -86
  6. package/packages/analyzers/dist/entry-point-analyzer.js +5 -239
  7. package/packages/analyzers/dist/index.js +1 -23
  8. package/packages/analyzers/dist/knowledge-producer.js +24 -113
  9. package/packages/analyzers/dist/pattern-analyzer.js +5 -359
  10. package/packages/analyzers/dist/regex-call-graph.js +1 -428
  11. package/packages/analyzers/dist/structure-analyzer.js +4 -258
  12. package/packages/analyzers/dist/symbol-analyzer.js +13 -442
  13. package/packages/analyzers/dist/ts-call-graph.js +1 -160
  14. package/packages/analyzers/dist/types.js +0 -1
  15. package/packages/chunker/dist/call-graph-extractor.js +1 -90
  16. package/packages/chunker/dist/chunker-factory.js +1 -36
  17. package/packages/chunker/dist/chunker.interface.js +0 -1
  18. package/packages/chunker/dist/code-chunker.js +14 -134
  19. package/packages/chunker/dist/generic-chunker.js +5 -72
  20. package/packages/chunker/dist/index.js +1 -21
  21. package/packages/chunker/dist/markdown-chunker.js +7 -119
  22. package/packages/chunker/dist/treesitter-chunker.js +8 -234
  23. package/packages/cli/dist/commands/analyze.js +3 -112
  24. package/packages/cli/dist/commands/context-cmds.js +1 -155
  25. package/packages/cli/dist/commands/environment.js +2 -204
  26. package/packages/cli/dist/commands/execution.js +1 -137
  27. package/packages/cli/dist/commands/graph.js +7 -81
  28. package/packages/cli/dist/commands/init.js +9 -87
  29. package/packages/cli/dist/commands/knowledge.js +1 -139
  30. package/packages/cli/dist/commands/search.js +8 -267
  31. package/packages/cli/dist/commands/system.js +4 -241
  32. package/packages/cli/dist/commands/workspace.js +2 -388
  33. package/packages/cli/dist/context.js +1 -14
  34. package/packages/cli/dist/helpers.js +3 -458
  35. package/packages/cli/dist/index.d.ts +1 -1
  36. package/packages/cli/dist/index.js +3 -69
  37. package/packages/cli/dist/kb-init.js +1 -82
  38. package/packages/cli/dist/types.js +0 -1
  39. package/packages/core/dist/constants.js +1 -43
  40. package/packages/core/dist/content-detector.js +1 -79
  41. package/packages/core/dist/errors.js +1 -40
  42. package/packages/core/dist/index.js +1 -9
  43. package/packages/core/dist/logger.js +1 -34
  44. package/packages/core/dist/types.js +0 -1
  45. package/packages/embeddings/dist/embedder.interface.js +0 -1
  46. package/packages/embeddings/dist/index.js +1 -5
  47. package/packages/embeddings/dist/onnx-embedder.js +1 -82
  48. package/packages/indexer/dist/file-hasher.js +1 -13
  49. package/packages/indexer/dist/filesystem-crawler.js +1 -125
  50. package/packages/indexer/dist/graph-extractor.js +1 -111
  51. package/packages/indexer/dist/incremental-indexer.js +1 -278
  52. package/packages/indexer/dist/index.js +1 -14
  53. package/packages/server/dist/api.js +1 -9
  54. package/packages/server/dist/config.js +1 -75
  55. package/packages/server/dist/curated-manager.js +9 -356
  56. package/packages/server/dist/index.js +1 -134
  57. package/packages/server/dist/replay-interceptor.js +1 -38
  58. package/packages/server/dist/resources/resources.js +2 -40
  59. package/packages/server/dist/server.js +1 -247
  60. package/packages/server/dist/tools/analyze.tools.js +1 -288
  61. package/packages/server/dist/tools/forge.tools.js +11 -499
  62. package/packages/server/dist/tools/forget.tool.js +3 -39
  63. package/packages/server/dist/tools/graph.tool.js +5 -110
  64. package/packages/server/dist/tools/list.tool.js +5 -53
  65. package/packages/server/dist/tools/lookup.tool.js +8 -51
  66. package/packages/server/dist/tools/onboard.tool.js +2 -112
  67. package/packages/server/dist/tools/produce.tool.js +4 -74
  68. package/packages/server/dist/tools/read.tool.js +4 -47
  69. package/packages/server/dist/tools/reindex.tool.js +2 -70
  70. package/packages/server/dist/tools/remember.tool.js +3 -42
  71. package/packages/server/dist/tools/replay.tool.js +6 -88
  72. package/packages/server/dist/tools/search.tool.js +17 -327
  73. package/packages/server/dist/tools/status.tool.js +3 -68
  74. package/packages/server/dist/tools/toolkit.tools.js +20 -1673
  75. package/packages/server/dist/tools/update.tool.js +3 -39
  76. package/packages/server/dist/tools/utility.tools.js +19 -456
  77. package/packages/store/dist/graph-store.interface.js +0 -1
  78. package/packages/store/dist/index.js +1 -9
  79. package/packages/store/dist/lance-store.js +1 -258
  80. package/packages/store/dist/sqlite-graph-store.js +8 -309
  81. package/packages/store/dist/store-factory.js +1 -14
  82. package/packages/store/dist/store.interface.js +0 -1
  83. package/packages/tools/dist/batch.js +1 -45
  84. package/packages/tools/dist/changelog.js +2 -112
  85. package/packages/tools/dist/check.js +2 -59
  86. package/packages/tools/dist/checkpoint.js +2 -43
  87. package/packages/tools/dist/codemod.js +2 -69
  88. package/packages/tools/dist/compact.js +3 -60
  89. package/packages/tools/dist/data-transform.js +1 -124
  90. package/packages/tools/dist/dead-symbols.js +2 -71
  91. package/packages/tools/dist/delegate.js +3 -128
  92. package/packages/tools/dist/diff-parse.js +3 -153
  93. package/packages/tools/dist/digest.js +7 -242
  94. package/packages/tools/dist/encode.js +1 -46
  95. package/packages/tools/dist/env-info.js +1 -58
  96. package/packages/tools/dist/eval.js +3 -79
  97. package/packages/tools/dist/evidence-map.js +3 -203
  98. package/packages/tools/dist/file-summary.js +2 -106
  99. package/packages/tools/dist/file-walk.js +1 -75
  100. package/packages/tools/dist/find-examples.js +3 -48
  101. package/packages/tools/dist/find.js +1 -120
  102. package/packages/tools/dist/forge-classify.js +2 -319
  103. package/packages/tools/dist/forge-ground.js +1 -184
  104. package/packages/tools/dist/git-context.js +3 -46
  105. package/packages/tools/dist/graph-query.js +1 -194
  106. package/packages/tools/dist/health.js +1 -118
  107. package/packages/tools/dist/http-request.js +1 -58
  108. package/packages/tools/dist/index.js +1 -273
  109. package/packages/tools/dist/lane.js +7 -227
  110. package/packages/tools/dist/measure.js +2 -119
  111. package/packages/tools/dist/onboard.js +42 -1136
  112. package/packages/tools/dist/parse-output.js +2 -158
  113. package/packages/tools/dist/process-manager.js +1 -69
  114. package/packages/tools/dist/queue.js +2 -126
  115. package/packages/tools/dist/regex-test.js +1 -39
  116. package/packages/tools/dist/rename.js +2 -70
  117. package/packages/tools/dist/replay.js +6 -108
  118. package/packages/tools/dist/schema-validate.js +1 -141
  119. package/packages/tools/dist/scope-map.js +1 -72
  120. package/packages/tools/dist/snippet.js +1 -80
  121. package/packages/tools/dist/stash.js +2 -60
  122. package/packages/tools/dist/stratum-card.js +5 -238
  123. package/packages/tools/dist/symbol.js +3 -87
  124. package/packages/tools/dist/test-run.js +2 -55
  125. package/packages/tools/dist/text-utils.js +2 -31
  126. package/packages/tools/dist/time-utils.js +1 -135
  127. package/packages/tools/dist/trace.js +2 -114
  128. package/packages/tools/dist/truncation.js +10 -41
  129. package/packages/tools/dist/watch.js +1 -61
  130. package/packages/tools/dist/web-fetch.js +9 -244
  131. package/packages/tools/dist/web-search.js +1 -46
  132. package/packages/tools/dist/workset.js +2 -77
  133. package/packages/tui/dist/App.js +260 -52468
  134. package/packages/tui/dist/index.js +286 -54551
  135. package/packages/tui/dist/panels/CuratedPanel.js +211 -34291
  136. package/packages/tui/dist/panels/LogPanel.js +259 -51703
  137. package/packages/tui/dist/panels/SearchPanel.js +212 -34824
  138. package/packages/tui/dist/panels/StatusPanel.js +211 -34304
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # @anvpx/kb
1
+ # @vpxa/kb
2
2
 
3
3
  Local-first AI developer toolkit — knowledge base, code analysis, context management, and developer tools for LLM agents.
4
4
 
@@ -31,7 +31,7 @@ The KB auto-indexes configured source directories on startup, stores embeddings
31
31
 
32
32
  ```bash
33
33
  # Install
34
- pnpm add -D @anvpx/kb
34
+ pnpm add -D @vpxa/kb
35
35
 
36
36
  # Initialize in your project
37
37
  npx kb init
@@ -285,7 +285,7 @@ kb init [--force]
285
285
  ## Architecture
286
286
 
287
287
  ```text
288
- @anvpx/kb
288
+ @vpxa/kb
289
289
  ├── packages/
290
290
  │ ├── core/ — types, config, logger, constants
291
291
  │ ├── store/ — LanceDB vector store
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vpxa/kb",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
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};