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,51 @@
1
+ // Exponential backoff retry for LLM summarization calls.
2
+ //
3
+ // Retryable: rate limits (429), server errors (5xx), network errors (ECONNRESET etc.)
4
+ // Not retryable: auth (401/403), not-found (404), bad-request (400) — retrying won't help.
5
+ export const DEFAULT_RETRY_OPTIONS = {
6
+ maxAttempts: 4, // 1 initial + 3 retries
7
+ baseDelayMs: 1000,
8
+ maxDelayMs: 15000,
9
+ };
10
+ function isRetryable(err) {
11
+ const status = err?.status ?? err?.statusCode;
12
+ if (typeof status === "number") {
13
+ if (status === 400 || status === 401 || status === 403 || status === 404)
14
+ return false;
15
+ if (status === 429 || status >= 500)
16
+ return true;
17
+ }
18
+ const code = err?.code;
19
+ if (typeof code === "string" &&
20
+ ["ECONNRESET", "ETIMEDOUT", "ECONNREFUSED", "ENOTFOUND", "EPIPE"].includes(code)) {
21
+ return true;
22
+ }
23
+ const msg = (err instanceof Error ? err.message : String(err)).toLowerCase();
24
+ return msg.includes("timeout") || msg.includes("connection") ||
25
+ msg.includes("network") || msg.includes("socket") ||
26
+ msg.includes("fetch failed");
27
+ }
28
+ function sleep(ms) {
29
+ return new Promise(resolve => setTimeout(resolve, ms));
30
+ }
31
+ export async function withRetry(fn, opts = DEFAULT_RETRY_OPTIONS, label = "operation") {
32
+ for (let attempt = 0; attempt < opts.maxAttempts; attempt++) {
33
+ try {
34
+ return await fn();
35
+ }
36
+ catch (err) {
37
+ const isLast = attempt === opts.maxAttempts - 1;
38
+ if (!isRetryable(err) || isLast)
39
+ throw err;
40
+ // Exponential backoff with ±10% jitter to avoid thundering herd
41
+ const base = opts.baseDelayMs * 2 ** attempt;
42
+ const jitter = base * 0.1 * (Math.random() * 2 - 1);
43
+ const wait = Math.min(base + jitter, opts.maxDelayMs);
44
+ console.warn(`[retry] ${label} — attempt ${attempt + 1}/${opts.maxAttempts} failed, ` +
45
+ `retrying in ${Math.round(wait)}ms — ${err instanceof Error ? err.message : err}`);
46
+ await sleep(wait);
47
+ }
48
+ }
49
+ // unreachable — loop always throws or returns
50
+ throw new Error("withRetry: exhausted attempts");
51
+ }
@@ -0,0 +1,3 @@
1
+ import type { CodeEdge, CodeNode } from "../types.js";
2
+ import type { TopologicalResult } from "./types.js";
3
+ export declare function buildTopologicalOrder(nodes: CodeNode[], edges: CodeEdge[]): TopologicalResult;
@@ -0,0 +1,105 @@
1
+ //Topo Sort
2
+ export function buildTopologicalOrder(nodes, edges) {
3
+ //file nodes will be handled seperately.
4
+ const fileNodes = [];
5
+ const regularNodesSet = new Set();
6
+ // const regularNodes: string[] = [];
7
+ for (const node of nodes) {
8
+ if (node.type !== "FILE") {
9
+ // regularNodes.push(node.id);
10
+ regularNodesSet.add(node.id);
11
+ }
12
+ else {
13
+ fileNodes.push(node.id);
14
+ }
15
+ }
16
+ const inDegree = new Map();
17
+ const dependents = new Map(); // dependents[X] = list of nodes that depend on X (parent of X)
18
+ // When X is summarized → decrement in-degree of each dependent (parent node)
19
+ // If dependent's in-degree hits 0 → it's ready to summarize
20
+ for (const nodeId of regularNodesSet) {
21
+ inDegree.set(nodeId, 0);
22
+ }
23
+ for (const edge of edges) {
24
+ if (!regularNodesSet.has(edge.from) || !regularNodesSet.has(edge.to))
25
+ continue; // Skip edges that involve FILE nodes — they're handled separately
26
+ const currentInDegree = inDegree.get(edge.from) || 0;
27
+ let currentDependents = dependents.get(edge.to) || [];
28
+ inDegree.set(edge.from, currentInDegree + 1);
29
+ currentDependents?.push(edge.from);
30
+ dependents.set(edge.to, currentDependents);
31
+ }
32
+ // Now our indegree map is ready — push nodes with 0 indegree into the queue.
33
+ const queue = [];
34
+ // nodeOrder is now a nested array — each inner array is one parallel level.
35
+ // All nodes at the same level are independent and can be summarized concurrently.
36
+ const nodeOrder = [];
37
+ for (const [nodeId, degreeVal] of inDegree) {
38
+ if (!degreeVal) {
39
+ queue.push(nodeId);
40
+ }
41
+ }
42
+ while (queue.length !== 0) {
43
+ // Snapshot the current queue size — these are exactly the nodes at this level.
44
+ // New nodes pushed during this loop belong to the NEXT level.
45
+ let sz = queue.length;
46
+ const currentLevel = [];
47
+ while (sz--) {
48
+ const nodeId = queue.shift();
49
+ if (!nodeId)
50
+ continue;
51
+ currentLevel.push(nodeId);
52
+ for (const parentNodeId of (dependents.get(nodeId) || [])) {
53
+ let currentIndegree = inDegree.get(parentNodeId) || 0;
54
+ inDegree.set(parentNodeId, Math.max(--currentIndegree, 0));
55
+ if (currentIndegree === 0) {
56
+ // Parent's deps are all done — eligible for next level
57
+ queue.push(parentNodeId);
58
+ }
59
+ }
60
+ }
61
+ nodeOrder.push(currentLevel);
62
+ }
63
+ // nodeOrder is now ready — but cyclic nodes won't appear in it.
64
+ // Count total processed nodes across all levels to detect cycles.
65
+ const totalProcessed = nodeOrder.reduce((sum, level) => sum + level.length, 0);
66
+ const cycleGroups = [];
67
+ if (totalProcessed !== regularNodesSet.size) {
68
+ const processedSet = new Set(nodeOrder.flat());
69
+ const unvisited = new Set();
70
+ for (const regNode of regularNodesSet) {
71
+ if (!processedSet.has(regNode)) {
72
+ unvisited.add(regNode);
73
+ }
74
+ }
75
+ //now we have the unvisited nodes.
76
+ // undirected adjacency among cyclic nodes only
77
+ const cycleAdj = new Map();
78
+ for (const id of unvisited)
79
+ cycleAdj.set(id, []);
80
+ for (const edge of edges) {
81
+ if (!unvisited.has(edge.from) || !unvisited.has(edge.to))
82
+ continue; //if any of the node is not in unvisited meaning it is not a cyclic.
83
+ cycleAdj.get(edge.from).push(edge.to);
84
+ cycleAdj.get(edge.to).push(edge.from);
85
+ }
86
+ // DFS to find connected components — each = one CycleGroup
87
+ for (const startId of unvisited) {
88
+ const component = [];
89
+ dfs(startId, unvisited, cycleAdj, component);
90
+ if (component.length > 0) {
91
+ cycleGroups.push({ nodeIds: component, size: component.length });
92
+ }
93
+ }
94
+ }
95
+ return { nodeOrder, cycleGroups, fileNodes };
96
+ }
97
+ function dfs(nodeId, unvisited, adj, component) {
98
+ unvisited.delete(nodeId); // mark visited
99
+ component.push(nodeId);
100
+ for (const adjNode of (adj.get(nodeId) || [])) {
101
+ if (!unvisited.has(adjNode))
102
+ continue; //perform dfs only of adjacent node is not visited
103
+ dfs(adjNode, unvisited, adj, component);
104
+ }
105
+ }
@@ -0,0 +1,57 @@
1
+ import type { PipelineResult } from "../pipeline/index.js";
2
+ export interface NodeSummary {
3
+ technicalSummary: string;
4
+ businessSummary: string;
5
+ security: {
6
+ severity: "none" | "low" | "medium" | "high";
7
+ summary: string;
8
+ };
9
+ model: string;
10
+ summarizedAt: string;
11
+ tokensUsed?: number;
12
+ }
13
+ export interface CycleGroup {
14
+ nodeIds: string[];
15
+ size: number;
16
+ }
17
+ export interface SummaryCheckpoint {
18
+ graphId: string;
19
+ commitHash: string;
20
+ status: "running" | "paused" | "completed";
21
+ createdAt: string;
22
+ updatedAt: string;
23
+ nodeOrder: string[][];
24
+ cycleGroups: CycleGroup[];
25
+ fileNodes: string[];
26
+ lastCompletedLevel: number;
27
+ lastCompletedCycleGroup: number;
28
+ lastCompletedFileNode: number;
29
+ totalNodes: number;
30
+ completedNodes: number;
31
+ }
32
+ export interface SummarizationCallbacks {
33
+ onStarted: (totalNodes: number) => void;
34
+ onProgress: (completed: number, total: number, nodeName: string) => void;
35
+ onPause: () => void;
36
+ onCancel: (cleanedUp: boolean) => void;
37
+ onComplete: () => void;
38
+ onError: (error: string) => void;
39
+ }
40
+ export interface SummarizationInput {
41
+ job: import("../jobs/types.js").Job;
42
+ queue: import("../jobs/queue/interface.js").JobQueue;
43
+ graphId: string;
44
+ commitHash: string;
45
+ repoPath: string;
46
+ previousCommitHash?: string;
47
+ routes: PipelineResult["routes"];
48
+ callbacks: SummarizationCallbacks;
49
+ }
50
+ export interface TopologicalResult {
51
+ nodeOrder: string[][];
52
+ cycleGroups: CycleGroup[];
53
+ fileNodes: string[];
54
+ }
55
+ export declare const MAX_GROUP_SUMMARY_SIZE = 3;
56
+ export declare const MAPREDUCE_TOKEN_THRESHOLD = 1200;
57
+ export declare const FILE_BATCH_SIZE = 10;
@@ -0,0 +1,17 @@
1
+ //storing types used for summarization flow
2
+ // Edge types that drive topological sort order.
3
+ // A node must wait for all nodes it has these edges TO before being summarized.
4
+ // export const HARD_DEPENDENCY_EDGES = new Set([
5
+ // "CALLS",
6
+ // "READS_FROM",
7
+ // "WRITES_TO",
8
+ // "GUARDS",
9
+ // ]);
10
+ // Cycle groups at or below this size → summarize together in one LLM call
11
+ // Above this → summarize individually
12
+ export const MAX_GROUP_SUMMARY_SIZE = 3;
13
+ // Nodes whose source code exceeds this token estimate → MapReduce
14
+ // ~1200 tokens ≈ 900 lines — only very large files hit this
15
+ export const MAPREDUCE_TOKEN_THRESHOLD = 1200;
16
+ // Batch size for the files to be summarized at a time
17
+ export const FILE_BATCH_SIZE = 10;
@@ -0,0 +1,78 @@
1
+ export type Language = "javascript" | "typescript" | "python" | "unknown";
2
+ export type Framework = "nextjs" | "react" | "express" | "fastify" | "koa" | "unknown";
3
+ export type FrontendFramework = "nextjs" | "react";
4
+ export type BackendFramework = "express" | "fastify" | "koa";
5
+ export type RouterType = "app" | "pages" | "app+pages" | "react-router" | "none";
6
+ export type ProjectType = "frontend" | "backend" | "fullstack" | "unknown";
7
+ export type StateLibrary = "zustand" | "redux" | "recoil" | "jotai" | "context-only";
8
+ export type DataFetchingLibrary = "react-query" | "swr" | "axios" | "fetch";
9
+ export type DatabaseLibrary = "prisma" | "drizzle" | "mongodb" | "firebase" | "supabase" | "planetscale" | "postgres" | "mysql" | "sqlite";
10
+ export interface ProjectFingerprint {
11
+ language: Language;
12
+ projectType: ProjectType;
13
+ framework: Framework;
14
+ router: RouterType;
15
+ stateManagement: StateLibrary[];
16
+ dataFetching: DataFetchingLibrary[];
17
+ databases: DatabaseLibrary[];
18
+ rawDependencies: Record<string, string>;
19
+ }
20
+ export type RouteNodeType = "PAGE" | "LAYOUT" | "API_ROUTE" | "LOADING" | "ERROR" | "MIDDLEWARE" | "NOT_FOUND";
21
+ export interface RouteNode {
22
+ type: RouteNodeType;
23
+ nodeId?: string;
24
+ urlPath: string;
25
+ filePath: string;
26
+ isDynamic: boolean;
27
+ isCatchAll: boolean;
28
+ isGroupRoute: boolean;
29
+ layoutPath?: string;
30
+ params?: string[];
31
+ httpMethods?: string[];
32
+ }
33
+ export interface BackendRouteNode {
34
+ type: "BACKEND_ROUTE";
35
+ nodeId?: string;
36
+ urlPath: string;
37
+ filePath: string;
38
+ httpMethod: string;
39
+ handlerName?: string;
40
+ framework: BackendFramework;
41
+ isDynamic: boolean;
42
+ params?: string[];
43
+ inlineHandler?: {
44
+ rawCode: string;
45
+ startLine: number;
46
+ endLine: number;
47
+ };
48
+ }
49
+ export type NodeType = "COMPONENT" | "HOOK" | "FUNCTION" | "STATE_STORE" | "UTILITY" | "FILE" | "GHOST" | "ROUTE" | "TEST" | "STORY" | "THIRD_PARTY";
50
+ export interface CodeNode {
51
+ id: string;
52
+ name: string;
53
+ type: NodeType;
54
+ filePath: string;
55
+ startLine: number;
56
+ endLine: number;
57
+ rawCode?: string;
58
+ codeHash?: string;
59
+ technicalSummary?: string;
60
+ businessSummary?: string;
61
+ security?: {
62
+ severity: "none" | "low" | "medium" | "high";
63
+ summary: string;
64
+ };
65
+ summaryModel?: string;
66
+ summarizedAt?: string;
67
+ isEmbedded?: boolean;
68
+ parentFile?: string;
69
+ score?: Number;
70
+ metadata: Record<string, unknown>;
71
+ }
72
+ export type EdgeType = "CALLS" | "IMPORTS" | "READS_FROM" | "WRITES_TO" | "PROP_PASS" | "EMITS" | "LISTENS" | "WRAPPED_BY" | "GUARDS" | "HANDLES" | "TESTS" | "USES";
73
+ export interface CodeEdge {
74
+ from: string;
75
+ to: string;
76
+ type: EdgeType;
77
+ metadata?: Record<string, unknown>;
78
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "devlensio",
3
+ "version": "0.2.0",
4
+ "description": "Codebase intelligence engine for TypeScript/JavaScript/Reactjs/Nextjs repositories.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "scripts": {
16
+ "dev": "bun --watch src/server/index.ts",
17
+ "start": "bun src/server/index.ts",
18
+ "build": "tsc --project tsconfig.build.json",
19
+ "test": "bun test",
20
+ "export-graph": "bun src/debug/exportGraph.ts"
21
+ },
22
+ "keywords": ["codebase", "graph", "ast", "typescript", "javascript", "devtools"],
23
+ "author": "",
24
+ "license": "AGPL-3.0",
25
+ "files": [
26
+ "dist/**/*"
27
+ ],
28
+ "dependencies": {
29
+ "@anthropic-ai/sdk": "^0.104.2",
30
+ "@google/genai": "^2.8.0",
31
+ "dotenv": "^17.4.2",
32
+ "openai": "^6.42.0",
33
+ "simple-git": "^3.36.0",
34
+ "ts-morph": "^28.0.0",
35
+ "uuid": "^11.0.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/bun": "^1.3.9",
39
+ "@types/jest": "^30.0.0",
40
+ "@types/node": "^25.3.0",
41
+ "@types/uuid": "^11.0.0",
42
+ "jest": "^30.2.0",
43
+ "ts-jest": "^29.4.6",
44
+ "ts-node": "^10.9.2",
45
+ "ts-node-dev": "^2.0.0",
46
+ "typescript": "^5.9.3"
47
+ }
48
+ }