devlensio 0.6.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/README.md +89 -31
  2. package/dist/extractors/detectLanguage.d.ts +2 -0
  3. package/dist/extractors/detectLanguage.js +33 -0
  4. package/dist/extractors/index.d.ts +6 -0
  5. package/dist/extractors/index.js +116 -0
  6. package/dist/extractors/runner.d.ts +7 -0
  7. package/dist/extractors/runner.js +194 -0
  8. package/dist/extractors/types.d.ts +37 -0
  9. package/dist/extractors/types.js +8 -0
  10. package/dist/graph/buildLookup.d.ts +1 -0
  11. package/dist/graph/buildLookup.js +2 -1
  12. package/dist/graph/edges/callEdges.js +19 -5
  13. package/dist/graph/edges/callEdges.test.d.ts +1 -0
  14. package/dist/graph/edges/callEdges.test.js +200 -0
  15. package/dist/graph/edges/importEdges.js +12 -0
  16. package/dist/graph/edges/inheritanceEdges.d.ts +3 -0
  17. package/dist/graph/edges/inheritanceEdges.js +73 -0
  18. package/dist/graph/edges/inheritanceEdges.test.d.ts +1 -0
  19. package/dist/graph/edges/inheritanceEdges.test.js +140 -0
  20. package/dist/graph/index.js +4 -0
  21. package/dist/parser/classes.test.d.ts +1 -0
  22. package/dist/parser/classes.test.js +360 -0
  23. package/dist/parser/extractors/classes.d.ts +5 -0
  24. package/dist/parser/extractors/classes.js +241 -0
  25. package/dist/parser/index.d.ts +2 -0
  26. package/dist/parser/index.js +7 -1
  27. package/dist/pipeline/index.d.ts +2 -1
  28. package/dist/pipeline/index.js +30 -42
  29. package/dist/scoring/index.js +8 -0
  30. package/dist/scoring/index.test.js +43 -0
  31. package/dist/scoring/nodeScorer.js +2 -0
  32. package/dist/scoring/pruneDisconnected.d.ts +8 -0
  33. package/dist/scoring/pruneDisconnected.js +66 -0
  34. package/dist/scoring/pruneDisconnected.test.d.ts +1 -0
  35. package/dist/scoring/pruneDisconnected.test.js +123 -0
  36. package/dist/summarizer/prompts.d.ts +1 -1
  37. package/dist/types.d.ts +5 -5
  38. package/extractors/go/bin/darwin-amd64/devlens_go_extractor +0 -0
  39. package/extractors/go/bin/darwin-arm64/devlens_go_extractor +0 -0
  40. package/extractors/go/bin/linux-amd64/devlens_go_extractor +0 -0
  41. package/extractors/go/bin/linux-arm64/devlens_go_extractor +0 -0
  42. package/extractors/go/bin/windows-amd64/devlens_go_extractor.exe +0 -0
  43. package/extractors/go/build.mjs +43 -0
  44. package/extractors/go/calls.go +289 -0
  45. package/extractors/go/contract.go +140 -0
  46. package/extractors/go/extractor.go +161 -0
  47. package/extractors/go/fingerprint.go +167 -0
  48. package/extractors/go/go.mod +3 -0
  49. package/extractors/go/imports.go +90 -0
  50. package/extractors/go/inheritance.go +138 -0
  51. package/extractors/go/lookup.go +106 -0
  52. package/extractors/go/main.go +57 -0
  53. package/extractors/go/nodes.go +177 -0
  54. package/extractors/go/orm_edges.go +277 -0
  55. package/extractors/go/parser.go +469 -0
  56. package/extractors/go/routes.go +521 -0
  57. package/extractors/go/tests.go +45 -0
  58. package/extractors/go/thirdparty.go +138 -0
  59. package/extractors/go/typeload.go +178 -0
  60. package/extractors/go/walker.go +67 -0
  61. package/extractors/java/build.mjs +90 -0
  62. package/extractors/java/devlens_java_extractor.jar +0 -0
  63. package/extractors/java/src/devlens/extractor/Contract.java +171 -0
  64. package/extractors/java/src/devlens/extractor/Extractor.java +262 -0
  65. package/extractors/java/src/devlens/extractor/ExtractorResult.java +12 -0
  66. package/extractors/java/src/devlens/extractor/Fingerprint.java +239 -0
  67. package/extractors/java/src/devlens/extractor/LookupMaps.java +123 -0
  68. package/extractors/java/src/devlens/extractor/Main.java +66 -0
  69. package/extractors/java/src/devlens/extractor/Parser.java +522 -0
  70. package/extractors/java/src/devlens/extractor/SourceWalker.java +82 -0
  71. package/extractors/java/src/devlens/extractor/ThirdParty.java +141 -0
  72. package/extractors/java/src/devlens/extractor/TypeSolverFactory.java +43 -0
  73. package/extractors/java/src/devlens/extractor/edges/Calls.java +266 -0
  74. package/extractors/java/src/devlens/extractor/edges/Enrich.java +54 -0
  75. package/extractors/java/src/devlens/extractor/edges/Imports.java +154 -0
  76. package/extractors/java/src/devlens/extractor/edges/Inheritance.java +79 -0
  77. package/extractors/java/src/devlens/extractor/edges/OrmEdges.java +209 -0
  78. package/extractors/java/src/devlens/extractor/edges/Routes.java +143 -0
  79. package/extractors/java/src/devlens/extractor/edges/Tests.java +53 -0
  80. package/extractors/python/devlens_extractors_python/__init__.py +3 -0
  81. package/extractors/python/devlens_extractors_python/__main__.py +33 -0
  82. package/extractors/python/devlens_extractors_python/contract.py +94 -0
  83. package/extractors/python/devlens_extractors_python/edges/__init__.py +28 -0
  84. package/extractors/python/devlens_extractors_python/edges/calls.py +112 -0
  85. package/extractors/python/devlens_extractors_python/edges/enrich.py +44 -0
  86. package/extractors/python/devlens_extractors_python/edges/imports.py +155 -0
  87. package/extractors/python/devlens_extractors_python/edges/inheritance.py +97 -0
  88. package/extractors/python/devlens_extractors_python/edges/orm_edges.py +225 -0
  89. package/extractors/python/devlens_extractors_python/edges/routes/__init__.py +28 -0
  90. package/extractors/python/devlens_extractors_python/edges/routes/common.py +79 -0
  91. package/extractors/python/devlens_extractors_python/edges/routes/decorators.py +212 -0
  92. package/extractors/python/devlens_extractors_python/edges/routes/django_urls.py +213 -0
  93. package/extractors/python/devlens_extractors_python/edges/routes/drf.py +128 -0
  94. package/extractors/python/devlens_extractors_python/edges/tests.py +53 -0
  95. package/extractors/python/devlens_extractors_python/extractor.py +107 -0
  96. package/extractors/python/devlens_extractors_python/fingerprint.py +201 -0
  97. package/extractors/python/devlens_extractors_python/lookup.py +72 -0
  98. package/extractors/python/devlens_extractors_python/parser/__init__.py +72 -0
  99. package/extractors/python/devlens_extractors_python/parser/classes.py +109 -0
  100. package/extractors/python/devlens_extractors_python/parser/functions.py +163 -0
  101. package/extractors/python/devlens_extractors_python/parser/walker.py +30 -0
  102. package/extractors/python/devlens_extractors_python/third_party.py +103 -0
  103. package/extractors/python/pyproject.toml +16 -0
  104. package/extractors/python/setup.mjs +54 -0
  105. package/extractors/rust/Cargo.toml +29 -0
  106. package/extractors/rust/bin/darwin-amd64/devlens_rust_extractor +0 -0
  107. package/extractors/rust/bin/darwin-arm64/devlens_rust_extractor +0 -0
  108. package/extractors/rust/bin/linux-amd64/devlens_rust_extractor +0 -0
  109. package/extractors/rust/bin/linux-arm64/devlens_rust_extractor +0 -0
  110. package/extractors/rust/bin/windows-amd64/devlens_rust_extractor.exe +0 -0
  111. package/extractors/rust/build.mjs +85 -0
  112. package/extractors/rust/src/calls.rs +295 -0
  113. package/extractors/rust/src/contract.rs +236 -0
  114. package/extractors/rust/src/enrich.rs +107 -0
  115. package/extractors/rust/src/extractor.rs +199 -0
  116. package/extractors/rust/src/fingerprint.rs +238 -0
  117. package/extractors/rust/src/imports.rs +101 -0
  118. package/extractors/rust/src/inheritance.rs +233 -0
  119. package/extractors/rust/src/lookup.rs +226 -0
  120. package/extractors/rust/src/main.rs +53 -0
  121. package/extractors/rust/src/module_map.rs +131 -0
  122. package/extractors/rust/src/nodes.rs +174 -0
  123. package/extractors/rust/src/orm_edges.rs +125 -0
  124. package/extractors/rust/src/parser.rs +859 -0
  125. package/extractors/rust/src/routes.rs +1265 -0
  126. package/extractors/rust/src/tests.rs +109 -0
  127. package/extractors/rust/src/thirdparty.rs +114 -0
  128. package/extractors/rust/src/walker.rs +82 -0
  129. package/package.json +22 -4
@@ -0,0 +1,109 @@
1
+ // tests.rs — TESTS edges from TEST leaf nodes to production symbols.
2
+ //
3
+ // Rust test naming (documented cargo convention): `fn test_foo` / `fn foo_test`
4
+ // inside #[cfg(test)] mods or `tests/` integration files. Resolution ladder:
5
+ // 1. same-file name-match (`test_helper` → `helper` in the same file)
6
+ // 2. the file's use symbol maps (integration tests `use crate::foo::bar` →
7
+ // src symbol — the playbook's symbol-map refinement)
8
+ // 3. crate-wide name-match (closest_by_path)
9
+ // Sub-tests (t.Run equivalents — #[test] with nested logic) ride in
10
+ // metadata.testCases only (V1).
11
+
12
+ use crate::contract::{edge, CodeEdge};
13
+ use crate::extractor::{Options, ParsedRepo};
14
+ use crate::lookup::LookupMaps;
15
+ use crate::module_map::ModuleMap;
16
+
17
+ pub fn detect_tests(
18
+ repo: &ParsedRepo,
19
+ module_map: &ModuleMap,
20
+ lookup: &LookupMaps,
21
+ _opts: &Options,
22
+ ) -> Vec<CodeEdge> {
23
+ let mut edges = vec![];
24
+ for pf in &repo.files {
25
+ if !pf.is_test_file {
26
+ continue;
27
+ }
28
+ let from = format!("file::{}", pf.rel_path);
29
+ let base_module = crate::walker::module_path_for_file(&pf.rel_path);
30
+ for name in &pf.test_fns {
31
+ // 1. same-file name-match: test_helper → helper; helper_test → helper
32
+ let candidate = strip_test_affix(name);
33
+ if let Some(target) = same_file_symbol(&pf.rel_path, &candidate, lookup) {
34
+ edges.push(edge(&from, &target, "TESTS"));
35
+ continue;
36
+ }
37
+ // 2. use symbol maps: `use crate::models::user::User;` → user.rs
38
+ if let Some(target) = use_map_symbol(pf, &candidate, &base_module, module_map, lookup) {
39
+ edges.push(edge(&from, &target, "TESTS"));
40
+ continue;
41
+ }
42
+ // 3. crate-wide name-match
43
+ if let Some(target) = lookup.closest_by_path(&candidate, &pf.rel_path) {
44
+ edges.push(edge(&from, &target, "TESTS"));
45
+ }
46
+ }
47
+ }
48
+ edges
49
+ }
50
+
51
+ fn strip_test_affix(name: &str) -> String {
52
+ if let Some(rest) = name.strip_prefix("test_") {
53
+ return rest.to_string();
54
+ }
55
+ if let Some(rest) = name.strip_suffix("_test") {
56
+ return rest.to_string();
57
+ }
58
+ name.to_string()
59
+ }
60
+
61
+ fn same_file_symbol(rel: &str, name: &str, lookup: &LookupMaps) -> Option<String> {
62
+ let ids = lookup.nodes_by_name.get(name)?;
63
+ ids.iter()
64
+ .find(|id| id.starts_with(&format!("{}::", rel)))
65
+ .cloned()
66
+ }
67
+
68
+ fn use_map_symbol(
69
+ pf: &crate::parser::ParsedFile,
70
+ name: &str,
71
+ base_module: &str,
72
+ module_map: &ModuleMap,
73
+ lookup: &LookupMaps,
74
+ ) -> Option<String> {
75
+ // direct: `use crate::x::Name;` — alias Name → path ending in Name
76
+ if let Some(target) = lookup
77
+ .symbol_maps
78
+ .get(&pf.rel_path)
79
+ .and_then(|syms| syms.get(name))
80
+ {
81
+ if let Some((file, rest)) = module_map.resolve(target, base_module) {
82
+ if rest.is_empty() {
83
+ // the use named a module — edge to its file
84
+ return Some(format!("file::{}", file));
85
+ }
86
+ let file_module = crate::walker::module_path_for_file(&file);
87
+ let key = if file_module.is_empty() {
88
+ rest.clone()
89
+ } else {
90
+ format!("{}::{}", file_module, rest)
91
+ };
92
+ if let Some(id) = lookup.item_by_module_name.get(&key) {
93
+ return Some(id.clone());
94
+ }
95
+ return lookup.closest_by_path(&rest, &file);
96
+ }
97
+ }
98
+ // glob: `use crate::models::*;` — try each glob base
99
+ if let Some(globs) = lookup.glob_maps.get(&pf.rel_path) {
100
+ for g in globs {
101
+ if let Some((file, _)) = module_map.resolve(&format!("{}::{}", g, name), base_module) {
102
+ if let Some(id) = lookup.closest_by_path(name, &file) {
103
+ return Some(id);
104
+ }
105
+ }
106
+ }
107
+ }
108
+ None
109
+ }
@@ -0,0 +1,114 @@
1
+ // thirdparty.rs — [crate]/name third-party registry + gating (mirrors
2
+ // go/thirdparty.go and python/third_party.py).
3
+ //
4
+ // Gating rule (non-negotiable contract): options.includeThirdPartyLibs (both
5
+ // spellings) is THE gate — absent/empty → ZERO third-party nodes. All
6
+ // detectors (imports/calls/inheritance) share ONE registry.
7
+
8
+ use crate::contract::{base_metadata, code_node, CodeNode, NODE_THIRD_PARTY};
9
+ use crate::fingerprint::ParsedManifest;
10
+ use std::collections::{HashMap, HashSet};
11
+
12
+ /// std crates — first path segment of a use path in this set → skip tier
13
+ /// (no node, no edge).
14
+ pub const STD_CRATES: [&str; 3] = ["std", "core", "alloc"];
15
+
16
+ pub struct ThirdPartyRegistry {
17
+ /// gated (allowed) crate names — empty = gate everything
18
+ allowed: HashSet<String>,
19
+ /// crate name → node (id [crate]/name)
20
+ crate_nodes: HashMap<String, CodeNode>,
21
+ /// crate name::member → node (id [crate]/name::member)
22
+ member_nodes: HashMap<String, CodeNode>,
23
+ }
24
+
25
+ impl ThirdPartyRegistry {
26
+ pub fn new(allowed: &[String]) -> Self {
27
+ ThirdPartyRegistry {
28
+ allowed: allowed.iter().cloned().collect(),
29
+ crate_nodes: HashMap::new(),
30
+ member_nodes: HashMap::new(),
31
+ }
32
+ }
33
+
34
+ /// Strict membership gate (mirrors go's `permitted`): absent/empty
35
+ /// `allowed` → ZERO third-party nodes (the engine controls inclusion via
36
+ /// options.includeThirdPartyLibs). Hyphen/underscore normalization for
37
+ /// Cargo crate names (dep `actix-web` ↔ crate `actix_web`).
38
+ pub fn gated(&self, crate_name: &str) -> bool {
39
+ if self.allowed.contains(crate_name) {
40
+ return true;
41
+ }
42
+ let underscored = crate_name.replace('-', "_");
43
+ self.allowed
44
+ .iter()
45
+ .any(|a| a.replace('-', "_") == underscored)
46
+ }
47
+
48
+ /// crate node [crate]/name — None when not gated.
49
+ pub fn crate_node(&mut self, crate_name: &str) -> Option<CodeNode> {
50
+ if !self.gated(crate_name) {
51
+ return None;
52
+ }
53
+ if let Some(n) = self.crate_nodes.get(crate_name) {
54
+ return Some(n.clone());
55
+ }
56
+ let mut meta = base_metadata();
57
+ meta.insert("crateName".to_string(), crate_name.into());
58
+ let n = code_node(
59
+ &crate::lookup::third_party_id(crate_name),
60
+ "",
61
+ crate_name,
62
+ NODE_THIRD_PARTY,
63
+ 0,
64
+ 0,
65
+ "",
66
+ meta,
67
+ );
68
+ self.crate_nodes.insert(crate_name.to_string(), n.clone());
69
+ Some(n)
70
+ }
71
+
72
+ /// member node [crate]/name::member (e.g. serde::Serialize) — None when
73
+ /// not gated.
74
+ pub fn member_node(&mut self, crate_name: &str, member: &str) -> Option<CodeNode> {
75
+ if !self.gated(crate_name) {
76
+ return None;
77
+ }
78
+ let key = format!("{}::{}", crate_name, member);
79
+ if let Some(n) = self.member_nodes.get(&key) {
80
+ return Some(n.clone());
81
+ }
82
+ let mut meta = base_metadata();
83
+ meta.insert("crateName".to_string(), crate_name.into());
84
+ meta.insert("member".to_string(), member.into());
85
+ let id = format!("{}::{}", crate::lookup::third_party_id(crate_name), member);
86
+ let n = code_node(&id, "", &key, NODE_THIRD_PARTY, 0, 0, "", meta);
87
+ self.member_nodes.insert(key, n.clone());
88
+ Some(n)
89
+ }
90
+
91
+ /// All created nodes (collected last in the pipeline — like go).
92
+ pub fn all_nodes(&self) -> Vec<CodeNode> {
93
+ let mut out: Vec<CodeNode> = self.crate_nodes.values().cloned().collect();
94
+ out.extend(self.member_nodes.values().cloned());
95
+ out
96
+ }
97
+ }
98
+
99
+ /// First segment of a path (e.g. "serde" from "serde::Serialize").
100
+ pub fn first_segment(path: &str) -> &str {
101
+ path.split("::").next().unwrap_or(path)
102
+ }
103
+
104
+ /// True when the first segment is a std crate (skip tier).
105
+ pub fn is_std(first: &str) -> bool {
106
+ STD_CRATES.contains(&first)
107
+ }
108
+
109
+ /// True when the first segment names an external crate (in the manifest's
110
+ /// dependency set). NOT std, NOT a local module — decided by callers using
111
+ /// the module map.
112
+ pub fn is_external_crate(first: &str, mf: &ParsedManifest) -> bool {
113
+ mf.is_external_crate(first)
114
+ }
@@ -0,0 +1,82 @@
1
+ // walker.rs — .rs file discovery with IGNORE pruning at the frontier
2
+ // (mirrors go/walker.go). Deterministic: read_dir entries are sorted.
3
+
4
+ use std::path::Path;
5
+
6
+ const IGNORE_DIRS: [&str; 9] = [
7
+ ".git",
8
+ "node_modules",
9
+ "target", // cargo build dir (can be ~1GB)
10
+ "vendor", // vendored deps — never walked for nodes (like go)
11
+ "bin",
12
+ "dist",
13
+ "build",
14
+ ".cargo",
15
+ ".idea",
16
+ ];
17
+
18
+ /// Recursively collect .rs files under repo_path, pruning IGNORE dirs at the
19
+ /// frontier (never descend into them).
20
+ pub fn collect_rs_files(repo_path: &str) -> Vec<String> {
21
+ let mut out = vec![];
22
+ walk(repo_path, repo_path, &mut out);
23
+ out.sort();
24
+ out
25
+ }
26
+
27
+ fn walk(root: &str, dir: &str, out: &mut Vec<String>) {
28
+ let entries = match std::fs::read_dir(dir) {
29
+ Ok(e) => e,
30
+ Err(_) => return,
31
+ };
32
+ let mut names: Vec<String> = entries
33
+ .flatten()
34
+ .map(|e| e.file_name().to_string_lossy().to_string())
35
+ .collect();
36
+ names.sort();
37
+ for name in names {
38
+ if name.starts_with('.') && name != ".github" {
39
+ continue;
40
+ }
41
+ let full = format!("{}/{}", dir, name);
42
+ let rel = full
43
+ .strip_prefix(root)
44
+ .unwrap_or(&full)
45
+ .trim_start_matches('/');
46
+ let path = Path::new(&full);
47
+ if path.is_dir() {
48
+ if IGNORE_DIRS.contains(&name.as_str()) {
49
+ continue;
50
+ }
51
+ walk(root, &full, out);
52
+ } else if name.ends_with(".rs") {
53
+ out.push(rel.to_string());
54
+ }
55
+ }
56
+ }
57
+
58
+ /// Derive a file's module path within its crate: strip the crate's `src`
59
+ /// segment, drop `mod.rs`/`main.rs`/`lib.rs` suffixes, map / → ::.
60
+ /// src/models/user.rs → models::user
61
+ /// src/foo/mod.rs → foo
62
+ /// src/main.rs → (crate root) ""
63
+ /// crates/a/src/bar.rs → crates::a::bar (workspace member)
64
+ pub fn module_path_for_file(rel_path: &str) -> String {
65
+ let mut parts: Vec<&str> = rel_path.split('/').collect();
66
+ if let Some(idx) = parts.iter().position(|p| *p == "src") {
67
+ parts = parts[idx + 1..].to_vec();
68
+ }
69
+ let mut last = parts.pop().unwrap_or("");
70
+ if last == "mod.rs" || last == "main.rs" || last == "lib.rs" {
71
+ last = "";
72
+ } else {
73
+ last = last.strip_suffix(".rs").unwrap_or(last);
74
+ }
75
+ if last.is_empty() {
76
+ return parts.join("::");
77
+ }
78
+ if parts.is_empty() {
79
+ return last.to_string();
80
+ }
81
+ format!("{}::{}", parts.join("::"), last)
82
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "devlensio",
3
- "version": "0.6.2",
4
- "description": "Codebase intelligence engine for TypeScript/JavaScript/Reactjs/Nextjs repositories.",
3
+ "version": "1.0.0",
4
+ "description": "Codebase intelligence engine for TypeScript/JavaScript/React/Next.js, Python, Java, Go, and Rust repositories.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.js",
@@ -17,13 +17,31 @@
17
17
  "start": "bun src/server/index.ts",
18
18
  "build": "tsc --project tsconfig.build.json",
19
19
  "test": "bun test",
20
- "export-graph": "bun src/debug/exportGraph.ts"
20
+ "export-graph": "bun src/debug/exportGraph.ts",
21
+ "prepack": "node extractors/java/build.mjs && node extractors/go/build.mjs && node extractors/rust/build.mjs",
22
+ "postinstall": "node extractors/python/setup.mjs"
21
23
  },
22
24
  "keywords": ["codebase", "graph", "ast", "typescript", "javascript", "devtools"],
23
25
  "author": "",
24
26
  "license": "AGPL-3.0",
25
27
  "files": [
26
- "dist/**/*"
28
+ "dist/**/*",
29
+ "extractors/java/**",
30
+ "!extractors/java/classes",
31
+ "!extractors/java/lib",
32
+ "!extractors/java/sources.txt",
33
+ "extractors/python/**",
34
+ "!extractors/python/.venv",
35
+ "!extractors/python/*.egg-info",
36
+ "!extractors/python/**/__pycache__",
37
+ "extractors/go/**",
38
+ "!extractors/go/tests",
39
+ "extractors/rust/**",
40
+ "!extractors/rust/tests",
41
+ "!extractors/rust/target",
42
+ "!extractors/rust/Cargo.lock",
43
+ "!extractors/python/tests",
44
+ "!extractors/java/tests"
27
45
  ],
28
46
  "dependencies": {
29
47
  "@anthropic-ai/sdk": "^0.104.2",