@yuaone/core 0.3.2 → 0.4.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/dist/agent-loop.d.ts +62 -0
  2. package/dist/agent-loop.d.ts.map +1 -1
  3. package/dist/agent-loop.js +705 -18
  4. package/dist/agent-loop.js.map +1 -1
  5. package/dist/background-agent.d.ts +110 -0
  6. package/dist/background-agent.d.ts.map +1 -0
  7. package/dist/background-agent.js +255 -0
  8. package/dist/background-agent.js.map +1 -0
  9. package/dist/coding-standards.d.ts +45 -0
  10. package/dist/coding-standards.d.ts.map +1 -0
  11. package/dist/coding-standards.js +1152 -0
  12. package/dist/coding-standards.js.map +1 -0
  13. package/dist/constants.d.ts.map +1 -1
  14. package/dist/constants.js +2 -6
  15. package/dist/constants.js.map +1 -1
  16. package/dist/context-manager.d.ts +6 -0
  17. package/dist/context-manager.d.ts.map +1 -1
  18. package/dist/context-manager.js +23 -4
  19. package/dist/context-manager.js.map +1 -1
  20. package/dist/index.d.ts +28 -4
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +26 -2
  23. package/dist/index.js.map +1 -1
  24. package/dist/llm-client.d.ts +8 -3
  25. package/dist/llm-client.d.ts.map +1 -1
  26. package/dist/llm-client.js +64 -13
  27. package/dist/llm-client.js.map +1 -1
  28. package/dist/plugin-auto-loader.d.ts +108 -0
  29. package/dist/plugin-auto-loader.d.ts.map +1 -0
  30. package/dist/plugin-auto-loader.js +743 -0
  31. package/dist/plugin-auto-loader.js.map +1 -0
  32. package/dist/plugin-registry.d.ts +112 -0
  33. package/dist/plugin-registry.d.ts.map +1 -0
  34. package/dist/plugin-registry.js +319 -0
  35. package/dist/plugin-registry.js.map +1 -0
  36. package/dist/plugin-types.d.ts +388 -0
  37. package/dist/plugin-types.d.ts.map +1 -0
  38. package/dist/plugin-types.js +8 -0
  39. package/dist/plugin-types.js.map +1 -0
  40. package/dist/plugin-validator.d.ts +54 -0
  41. package/dist/plugin-validator.d.ts.map +1 -0
  42. package/dist/plugin-validator.js +129 -0
  43. package/dist/plugin-validator.js.map +1 -0
  44. package/dist/repo-knowledge-graph.d.ts +112 -0
  45. package/dist/repo-knowledge-graph.d.ts.map +1 -0
  46. package/dist/repo-knowledge-graph.js +561 -0
  47. package/dist/repo-knowledge-graph.js.map +1 -0
  48. package/dist/role-registry.js +1 -1
  49. package/dist/role-registry.js.map +1 -1
  50. package/dist/self-debug-loop.d.ts +257 -0
  51. package/dist/self-debug-loop.d.ts.map +1 -0
  52. package/dist/self-debug-loop.js +870 -0
  53. package/dist/self-debug-loop.js.map +1 -0
  54. package/dist/skill-learner.d.ts +136 -0
  55. package/dist/skill-learner.d.ts.map +1 -0
  56. package/dist/skill-learner.js +382 -0
  57. package/dist/skill-learner.js.map +1 -0
  58. package/dist/skill-loader.d.ts +90 -0
  59. package/dist/skill-loader.d.ts.map +1 -0
  60. package/dist/skill-loader.js +309 -0
  61. package/dist/skill-loader.js.map +1 -0
  62. package/dist/specialist-registry.d.ts +132 -0
  63. package/dist/specialist-registry.d.ts.map +1 -0
  64. package/dist/specialist-registry.js +413 -0
  65. package/dist/specialist-registry.js.map +1 -0
  66. package/dist/sub-agent-prompts.d.ts +45 -0
  67. package/dist/sub-agent-prompts.d.ts.map +1 -0
  68. package/dist/sub-agent-prompts.js +177 -0
  69. package/dist/sub-agent-prompts.js.map +1 -0
  70. package/dist/sub-agent-router.d.ts +75 -0
  71. package/dist/sub-agent-router.d.ts.map +1 -0
  72. package/dist/sub-agent-router.js +174 -0
  73. package/dist/sub-agent-router.js.map +1 -0
  74. package/dist/sub-agent.d.ts +48 -0
  75. package/dist/sub-agent.d.ts.map +1 -1
  76. package/dist/sub-agent.js +108 -5
  77. package/dist/sub-agent.js.map +1 -1
  78. package/dist/system-prompt.d.ts +26 -0
  79. package/dist/system-prompt.d.ts.map +1 -1
  80. package/dist/system-prompt.js +177 -7
  81. package/dist/system-prompt.js.map +1 -1
  82. package/dist/task-classifier.d.ts +25 -1
  83. package/dist/task-classifier.d.ts.map +1 -1
  84. package/dist/task-classifier.js +171 -1
  85. package/dist/task-classifier.js.map +1 -1
  86. package/dist/tool-planner.d.ts +160 -0
  87. package/dist/tool-planner.d.ts.map +1 -0
  88. package/dist/tool-planner.js +501 -0
  89. package/dist/tool-planner.js.map +1 -0
  90. package/dist/types.d.ts +1 -1
  91. package/dist/types.d.ts.map +1 -1
  92. package/dist/world-state.d.ts.map +1 -1
  93. package/dist/world-state.js +8 -1
  94. package/dist/world-state.js.map +1 -1
  95. package/package.json +2 -1
  96. package/plugins/git/patterns/branch-patterns.json +101 -0
  97. package/plugins/git/patterns/commit-patterns.json +186 -0
  98. package/plugins/git/plugin.yaml +128 -0
  99. package/plugins/git/skills/branch-strategy.md +172 -0
  100. package/plugins/git/skills/commit-conv.md +178 -0
  101. package/plugins/git/skills/conflict-resolve.md +159 -0
  102. package/plugins/git/skills/history-clean.md +199 -0
  103. package/plugins/git/skills/pr-review.md +196 -0
  104. package/plugins/git/strategies/conflict-resolve.json +244 -0
  105. package/plugins/git/strategies/release-flow.json +292 -0
  106. package/plugins/git/validators/rules.json +348 -0
  107. package/plugins/react/patterns/anti-patterns.json +88 -0
  108. package/plugins/react/patterns/components.json +80 -0
  109. package/plugins/react/patterns/hooks.json +72 -0
  110. package/plugins/react/plugin.yaml +229 -0
  111. package/plugins/react/skills/bugfix.md +208 -0
  112. package/plugins/react/skills/component-gen.md +206 -0
  113. package/plugins/react/skills/hook-extract.md +208 -0
  114. package/plugins/react/skills/ssr.md +256 -0
  115. package/plugins/react/skills/test.md +273 -0
  116. package/plugins/react/strategies/build-fix.json +43 -0
  117. package/plugins/react/strategies/hook-loop-fix.json +36 -0
  118. package/plugins/react/strategies/hydration-fix.json +42 -0
  119. package/plugins/react/validators/rules.json +92 -0
  120. package/plugins/typescript/patterns/best-practices.json +25 -0
  121. package/plugins/typescript/patterns/common-errors.json +32 -0
  122. package/plugins/typescript/plugin.yaml +74 -0
  123. package/plugins/typescript/skills/debug.md +23 -0
  124. package/plugins/typescript/skills/migration.md +24 -0
  125. package/plugins/typescript/skills/refactor.md +22 -0
  126. package/plugins/typescript/skills/strict-mode.md +23 -0
  127. package/plugins/typescript/strategies/strict-migration.json +37 -0
  128. package/plugins/typescript/strategies/type-error-fix.json +37 -0
  129. package/plugins/typescript/validators/rules.json +28 -0
@@ -0,0 +1,112 @@
1
+ /**
2
+ * @module repo-knowledge-graph
3
+ * @description Repo Knowledge Graph — Code structure as a queryable graph.
4
+ *
5
+ * Nodes: File, Class, Function, Variable, Interface, Type, Module
6
+ * Edges: imports, calls, extends, implements, depends_on, exports, contains
7
+ *
8
+ * Enables: impact analysis, refactor safety, dependency tracing, dead code detection.
9
+ *
10
+ * Uses lightweight regex-based parsing (not full AST) for speed.
11
+ */
12
+ export type NodeType = "file" | "class" | "function" | "variable" | "interface" | "type" | "module";
13
+ export type EdgeType = "imports" | "calls" | "extends" | "implements" | "depends_on" | "exports" | "contains";
14
+ export interface GraphNode {
15
+ /** Unique: "file:src/index.ts" or "fn:src/index.ts:myFunc" */
16
+ id: string;
17
+ type: NodeType;
18
+ name: string;
19
+ filePath: string;
20
+ line?: number;
21
+ metadata?: Record<string, unknown>;
22
+ }
23
+ export interface GraphEdge {
24
+ from: string;
25
+ to: string;
26
+ type: EdgeType;
27
+ metadata?: Record<string, unknown>;
28
+ }
29
+ export interface ImpactReport {
30
+ /** Directly affected nodes */
31
+ direct: GraphNode[];
32
+ /** Transitively affected (2+ hops) */
33
+ transitive: GraphNode[];
34
+ /** Total impact radius */
35
+ radius: number;
36
+ /** Risk level based on affected node count */
37
+ risk: "low" | "medium" | "high" | "critical";
38
+ /** Files that need to be checked/updated */
39
+ affectedFiles: string[];
40
+ }
41
+ /**
42
+ * RepoKnowledgeGraph — queryable code structure graph.
43
+ *
44
+ * Builds a lightweight graph from TypeScript/JavaScript source files
45
+ * using regex-based parsing. Supports impact analysis, dead code detection,
46
+ * dependency tracing, and incremental updates.
47
+ */
48
+ export declare class RepoKnowledgeGraph {
49
+ private nodes;
50
+ private edges;
51
+ private storagePath;
52
+ constructor(projectRoot: string);
53
+ /**
54
+ * Load persisted graph from disk.
55
+ */
56
+ init(): Promise<void>;
57
+ /** Add a node to the graph */
58
+ addNode(node: GraphNode): void;
59
+ /** Add an edge (deduplicates) */
60
+ addEdge(edge: GraphEdge): void;
61
+ /** Find all callers of a function/method */
62
+ findCallers(nodeId: string): GraphNode[];
63
+ /** Find all dependents of a file/module */
64
+ findDependents(nodeId: string): GraphNode[];
65
+ /** Analyze impact of changing a node (BFS traversal) */
66
+ analyzeImpact(nodeId: string, maxDepth?: number): ImpactReport;
67
+ /**
68
+ * Find dead code — nodes with no incoming edges (except file nodes).
69
+ * File nodes are always "alive". Only exported symbols that are never
70
+ * imported elsewhere are considered dead.
71
+ */
72
+ findDeadCode(): GraphNode[];
73
+ /** Get subgraph for a specific file */
74
+ getFileGraph(filePath: string): {
75
+ nodes: GraphNode[];
76
+ edges: GraphEdge[];
77
+ };
78
+ /**
79
+ * Build graph from TypeScript source files (regex-based parsing).
80
+ * Scans .ts/.tsx files under projectRoot, extracts imports, exports,
81
+ * classes, functions, interfaces, and types.
82
+ */
83
+ buildFromProject(projectRoot: string, filePatterns?: string[]): Promise<void>;
84
+ /**
85
+ * Incrementally update graph for changed files.
86
+ * Removes old nodes/edges for the file, then re-indexes.
87
+ */
88
+ updateFiles(changedFiles: string[]): Promise<void>;
89
+ /** Persist graph to disk */
90
+ save(): Promise<void>;
91
+ /** Get graph statistics */
92
+ getStats(): {
93
+ nodes: number;
94
+ edges: number;
95
+ files: number;
96
+ };
97
+ /** Get a node by ID */
98
+ getNode(id: string): GraphNode | undefined;
99
+ /** Get all nodes */
100
+ getAllNodes(): GraphNode[];
101
+ /** Get all edges */
102
+ getAllEdges(): GraphEdge[];
103
+ private load;
104
+ private collectFiles;
105
+ private indexFile;
106
+ private parseImports;
107
+ private parseDeclarations;
108
+ private getLineNumber;
109
+ private findNodeByName;
110
+ private inferNodeType;
111
+ }
112
+ //# sourceMappingURL=repo-knowledge-graph.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repo-knowledge-graph.d.ts","sourceRoot":"","sources":["../src/repo-knowledge-graph.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,561 @@
1
+ /**
2
+ * @module repo-knowledge-graph
3
+ * @description Repo Knowledge Graph — Code structure as a queryable graph.
4
+ *
5
+ * Nodes: File, Class, Function, Variable, Interface, Type, Module
6
+ * Edges: imports, calls, extends, implements, depends_on, exports, contains
7
+ *
8
+ * Enables: impact analysis, refactor safety, dependency tracing, dead code detection.
9
+ *
10
+ * Uses lightweight regex-based parsing (not full AST) for speed.
11
+ */
12
+ import { readFile, writeFile, readdir, stat, mkdir } from "node:fs/promises";
13
+ import { join, relative, dirname, extname, basename } from "node:path";
14
+ // ─── Regex Patterns for Parsing ───
15
+ const IMPORT_PATTERN = /^import\s+(?:(?:type\s+)?(?:\{[^}]*\}|[\w*]+(?:\s+as\s+\w+)?|\*\s+as\s+\w+)(?:\s*,\s*(?:\{[^}]*\}|[\w*]+))*\s+from\s+)?['"]([^'"]+)['"]/gm;
16
+ const EXPORT_NAMED_PATTERN = /^export\s+(?:type\s+)?(?:interface|type|class|function|const|let|var|enum|abstract\s+class)\s+(\w+)/gm;
17
+ const EXPORT_DEFAULT_PATTERN = /^export\s+default\s+(?:class|function|abstract\s+class)\s+(\w+)/gm;
18
+ const CLASS_PATTERN = /^(?:export\s+)?(?:abstract\s+)?class\s+(\w+)(?:\s+extends\s+(\w+))?(?:\s+implements\s+([\w,\s]+))?/gm;
19
+ const FUNCTION_PATTERN = /^(?:export\s+)?(?:async\s+)?function\s+(\w+)/gm;
20
+ const INTERFACE_PATTERN = /^(?:export\s+)?interface\s+(\w+)(?:\s+extends\s+([\w,\s]+))?/gm;
21
+ const TYPE_ALIAS_PATTERN = /^(?:export\s+)?type\s+(\w+)\s*=/gm;
22
+ const CONST_EXPORT_PATTERN = /^(?:export\s+)?(?:const|let|var)\s+(\w+)/gm;
23
+ // ─── RepoKnowledgeGraph ───
24
+ /**
25
+ * RepoKnowledgeGraph — queryable code structure graph.
26
+ *
27
+ * Builds a lightweight graph from TypeScript/JavaScript source files
28
+ * using regex-based parsing. Supports impact analysis, dead code detection,
29
+ * dependency tracing, and incremental updates.
30
+ */
31
+ export class RepoKnowledgeGraph {
32
+ nodes = new Map();
33
+ edges = [];
34
+ storagePath;
35
+ constructor(projectRoot) {
36
+ this.storagePath = join(projectRoot, ".yuan", "knowledge-graph.json");
37
+ }
38
+ /**
39
+ * Load persisted graph from disk.
40
+ */
41
+ async init() {
42
+ await this.load();
43
+ }
44
+ /** Add a node to the graph */
45
+ addNode(node) {
46
+ this.nodes.set(node.id, node);
47
+ }
48
+ /** Add an edge (deduplicates) */
49
+ addEdge(edge) {
50
+ const exists = this.edges.some((e) => e.from === edge.from && e.to === edge.to && e.type === edge.type);
51
+ if (!exists) {
52
+ this.edges.push(edge);
53
+ }
54
+ }
55
+ /** Find all callers of a function/method */
56
+ findCallers(nodeId) {
57
+ const callerIds = this.edges
58
+ .filter((e) => e.to === nodeId && e.type === "calls")
59
+ .map((e) => e.from);
60
+ return callerIds
61
+ .map((id) => this.nodes.get(id))
62
+ .filter((n) => n !== undefined);
63
+ }
64
+ /** Find all dependents of a file/module */
65
+ findDependents(nodeId) {
66
+ const depIds = new Set();
67
+ for (const edge of this.edges) {
68
+ if (edge.to === nodeId && (edge.type === "imports" || edge.type === "depends_on")) {
69
+ depIds.add(edge.from);
70
+ }
71
+ }
72
+ return Array.from(depIds)
73
+ .map((id) => this.nodes.get(id))
74
+ .filter((n) => n !== undefined);
75
+ }
76
+ /** Analyze impact of changing a node (BFS traversal) */
77
+ analyzeImpact(nodeId, maxDepth = 3) {
78
+ const direct = [];
79
+ const transitive = [];
80
+ const visited = new Set();
81
+ visited.add(nodeId);
82
+ const queue = [];
83
+ // Seed with direct dependents
84
+ for (const edge of this.edges) {
85
+ if (edge.to === nodeId &&
86
+ (edge.type === "imports" ||
87
+ edge.type === "depends_on" ||
88
+ edge.type === "calls" ||
89
+ edge.type === "extends" ||
90
+ edge.type === "implements")) {
91
+ if (!visited.has(edge.from)) {
92
+ visited.add(edge.from);
93
+ queue.push({ id: edge.from, depth: 1 });
94
+ }
95
+ }
96
+ }
97
+ while (queue.length > 0) {
98
+ const item = queue.shift();
99
+ const node = this.nodes.get(item.id);
100
+ if (!node)
101
+ continue;
102
+ if (item.depth === 1) {
103
+ direct.push(node);
104
+ }
105
+ else {
106
+ transitive.push(node);
107
+ }
108
+ // Continue BFS if within depth limit
109
+ if (item.depth < maxDepth) {
110
+ for (const edge of this.edges) {
111
+ if (edge.to === item.id &&
112
+ !visited.has(edge.from) &&
113
+ (edge.type === "imports" ||
114
+ edge.type === "depends_on" ||
115
+ edge.type === "calls")) {
116
+ visited.add(edge.from);
117
+ queue.push({ id: edge.from, depth: item.depth + 1 });
118
+ }
119
+ }
120
+ }
121
+ }
122
+ // Collect unique affected files
123
+ const affectedFiles = new Set();
124
+ for (const n of [...direct, ...transitive]) {
125
+ affectedFiles.add(n.filePath);
126
+ }
127
+ const totalAffected = direct.length + transitive.length;
128
+ let risk;
129
+ if (totalAffected === 0)
130
+ risk = "low";
131
+ else if (totalAffected <= 3)
132
+ risk = "medium";
133
+ else if (totalAffected <= 10)
134
+ risk = "high";
135
+ else
136
+ risk = "critical";
137
+ return {
138
+ direct,
139
+ transitive,
140
+ radius: visited.size - 1,
141
+ risk,
142
+ affectedFiles: Array.from(affectedFiles),
143
+ };
144
+ }
145
+ /**
146
+ * Find dead code — nodes with no incoming edges (except file nodes).
147
+ * File nodes are always "alive". Only exported symbols that are never
148
+ * imported elsewhere are considered dead.
149
+ */
150
+ findDeadCode() {
151
+ const nodesWithIncoming = new Set();
152
+ for (const edge of this.edges) {
153
+ nodesWithIncoming.add(edge.to);
154
+ }
155
+ const dead = [];
156
+ for (const [id, node] of this.nodes) {
157
+ // Skip file/module nodes — they're always roots
158
+ if (node.type === "file" || node.type === "module")
159
+ continue;
160
+ if (!nodesWithIncoming.has(id)) {
161
+ dead.push(node);
162
+ }
163
+ }
164
+ return dead;
165
+ }
166
+ /** Get subgraph for a specific file */
167
+ getFileGraph(filePath) {
168
+ const fileNodes = [];
169
+ const fileEdges = [];
170
+ const nodeIds = new Set();
171
+ for (const [id, node] of this.nodes) {
172
+ if (node.filePath === filePath) {
173
+ fileNodes.push(node);
174
+ nodeIds.add(id);
175
+ }
176
+ }
177
+ for (const edge of this.edges) {
178
+ if (nodeIds.has(edge.from) || nodeIds.has(edge.to)) {
179
+ fileEdges.push(edge);
180
+ }
181
+ }
182
+ return { nodes: fileNodes, edges: fileEdges };
183
+ }
184
+ /**
185
+ * Build graph from TypeScript source files (regex-based parsing).
186
+ * Scans .ts/.tsx files under projectRoot, extracts imports, exports,
187
+ * classes, functions, interfaces, and types.
188
+ */
189
+ async buildFromProject(projectRoot, filePatterns) {
190
+ const extensions = new Set([".ts", ".tsx", ".js", ".jsx"]);
191
+ const ignorePatterns = [
192
+ "node_modules",
193
+ "dist",
194
+ ".git",
195
+ ".next",
196
+ "coverage",
197
+ "__pycache__",
198
+ ];
199
+ const files = await this.collectFiles(projectRoot, extensions, ignorePatterns);
200
+ // Apply file pattern filter if provided
201
+ const filtered = filePatterns
202
+ ? files.filter((f) => filePatterns.some((p) => {
203
+ try {
204
+ return new RegExp(p).test(f);
205
+ }
206
+ catch {
207
+ return f.includes(p);
208
+ }
209
+ }))
210
+ : files;
211
+ for (const filePath of filtered) {
212
+ await this.indexFile(projectRoot, filePath);
213
+ }
214
+ }
215
+ /**
216
+ * Incrementally update graph for changed files.
217
+ * Removes old nodes/edges for the file, then re-indexes.
218
+ */
219
+ async updateFiles(changedFiles) {
220
+ for (const filePath of changedFiles) {
221
+ // Remove old nodes for this file
222
+ const oldNodeIds = [];
223
+ for (const [id, node] of this.nodes) {
224
+ if (node.filePath === filePath) {
225
+ oldNodeIds.push(id);
226
+ }
227
+ }
228
+ for (const id of oldNodeIds) {
229
+ this.nodes.delete(id);
230
+ }
231
+ // Remove old edges involving these nodes
232
+ const oldIdSet = new Set(oldNodeIds);
233
+ this.edges = this.edges.filter((e) => !oldIdSet.has(e.from) && !oldIdSet.has(e.to));
234
+ // Determine project root from storage path
235
+ const projectRoot = dirname(dirname(this.storagePath)); // .yuan/../..
236
+ // Re-index if file still exists
237
+ try {
238
+ await stat(filePath);
239
+ await this.indexFile(projectRoot, filePath);
240
+ }
241
+ catch {
242
+ // File was deleted — removal above is sufficient
243
+ }
244
+ }
245
+ }
246
+ /** Persist graph to disk */
247
+ async save() {
248
+ const dir = dirname(this.storagePath);
249
+ await mkdir(dir, { recursive: true });
250
+ const data = {
251
+ nodes: Array.from(this.nodes.values()),
252
+ edges: this.edges,
253
+ version: 1,
254
+ updatedAt: Date.now(),
255
+ };
256
+ await writeFile(this.storagePath, JSON.stringify(data), "utf-8");
257
+ }
258
+ /** Get graph statistics */
259
+ getStats() {
260
+ const files = new Set();
261
+ for (const node of this.nodes.values()) {
262
+ files.add(node.filePath);
263
+ }
264
+ return {
265
+ nodes: this.nodes.size,
266
+ edges: this.edges.length,
267
+ files: files.size,
268
+ };
269
+ }
270
+ /** Get a node by ID */
271
+ getNode(id) {
272
+ return this.nodes.get(id);
273
+ }
274
+ /** Get all nodes */
275
+ getAllNodes() {
276
+ return Array.from(this.nodes.values());
277
+ }
278
+ /** Get all edges */
279
+ getAllEdges() {
280
+ return [...this.edges];
281
+ }
282
+ // ─── Private: Persistence ───
283
+ async load() {
284
+ try {
285
+ const raw = await readFile(this.storagePath, "utf-8");
286
+ const data = JSON.parse(raw);
287
+ this.nodes.clear();
288
+ this.edges = [];
289
+ for (const node of data.nodes) {
290
+ this.nodes.set(node.id, node);
291
+ }
292
+ this.edges = data.edges ?? [];
293
+ }
294
+ catch {
295
+ // No persisted graph — start fresh
296
+ }
297
+ }
298
+ // ─── Private: File Collection ───
299
+ async collectFiles(dir, extensions, ignore) {
300
+ const results = [];
301
+ try {
302
+ const entries = await readdir(dir, { withFileTypes: true });
303
+ for (const entry of entries) {
304
+ if (ignore.includes(entry.name))
305
+ continue;
306
+ const fullPath = join(dir, entry.name);
307
+ if (entry.isDirectory()) {
308
+ const sub = await this.collectFiles(fullPath, extensions, ignore);
309
+ results.push(...sub);
310
+ }
311
+ else if (entry.isFile() && extensions.has(extname(entry.name))) {
312
+ results.push(fullPath);
313
+ }
314
+ }
315
+ }
316
+ catch {
317
+ // Permission error or similar — skip
318
+ }
319
+ return results;
320
+ }
321
+ // ─── Private: Indexing ───
322
+ async indexFile(projectRoot, filePath) {
323
+ let content;
324
+ try {
325
+ content = await readFile(filePath, "utf-8");
326
+ }
327
+ catch {
328
+ return;
329
+ }
330
+ const relPath = relative(projectRoot, filePath);
331
+ const fileId = `file:${relPath}`;
332
+ // Add file node
333
+ this.addNode({
334
+ id: fileId,
335
+ type: "file",
336
+ name: basename(filePath),
337
+ filePath: relPath,
338
+ });
339
+ // Parse imports
340
+ this.parseImports(content, relPath, fileId, projectRoot);
341
+ // Parse exports and declarations
342
+ this.parseDeclarations(content, relPath, fileId);
343
+ }
344
+ parseImports(content, relPath, fileId, _projectRoot) {
345
+ // Reset lastIndex for global regex
346
+ IMPORT_PATTERN.lastIndex = 0;
347
+ let match;
348
+ while ((match = IMPORT_PATTERN.exec(content)) !== null) {
349
+ const importPath = match[1];
350
+ // Resolve relative imports to file nodes
351
+ if (importPath.startsWith(".")) {
352
+ const dir = dirname(relPath);
353
+ let resolved = join(dir, importPath);
354
+ // Normalize extension
355
+ if (!extname(resolved)) {
356
+ resolved += ".ts"; // Default assumption
357
+ }
358
+ // Remove leading ./ and normalize
359
+ resolved = resolved.replace(/\\/g, "/");
360
+ const targetId = `file:${resolved}`;
361
+ this.addEdge({
362
+ from: fileId,
363
+ to: targetId,
364
+ type: "imports",
365
+ metadata: { importPath },
366
+ });
367
+ }
368
+ else {
369
+ // External module — create module node
370
+ const moduleId = `module:${importPath}`;
371
+ if (!this.nodes.has(moduleId)) {
372
+ this.addNode({
373
+ id: moduleId,
374
+ type: "module",
375
+ name: importPath,
376
+ filePath: "",
377
+ });
378
+ }
379
+ this.addEdge({
380
+ from: fileId,
381
+ to: moduleId,
382
+ type: "imports",
383
+ metadata: { importPath },
384
+ });
385
+ }
386
+ }
387
+ }
388
+ parseDeclarations(content, relPath, fileId) {
389
+ const lines = content.split("\n");
390
+ // Track which patterns we're using with their node types
391
+ const patterns = [
392
+ { regex: CLASS_PATTERN, nodeType: "class", prefix: "class" },
393
+ { regex: FUNCTION_PATTERN, nodeType: "function", prefix: "fn" },
394
+ { regex: INTERFACE_PATTERN, nodeType: "interface", prefix: "iface" },
395
+ { regex: TYPE_ALIAS_PATTERN, nodeType: "type", prefix: "type" },
396
+ ];
397
+ for (const { regex, nodeType, prefix } of patterns) {
398
+ regex.lastIndex = 0;
399
+ let match;
400
+ while ((match = regex.exec(content)) !== null) {
401
+ const name = match[1];
402
+ const nodeId = `${prefix}:${relPath}:${name}`;
403
+ const line = this.getLineNumber(content, match.index);
404
+ this.addNode({
405
+ id: nodeId,
406
+ type: nodeType,
407
+ name,
408
+ filePath: relPath,
409
+ line,
410
+ });
411
+ // File contains this declaration
412
+ this.addEdge({
413
+ from: fileId,
414
+ to: nodeId,
415
+ type: "contains",
416
+ });
417
+ // Handle class extends
418
+ if (nodeType === "class" && match[2]) {
419
+ const parentName = match[2].trim();
420
+ // Create a tentative edge — target might be resolved later
421
+ this.addEdge({
422
+ from: nodeId,
423
+ to: `class:*:${parentName}`,
424
+ type: "extends",
425
+ metadata: { parentName },
426
+ });
427
+ }
428
+ // Handle class implements
429
+ if (nodeType === "class" && match[3]) {
430
+ const ifaces = match[3]
431
+ .split(",")
432
+ .map((s) => s.trim())
433
+ .filter(Boolean);
434
+ for (const iface of ifaces) {
435
+ this.addEdge({
436
+ from: nodeId,
437
+ to: `iface:*:${iface}`,
438
+ type: "implements",
439
+ metadata: { interfaceName: iface },
440
+ });
441
+ }
442
+ }
443
+ // Handle interface extends
444
+ if (nodeType === "interface" && match[2]) {
445
+ const parents = match[2]
446
+ .split(",")
447
+ .map((s) => s.trim())
448
+ .filter(Boolean);
449
+ for (const parent of parents) {
450
+ this.addEdge({
451
+ from: nodeId,
452
+ to: `iface:*:${parent}`,
453
+ type: "extends",
454
+ metadata: { parentName: parent },
455
+ });
456
+ }
457
+ }
458
+ }
459
+ }
460
+ // Parse exported constants/variables
461
+ CONST_EXPORT_PATTERN.lastIndex = 0;
462
+ let constMatch;
463
+ while ((constMatch = CONST_EXPORT_PATTERN.exec(content)) !== null) {
464
+ const name = constMatch[1];
465
+ // Only track exports (not internal variables)
466
+ if (constMatch[0].startsWith("export")) {
467
+ const nodeId = `var:${relPath}:${name}`;
468
+ const line = this.getLineNumber(content, constMatch.index);
469
+ this.addNode({
470
+ id: nodeId,
471
+ type: "variable",
472
+ name,
473
+ filePath: relPath,
474
+ line,
475
+ });
476
+ this.addEdge({
477
+ from: fileId,
478
+ to: nodeId,
479
+ type: "exports",
480
+ });
481
+ }
482
+ }
483
+ // Parse export default
484
+ EXPORT_DEFAULT_PATTERN.lastIndex = 0;
485
+ let defaultMatch;
486
+ while ((defaultMatch = EXPORT_DEFAULT_PATTERN.exec(content)) !== null) {
487
+ const name = defaultMatch[1];
488
+ const nodeId = `default:${relPath}:${name}`;
489
+ const line = this.getLineNumber(content, defaultMatch.index);
490
+ this.addNode({
491
+ id: nodeId,
492
+ type: "class",
493
+ name,
494
+ filePath: relPath,
495
+ line,
496
+ metadata: { isDefault: true },
497
+ });
498
+ this.addEdge({
499
+ from: fileId,
500
+ to: nodeId,
501
+ type: "exports",
502
+ });
503
+ }
504
+ // Parse named exports
505
+ EXPORT_NAMED_PATTERN.lastIndex = 0;
506
+ let namedMatch;
507
+ while ((namedMatch = EXPORT_NAMED_PATTERN.exec(content)) !== null) {
508
+ const name = namedMatch[1];
509
+ // Check if we already have this node (from class/function parsing above)
510
+ const existingId = this.findNodeByName(relPath, name);
511
+ const nodeId = existingId ?? `export:${relPath}:${name}`;
512
+ if (!existingId) {
513
+ const line = this.getLineNumber(content, namedMatch.index);
514
+ this.addNode({
515
+ id: nodeId,
516
+ type: this.inferNodeType(namedMatch[0]),
517
+ name,
518
+ filePath: relPath,
519
+ line,
520
+ });
521
+ }
522
+ this.addEdge({
523
+ from: fileId,
524
+ to: nodeId,
525
+ type: "exports",
526
+ });
527
+ }
528
+ }
529
+ // ─── Private: Utilities ───
530
+ getLineNumber(content, index) {
531
+ let line = 1;
532
+ for (let i = 0; i < index; i++) {
533
+ if (content[i] === "\n")
534
+ line++;
535
+ }
536
+ return line;
537
+ }
538
+ findNodeByName(filePath, name) {
539
+ const prefixes = ["class", "fn", "iface", "type", "var", "default"];
540
+ for (const prefix of prefixes) {
541
+ const id = `${prefix}:${filePath}:${name}`;
542
+ if (this.nodes.has(id))
543
+ return id;
544
+ }
545
+ return undefined;
546
+ }
547
+ inferNodeType(declaration) {
548
+ if (/\bclass\b/.test(declaration))
549
+ return "class";
550
+ if (/\bfunction\b/.test(declaration))
551
+ return "function";
552
+ if (/\binterface\b/.test(declaration))
553
+ return "interface";
554
+ if (/\btype\b/.test(declaration))
555
+ return "type";
556
+ if (/\b(?:const|let|var)\b/.test(declaration))
557
+ return "variable";
558
+ return "variable";
559
+ }
560
+ }
561
+ //# sourceMappingURL=repo-knowledge-graph.js.map