devlensio 0.2.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 (136) hide show
  1. package/LICENSE +674 -0
  2. package/dist/clustering/index.d.ts +27 -0
  3. package/dist/clustering/index.js +149 -0
  4. package/dist/config/index.d.ts +10 -0
  5. package/dist/config/index.js +78 -0
  6. package/dist/config/providers/file.d.ts +19 -0
  7. package/dist/config/providers/file.js +215 -0
  8. package/dist/config/providers/request.d.ts +2 -0
  9. package/dist/config/providers/request.js +72 -0
  10. package/dist/config/types.d.ts +46 -0
  11. package/dist/config/types.js +81 -0
  12. package/dist/config/writer.d.ts +29 -0
  13. package/dist/config/writer.js +103 -0
  14. package/dist/filesystem/appRouter.d.ts +2 -0
  15. package/dist/filesystem/appRouter.js +126 -0
  16. package/dist/filesystem/backendRoutes.d.ts +2 -0
  17. package/dist/filesystem/backendRoutes.js +161 -0
  18. package/dist/filesystem/index.d.ts +2 -0
  19. package/dist/filesystem/index.js +28 -0
  20. package/dist/filesystem/index.test.d.ts +1 -0
  21. package/dist/filesystem/index.test.js +178 -0
  22. package/dist/filesystem/pagesRouter.d.ts +2 -0
  23. package/dist/filesystem/pagesRouter.js +109 -0
  24. package/dist/fingerprint/detectors.d.ts +8 -0
  25. package/dist/fingerprint/detectors.js +174 -0
  26. package/dist/fingerprint/index.d.ts +2 -0
  27. package/dist/fingerprint/index.js +41 -0
  28. package/dist/fingerprint/index.test.d.ts +1 -0
  29. package/dist/fingerprint/index.test.js +148 -0
  30. package/dist/graph/buildLookup.d.ts +10 -0
  31. package/dist/graph/buildLookup.js +32 -0
  32. package/dist/graph/edges/callEdges.d.ts +7 -0
  33. package/dist/graph/edges/callEdges.js +145 -0
  34. package/dist/graph/edges/eventEdges.d.ts +7 -0
  35. package/dist/graph/edges/eventEdges.js +203 -0
  36. package/dist/graph/edges/guardEdges.d.ts +3 -0
  37. package/dist/graph/edges/guardEdges.js +232 -0
  38. package/dist/graph/edges/hookEdges.d.ts +3 -0
  39. package/dist/graph/edges/hookEdges.js +54 -0
  40. package/dist/graph/edges/importEdges.d.ts +8 -0
  41. package/dist/graph/edges/importEdges.js +224 -0
  42. package/dist/graph/edges/propEdges.d.ts +3 -0
  43. package/dist/graph/edges/propEdges.js +142 -0
  44. package/dist/graph/edges/routeEdge.d.ts +3 -0
  45. package/dist/graph/edges/routeEdge.js +124 -0
  46. package/dist/graph/edges/stateEdges.d.ts +3 -0
  47. package/dist/graph/edges/stateEdges.js +206 -0
  48. package/dist/graph/edges/testEdges.d.ts +3 -0
  49. package/dist/graph/edges/testEdges.js +143 -0
  50. package/dist/graph/edges/utils.d.ts +2 -0
  51. package/dist/graph/edges/utils.js +25 -0
  52. package/dist/graph/index.d.ts +6 -0
  53. package/dist/graph/index.js +65 -0
  54. package/dist/graph/index.test.d.ts +1 -0
  55. package/dist/graph/index.test.js +542 -0
  56. package/dist/graph/thirdPartyLibs.d.ts +8 -0
  57. package/dist/graph/thirdPartyLibs.js +162 -0
  58. package/dist/index.d.ts +15 -0
  59. package/dist/index.js +15 -0
  60. package/dist/jobs/index.d.ts +5 -0
  61. package/dist/jobs/index.js +11 -0
  62. package/dist/jobs/queue/interface.d.ts +13 -0
  63. package/dist/jobs/queue/interface.js +1 -0
  64. package/dist/jobs/queue/memory.d.ts +24 -0
  65. package/dist/jobs/queue/memory.js +291 -0
  66. package/dist/jobs/runner.d.ts +3 -0
  67. package/dist/jobs/runner.js +136 -0
  68. package/dist/jobs/types.d.ts +112 -0
  69. package/dist/jobs/types.js +33 -0
  70. package/dist/parser/directives.d.ts +4 -0
  71. package/dist/parser/directives.js +31 -0
  72. package/dist/parser/extractors/components.d.ts +5 -0
  73. package/dist/parser/extractors/components.js +240 -0
  74. package/dist/parser/extractors/functions.d.ts +4 -0
  75. package/dist/parser/extractors/functions.js +240 -0
  76. package/dist/parser/extractors/hooks.d.ts +4 -0
  77. package/dist/parser/extractors/hooks.js +128 -0
  78. package/dist/parser/extractors/stores.d.ts +3 -0
  79. package/dist/parser/extractors/stores.js +181 -0
  80. package/dist/parser/index.d.ts +14 -0
  81. package/dist/parser/index.js +168 -0
  82. package/dist/parser/index.test.d.ts +1 -0
  83. package/dist/parser/index.test.js +319 -0
  84. package/dist/parser/typeUtils.d.ts +9 -0
  85. package/dist/parser/typeUtils.js +46 -0
  86. package/dist/pipeline/index.d.ts +50 -0
  87. package/dist/pipeline/index.js +249 -0
  88. package/dist/scoring/connectionCounter.d.ts +28 -0
  89. package/dist/scoring/connectionCounter.js +134 -0
  90. package/dist/scoring/fileScorer.d.ts +2 -0
  91. package/dist/scoring/fileScorer.js +44 -0
  92. package/dist/scoring/index.d.ts +22 -0
  93. package/dist/scoring/index.js +130 -0
  94. package/dist/scoring/index.test.d.ts +1 -0
  95. package/dist/scoring/index.test.js +453 -0
  96. package/dist/scoring/nodeScorer.d.ts +3 -0
  97. package/dist/scoring/nodeScorer.js +108 -0
  98. package/dist/scoring/noiseFilter.d.ts +18 -0
  99. package/dist/scoring/noiseFilter.js +92 -0
  100. package/dist/storage/fileStorage.d.ts +117 -0
  101. package/dist/storage/fileStorage.js +616 -0
  102. package/dist/storage/index.d.ts +4 -0
  103. package/dist/storage/index.js +2 -0
  104. package/dist/storage/interface.d.ts +27 -0
  105. package/dist/storage/interface.js +1 -0
  106. package/dist/summarizer/checkpoint.d.ts +15 -0
  107. package/dist/summarizer/checkpoint.js +110 -0
  108. package/dist/summarizer/index.d.ts +2 -0
  109. package/dist/summarizer/index.js +281 -0
  110. package/dist/summarizer/mapreduce.d.ts +4 -0
  111. package/dist/summarizer/mapreduce.js +87 -0
  112. package/dist/summarizer/prompts.d.ts +22 -0
  113. package/dist/summarizer/prompts.js +205 -0
  114. package/dist/summarizer/providers/anthropic.d.ts +9 -0
  115. package/dist/summarizer/providers/anthropic.js +78 -0
  116. package/dist/summarizer/providers/gemini.d.ts +9 -0
  117. package/dist/summarizer/providers/gemini.js +79 -0
  118. package/dist/summarizer/providers/index.d.ts +3 -0
  119. package/dist/summarizer/providers/index.js +43 -0
  120. package/dist/summarizer/providers/ollama.d.ts +9 -0
  121. package/dist/summarizer/providers/ollama.js +23 -0
  122. package/dist/summarizer/providers/openRouter.d.ts +9 -0
  123. package/dist/summarizer/providers/openRouter.js +19 -0
  124. package/dist/summarizer/providers/openai.d.ts +9 -0
  125. package/dist/summarizer/providers/openai.js +72 -0
  126. package/dist/summarizer/providers/types.d.ts +32 -0
  127. package/dist/summarizer/providers/types.js +1 -0
  128. package/dist/summarizer/retry.d.ts +7 -0
  129. package/dist/summarizer/retry.js +51 -0
  130. package/dist/summarizer/topological.d.ts +3 -0
  131. package/dist/summarizer/topological.js +105 -0
  132. package/dist/summarizer/types.d.ts +57 -0
  133. package/dist/summarizer/types.js +17 -0
  134. package/dist/types.d.ts +78 -0
  135. package/dist/types.js +1 -0
  136. package/package.json +48 -0
@@ -0,0 +1,206 @@
1
+ const REDUX_READ_HOOKS = ["useSelector", "useSelectorShallowEqual"]; // Redux hooks that indicate reads
2
+ const REDUX_WRITE_HOOKS = ["useDispatch"]; // Redux hooks that indicate writes
3
+ // Recoil hooks and what they mean
4
+ const RECOIL_READ_HOOKS = ["useRecoilValue", "useRecoilValueLoadable"];
5
+ const RECOIL_WRITE_HOOKS = ["useSetRecoilState", "useResetRecoilState"];
6
+ const RECOIL_BOTH_HOOKS = ["useRecoilState", "useRecoilStateLoadable"];
7
+ // Jotai hooks and what they mean
8
+ const JOTAI_READ_HOOKS = ["useAtomValue"];
9
+ const JOTAI_WRITE_HOOKS = ["useSetAtom"];
10
+ const JOTAI_BOTH_HOOKS = ["useAtom"];
11
+ // Context hooks
12
+ const CONTEXT_HOOKS = ["useContext"];
13
+ // Main function to detect state edges based on hooks used in components and hooks
14
+ export function detectStateEdges(nodes, lookupMp) {
15
+ const edges = [];
16
+ // Build a fast name → store node map from storeNodes
17
+ // This is separate from nodesByName because we only want
18
+ // to match against STATE_STORE nodes not all nodes
19
+ const storesByName = new Map();
20
+ for (const store of lookupMp.storeNodes) {
21
+ storesByName.set(store.name, store);
22
+ }
23
+ // Separate Redux stores for useSelector/useDispatch
24
+ // which don't reference a store by name directly
25
+ const reduxStores = lookupMp.storeNodes.filter((n) => n.metadata.storeType === "redux");
26
+ for (const node of nodes) {
27
+ // Only components and hooks use state
28
+ if (node.type !== "COMPONENT" && node.type !== "HOOK")
29
+ continue;
30
+ // COMPONENT nodes store hook calls in metadata.hooks; HOOK nodes use metadata.dependencies
31
+ const hooks = (node.type === "HOOK" ? node.metadata.dependencies : node.metadata.hooks);
32
+ if (!hooks || hooks.length === 0)
33
+ continue;
34
+ for (const hookName of hooks) {
35
+ // ─── Zustand ──────────────────────────────────────────────────────────
36
+ // Zustand stores are used directly by their hook name
37
+ // e.g. useCartStore, useAuthStore
38
+ const zustandStore = storesByName.get(hookName);
39
+ if (zustandStore && zustandStore.metadata.storeType === "zustand") {
40
+ // Create both edges — can't distinguish read vs write without
41
+ // expensive type analysis
42
+ edges.push({
43
+ from: node.id,
44
+ to: zustandStore.id,
45
+ type: "READS_FROM",
46
+ metadata: { hookUsed: hookName, storeType: "zustand" },
47
+ });
48
+ edges.push({
49
+ from: node.id,
50
+ to: zustandStore.id,
51
+ type: "WRITES_TO",
52
+ metadata: { hookUsed: hookName, storeType: "zustand" },
53
+ });
54
+ continue;
55
+ }
56
+ // ─── Redux ────────────────────────────────────────────────────────────
57
+ // Redux uses useSelector (read) and useDispatch (write)
58
+ // Neither references a specific store by name so we connect
59
+ // to all Redux stores found in the codebase
60
+ if (REDUX_READ_HOOKS.includes(hookName)) {
61
+ for (const store of reduxStores) {
62
+ edges.push({
63
+ from: node.id,
64
+ to: store.id,
65
+ type: "READS_FROM",
66
+ metadata: { hookUsed: hookName, storeType: "redux" },
67
+ });
68
+ }
69
+ continue;
70
+ }
71
+ if (REDUX_WRITE_HOOKS.includes(hookName)) {
72
+ for (const store of reduxStores) {
73
+ edges.push({
74
+ from: node.id,
75
+ to: store.id,
76
+ type: "WRITES_TO",
77
+ metadata: { hookUsed: hookName, storeType: "redux" },
78
+ });
79
+ }
80
+ continue;
81
+ }
82
+ // ─── Context ──────────────────────────────────────────────────────────
83
+ // useContext(AuthContext) — the parser now extracts the argument name
84
+ // and stores it in metadata.contextRefs (e.g. ["AuthContext"]).
85
+ // We do a direct name lookup so the edge is always correct.
86
+ // useContext is read-only — no WRITES_TO edge here.
87
+ if (CONTEXT_HOOKS.includes(hookName)) {
88
+ const contextRefs = node.metadata.contextRefs;
89
+ if (!contextRefs || contextRefs.length === 0)
90
+ continue;
91
+ for (const ref of contextRefs) {
92
+ const contextStore = storesByName.get(ref);
93
+ if (contextStore && contextStore.metadata.storeType === "context") {
94
+ edges.push({
95
+ from: node.id,
96
+ to: contextStore.id,
97
+ type: "READS_FROM",
98
+ metadata: { hookUsed: hookName, storeType: "context" },
99
+ });
100
+ }
101
+ }
102
+ continue;
103
+ }
104
+ // ─── Recoil ───────────────────────────────────────────────────────────
105
+ if (RECOIL_READ_HOOKS.includes(hookName)) {
106
+ // Find recoil atoms referenced in this component
107
+ for (const ref of hooks) {
108
+ const atomStore = storesByName.get(ref);
109
+ if (atomStore && atomStore.metadata.storeType === "recoil") {
110
+ edges.push({
111
+ from: node.id,
112
+ to: atomStore.id,
113
+ type: "READS_FROM",
114
+ metadata: { hookUsed: hookName, storeType: "recoil" },
115
+ });
116
+ }
117
+ }
118
+ continue;
119
+ }
120
+ if (RECOIL_WRITE_HOOKS.includes(hookName)) {
121
+ for (const ref of hooks) {
122
+ const atomStore = storesByName.get(ref);
123
+ if (atomStore && atomStore.metadata.storeType === "recoil") {
124
+ edges.push({
125
+ from: node.id,
126
+ to: atomStore.id,
127
+ type: "WRITES_TO",
128
+ metadata: { hookUsed: hookName, storeType: "recoil" },
129
+ });
130
+ }
131
+ }
132
+ continue;
133
+ }
134
+ if (RECOIL_BOTH_HOOKS.includes(hookName)) {
135
+ for (const ref of hooks) {
136
+ const atomStore = storesByName.get(ref);
137
+ if (atomStore && atomStore.metadata.storeType === "recoil") {
138
+ edges.push({
139
+ from: node.id,
140
+ to: atomStore.id,
141
+ type: "READS_FROM",
142
+ metadata: { hookUsed: hookName, storeType: "recoil" },
143
+ });
144
+ edges.push({
145
+ from: node.id,
146
+ to: atomStore.id,
147
+ type: "WRITES_TO",
148
+ metadata: { hookUsed: hookName, storeType: "recoil" },
149
+ });
150
+ }
151
+ }
152
+ continue;
153
+ }
154
+ // ─── Jotai ────────────────────────────────────────────────────────────
155
+ if (JOTAI_READ_HOOKS.includes(hookName)) {
156
+ for (const ref of hooks) {
157
+ const atomStore = storesByName.get(ref);
158
+ if (atomStore && atomStore.metadata.storeType === "jotai") {
159
+ edges.push({
160
+ from: node.id,
161
+ to: atomStore.id,
162
+ type: "READS_FROM",
163
+ metadata: { hookUsed: hookName, storeType: "jotai" },
164
+ });
165
+ }
166
+ }
167
+ continue;
168
+ }
169
+ if (JOTAI_WRITE_HOOKS.includes(hookName)) {
170
+ for (const ref of hooks) {
171
+ const atomStore = storesByName.get(ref);
172
+ if (atomStore && atomStore.metadata.storeType === "jotai") {
173
+ edges.push({
174
+ from: node.id,
175
+ to: atomStore.id,
176
+ type: "WRITES_TO",
177
+ metadata: { hookUsed: hookName, storeType: "jotai" },
178
+ });
179
+ }
180
+ }
181
+ continue;
182
+ }
183
+ if (JOTAI_BOTH_HOOKS.includes(hookName)) {
184
+ for (const ref of hooks) {
185
+ const atomStore = storesByName.get(ref);
186
+ if (atomStore && atomStore.metadata.storeType === "jotai") {
187
+ edges.push({
188
+ from: node.id,
189
+ to: atomStore.id,
190
+ type: "READS_FROM",
191
+ metadata: { hookUsed: hookName, storeType: "jotai" },
192
+ });
193
+ edges.push({
194
+ from: node.id,
195
+ to: atomStore.id,
196
+ type: "WRITES_TO",
197
+ metadata: { hookUsed: hookName, storeType: "jotai" },
198
+ });
199
+ }
200
+ }
201
+ continue;
202
+ }
203
+ }
204
+ }
205
+ return edges;
206
+ }
@@ -0,0 +1,3 @@
1
+ import type { CodeEdge } from "../../types.js";
2
+ import type { LookupMaps } from "../buildLookup.js";
3
+ export declare function detectTestEdges(lookup: LookupMaps, repoPath: string): CodeEdge[];
@@ -0,0 +1,143 @@
1
+ //This file will detect the TEST edges from the test/story files to the components they are testing.
2
+ import path from "path";
3
+ import fs from "fs";
4
+ import { Project } from "ts-morph";
5
+ import { isLocalImport } from "./importEdges.js";
6
+ function getConfigPath(repoPath) {
7
+ const tsConfig = path.join(repoPath, "tsconfig.json");
8
+ const jsconfig = path.join(repoPath, "jsconfig.json");
9
+ if (fs.existsSync(tsConfig))
10
+ return tsConfig;
11
+ if (fs.existsSync(jsconfig))
12
+ return jsconfig;
13
+ return undefined;
14
+ }
15
+ function addFilesRecursively(dir, project) {
16
+ const IGNORE_DIRS = ["node_modules", "dist", "build", ".next", "coverage", ".git"];
17
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
18
+ for (const entry of entries) {
19
+ if (entry.isDirectory()) {
20
+ if (IGNORE_DIRS.includes(entry.name))
21
+ continue;
22
+ addFilesRecursively(path.join(dir, entry.name), project);
23
+ }
24
+ else if (/\.(ts|tsx|js|jsx)$/.test(entry.name)) {
25
+ project.addSourceFileAtPath(path.join(dir, entry.name));
26
+ }
27
+ }
28
+ }
29
+ function resolveAlias(moduleSpecifier, repoPath, currentDir) {
30
+ // Try to read tsconfig paths
31
+ const tsconfigPath = path.join(repoPath, "tsconfig.json");
32
+ if (fs.existsSync(tsconfigPath)) {
33
+ try {
34
+ const tsconfig = JSON.parse(fs.readFileSync(tsconfigPath, "utf-8"));
35
+ const paths = tsconfig.compilerOptions?.paths ?? {};
36
+ const baseUrl = tsconfig.compilerOptions?.baseUrl ?? ".";
37
+ const base = path.join(repoPath, baseUrl);
38
+ // Check each alias — e.g. "@/*": ["./src/*"]
39
+ for (const [alias, targets] of Object.entries(paths)) {
40
+ const aliasPrefix = alias.replace("/*", "/");
41
+ if (moduleSpecifier.startsWith(aliasPrefix)) {
42
+ const rest = moduleSpecifier.slice(aliasPrefix.length);
43
+ const target = targets[0].replace("/*", "");
44
+ return path.join(base, target, rest);
45
+ }
46
+ }
47
+ }
48
+ catch {
49
+ // malformed tsconfig — fall through
50
+ }
51
+ }
52
+ // Fallback — relative import
53
+ return path.join(currentDir, moduleSpecifier);
54
+ }
55
+ // ─── detectTestEdges ──────────────────────────────────────────────────────────
56
+ //
57
+ // Creates TESTS edges from TEST/STORY file nodes to the actual component/
58
+ // function/hook nodes they import from production code.
59
+ //
60
+ // Resolution strategy:
61
+ // 1. Find all TEST and STORY file nodes
62
+ // 2. For each, read its import declarations via ts-morph
63
+ // 3. Resolve each named import to its target file
64
+ // 4. Look up the named export in nodesByFile to find the exact node
65
+ // 5. Create TESTS edge: testNode → TESTS → productionNode
66
+ export function detectTestEdges(lookup, repoPath) {
67
+ const edges = [];
68
+ const createdEdges = new Set();
69
+ //filter all TEST and STORY file nodes
70
+ const testFileNodes = [...lookup.fileNodesByPath.values()].filter(n => ["TEST", "STORY"].includes(n.type));
71
+ if (testFileNodes.length === 0)
72
+ return edges;
73
+ //setup ts-morph project for import resolution
74
+ const configPath = getConfigPath(repoPath);
75
+ const project = configPath
76
+ ? new Project({
77
+ tsConfigFilePath: configPath,
78
+ skipAddingFilesFromTsConfig: true, // we do not want it to consider node modules or other unwanted files. So we manually walk down the repo.
79
+ })
80
+ : new Project({ skipAddingFilesFromTsConfig: true });
81
+ addFilesRecursively(repoPath, project);
82
+ for (const testNode of testFileNodes) {
83
+ const absFilePath = path.join(repoPath, testNode.filePath);
84
+ const sourceFile = project.getSourceFile(absFilePath);
85
+ if (!sourceFile)
86
+ continue;
87
+ const importDecls = sourceFile.getImportDeclarations();
88
+ for (const importDecl of importDecls) {
89
+ const moduleSpecifier = importDecl.getModuleSpecifierValue();
90
+ if (!isLocalImport(moduleSpecifier))
91
+ continue; // we are not considering third party or library imports.
92
+ let resolvedPath;
93
+ const resolvedFile = importDecl.getModuleSpecifierSourceFile();
94
+ if (resolvedFile) {
95
+ resolvedPath = resolvedFile.getFilePath();
96
+ }
97
+ else {
98
+ //This code block will rarely hit because ts-morph will handlethe resolvedFile from the project
99
+ const currentDir = path.dirname(absFilePath);
100
+ let basePath = resolveAlias(moduleSpecifier, repoPath, currentDir);
101
+ const candidates = [
102
+ basePath,
103
+ basePath + ".ts", basePath + ".tsx",
104
+ basePath + ".js", basePath + ".jsx",
105
+ path.join(basePath, "index.ts"),
106
+ path.join(basePath, "index.tsx"),
107
+ ];
108
+ resolvedPath = candidates.find(c => fs.existsSync(c));
109
+ }
110
+ if (!resolvedPath)
111
+ continue;
112
+ const targetRelative = path.relative(repoPath, resolvedPath).replace(/\\/g, "/");
113
+ // Get named imports — { Button } from "./Button"
114
+ const namedImports = importDecl.getNamedImports();
115
+ for (const namedImport of namedImports) {
116
+ const importedName = namedImport.getName();
117
+ // Find the actual production node by name in the target file
118
+ const nodesInFile = lookup.nodesByFile.get(targetRelative) ?? [];
119
+ const targetNode = nodesInFile.find(n => n.name === importedName);
120
+ if (!targetNode)
121
+ continue;
122
+ // Skip if target is also a TEST/STORY node — test importing test
123
+ if (targetNode.type === "TEST" || targetNode.type === "STORY")
124
+ continue;
125
+ const edgeKey = `${testNode.id}→${targetNode.id}`;
126
+ if (createdEdges.has(edgeKey))
127
+ continue;
128
+ createdEdges.add(edgeKey);
129
+ edges.push({
130
+ from: testNode.id,
131
+ to: targetNode.id,
132
+ type: "TESTS",
133
+ metadata: {
134
+ importPath: moduleSpecifier,
135
+ testFileType: testNode.type,
136
+ },
137
+ });
138
+ }
139
+ }
140
+ }
141
+ console.log(` TESTS edges: ${edges.length}`);
142
+ return edges;
143
+ }
@@ -0,0 +1,2 @@
1
+ import type { CodeNode } from "../../types.js";
2
+ export declare function closestByPath(candidates: CodeNode[], referencePath: string): CodeNode;
@@ -0,0 +1,25 @@
1
+ // Picks the node whose filePath shares the most leading path segments
2
+ // with the reference path. When multiple nodes have the same name (e.g.
3
+ // two files both export a function called "handleSubmit"), this heuristic
4
+ // selects the one physically closest to the caller in the directory tree.
5
+ export function closestByPath(candidates, referencePath) {
6
+ const refParts = referencePath.split("/");
7
+ let best = candidates[0];
8
+ let bestScore = 0;
9
+ for (const candidate of candidates) {
10
+ const parts = candidate.filePath.split("/");
11
+ let score = 0;
12
+ const len = Math.min(refParts.length, parts.length);
13
+ for (let i = 0; i < len; i++) {
14
+ if (refParts[i] === parts[i])
15
+ score++;
16
+ else
17
+ break;
18
+ }
19
+ if (score > bestScore) {
20
+ bestScore = score;
21
+ best = candidate;
22
+ }
23
+ }
24
+ return best;
25
+ }
@@ -0,0 +1,6 @@
1
+ import type { BackendRouteNode, CodeEdge, CodeNode, ProjectFingerprint, RouteNode } from "../types.js";
2
+ export interface EdgeDetectionResult {
3
+ edges: CodeEdge[];
4
+ ghostNodes: CodeNode[];
5
+ }
6
+ export declare function detectEdges(nodes: CodeNode[], routeNodes: (RouteNode | BackendRouteNode)[], repoPath: string, fingerprint: ProjectFingerprint): EdgeDetectionResult;
@@ -0,0 +1,65 @@
1
+ import { buildLookupMaps } from "./buildLookup.js";
2
+ import { detectCallEdges } from "./edges/callEdges.js";
3
+ import { detectEventEdges } from "./edges/eventEdges.js";
4
+ import { detectGuardEdges } from "./edges/guardEdges.js";
5
+ import { detectHookEdges } from "./edges/hookEdges.js";
6
+ import { detectImportEdges } from "./edges/importEdges.js";
7
+ import { detectPropEdges } from "./edges/propEdges.js";
8
+ import { detectRouteEdges } from "./edges/routeEdge.js";
9
+ import { detectStateEdges } from "./edges/stateEdges.js";
10
+ import { detectTestEdges } from "./edges/testEdges.js";
11
+ export function detectEdges(nodes, routeNodes, repoPath, fingerprint) {
12
+ console.log(`Building lookup maps for edge detection for ${nodes.length} nodes...`);
13
+ //building lookup maps
14
+ const lookupMp = buildLookupMaps(nodes);
15
+ console.log("Running edge detectors...");
16
+ // importEdges MUST run before callEdges — it populates lookupMp.thirdPartyImportAliases
17
+ // as a side-effect, and callEdges reads that map to resolve third-party CALLS edges.
18
+ const importResult = detectImportEdges(lookupMp, repoPath);
19
+ const importEdges = importResult.edges;
20
+ const callResult = detectCallEdges(nodes, lookupMp);
21
+ const callEdges = callResult.edges;
22
+ const stateEdges = detectStateEdges(nodes, lookupMp);
23
+ const propEdges = detectPropEdges(nodes, lookupMp, repoPath);
24
+ const hookEdges = detectHookEdges(nodes, lookupMp);
25
+ const eventResults = detectEventEdges(lookupMp, repoPath);
26
+ const routeEdges = detectRouteEdges(nodes, lookupMp);
27
+ // GUARDS — middleware to route protection
28
+ const guardEdges = detectGuardEdges(nodes, lookupMp, routeNodes, repoPath, fingerprint);
29
+ const testEdges = detectTestEdges(lookupMp, repoPath); // This does not needs nodes, as it detect edges from the file
30
+ // Collect all dynamically-created third-party method nodes (dedup by id)
31
+ const newThirdPartyNodesMap = new Map();
32
+ for (const n of [...importResult.thirdPartyMethodNodes, ...callResult.newThirdPartyNodes]) {
33
+ if (!newThirdPartyNodesMap.has(n.id))
34
+ newThirdPartyNodesMap.set(n.id, n);
35
+ }
36
+ const newThirdPartyNodes = [...newThirdPartyNodesMap.values()];
37
+ console.log(`Running edge detectors...`);
38
+ console.log(` CALLS edges: ${callEdges.length}`);
39
+ console.log(` IMPORTS edges: ${importEdges.length}`);
40
+ console.log(` STATE edges: ${stateEdges.length}`);
41
+ console.log(` PROP edges: ${propEdges.length}`);
42
+ console.log(` HOOK edges: ${hookEdges.length}`);
43
+ console.log(` EVENT edges: ${eventResults.edges.length}`);
44
+ console.log(` ROUTE edges: ${routeEdges.length}`);
45
+ console.log(` GUARD edges: ${guardEdges.length}`);
46
+ console.log(` TEST edges: ${testEdges.length}`);
47
+ console.log(` Ghost nodes created: ${eventResults.ghostNodes.length}`);
48
+ console.log(` Third-party method nodes: ${newThirdPartyNodes.length}`);
49
+ const allEdges = [
50
+ ...callEdges,
51
+ ...importEdges,
52
+ ...stateEdges,
53
+ ...propEdges,
54
+ ...hookEdges,
55
+ ...eventResults.edges,
56
+ ...routeEdges,
57
+ ...guardEdges,
58
+ ...testEdges,
59
+ ];
60
+ console.log(`Total edges detected: ${allEdges.length}`);
61
+ return {
62
+ edges: allEdges,
63
+ ghostNodes: [...eventResults.ghostNodes, ...newThirdPartyNodes],
64
+ };
65
+ }
@@ -0,0 +1 @@
1
+ export {};