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,240 @@
1
+ import { SyntaxKind } from "ts-morph";
2
+ import { returnsJSX } from "./components.js";
3
+ import { detectFunctionDirective } from "../directives.js";
4
+ import { extractParams, extractReturnTypeAnnotation, extractBareTypeNames, extractReferencedInterfaces, } from "../typeUtils.js";
5
+ // these are used to detect the routes in the Nextjs
6
+ const HTTP_METHOD_EXPORTS = new Set(["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]);
7
+ function makeId(filePath, name) {
8
+ return `${filePath}::${name}`;
9
+ }
10
+ function extractFunctionCalls(node) {
11
+ const calls = node.getDescendantsOfKind(SyntaxKind.CallExpression);
12
+ const names = [];
13
+ for (const call of calls) {
14
+ const name = call.getExpression().getText();
15
+ // Skip hooks, they are handled by hooks extractor
16
+ if (name.startsWith("use"))
17
+ continue;
18
+ // Skip console calls, they are noise
19
+ if (name.startsWith("console"))
20
+ continue;
21
+ names.push(name);
22
+ }
23
+ return [...new Set(names)];
24
+ }
25
+ function extractHookCalls(node) {
26
+ const calls = node.getDescendantsOfKind(SyntaxKind.CallExpression);
27
+ const hooks = [];
28
+ for (const call of calls) {
29
+ const name = call.getExpression().getText();
30
+ // Only capture custom hooks — use[A-Z] pattern
31
+ // Built-in React hooks (useState etc.) are not in our node graph
32
+ if (/^use[A-Z]/.test(name))
33
+ hooks.push(name);
34
+ }
35
+ return [...new Set(hooks)];
36
+ }
37
+ function extractApiCalls(node) {
38
+ const calls = node.getDescendantsOfKind(SyntaxKind.CallExpression);
39
+ const apiCalls = [];
40
+ for (const call of calls) {
41
+ const text = call.getText();
42
+ const expr = call.getExpression().getText();
43
+ // ─── fetch ────────────────────────────────────────────────────────────────
44
+ if (expr === "fetch") {
45
+ const args = call.getArguments();
46
+ if (args.length > 0)
47
+ apiCalls.push(`fetch(${args[0].getText()})`);
48
+ }
49
+ // ─── axios ────────────────────────────────────────────────────────────────
50
+ if (expr === "axios.get" ||
51
+ expr === "axios.post" ||
52
+ expr === "axios.put" ||
53
+ expr === "axios.delete" ||
54
+ expr === "axios.patch" ||
55
+ expr === "axios") {
56
+ const args = call.getArguments();
57
+ if (args.length > 0)
58
+ apiCalls.push(`${expr}(${args[0].getText()})`);
59
+ }
60
+ // ─── React Query (useQuery, useMutation, useInfiniteQuery) ────────────────
61
+ if (expr === "useQuery" ||
62
+ expr === "useMutation" ||
63
+ expr === "useInfiniteQuery" ||
64
+ expr === "useSuspenseQuery") {
65
+ const args = call.getArguments();
66
+ if (args.length > 0)
67
+ apiCalls.push(`${expr}(${args[0].getText()})`);
68
+ }
69
+ // ─── SWR ──────────────────────────────────────────────────────────────────
70
+ if (expr === "useSWR" || expr === "useSWRMutation") {
71
+ const args = call.getArguments();
72
+ if (args.length > 0)
73
+ apiCalls.push(`${expr}(${args[0].getText()})`);
74
+ }
75
+ }
76
+ return [...new Set(apiCalls)];
77
+ }
78
+ function hasErrorHandling(node) {
79
+ const tryCatch = node.getDescendantsOfKind(SyntaxKind.TryStatement);
80
+ return tryCatch.length > 0;
81
+ }
82
+ function extractThrowStatements(node) {
83
+ const throws = node.getDescendantsOfKind(SyntaxKind.ThrowStatement);
84
+ return throws.length > 0;
85
+ }
86
+ export function extractFunctions(file, fileDirective = null) {
87
+ const nodes = [];
88
+ const filePath = file.getFilePath();
89
+ // ─── Function Declarations ─────────────────────────────────────────────────
90
+ for (const fn of file.getFunctions()) {
91
+ const name = fn.getName();
92
+ if (!name)
93
+ continue;
94
+ // Skip React components (uppercase and must return JSX) — handled by components extractor.
95
+ // Exception: HTTP method exports (GET, POST, etc.) are uppercase but are
96
+ // route handlers, not components. Captured in the dedicated section below.
97
+ if (/^[A-Z]/.test(name) && !HTTP_METHOD_EXPORTS.has(name) && returnsJSX(fn))
98
+ continue;
99
+ // Skip hooks - handled by hooks extractor
100
+ if (/^use[A-Z]/.test(name))
101
+ continue;
102
+ const typedParams = extractParams(fn);
103
+ const calls = extractFunctionCalls(fn);
104
+ const hookCalls = extractHookCalls(fn);
105
+ const apiCalls = extractApiCalls(fn);
106
+ const isAsync = fn.isAsync();
107
+ const hasErrors = hasErrorHandling(fn);
108
+ const throws = extractThrowStatements(fn);
109
+ const renderingBoundary = detectFunctionDirective(fn.getBody()) ?? fileDirective;
110
+ const returnType = extractReturnTypeAnnotation(fn);
111
+ const bareTypeNames = extractBareTypeNames([...typedParams.map((p) => p.type), returnType]);
112
+ const referencedTypes = extractReferencedInterfaces(file, bareTypeNames);
113
+ nodes.push({
114
+ id: makeId(filePath, name),
115
+ name,
116
+ type: "FUNCTION",
117
+ filePath,
118
+ startLine: fn.getStartLineNumber(),
119
+ endLine: fn.getEndLineNumber(),
120
+ rawCode: fn.getText(),
121
+ metadata: {
122
+ params: typedParams.map((p) => p.name),
123
+ parameters: typedParams,
124
+ returnType,
125
+ referencedTypes,
126
+ calls,
127
+ hookCalls,
128
+ apiCalls,
129
+ isAsync,
130
+ hasErrorHandling: hasErrors,
131
+ throws,
132
+ lineCount: fn.getEndLineNumber() - fn.getStartLineNumber(),
133
+ isHttpHandler: HTTP_METHOD_EXPORTS.has(name),
134
+ httpMethod: HTTP_METHOD_EXPORTS.has(name) ? name : undefined,
135
+ ...(renderingBoundary !== null && { renderingBoundary }),
136
+ },
137
+ });
138
+ }
139
+ // ─── Arrow Function Declarations ───────────────────────────────────────────
140
+ for (const variable of file.getVariableDeclarations()) {
141
+ const name = variable.getName();
142
+ // Skip React components and not nextJs HTTP routes
143
+ if (/^[A-Z]/.test(name) && !HTTP_METHOD_EXPORTS.has(name))
144
+ continue;
145
+ // Skip hooks
146
+ if (/^use[A-Z]/.test(name))
147
+ continue;
148
+ const initializer = variable.getInitializer();
149
+ if (!initializer)
150
+ continue;
151
+ const isArrow = initializer.getKind() === SyntaxKind.ArrowFunction;
152
+ if (!isArrow)
153
+ continue;
154
+ const typedParams = extractParams(initializer);
155
+ const calls = extractFunctionCalls(initializer);
156
+ const hookCalls = extractHookCalls(initializer);
157
+ const apiCalls = extractApiCalls(initializer);
158
+ const isAsync = initializer.getText().startsWith("async");
159
+ const hasErrors = hasErrorHandling(initializer);
160
+ const throws = extractThrowStatements(initializer);
161
+ const renderingBoundary = detectFunctionDirective(initializer.getBody?.()) ?? fileDirective;
162
+ const returnType = extractReturnTypeAnnotation(initializer);
163
+ const bareTypeNames = extractBareTypeNames([...typedParams.map((p) => p.type), returnType]);
164
+ const referencedTypes = extractReferencedInterfaces(file, bareTypeNames);
165
+ nodes.push({
166
+ id: makeId(filePath, name),
167
+ name,
168
+ type: "FUNCTION",
169
+ filePath,
170
+ startLine: variable.getStartLineNumber(),
171
+ endLine: variable.getEndLineNumber(),
172
+ rawCode: variable.getText(),
173
+ metadata: {
174
+ params: typedParams.map((p) => p.name),
175
+ parameters: typedParams,
176
+ returnType,
177
+ referencedTypes,
178
+ calls,
179
+ hookCalls,
180
+ apiCalls,
181
+ isAsync,
182
+ hasErrorHandling: hasErrors,
183
+ throws,
184
+ lineCount: variable.getEndLineNumber() - variable.getStartLineNumber(),
185
+ isHttpHandler: HTTP_METHOD_EXPORTS.has(name),
186
+ httpMethod: HTTP_METHOD_EXPORTS.has(name) ? name : undefined,
187
+ ...(renderingBoundary !== null && { renderingBoundary }),
188
+ },
189
+ });
190
+ }
191
+ // ─── HTTP Method Exports (re-exported via export { GET } pattern) ──────────
192
+ //
193
+ // Handles the case where a route.ts re-exports a handler defined elsewhere:
194
+ // import { myHandler } from "./handlers.js";
195
+ // export { myHandler as GET };
196
+ //
197
+ // In this case getFunctions() and getVariableDeclarations() won't find GET.
198
+ // We detect export specifiers that alias to an HTTP method name.
199
+ for (const exportDecl of file.getExportDeclarations()) {
200
+ for (const specifier of exportDecl.getNamedExports()) {
201
+ const exportedName = specifier.getAliasNode()?.getText()
202
+ ?? specifier.getName();
203
+ if (!HTTP_METHOD_EXPORTS.has(exportedName))
204
+ continue;
205
+ // The local name is what was imported — use it to find the original node
206
+ const localName = specifier.getName();
207
+ // Check if we already captured it above (direct export)
208
+ const alreadyCaptured = nodes.some(n => n.name === exportedName);
209
+ if (alreadyCaptured)
210
+ continue;
211
+ // We can't get line numbers reliably for re-exports, so use the
212
+ // export declaration's position as a proxy
213
+ nodes.push({
214
+ id: makeId(filePath, exportedName),
215
+ name: exportedName,
216
+ type: "FUNCTION",
217
+ filePath,
218
+ startLine: exportDecl.getStartLineNumber(),
219
+ endLine: exportDecl.getEndLineNumber(),
220
+ rawCode: exportDecl.getText(),
221
+ metadata: {
222
+ params: [],
223
+ calls: [],
224
+ apiCalls: [],
225
+ isAsync: false,
226
+ hasErrorHandling: false,
227
+ throws: false,
228
+ lineCount: 1,
229
+ isHttpHandler: true,
230
+ httpMethod: exportedName,
231
+ // Record that this is a re-export so routeEdges can follow
232
+ // the chain to the actual implementation if needed
233
+ isReExport: true,
234
+ reExportedFrom: localName,
235
+ },
236
+ });
237
+ }
238
+ }
239
+ return nodes;
240
+ }
@@ -0,0 +1,4 @@
1
+ import { SourceFile } from "ts-morph";
2
+ import type { CodeNode } from "../../types.js";
3
+ import { type RenderingBoundary } from "../directives.js";
4
+ export declare function extractHooks(file: SourceFile, fileDirective?: RenderingBoundary): CodeNode[];
@@ -0,0 +1,128 @@
1
+ import { SyntaxKind } from "ts-morph";
2
+ import { detectFunctionDirective } from "../directives.js";
3
+ import { extractParams, extractBareTypeNames, extractReferencedInterfaces, } from "../typeUtils.js";
4
+ function makeId(filePath, name) {
5
+ return `${filePath}::${name}`;
6
+ }
7
+ function extractDependencies(node) {
8
+ const calls = node.getDescendantsOfKind(SyntaxKind.CallExpression);
9
+ const deps = [];
10
+ for (const call of calls) {
11
+ const name = call.getExpression().getText();
12
+ if (name.startsWith("use")) {
13
+ deps.push(name);
14
+ }
15
+ }
16
+ return [...new Set(deps)];
17
+ }
18
+ function extractContextRefs(node) {
19
+ const refs = [];
20
+ for (const call of node.getDescendantsOfKind(SyntaxKind.CallExpression)) {
21
+ if (call.getExpression().getText() === "useContext") {
22
+ const arg = call.getArguments()[0];
23
+ if (arg)
24
+ refs.push(arg.getText());
25
+ }
26
+ }
27
+ return [...new Set(refs)];
28
+ }
29
+ // Try explicit annotation first; fall back to shape heuristic.
30
+ function extractReturnType(node) {
31
+ const explicit = node.getReturnTypeNode?.()?.getText();
32
+ if (explicit)
33
+ return explicit;
34
+ const returnStatements = node.getDescendantsOfKind(SyntaxKind.ReturnStatement);
35
+ if (returnStatements.length === 0)
36
+ return "void";
37
+ for (const ret of returnStatements) {
38
+ const expr = ret.getExpression();
39
+ if (!expr)
40
+ continue;
41
+ if (expr.getKind() === SyntaxKind.ArrayLiteralExpression)
42
+ return "array";
43
+ if (expr.getKind() === SyntaxKind.ObjectLiteralExpression)
44
+ return "object";
45
+ }
46
+ return "unknown";
47
+ }
48
+ export function extractHooks(file, fileDirective = null) {
49
+ const nodes = [];
50
+ const filePath = file.getFilePath();
51
+ // ─── Function Declaration Hooks ────────────────────────────────────────────
52
+ // e.g. function useAuth() { ... }
53
+ for (const fn of file.getFunctions()) {
54
+ const name = fn.getName();
55
+ if (!name)
56
+ continue;
57
+ // Hooks must start with "use" followed by uppercase
58
+ if (!/^use[A-Z]/.test(name))
59
+ continue;
60
+ const dependencies = extractDependencies(fn);
61
+ const returnType = extractReturnType(fn);
62
+ const isAsync = fn.isAsync();
63
+ const contextRefs = extractContextRefs(fn);
64
+ const renderingBoundary = detectFunctionDirective(fn.getBody()) ?? fileDirective;
65
+ const typedParams = extractParams(fn);
66
+ const bareTypeNames = extractBareTypeNames([...typedParams.map((p) => p.type), returnType]);
67
+ const referencedTypes = extractReferencedInterfaces(file, bareTypeNames);
68
+ nodes.push({
69
+ id: makeId(filePath, name),
70
+ name,
71
+ type: "HOOK",
72
+ filePath,
73
+ startLine: fn.getStartLineNumber(),
74
+ endLine: fn.getEndLineNumber(),
75
+ rawCode: fn.getText(),
76
+ metadata: {
77
+ dependencies,
78
+ contextRefs,
79
+ returnType,
80
+ parameters: typedParams,
81
+ referencedTypes,
82
+ isAsync,
83
+ ...(renderingBoundary !== null && { renderingBoundary }),
84
+ },
85
+ });
86
+ }
87
+ // ─── Arrow Function Hooks ──────────────────────────────────────────────────
88
+ // e.g. const useAuth = () => { ... }
89
+ for (const variable of file.getVariableDeclarations()) {
90
+ const name = variable.getName();
91
+ // Hooks must start with "use" followed by uppercase
92
+ if (!/^use[A-Z]/.test(name))
93
+ continue;
94
+ const initializer = variable.getInitializer();
95
+ if (!initializer)
96
+ continue;
97
+ const isArrow = initializer.getKind() === SyntaxKind.ArrowFunction;
98
+ if (!isArrow)
99
+ continue;
100
+ const dependencies = extractDependencies(initializer);
101
+ const returnType = extractReturnType(initializer);
102
+ const isAsync = initializer.asKind(SyntaxKind.ArrowFunction)?.isAsync() ?? false;
103
+ const contextRefs = extractContextRefs(initializer);
104
+ const renderingBoundary = detectFunctionDirective(initializer.getBody?.()) ?? fileDirective;
105
+ const typedParams = extractParams(initializer);
106
+ const bareTypeNames = extractBareTypeNames([...typedParams.map((p) => p.type), returnType]);
107
+ const referencedTypes = extractReferencedInterfaces(file, bareTypeNames);
108
+ nodes.push({
109
+ id: makeId(filePath, name),
110
+ name,
111
+ type: "HOOK",
112
+ filePath,
113
+ startLine: variable.getStartLineNumber(),
114
+ endLine: variable.getEndLineNumber(),
115
+ rawCode: variable.getText(),
116
+ metadata: {
117
+ dependencies,
118
+ contextRefs,
119
+ returnType,
120
+ parameters: typedParams,
121
+ referencedTypes,
122
+ isAsync,
123
+ ...(renderingBoundary !== null && { renderingBoundary }),
124
+ },
125
+ });
126
+ }
127
+ return nodes;
128
+ }
@@ -0,0 +1,3 @@
1
+ import { SourceFile } from "ts-morph";
2
+ import type { CodeNode } from "../../types.js";
3
+ export declare function extractStores(file: SourceFile): CodeNode[];
@@ -0,0 +1,181 @@
1
+ import { SyntaxKind } from "ts-morph";
2
+ function makeId(filePath, name) {
3
+ return `${filePath}::${name}`;
4
+ }
5
+ function extractStateShape(node, storeType) {
6
+ const properties = [];
7
+ const objLiterals = node.getDescendantsOfKind(SyntaxKind.ObjectLiteralExpression);
8
+ if (objLiterals.length === 0)
9
+ return properties;
10
+ //Takes the first object literal as the state shape
11
+ // Extracts all property names from that object
12
+ // Example: create((set) => ({ items: [], total: 0 })) → ["items", "total"]
13
+ if (storeType === "zustand") {
14
+ // First object literal is the state shape
15
+ const firstObj = objLiterals[0];
16
+ for (const prop of firstObj.getProperties()) {
17
+ const propName = prop.getName ? prop.getName() : null;
18
+ if (propName)
19
+ properties.push(propName);
20
+ }
21
+ }
22
+ // Looks for an initialState property within object literals
23
+ // Extracts properties from inside the initialState object
24
+ // Example: createSlice({ initialState: { items: [], total: 0 } }) → ["items", "total"]
25
+ if (storeType === "redux") {
26
+ // Look specifically for initialState object
27
+ for (const obj of objLiterals) {
28
+ for (const prop of obj.getProperties()) {
29
+ const propName = prop.getName ? prop.getName() : null;
30
+ if (propName === "initialState") {
31
+ const initializer = prop.getInitializer();
32
+ if (!initializer || initializer.getKind() !== SyntaxKind.ObjectLiteralExpression)
33
+ continue;
34
+ for (const innerProp of initializer.getProperties()) {
35
+ const innerName = innerProp.getName ? innerProp.getName() : null;
36
+ if (innerName)
37
+ properties.push(innerName);
38
+ }
39
+ }
40
+ }
41
+ }
42
+ }
43
+ // Looks for a key property and extracts its value as the state identifier
44
+ // Returns the key name (not property names)
45
+ // Example: atom({ key: "cartState", default: { items: [] } }) → ["cartState"]
46
+ if (storeType === "recoil") {
47
+ // Recoil has a single default value not a shape
48
+ // We store the key name as the state identifier
49
+ for (const obj of objLiterals) {
50
+ for (const prop of obj.getProperties()) {
51
+ const propName = prop.getName ? prop.getName() : null;
52
+ if (propName === "key") {
53
+ const initializer = prop.getInitializer
54
+ ? prop.getInitializer()
55
+ : null;
56
+ if (initializer)
57
+ properties.push(initializer.getText().replace(/['"]/g, ""));
58
+ }
59
+ }
60
+ }
61
+ }
62
+ if (storeType === "jotai") {
63
+ // Jotai atoms don't have a shape
64
+ // We just return empty — the atom name itself is the identifier
65
+ return [];
66
+ }
67
+ return properties;
68
+ }
69
+ //This function will extract action names from different store types.
70
+ function extractActions(node, storeType) {
71
+ const actions = [];
72
+ const objLiterals = node.getDescendantsOfKind(SyntaxKind.ObjectLiteralExpression);
73
+ // Finds function-valued properties in the first object literal
74
+ // Extracts names of arrow functions and function expressions
75
+ // Example: create((set) => ({ addItem: () => set({ items: [] }) })) → ["addItem"]
76
+ if (storeType === "zustand") {
77
+ // Actions are function-valued properties in the first object
78
+ for (const obj of objLiterals) {
79
+ for (const prop of obj.getProperties()) {
80
+ const initializer = prop.getInitializer
81
+ ? prop.getInitializer()
82
+ : null;
83
+ if (!initializer)
84
+ continue;
85
+ if (initializer.getKind() === SyntaxKind.ArrowFunction ||
86
+ initializer.getKind() === SyntaxKind.FunctionExpression) {
87
+ const name = prop.getName ? prop.getName() : null;
88
+ if (name)
89
+ actions.push(name);
90
+ }
91
+ }
92
+ }
93
+ }
94
+ // Looks specifically inside a reducers object
95
+ // Extracts property names from the reducers object
96
+ // Example: createSlice({ reducers: { addItem: (state) => {} } }) → ["addItem"]
97
+ if (storeType === "redux") {
98
+ // Actions live inside the reducers object specifically
99
+ for (const obj of objLiterals) {
100
+ for (const prop of obj.getProperties()) {
101
+ const propName = prop.getName ? prop.getName() : null;
102
+ if (propName === "reducers") {
103
+ const initializer = prop.getInitializer();
104
+ if (!initializer || initializer.getKind() !== SyntaxKind.ObjectLiteralExpression)
105
+ continue;
106
+ for (const reducerProp of initializer.getProperties()) {
107
+ const reducerName = reducerProp.getName ? reducerProp.getName() : null;
108
+ if (reducerName)
109
+ actions.push(reducerName);
110
+ }
111
+ }
112
+ }
113
+ }
114
+ }
115
+ if (storeType === "recoil" || storeType === "jotai") {
116
+ // No actions defined inside atom/selector definitions
117
+ // Actions happen via setters in components
118
+ return [];
119
+ }
120
+ return actions;
121
+ }
122
+ function detectStoreType(text) {
123
+ if (text.startsWith("create(") || text.startsWith("create<"))
124
+ return "zustand";
125
+ if (text.includes("createSlice") || text.includes("createReducer"))
126
+ return "redux";
127
+ if (text.includes("createContext"))
128
+ return "context";
129
+ if (text.startsWith("atom(") && text.includes("key:"))
130
+ return "recoil";
131
+ if (text.startsWith("selector(") || text.startsWith("atomFamily("))
132
+ return "recoil";
133
+ if (text.startsWith("atom(") && !text.includes("key:"))
134
+ return "jotai";
135
+ return "unknown";
136
+ }
137
+ export function extractStores(file) {
138
+ const nodes = [];
139
+ const filePath = file.getFilePath();
140
+ for (const variable of file.getVariableDeclarations()) {
141
+ const name = variable.getName();
142
+ const initializer = variable.getInitializer();
143
+ if (!initializer)
144
+ continue;
145
+ const text = initializer.getText();
146
+ // ─── Zustand Store ────────────────────────────────────────────────────────
147
+ // e.g. const useCartStore = create((set) => ({ ... }))
148
+ const isZustand = text.startsWith("create(") || text.startsWith("create<");
149
+ // ─── Redux Slice ──────────────────────────────────────────────────────────
150
+ // e.g. const cartSlice = createSlice({ name, initialState, reducers })
151
+ const isRedux = text.startsWith("createSlice(") || text.startsWith("createReducer(");
152
+ // ---- Recoil Atom Selector ──────────────────────────────────────────────────────────
153
+ const isRecoil = text.startsWith("atom(") || text.startsWith("selector(") || text.startsWith("atomFamily(") || text.startsWith("selectorFamily(");
154
+ // ----- Jotai Atom ──────────────────────────────────────────────────────────
155
+ const isJotai = text.startsWith("atom(") && !text.includes("key:"); // recoil atoms always have a key property , jotai atoms do not
156
+ // ─── React Context ────────────────────────────────────────────────────────
157
+ // e.g. const AuthContext = createContext(null)
158
+ const isContext = text.startsWith("createContext(") ||
159
+ text.startsWith("React.createContext(");
160
+ if (!isZustand && !isRedux && !isContext && !isRecoil && !isJotai)
161
+ continue;
162
+ const storeType = detectStoreType(text);
163
+ const stateShape = extractStateShape(initializer, storeType);
164
+ const actions = extractActions(initializer, storeType);
165
+ nodes.push({
166
+ id: makeId(filePath, name),
167
+ name,
168
+ type: "STATE_STORE",
169
+ filePath,
170
+ startLine: variable.getStartLineNumber(),
171
+ endLine: variable.getEndLineNumber(),
172
+ rawCode: variable.getText(),
173
+ metadata: {
174
+ storeType,
175
+ stateShape, // e.g. ["items", "total", "isOpen"]
176
+ actions, // e.g. ["addItem", "removeItem", "clearCart"]
177
+ },
178
+ });
179
+ }
180
+ return nodes;
181
+ }
@@ -0,0 +1,14 @@
1
+ import type { CodeNode } from "../types.js";
2
+ export interface ParserResult {
3
+ nodes: CodeNode[];
4
+ stats: {
5
+ totalFiles: number;
6
+ totalNodes: number;
7
+ componentCount: number;
8
+ hookCount: number;
9
+ functionCount: number;
10
+ storeCount: number;
11
+ skippedFiles: number;
12
+ };
13
+ }
14
+ export declare function parseRepo(repoPath: string): ParserResult;