@hawon/nexus 0.3.0 → 0.3.1

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 (152) hide show
  1. package/dist/cli/index.d.ts +2 -0
  2. package/{src/codebase/index.ts → dist/codebase/index.d.ts} +1 -6
  3. package/dist/codebase/index.js +2 -0
  4. package/dist/codebase/mapper.d.ts +2 -0
  5. package/dist/codebase/mapper.js +446 -0
  6. package/dist/codebase/onboard.d.ts +2 -0
  7. package/dist/codebase/onboard.js +179 -0
  8. package/dist/codebase/types.d.ts +39 -0
  9. package/dist/codebase/types.js +1 -0
  10. package/dist/config/index.js +1 -0
  11. package/dist/config/types.d.ts +13 -0
  12. package/dist/config/types.js +1 -0
  13. package/dist/config/validator.d.ts +2 -0
  14. package/dist/config/validator.js +342 -0
  15. package/{src/index.ts → dist/index.d.ts} +0 -17
  16. package/dist/mcp/server.d.ts +15 -0
  17. package/dist/memory-engine/index.js +2 -0
  18. package/dist/memory-engine/nexus-memory.d.ts +129 -0
  19. package/dist/memory-engine/nexus-memory.js +484 -0
  20. package/dist/memory-engine/semantic.d.ts +75 -0
  21. package/dist/memory-engine/semantic.js +510 -0
  22. package/dist/obsidian/daily-note.d.ts +2 -0
  23. package/dist/obsidian/daily-note.js +65 -0
  24. package/dist/obsidian/exporter.d.ts +3 -0
  25. package/dist/obsidian/exporter.js +259 -0
  26. package/dist/obsidian/index.js +3 -0
  27. package/dist/obsidian/moc.d.ts +2 -0
  28. package/dist/obsidian/moc.js +150 -0
  29. package/dist/obsidian/types.d.ts +15 -0
  30. package/dist/obsidian/types.js +1 -0
  31. package/dist/parser/discover.d.ts +2 -0
  32. package/dist/parser/discover.js +52 -0
  33. package/dist/parser/index.d.ts +5 -0
  34. package/dist/parser/index.js +4 -0
  35. package/dist/parser/openclaw-parser.d.ts +3 -0
  36. package/dist/parser/openclaw-parser.js +214 -0
  37. package/dist/parser/parse.d.ts +2 -0
  38. package/dist/parser/parse.js +210 -0
  39. package/dist/parser/types.d.ts +46 -0
  40. package/dist/parser/types.js +1 -0
  41. package/dist/parser/unified.d.ts +6 -0
  42. package/dist/parser/unified.js +37 -0
  43. package/dist/promptguard/advanced-rules.d.ts +16 -0
  44. package/dist/promptguard/advanced-rules.js +236 -0
  45. package/dist/promptguard/entropy.d.ts +57 -0
  46. package/dist/promptguard/entropy.js +256 -0
  47. package/dist/promptguard/evolution/auto-update.d.ts +82 -0
  48. package/dist/promptguard/evolution/auto-update.js +138 -0
  49. package/dist/promptguard/evolution/corpus.d.ts +83 -0
  50. package/dist/promptguard/evolution/corpus.js +127 -0
  51. package/dist/promptguard/evolution/index.d.ts +6 -0
  52. package/dist/promptguard/evolution/index.js +3 -0
  53. package/dist/promptguard/evolution/rule-evolver.d.ts +56 -0
  54. package/dist/promptguard/evolution/rule-evolver.js +264 -0
  55. package/dist/promptguard/index.js +8 -0
  56. package/dist/promptguard/multilingual-rules.d.ts +15 -0
  57. package/dist/promptguard/multilingual-rules.js +333 -0
  58. package/dist/promptguard/normalize.d.ts +35 -0
  59. package/dist/promptguard/normalize.js +198 -0
  60. package/dist/promptguard/rules.d.ts +13 -0
  61. package/dist/promptguard/rules.js +211 -0
  62. package/dist/promptguard/scanner.d.ts +44 -0
  63. package/dist/promptguard/scanner.js +217 -0
  64. package/dist/promptguard/semantic.d.ts +18 -0
  65. package/dist/promptguard/semantic.js +267 -0
  66. package/dist/promptguard/token-analysis.d.ts +27 -0
  67. package/dist/promptguard/token-analysis.js +236 -0
  68. package/dist/promptguard/types.d.ts +85 -0
  69. package/dist/promptguard/types.js +1 -0
  70. package/dist/review/analyzer.d.ts +2 -0
  71. package/dist/review/analyzer.js +554 -0
  72. package/dist/review/diff-reviewer.d.ts +2 -0
  73. package/dist/review/diff-reviewer.js +142 -0
  74. package/dist/review/index.d.ts +3 -0
  75. package/dist/review/index.js +2 -0
  76. package/dist/review/types.d.ts +24 -0
  77. package/dist/review/types.js +1 -0
  78. package/dist/shared/stop-words.d.ts +1 -0
  79. package/dist/shared/stop-words.js +21 -0
  80. package/dist/skills/index.d.ts +2 -0
  81. package/dist/skills/index.js +1 -0
  82. package/dist/skills/memory-skill-engine.d.ts +124 -0
  83. package/dist/skills/memory-skill-engine.js +832 -0
  84. package/dist/testing/health-check.d.ts +2 -0
  85. package/dist/testing/health-check.js +386 -0
  86. package/dist/testing/index.js +2 -0
  87. package/dist/testing/test-fixer.d.ts +2 -0
  88. package/dist/testing/test-fixer.js +103 -0
  89. package/dist/testing/types.d.ts +17 -0
  90. package/dist/testing/types.js +1 -0
  91. package/package.json +9 -4
  92. package/scripts/auto-skill.sh +0 -54
  93. package/scripts/auto-sync.sh +0 -11
  94. package/scripts/benchmark.ts +0 -444
  95. package/scripts/demo-hero.sh +0 -52
  96. package/scripts/demo-injection.sh +0 -25
  97. package/scripts/demo-map.sh +0 -19
  98. package/scripts/demo-review.sh +0 -40
  99. package/scripts/demo-sessions.sh +0 -19
  100. package/scripts/demo.sh +0 -101
  101. package/scripts/scan-tool-result.sh +0 -46
  102. package/src/cli/index.ts +0 -922
  103. package/src/codebase/mapper.ts +0 -485
  104. package/src/codebase/onboard.ts +0 -200
  105. package/src/codebase/types.ts +0 -43
  106. package/src/config/types.ts +0 -14
  107. package/src/config/validator.ts +0 -368
  108. package/src/mcp/server.ts +0 -309
  109. package/src/memory-engine/nexus-memory.test.ts +0 -437
  110. package/src/memory-engine/nexus-memory.ts +0 -631
  111. package/src/memory-engine/semantic.ts +0 -380
  112. package/src/obsidian/daily-note.ts +0 -84
  113. package/src/obsidian/exporter.ts +0 -310
  114. package/src/obsidian/moc.ts +0 -169
  115. package/src/obsidian/types.ts +0 -16
  116. package/src/parser/discover.ts +0 -57
  117. package/src/parser/index.ts +0 -15
  118. package/src/parser/openclaw-parser.ts +0 -275
  119. package/src/parser/parse.ts +0 -263
  120. package/src/parser/types.ts +0 -45
  121. package/src/parser/unified.ts +0 -61
  122. package/src/promptguard/advanced-rules.ts +0 -290
  123. package/src/promptguard/entropy.ts +0 -314
  124. package/src/promptguard/evolution/auto-update.ts +0 -192
  125. package/src/promptguard/evolution/corpus.ts +0 -224
  126. package/src/promptguard/evolution/index.ts +0 -23
  127. package/src/promptguard/evolution/rule-evolver.ts +0 -347
  128. package/src/promptguard/multilingual-rules.ts +0 -344
  129. package/src/promptguard/normalize.ts +0 -240
  130. package/src/promptguard/rules.ts +0 -257
  131. package/src/promptguard/scanner.test.ts +0 -262
  132. package/src/promptguard/scanner.ts +0 -285
  133. package/src/promptguard/semantic.ts +0 -317
  134. package/src/promptguard/token-analysis.ts +0 -304
  135. package/src/promptguard/types.ts +0 -83
  136. package/src/review/analyzer.test.ts +0 -279
  137. package/src/review/analyzer.ts +0 -944
  138. package/src/review/diff-reviewer.ts +0 -191
  139. package/src/review/index.ts +0 -10
  140. package/src/review/types.ts +0 -38
  141. package/src/shared/stop-words.ts +0 -21
  142. package/src/skills/index.ts +0 -13
  143. package/src/skills/memory-skill-engine.ts +0 -1044
  144. package/src/testing/health-check.ts +0 -404
  145. package/src/testing/test-fixer.ts +0 -129
  146. package/src/testing/types.ts +0 -18
  147. package/tsconfig.json +0 -16
  148. /package/{src/config/index.ts → dist/config/index.d.ts} +0 -0
  149. /package/{src/memory-engine/index.ts → dist/memory-engine/index.d.ts} +0 -0
  150. /package/{src/obsidian/index.ts → dist/obsidian/index.d.ts} +0 -0
  151. /package/{src/promptguard/index.ts → dist/promptguard/index.d.ts} +0 -0
  152. /package/{src/testing/index.ts → dist/testing/index.d.ts} +0 -0
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -1,8 +1,3 @@
1
1
  export { mapCodebase } from "./mapper.js";
2
2
  export { generateOnboardingGuide } from "./onboard.js";
3
- export type {
4
- FileNode,
5
- DependencyEdge,
6
- CodebaseMap,
7
- MapOptions,
8
- } from "./types.js";
3
+ export type { FileNode, DependencyEdge, CodebaseMap, MapOptions, } from "./types.js";
@@ -0,0 +1,2 @@
1
+ export { mapCodebase } from "./mapper.js";
2
+ export { generateOnboardingGuide } from "./onboard.js";
@@ -0,0 +1,2 @@
1
+ import type { CodebaseMap, MapOptions } from "./types.js";
2
+ export declare function mapCodebase(options: MapOptions): Promise<CodebaseMap>;
@@ -0,0 +1,446 @@
1
+ import { readdir, readFile, stat } from "node:fs/promises";
2
+ import { join, relative, extname, resolve } from "node:path";
3
+ const DEFAULT_IGNORE = [
4
+ "node_modules",
5
+ ".git",
6
+ "dist",
7
+ "build",
8
+ "__pycache__",
9
+ ".next",
10
+ ".nuxt",
11
+ "coverage",
12
+ ".cache",
13
+ ".venv",
14
+ "venv",
15
+ "target",
16
+ ];
17
+ const LANGUAGE_MAP = {
18
+ ".ts": "TypeScript",
19
+ ".tsx": "TypeScript",
20
+ ".js": "JavaScript",
21
+ ".jsx": "JavaScript",
22
+ ".mjs": "JavaScript",
23
+ ".cjs": "JavaScript",
24
+ ".py": "Python",
25
+ ".go": "Go",
26
+ ".rs": "Rust",
27
+ ".java": "Java",
28
+ ".kt": "Kotlin",
29
+ ".rb": "Ruby",
30
+ ".php": "PHP",
31
+ ".c": "C",
32
+ ".cpp": "C++",
33
+ ".cc": "C++",
34
+ ".h": "C",
35
+ ".hpp": "C++",
36
+ ".cs": "C#",
37
+ ".swift": "Swift",
38
+ ".scala": "Scala",
39
+ ".lua": "Lua",
40
+ ".sh": "Shell",
41
+ ".bash": "Shell",
42
+ ".zsh": "Shell",
43
+ ".json": "JSON",
44
+ ".yaml": "YAML",
45
+ ".yml": "YAML",
46
+ ".toml": "TOML",
47
+ ".xml": "XML",
48
+ ".html": "HTML",
49
+ ".css": "CSS",
50
+ ".scss": "SCSS",
51
+ ".sql": "SQL",
52
+ ".md": "Markdown",
53
+ ".vue": "Vue",
54
+ ".svelte": "Svelte",
55
+ };
56
+ function detectLanguage(filePath) {
57
+ const ext = extname(filePath).toLowerCase();
58
+ return LANGUAGE_MAP[ext];
59
+ }
60
+ function extractImports(content, language) {
61
+ return extractImportsWithType(content, language).map((e) => e.path);
62
+ }
63
+ function extractImportsWithType(content, language) {
64
+ const imports = [];
65
+ if (!language)
66
+ return imports;
67
+ switch (language) {
68
+ case "TypeScript":
69
+ case "JavaScript": {
70
+ // import ... from "..."
71
+ const esImports = content.matchAll(/import\s+.*?\s+from\s+["']([^"']+)["']/g);
72
+ for (const m of esImports)
73
+ imports.push({ path: m[1], type: "import" });
74
+ // import "..."
75
+ const sideEffects = content.matchAll(/import\s+["']([^"']+)["']/g);
76
+ for (const m of sideEffects)
77
+ imports.push({ path: m[1], type: "import" });
78
+ // require("...")
79
+ const requires = content.matchAll(/require\s*\(\s*["']([^"']+)["']\s*\)/g);
80
+ for (const m of requires)
81
+ imports.push({ path: m[1], type: "require" });
82
+ // export ... from "..."
83
+ const reExports = content.matchAll(/export\s+.*?\s+from\s+["']([^"']+)["']/g);
84
+ for (const m of reExports)
85
+ imports.push({ path: m[1], type: "import" });
86
+ break;
87
+ }
88
+ case "Python": {
89
+ const fromImports = content.matchAll(/from\s+(\S+)\s+import/g);
90
+ for (const m of fromImports)
91
+ imports.push({ path: m[1], type: "import" });
92
+ const directImports = content.matchAll(/^import\s+(\S+)/gm);
93
+ for (const m of directImports)
94
+ imports.push({ path: m[1], type: "import" });
95
+ break;
96
+ }
97
+ case "Go": {
98
+ const singleImport = content.matchAll(/import\s+"([^"]+)"/g);
99
+ for (const m of singleImport)
100
+ imports.push({ path: m[1], type: "import" });
101
+ const blockImport = content.matchAll(/import\s*\(([\s\S]*?)\)/g);
102
+ for (const m of blockImport) {
103
+ const pkgs = m[1].matchAll(/"([^"]+)"/g);
104
+ for (const p of pkgs)
105
+ imports.push({ path: p[1], type: "import" });
106
+ }
107
+ break;
108
+ }
109
+ case "Rust": {
110
+ const useStmts = content.matchAll(/use\s+([^;]+);/g);
111
+ for (const m of useStmts)
112
+ imports.push({ path: m[1].trim(), type: "import" });
113
+ break;
114
+ }
115
+ case "Java":
116
+ case "Kotlin": {
117
+ const javaImports = content.matchAll(/import\s+(?:static\s+)?([^;]+);/g);
118
+ for (const m of javaImports)
119
+ imports.push({ path: m[1].trim(), type: "import" });
120
+ break;
121
+ }
122
+ }
123
+ return imports;
124
+ }
125
+ function extractExports(content, language) {
126
+ const exports = [];
127
+ if (!language)
128
+ return exports;
129
+ switch (language) {
130
+ case "TypeScript":
131
+ case "JavaScript": {
132
+ // export function/const/class/type/interface/enum NAME
133
+ const namedExports = content.matchAll(/export\s+(?:default\s+)?(?:function|const|let|var|class|type|interface|enum|abstract\s+class)\s+(\w+)/g);
134
+ for (const m of namedExports)
135
+ exports.push(m[1]);
136
+ // export { ... }
137
+ const braceExports = content.matchAll(/export\s*\{([^}]+)\}/g);
138
+ for (const m of braceExports) {
139
+ const names = m[1].split(",").map((s) => s.trim().split(/\s+as\s+/).pop().trim());
140
+ exports.push(...names.filter(Boolean));
141
+ }
142
+ // export default
143
+ if (/export\s+default\s/.test(content) && !exports.includes("default")) {
144
+ exports.push("default");
145
+ }
146
+ // module.exports
147
+ if (/module\.exports\s*=/.test(content)) {
148
+ exports.push("module.exports");
149
+ }
150
+ break;
151
+ }
152
+ case "Python": {
153
+ // __all__ = [...]
154
+ const allMatch = content.match(/__all__\s*=\s*\[([^\]]+)\]/);
155
+ if (allMatch) {
156
+ const names = allMatch[1].matchAll(/["'](\w+)["']/g);
157
+ for (const n of names)
158
+ exports.push(n[1]);
159
+ }
160
+ break;
161
+ }
162
+ case "Rust": {
163
+ const pubItems = content.matchAll(/pub\s+(?:fn|struct|enum|trait|type|const|static|mod)\s+(\w+)/g);
164
+ for (const m of pubItems)
165
+ exports.push(m[1]);
166
+ break;
167
+ }
168
+ case "Go": {
169
+ // Exported identifiers start with uppercase
170
+ const funcs = content.matchAll(/func\s+(?:\([^)]*\)\s+)?([A-Z]\w*)\s*\(/g);
171
+ for (const m of funcs)
172
+ exports.push(m[1]);
173
+ const types = content.matchAll(/type\s+([A-Z]\w*)\s+/g);
174
+ for (const m of types)
175
+ exports.push(m[1]);
176
+ break;
177
+ }
178
+ }
179
+ return exports;
180
+ }
181
+ function extractFunctions(content, language) {
182
+ const functions = [];
183
+ if (!language)
184
+ return functions;
185
+ switch (language) {
186
+ case "TypeScript":
187
+ case "JavaScript": {
188
+ // function name(
189
+ const funcDecls = content.matchAll(/function\s+(\w+)\s*\(/g);
190
+ for (const m of funcDecls)
191
+ functions.push(m[1]);
192
+ // const/let/var name = ( | => | function
193
+ const arrowFuncs = content.matchAll(/(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?(?:\([^)]*\)|[^=])\s*=>/g);
194
+ for (const m of arrowFuncs)
195
+ functions.push(m[1]);
196
+ // const name = function
197
+ const funcExprs = content.matchAll(/(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?function/g);
198
+ for (const m of funcExprs)
199
+ functions.push(m[1]);
200
+ // method definitions in classes: name(
201
+ const methods = content.matchAll(/^\s+(?:async\s+)?(\w+)\s*\([^)]*\)\s*(?::\s*\w+)?\s*\{/gm);
202
+ for (const m of methods) {
203
+ if (!["if", "for", "while", "switch", "catch", "constructor"].includes(m[1])) {
204
+ functions.push(m[1]);
205
+ }
206
+ }
207
+ break;
208
+ }
209
+ case "Python": {
210
+ const defs = content.matchAll(/def\s+(\w+)\s*\(/g);
211
+ for (const m of defs)
212
+ functions.push(m[1]);
213
+ break;
214
+ }
215
+ case "Go": {
216
+ const goFuncs = content.matchAll(/func\s+(?:\([^)]*\)\s+)?(\w+)\s*\(/g);
217
+ for (const m of goFuncs)
218
+ functions.push(m[1]);
219
+ break;
220
+ }
221
+ case "Rust": {
222
+ const rsFuncs = content.matchAll(/fn\s+(\w+)\s*[(<]/g);
223
+ for (const m of rsFuncs)
224
+ functions.push(m[1]);
225
+ break;
226
+ }
227
+ case "Java":
228
+ case "Kotlin": {
229
+ const javaMethods = content.matchAll(/(?:public|private|protected|static|\s)+[\w<>\[\]]+\s+(\w+)\s*\(/g);
230
+ for (const m of javaMethods) {
231
+ if (!["if", "for", "while", "switch", "catch", "class"].includes(m[1])) {
232
+ functions.push(m[1]);
233
+ }
234
+ }
235
+ break;
236
+ }
237
+ }
238
+ return [...new Set(functions)];
239
+ }
240
+ function extractClasses(content, language) {
241
+ const classes = [];
242
+ if (!language)
243
+ return classes;
244
+ switch (language) {
245
+ case "TypeScript":
246
+ case "JavaScript":
247
+ case "Java":
248
+ case "Kotlin":
249
+ case "Python": {
250
+ const classDecls = content.matchAll(/class\s+(\w+)/g);
251
+ for (const m of classDecls)
252
+ classes.push(m[1]);
253
+ break;
254
+ }
255
+ case "Rust": {
256
+ const structs = content.matchAll(/(?:struct|enum|trait)\s+(\w+)/g);
257
+ for (const m of structs)
258
+ classes.push(m[1]);
259
+ break;
260
+ }
261
+ case "Go": {
262
+ const goTypes = content.matchAll(/type\s+(\w+)\s+struct/g);
263
+ for (const m of goTypes)
264
+ classes.push(m[1]);
265
+ break;
266
+ }
267
+ case "C++":
268
+ case "C#": {
269
+ const cppClasses = content.matchAll(/(?:class|struct)\s+(\w+)/g);
270
+ for (const m of cppClasses)
271
+ classes.push(m[1]);
272
+ break;
273
+ }
274
+ }
275
+ return [...new Set(classes)];
276
+ }
277
+ async function walkDirectory(dir, rootDir, ignoreSet, maxFiles, maxDepth, currentDepth, files) {
278
+ if (currentDepth > maxDepth || files.length >= maxFiles)
279
+ return;
280
+ let entries;
281
+ try {
282
+ entries = await readdir(dir, { withFileTypes: true });
283
+ }
284
+ catch {
285
+ return;
286
+ }
287
+ for (const entry of entries) {
288
+ if (files.length >= maxFiles)
289
+ break;
290
+ if (ignoreSet.has(entry.name))
291
+ continue;
292
+ if (entry.name.startsWith(".") && entry.name !== ".")
293
+ continue;
294
+ const fullPath = join(dir, entry.name);
295
+ const relPath = relative(rootDir, fullPath);
296
+ if (entry.isDirectory()) {
297
+ await walkDirectory(fullPath, rootDir, ignoreSet, maxFiles, maxDepth, currentDepth + 1, files);
298
+ }
299
+ else if (entry.isFile()) {
300
+ const language = detectLanguage(entry.name);
301
+ let fileStat;
302
+ try {
303
+ fileStat = await stat(fullPath);
304
+ }
305
+ catch {
306
+ continue;
307
+ }
308
+ // Skip binary/large files
309
+ if (fileStat.size > 1_000_000)
310
+ continue;
311
+ let content = "";
312
+ let lineCount = 0;
313
+ let imports = [];
314
+ let importInfos = [];
315
+ let exports = [];
316
+ let functions = [];
317
+ let classes = [];
318
+ if (language) {
319
+ try {
320
+ content = await readFile(fullPath, "utf-8");
321
+ lineCount = content.split("\n").length;
322
+ importInfos = extractImportsWithType(content, language);
323
+ imports = importInfos.map((e) => e.path);
324
+ exports = extractExports(content, language);
325
+ functions = extractFunctions(content, language);
326
+ classes = extractClasses(content, language);
327
+ }
328
+ catch {
329
+ // Binary file or encoding issue
330
+ }
331
+ }
332
+ files.push({
333
+ path: relPath,
334
+ type: "file",
335
+ language,
336
+ size: fileStat.size,
337
+ imports,
338
+ importInfos,
339
+ exports,
340
+ functions,
341
+ classes,
342
+ lineCount,
343
+ });
344
+ }
345
+ }
346
+ }
347
+ function resolveImportPath(fromFile, importSpecifier, allFilePaths) {
348
+ // Skip external/node modules
349
+ if (!importSpecifier.startsWith(".") &&
350
+ !importSpecifier.startsWith("/")) {
351
+ return null;
352
+ }
353
+ const fromDir = fromFile.includes("/")
354
+ ? fromFile.substring(0, fromFile.lastIndexOf("/"))
355
+ : ".";
356
+ let resolved;
357
+ if (importSpecifier.startsWith("/")) {
358
+ resolved = importSpecifier.substring(1);
359
+ }
360
+ else {
361
+ // Resolve relative path
362
+ const parts = fromDir.split("/").filter(Boolean);
363
+ const importParts = importSpecifier.split("/");
364
+ for (const p of importParts) {
365
+ if (p === "..")
366
+ parts.pop();
367
+ else if (p !== ".")
368
+ parts.push(p);
369
+ }
370
+ resolved = parts.join("/");
371
+ }
372
+ // Try exact match first, then with extensions
373
+ const extensions = ["", ".ts", ".tsx", ".js", ".jsx", ".mjs", "/index.ts", "/index.tsx", "/index.js", "/index.jsx"];
374
+ for (const ext of extensions) {
375
+ const candidate = resolved + ext;
376
+ // Strip .js extension that TypeScript ESM uses for .ts files
377
+ const tsCandidate = candidate.replace(/\.js$/, ".ts");
378
+ if (allFilePaths.has(candidate))
379
+ return candidate;
380
+ if (allFilePaths.has(tsCandidate))
381
+ return tsCandidate;
382
+ }
383
+ return null;
384
+ }
385
+ function buildDependencyGraph(files) {
386
+ const edges = [];
387
+ const allPaths = new Set(files.map((f) => f.path));
388
+ for (const file of files) {
389
+ const infos = file.importInfos ?? file.imports.map((p) => ({ path: p, type: "import" }));
390
+ for (const info of infos) {
391
+ const resolved = resolveImportPath(file.path, info.path, allPaths);
392
+ if (resolved) {
393
+ edges.push({ from: file.path, to: resolved, type: info.type });
394
+ }
395
+ }
396
+ }
397
+ return edges;
398
+ }
399
+ function findEntryPoints(files, edges) {
400
+ const imported = new Set(edges.map((e) => e.to));
401
+ return files
402
+ .filter((f) => f.language && !imported.has(f.path))
403
+ .map((f) => f.path);
404
+ }
405
+ function findHotspots(files, edges, topN = 10) {
406
+ const incomingCount = new Map();
407
+ for (const edge of edges) {
408
+ incomingCount.set(edge.to, (incomingCount.get(edge.to) ?? 0) + 1);
409
+ }
410
+ return [...incomingCount.entries()]
411
+ .sort((a, b) => b[1] - a[1])
412
+ .slice(0, topN)
413
+ .map(([path]) => path);
414
+ }
415
+ export async function mapCodebase(options) {
416
+ const root = resolve(options.root);
417
+ const ignorePatterns = options.ignore ?? DEFAULT_IGNORE;
418
+ const ignoreSet = new Set(ignorePatterns);
419
+ const maxFiles = options.maxFiles ?? 10_000;
420
+ const maxDepth = options.maxDepth ?? 50;
421
+ const files = [];
422
+ await walkDirectory(root, root, ignoreSet, maxFiles, maxDepth, 0, files);
423
+ const edges = buildDependencyGraph(files);
424
+ // Compute language stats
425
+ const languages = {};
426
+ let totalLines = 0;
427
+ for (const file of files) {
428
+ if (file.language) {
429
+ languages[file.language] = (languages[file.language] ?? 0) + 1;
430
+ }
431
+ totalLines += file.lineCount;
432
+ }
433
+ const entryPoints = findEntryPoints(files, edges);
434
+ const hotspots = findHotspots(files, edges);
435
+ return {
436
+ root,
437
+ files,
438
+ dependencies: edges,
439
+ languages,
440
+ totalFiles: files.length,
441
+ totalLines,
442
+ entryPoints,
443
+ hotspots,
444
+ generatedAt: new Date().toISOString(),
445
+ };
446
+ }
@@ -0,0 +1,2 @@
1
+ import type { CodebaseMap } from "./types.js";
2
+ export declare function generateOnboardingGuide(map: CodebaseMap): string;
@@ -0,0 +1,179 @@
1
+ function buildDirectoryTree(files, maxDepth) {
2
+ const tree = new Map();
3
+ for (const filePath of files) {
4
+ const parts = filePath.split("/");
5
+ for (let i = 0; i < parts.length && i < maxDepth; i++) {
6
+ const dir = parts.slice(0, i).join("/") || ".";
7
+ const child = parts[i];
8
+ if (!tree.has(dir))
9
+ tree.set(dir, new Set());
10
+ tree.get(dir).add(child);
11
+ }
12
+ }
13
+ function renderTree(dir, prefix, isLast, depth) {
14
+ if (depth > maxDepth)
15
+ return "";
16
+ const children = tree.get(dir);
17
+ if (!children)
18
+ return "";
19
+ const sorted = [...children].sort();
20
+ const lines = [];
21
+ for (let i = 0; i < sorted.length; i++) {
22
+ const child = sorted[i];
23
+ const last = i === sorted.length - 1;
24
+ const connector = last ? "└── " : "├── ";
25
+ const childPrefix = last ? " " : "│ ";
26
+ const childPath = dir === "." ? child : `${dir}/${child}`;
27
+ const isDir = tree.has(childPath);
28
+ lines.push(`${prefix}${connector}${child}${isDir ? "/" : ""}`);
29
+ if (isDir) {
30
+ lines.push(renderTree(childPath, prefix + childPrefix, last, depth + 1));
31
+ }
32
+ }
33
+ return lines.filter(Boolean).join("\n");
34
+ }
35
+ return renderTree(".", "", false, 0);
36
+ }
37
+ function describeEntryPoint(path, map) {
38
+ const file = map.files.find((f) => f.path === path);
39
+ if (!file)
40
+ return path;
41
+ const parts = [];
42
+ if (file.language)
43
+ parts.push(file.language);
44
+ if (file.functions.length > 0) {
45
+ parts.push(`functions: ${file.functions.slice(0, 5).join(", ")}${file.functions.length > 5 ? "..." : ""}`);
46
+ }
47
+ if (file.classes.length > 0) {
48
+ parts.push(`classes: ${file.classes.join(", ")}`);
49
+ }
50
+ if (file.exports.length > 0) {
51
+ parts.push(`exports: ${file.exports.slice(0, 5).join(", ")}${file.exports.length > 5 ? "..." : ""}`);
52
+ }
53
+ return parts.length > 0 ? `${path} (${parts.join(" | ")})` : path;
54
+ }
55
+ function summarizeDependencies(map) {
56
+ const lines = [];
57
+ if (map.dependencies.length === 0) {
58
+ lines.push("No internal dependencies detected.");
59
+ return lines.join("\n");
60
+ }
61
+ // Group by source file
62
+ const bySource = new Map();
63
+ for (const edge of map.dependencies) {
64
+ if (!bySource.has(edge.from))
65
+ bySource.set(edge.from, []);
66
+ bySource.get(edge.from).push(edge.to);
67
+ }
68
+ // Show top importers
69
+ const topImporters = [...bySource.entries()]
70
+ .sort((a, b) => b[1].length - a[1].length)
71
+ .slice(0, 10);
72
+ for (const [source, targets] of topImporters) {
73
+ lines.push(`- **${source}** imports from ${targets.length} file(s): ${targets.slice(0, 5).join(", ")}${targets.length > 5 ? "..." : ""}`);
74
+ }
75
+ return lines.join("\n");
76
+ }
77
+ export function generateOnboardingGuide(map) {
78
+ const sections = [];
79
+ // Header
80
+ sections.push(`# Codebase Onboarding Guide`);
81
+ sections.push(`> Generated at ${map.generatedAt}`);
82
+ sections.push("");
83
+ // Project overview
84
+ sections.push(`## Project Overview`);
85
+ sections.push("");
86
+ sections.push(`| Metric | Value |`);
87
+ sections.push(`|--------|-------|`);
88
+ sections.push(`| Root | \`${map.root}\` |`);
89
+ sections.push(`| Total Files | ${map.totalFiles} |`);
90
+ sections.push(`| Total Lines | ${map.totalLines.toLocaleString()} |`);
91
+ sections.push(`| Languages | ${Object.keys(map.languages).length} |`);
92
+ sections.push(`| Internal Dependencies | ${map.dependencies.length} |`);
93
+ sections.push("");
94
+ // Language breakdown
95
+ sections.push(`## Languages`);
96
+ sections.push("");
97
+ const sortedLangs = Object.entries(map.languages).sort((a, b) => b[1] - a[1]);
98
+ for (const [lang, count] of sortedLangs) {
99
+ const pct = ((count / map.totalFiles) * 100).toFixed(1);
100
+ sections.push(`- **${lang}**: ${count} files (${pct}%)`);
101
+ }
102
+ sections.push("");
103
+ // Directory structure
104
+ sections.push(`## Directory Structure`);
105
+ sections.push("");
106
+ sections.push("```");
107
+ sections.push(buildDirectoryTree(map.files.map((f) => f.path), 3));
108
+ sections.push("```");
109
+ sections.push("");
110
+ // Entry points
111
+ sections.push(`## Entry Points`);
112
+ sections.push("");
113
+ if (map.entryPoints.length === 0) {
114
+ sections.push("No clear entry points detected (all files are imported by something).");
115
+ }
116
+ else {
117
+ sections.push("These files are not imported by any other file in the project, making them likely entry points:");
118
+ sections.push("");
119
+ const displayEntryPoints = map.entryPoints.slice(0, 20);
120
+ for (const ep of displayEntryPoints) {
121
+ sections.push(`- ${describeEntryPoint(ep, map)}`);
122
+ }
123
+ if (map.entryPoints.length > 20) {
124
+ sections.push(`- ...and ${map.entryPoints.length - 20} more`);
125
+ }
126
+ }
127
+ sections.push("");
128
+ // Hotspots
129
+ sections.push(`## Hotspot Files (Start Reading Here)`);
130
+ sections.push("");
131
+ if (map.hotspots.length === 0) {
132
+ sections.push("No hotspots detected.");
133
+ }
134
+ else {
135
+ sections.push("These files are imported most frequently, making them core modules:");
136
+ sections.push("");
137
+ for (const hp of map.hotspots) {
138
+ const incomingCount = map.dependencies.filter((e) => e.to === hp).length;
139
+ sections.push(`- **${hp}** (imported by ${incomingCount} files)`);
140
+ }
141
+ }
142
+ sections.push("");
143
+ // Dependency graph summary
144
+ sections.push(`## Dependency Graph`);
145
+ sections.push("");
146
+ sections.push(summarizeDependencies(map));
147
+ sections.push("");
148
+ // Key modules
149
+ const moduleGroups = new Map();
150
+ for (const file of map.files) {
151
+ if (!file.language)
152
+ continue;
153
+ const topDir = file.path.includes("/") ? file.path.split("/")[0] : ".";
154
+ if (!moduleGroups.has(topDir))
155
+ moduleGroups.set(topDir, []);
156
+ moduleGroups.get(topDir).push(file.path);
157
+ }
158
+ if (moduleGroups.size > 1) {
159
+ sections.push(`## Key Modules`);
160
+ sections.push("");
161
+ const sortedModules = [...moduleGroups.entries()].sort((a, b) => b[1].length - a[1].length);
162
+ for (const [dir, modulePaths] of sortedModules.slice(0, 15)) {
163
+ const langCounts = new Map();
164
+ for (const p of modulePaths) {
165
+ const file = map.files.find((f) => f.path === p);
166
+ if (file?.language) {
167
+ langCounts.set(file.language, (langCounts.get(file.language) ?? 0) + 1);
168
+ }
169
+ }
170
+ const langSummary = [...langCounts.entries()]
171
+ .sort((a, b) => b[1] - a[1])
172
+ .map(([l, c]) => `${l}: ${c}`)
173
+ .join(", ");
174
+ sections.push(`- **${dir}/**: ${modulePaths.length} files (${langSummary})`);
175
+ }
176
+ sections.push("");
177
+ }
178
+ return sections.join("\n");
179
+ }
@@ -0,0 +1,39 @@
1
+ export type ImportInfo = {
2
+ path: string;
3
+ type: "import" | "require";
4
+ };
5
+ export type FileNode = {
6
+ path: string;
7
+ type: "file" | "directory";
8
+ language?: string;
9
+ size: number;
10
+ imports: string[];
11
+ /** Structured import info with type tracking. */
12
+ importInfos?: ImportInfo[];
13
+ exports: string[];
14
+ functions: string[];
15
+ classes: string[];
16
+ lineCount: number;
17
+ };
18
+ export type DependencyEdge = {
19
+ from: string;
20
+ to: string;
21
+ type: "import" | "require";
22
+ };
23
+ export type CodebaseMap = {
24
+ root: string;
25
+ files: FileNode[];
26
+ dependencies: DependencyEdge[];
27
+ languages: Record<string, number>;
28
+ totalFiles: number;
29
+ totalLines: number;
30
+ entryPoints: string[];
31
+ hotspots: string[];
32
+ generatedAt: string;
33
+ };
34
+ export type MapOptions = {
35
+ root: string;
36
+ ignore?: string[];
37
+ maxFiles?: number;
38
+ maxDepth?: number;
39
+ };
@@ -0,0 +1 @@
1
+ export {};