devlensio 0.6.1 → 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 +36 -48
  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,178 @@
1
+ // typeload.go — go/types type-checking, fully self-contained.
2
+ //
3
+ // REVISED from the plan: go/packages is NOT stdlib (it lives in
4
+ // golang.org/x/tools) and would drag in a build-time network dep + a runtime
5
+ // dependency on the `go` command. Instead we type-check the module's own
6
+ // packages in-process:
7
+ //
8
+ // - local packages: checked in dependency order (Go import graphs are
9
+ // acyclic) from the already-parsed ASTs
10
+ // - std packages: importer.Default() (compiled-in export data; degrades to
11
+ // import errors on machines without a GOROOT — harmless, errors ignored)
12
+ // - external packages: import error → unresolved (degraded) — calls to them
13
+ // resolve via name-based [mod] lazy edges anyway
14
+ //
15
+ // All checker errors are swallowed (we're type-RESOLVING, not verifying).
16
+ // The result is per-file *types.Info: Selections/Uses give exact local call
17
+ // targets, and package scopes give exact type objects for IMPLEMENTS.
18
+ //
19
+ // The binary stays static and offline — no `go` command needed at runtime.
20
+
21
+ package main
22
+
23
+ import (
24
+ "go/ast"
25
+ "go/importer"
26
+ "go/token"
27
+ "go/types"
28
+ "sort"
29
+ )
30
+
31
+ type TypeInfo struct {
32
+ Mode string // "typed" — informational
33
+ Infos map[string]*types.Info // rel file path → type info for that file
34
+ Pkgs map[string]*types.Package // import path → checked package
35
+ Checks int
36
+ Failures int
37
+ }
38
+
39
+ // loadTypes — type-check every package in dependency order.
40
+ func loadTypes(pr *parsedRepo, addError errFn) *TypeInfo {
41
+ ti := &TypeInfo{
42
+ Mode: "typed",
43
+ Infos: map[string]*types.Info{},
44
+ Pkgs: map[string]*types.Package{},
45
+ }
46
+
47
+ // Check units: a package's own files (non-test + same-package test files)
48
+ // and — for *_test.go files declaring a DIFFERENT package name
49
+ // (package foo_test) — a synthetic package with import path <dir>_test.
50
+ type unit struct {
51
+ path string
52
+ files []*ParsedFile
53
+ fset *token.FileSet
54
+ }
55
+ units := []*unit{}
56
+ byPath := map[string]*unit{}
57
+ for _, pkg := range pr.packages {
58
+ own := &unit{path: pkg.ImportPath, fset: pkg.fset}
59
+ ext := &unit{path: pkg.ImportPath + "_test", fset: pkg.fset}
60
+ byPath[own.path] = own
61
+ byPath[ext.path] = ext
62
+ for _, pf := range pkg.Files {
63
+ if pf.IsTest && pf.PkgName != pkg.Name {
64
+ ext.files = append(ext.files, pf)
65
+ } else {
66
+ own.files = append(own.files, pf)
67
+ }
68
+ }
69
+ units = append(units, own, ext)
70
+ }
71
+
72
+ // internal deps per unit (imports that resolve to local packages)
73
+ depPaths := func(u *unit) []string {
74
+ seen := map[string]bool{}
75
+ var deps []string
76
+ for _, pf := range u.files {
77
+ for _, imp := range pf.Imports {
78
+ if _, ok := byPath[imp.Path]; ok && !seen[imp.Path] {
79
+ seen[imp.Path] = true
80
+ deps = append(deps, imp.Path)
81
+ }
82
+ }
83
+ }
84
+ sort.Strings(deps)
85
+ return deps
86
+ }
87
+
88
+ // importer: local packages in-memory; std via importer.Default(); external
89
+ // → error (degraded, fine).
90
+ stdImp := importer.Default()
91
+ imp := importerFunc(func(path string) (*types.Package, error) {
92
+ if p, ok := ti.Pkgs[path]; ok {
93
+ return p, nil
94
+ }
95
+ if _, local := byPath[path]; local {
96
+ return nil, errNotCheckedYet
97
+ }
98
+ return stdImp.Import(path)
99
+ })
100
+
101
+ // topological order over the internal import graph
102
+ done := map[string]bool{}
103
+ var order []*unit
104
+ for len(order) < len(units) {
105
+ progress := false
106
+ for _, u := range units {
107
+ if done[u.path] || len(u.files) == 0 {
108
+ done[u.path] = true
109
+ continue
110
+ }
111
+ ready := true
112
+ for _, d := range depPaths(u) {
113
+ if !done[d] {
114
+ ready = false
115
+ break
116
+ }
117
+ }
118
+ if ready {
119
+ done[u.path] = true
120
+ order = append(order, u)
121
+ progress = true
122
+ }
123
+ }
124
+ if !progress {
125
+ // cycle / unresolvable internal dep — check what we can
126
+ for _, u := range units {
127
+ if !done[u.path] && len(u.files) > 0 {
128
+ done[u.path] = true
129
+ order = append(order, u)
130
+ }
131
+ }
132
+ break
133
+ }
134
+ }
135
+
136
+ for _, u := range order {
137
+ if len(u.files) == 0 {
138
+ continue
139
+ }
140
+ files := make([]*ast.File, 0, len(u.files))
141
+ for _, pf := range u.files {
142
+ files = append(files, pf.AST)
143
+ }
144
+ info := &types.Info{
145
+ Types: map[ast.Expr]types.TypeAndValue{},
146
+ Uses: map[*ast.Ident]types.Object{},
147
+ Selections: map[*ast.SelectorExpr]*types.Selection{},
148
+ Defs: map[*ast.Ident]types.Object{},
149
+ Implicits: map[ast.Node]types.Object{},
150
+ }
151
+ conf := types.Config{
152
+ Importer: imp,
153
+ Error: func(error) {}, // swallow — resolution, not verification
154
+ }
155
+ pkg, _ := conf.Check(u.path, u.fset, files, info)
156
+ if pkg == nil {
157
+ ti.Failures++
158
+ continue
159
+ }
160
+ ti.Pkgs[u.path] = pkg
161
+ ti.Checks++
162
+ for _, pf := range u.files {
163
+ ti.Infos[pf.RelPath] = info
164
+ }
165
+ }
166
+ return ti
167
+ }
168
+
169
+ var errNotCheckedYet = &errNotChecked{}
170
+
171
+ type errNotChecked struct{}
172
+
173
+ func (*errNotChecked) Error() string { return "package not checked yet" }
174
+
175
+ // importerFunc adapts a function to types.Importer.
176
+ type importerFunc func(path string) (*types.Package, error)
177
+
178
+ func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }
@@ -0,0 +1,67 @@
1
+ // walker.go — file discovery: prune IGNORE dirs at the frontier, collect
2
+ // .go files, group them into packages (one package = one directory).
3
+
4
+ package main
5
+
6
+ import (
7
+ "io/fs"
8
+ "os"
9
+ "path/filepath"
10
+ "sort"
11
+ "strings"
12
+ )
13
+
14
+ // IGNORE_DIRS — pruned at the frontier (never descended into). vendor/ holds
15
+ // third-party copies (go list resolves types there; we never walk it for
16
+ // nodes). bin/dist/build/out/target are build outputs.
17
+ var ignoreDirs = map[string]bool{
18
+ ".git": true, "node_modules": true, "vendor": true, "bin": true,
19
+ "dist": true, "build": true, "out": true, "target": true,
20
+ "coverage": true, ".idea": true, ".vscode": true, ".cache": true,
21
+ }
22
+
23
+ // goFiles walks repoPath and returns relative paths of all .go files,
24
+ // pruned at the frontier (never descend into pruned dirs).
25
+ func goFiles(repoPath string) []string {
26
+ var files []string
27
+ filepath.WalkDir(repoPath, func(p string, d fs.DirEntry, err error) error {
28
+ if err != nil {
29
+ return nil // unreadable entry — skip silently
30
+ }
31
+ if d.IsDir() {
32
+ base := d.Name()
33
+ if p != repoPath && (ignoreDirs[base] || strings.HasPrefix(base, ".")) {
34
+ return filepath.SkipDir
35
+ }
36
+ return nil
37
+ }
38
+ if strings.HasSuffix(d.Name(), ".go") {
39
+ rel, _ := filepath.Rel(repoPath, p)
40
+ files = append(files, filepath.ToSlash(rel))
41
+ }
42
+ return nil
43
+ })
44
+ sort.Strings(files)
45
+ return files
46
+ }
47
+
48
+ // groupByDir → rel dir → []rel file paths (deterministic order).
49
+ func groupByDir(files []string) map[string][]string {
50
+ groups := map[string][]string{}
51
+ for _, f := range files {
52
+ dir := "."
53
+ if i := strings.LastIndex(f, "/"); i >= 0 {
54
+ dir = f[:i]
55
+ }
56
+ groups[dir] = append(groups[dir], f)
57
+ }
58
+ for _, list := range groups {
59
+ sort.Strings(list)
60
+ }
61
+ return groups
62
+ }
63
+
64
+ func dirExists(repoPath, rel string) bool {
65
+ info, err := os.Stat(filepath.Join(repoPath, rel))
66
+ return err == nil && info.IsDir()
67
+ }
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+ // DevLens Java extractor — fat-jar build (cross-platform: Linux/macOS/Windows).
3
+ //
4
+ // Node is guaranteed on every machine that installs an npm package, so this
5
+ // replaces the bash build.sh: downloads JavaParser + Gson from Maven Central,
6
+ // compiles src/, and merges EVERYTHING into one self-contained jar so the
7
+ // engine can run `java -jar devlens_java_extractor.jar` with no classpath.
8
+ //
9
+ // Usage: node build.mjs (idempotent — run from this directory)
10
+
11
+ import { execFileSync } from "node:child_process";
12
+ import { existsSync, mkdirSync, readdirSync, rmSync, writeFileSync, readFileSync } from "node:fs";
13
+ import { dirname, join } from "node:path";
14
+ import { fileURLToPath } from "node:url";
15
+
16
+ const HERE = dirname(fileURLToPath(import.meta.url));
17
+ const VERSIONS = {
18
+ "javaparser-core.jar": "com/github/javaparser/javaparser-core/3.28.2/javaparser-core-3.28.2.jar",
19
+ "javaparser-symbol-solver-core.jar": "com/github/javaparser/javaparser-symbol-solver-core/3.28.2/javaparser-symbol-solver-core-3.28.2.jar",
20
+ "gson.jar": "com/google/code/gson/gson/2.14.0/gson-2.14.0.jar",
21
+ "javassist.jar": "org/javassist/javassist/3.32.0-GA/javassist-3.32.0-GA.jar",
22
+ "guava.jar": "com/google/guava/guava/33.6.0-jre/guava-33.6.0-jre.jar",
23
+ "checker-qual.jar": "org/checkerframework/checker-qual/4.2.2/checker-qual-4.2.2.jar",
24
+ };
25
+ const MAVEN = "https://repo1.maven.org/maven2/";
26
+
27
+ const libDir = join(HERE, "lib");
28
+ const classesDir = join(HERE, "classes");
29
+ mkdirSync(libDir, { recursive: true });
30
+ mkdirSync(classesDir, { recursive: true });
31
+
32
+ function run(cmd, args, opts = {}) {
33
+ execFileSync(cmd, args, { stdio: "inherit", ...opts });
34
+ }
35
+
36
+ // ── 1. fetch dependencies (cache on disk; idempotent) ────────────────
37
+ console.log("==> Fetching dependencies (Maven Central)");
38
+ for (const [jar, path] of Object.entries(VERSIONS)) {
39
+ const dest = join(libDir, jar);
40
+ if (existsSync(dest) && readFileSync(dest).length > 0) {
41
+ console.log(` cached ${jar}`);
42
+ continue;
43
+ }
44
+ console.log(` downloading ${jar}`);
45
+ const res = await fetch(MAVEN + path);
46
+ if (!res.ok) throw new Error(`download failed (${res.status}): ${MAVEN + path}`);
47
+ writeFileSync(dest, Buffer.from(await res.arrayBuffer()));
48
+ }
49
+
50
+ // ── 2. compile ───────────────────────────────────────────────────────
51
+ console.log("==> Compiling");
52
+ const sources = [];
53
+ function collect(dir) {
54
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
55
+ const p = join(dir, entry.name);
56
+ if (entry.isDirectory()) collect(p);
57
+ else if (entry.name.endsWith(".java")) sources.push(p);
58
+ }
59
+ }
60
+ collect(join(HERE, "src"));
61
+ run(process.env.JAVAC || "javac",
62
+ ["--release", "17", "-cp", join(libDir, "*"), "-d", classesDir, ...sources]);
63
+
64
+ // ── 3. assemble fat jar ──────────────────────────────────────────────
65
+ console.log("==> Assembling fat jar");
66
+ const fatDir = join(HERE, "fat");
67
+ rmSync(fatDir, { recursive: true, force: true });
68
+ mkdirSync(fatDir, { recursive: true });
69
+ for (const jar of readdirSync(libDir).filter((f) => f.endsWith(".jar"))) {
70
+ run("jar", ["xf", join(libDir, jar)], { cwd: fatDir });
71
+ }
72
+ // strip other jars' META-INF signatures + multi-release leftovers are fine
73
+ for (const sig of [".SF", ".DSA", ".RSA"]) {
74
+ for (const f of readdirSync(join(fatDir, "META-INF"))) {
75
+ if (f.endsWith(sig)) rmSync(join(fatDir, "META-INF", f));
76
+ }
77
+ }
78
+ for (const entry of readdirSync(classesDir)) {
79
+ run(process.platform === "win32" ? "xcopy" : "cp",
80
+ process.platform === "win32"
81
+ ? [join(classesDir, entry), join(fatDir, entry), "/E", "/Y"]
82
+ : ["-r", join(classesDir, entry), fatDir],
83
+ process.platform === "win32" ? { shell: true } : {});
84
+ }
85
+ run("jar", ["cfe", join(HERE, "devlens_java_extractor.jar"),
86
+ "devlens.extractor.Main", "-C", fatDir, "."]);
87
+ rmSync(fatDir, { recursive: true, force: true });
88
+
89
+ const size = (readFileSync(join(HERE, "devlens_java_extractor.jar")).length / 1e6).toFixed(1);
90
+ console.log(`==> Done: ${size}M devlens_java_extractor.jar`);
@@ -0,0 +1,171 @@
1
+ package devlens.extractor;
2
+
3
+ import java.nio.charset.StandardCharsets;
4
+ import java.security.MessageDigest;
5
+ import java.util.ArrayList;
6
+ import java.util.LinkedHashMap;
7
+ import java.util.List;
8
+ import java.util.Map;
9
+
10
+ /**
11
+ * Contract — the exact JSON shape DevLens Engine expects (mirrors
12
+ * contract.py from the Python extractor). Every key here is camelCase to
13
+ * match the TypeScript interfaces (CodeNode / CodeEdge / BackendRouteNode).
14
+ * This class is the single source of truth for node/edge shapes; other
15
+ * modules never spell keys by hand.
16
+ */
17
+ public final class Contract {
18
+
19
+ private Contract() {}
20
+
21
+ /** SHA-256 of the raw code, first 16 hex chars (mirrors contract.py). */
22
+ public static String codeHash(String rawCode) {
23
+ try {
24
+ MessageDigest md = MessageDigest.getInstance("SHA-256");
25
+ byte[] digest = md.digest(rawCode.getBytes(StandardCharsets.UTF_8));
26
+ StringBuilder sb = new StringBuilder(16);
27
+ for (int i = 0; i < 8; i++) {
28
+ sb.append(String.format("%02x", digest[i]));
29
+ }
30
+ return sb.toString();
31
+ } catch (Exception e) {
32
+ return "";
33
+ }
34
+ }
35
+
36
+ /** FILE / TEST node — id format: file::rel/path/File.java. */
37
+ public static Map<String, Object> fileNode(String relPath, int endLine,
38
+ String nodeType, String language) {
39
+ Map<String, Object> metadata = new LinkedHashMap<>();
40
+ metadata.put("nodeCount", 0);
41
+ metadata.put("childNodeIds", new ArrayList<String>());
42
+ metadata.put("language", language);
43
+
44
+ Map<String, Object> node = new LinkedHashMap<>();
45
+ node.put("id", "file::" + relPath);
46
+ node.put("name", relPath.substring(relPath.lastIndexOf('/') + 1));
47
+ node.put("type", nodeType);
48
+ node.put("filePath", relPath);
49
+ node.put("startLine", 1);
50
+ node.put("endLine", endLine);
51
+ node.put("parentFile", "file::" + relPath);
52
+ node.put("metadata", metadata);
53
+ return node;
54
+ }
55
+
56
+ /**
57
+ * CLASS / INTERFACE / ENUM / METHOD node — id format:
58
+ * rel/path/File.java::TypeName or rel/path/File.java::Type.method
59
+ */
60
+ public static Map<String, Object> codeNode(String relPath, String name, String nodeType,
61
+ int startLine, int endLine, String rawCode,
62
+ Map<String, Object> metadata, String language) {
63
+ Map<String, Object> node = new LinkedHashMap<>();
64
+ node.put("id", relPath + "::" + name);
65
+ node.put("name", name);
66
+ node.put("type", nodeType);
67
+ node.put("filePath", relPath);
68
+ node.put("startLine", startLine);
69
+ node.put("endLine", endLine);
70
+ node.put("rawCode", rawCode);
71
+ node.put("codeHash", codeHash(rawCode));
72
+ node.put("parentFile", "file::" + relPath);
73
+
74
+ Map<String, Object> meta = new LinkedHashMap<>();
75
+ meta.put("language", language);
76
+ if (metadata != null) {
77
+ meta.putAll(metadata);
78
+ }
79
+ node.put("metadata", meta);
80
+ return node;
81
+ }
82
+
83
+ /** THIRD_PARTY node — id format: [mvn]/prefix or [mvn]/prefix::Member. */
84
+ public static Map<String, Object> thirdPartyNode(String id, String name,
85
+ Map<String, Object> metadata) {
86
+ Map<String, Object> node = new LinkedHashMap<>();
87
+ node.put("id", id);
88
+ node.put("name", name);
89
+ node.put("type", "THIRD_PARTY");
90
+ node.put("filePath", id);
91
+ node.put("startLine", 0);
92
+ node.put("endLine", 0);
93
+ node.put("metadata", metadata == null ? new LinkedHashMap<String, Object>() : metadata);
94
+ return node;
95
+ }
96
+
97
+ /** ROUTE code node — id format: rel::METHOD /path (mirrors python common.py). */
98
+ public static Map<String, Object> routeNode(String relPath, String httpMethod,
99
+ String urlPath, boolean isDynamic,
100
+ List<String> params, String framework,
101
+ String handlerName) {
102
+ Map<String, Object> metadata = new LinkedHashMap<>();
103
+ metadata.put("urlPath", urlPath);
104
+ metadata.put("httpMethod", httpMethod);
105
+ metadata.put("isDynamic", isDynamic);
106
+ metadata.put("params", params == null ? new ArrayList<String>() : params);
107
+ metadata.put("framework", framework);
108
+ metadata.put("handlerName", handlerName);
109
+ metadata.put("routeKind", "backend");
110
+
111
+ Map<String, Object> node = new LinkedHashMap<>();
112
+ node.put("id", relPath + "::" + httpMethod + " " + urlPath);
113
+ node.put("name", httpMethod + " " + urlPath);
114
+ node.put("type", "ROUTE");
115
+ node.put("filePath", relPath);
116
+ node.put("startLine", 0);
117
+ node.put("endLine", 0);
118
+ node.put("parentFile", "file::" + relPath);
119
+ node.put("metadata", metadata);
120
+ return node;
121
+ }
122
+
123
+ /** BackendRouteNode dict for the `routes` array (engine BackendRouteNode). */
124
+ public static Map<String, Object> backendRoute(String urlPath, String filePath,
125
+ String httpMethod, String framework,
126
+ boolean isDynamic, List<String> params,
127
+ String handlerName, String nodeId) {
128
+ Map<String, Object> route = new LinkedHashMap<>();
129
+ route.put("type", "BACKEND_ROUTE");
130
+ route.put("urlPath", urlPath);
131
+ route.put("filePath", filePath);
132
+ route.put("httpMethod", httpMethod);
133
+ route.put("framework", framework);
134
+ route.put("isDynamic", isDynamic);
135
+ if (params != null && !params.isEmpty()) {
136
+ route.put("params", params);
137
+ }
138
+ if (handlerName != null) {
139
+ route.put("handlerName", handlerName);
140
+ }
141
+ if (nodeId != null) {
142
+ route.put("nodeId", nodeId);
143
+ }
144
+ return route;
145
+ }
146
+
147
+ /** Edge — {type, from, to, metadata?} (engine CodeEdge). */
148
+ public static Map<String, Object> edge(String type, String from, String to,
149
+ Map<String, Object> metadata) {
150
+ Map<String, Object> e = new LinkedHashMap<>();
151
+ e.put("type", type);
152
+ e.put("from", from);
153
+ e.put("to", to);
154
+ if (metadata != null && !metadata.isEmpty()) {
155
+ e.put("metadata", metadata);
156
+ }
157
+ return e;
158
+ }
159
+
160
+ /** Deterministic edge-key for dedupe: from|type|to. */
161
+ public static String edgeKey(Map<String, Object> edge) {
162
+ return edge.get("from") + "|" + edge.get("type") + "|" + edge.get("to");
163
+ }
164
+
165
+ public static Map<String, Object> error(String file, String message) {
166
+ Map<String, Object> err = new LinkedHashMap<>();
167
+ err.put("file", file);
168
+ err.put("error", message);
169
+ return err;
170
+ }
171
+ }