@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.
- package/dist/cli/index.d.ts +2 -0
- package/{src/codebase/index.ts → dist/codebase/index.d.ts} +1 -6
- package/dist/codebase/index.js +2 -0
- package/dist/codebase/mapper.d.ts +2 -0
- package/dist/codebase/mapper.js +446 -0
- package/dist/codebase/onboard.d.ts +2 -0
- package/dist/codebase/onboard.js +179 -0
- package/dist/codebase/types.d.ts +39 -0
- package/dist/codebase/types.js +1 -0
- package/dist/config/index.js +1 -0
- package/dist/config/types.d.ts +13 -0
- package/dist/config/types.js +1 -0
- package/dist/config/validator.d.ts +2 -0
- package/dist/config/validator.js +342 -0
- package/{src/index.ts → dist/index.d.ts} +0 -17
- package/dist/mcp/server.d.ts +15 -0
- package/dist/memory-engine/index.js +2 -0
- package/dist/memory-engine/nexus-memory.d.ts +129 -0
- package/dist/memory-engine/nexus-memory.js +484 -0
- package/dist/memory-engine/semantic.d.ts +75 -0
- package/dist/memory-engine/semantic.js +510 -0
- package/dist/obsidian/daily-note.d.ts +2 -0
- package/dist/obsidian/daily-note.js +65 -0
- package/dist/obsidian/exporter.d.ts +3 -0
- package/dist/obsidian/exporter.js +259 -0
- package/dist/obsidian/index.js +3 -0
- package/dist/obsidian/moc.d.ts +2 -0
- package/dist/obsidian/moc.js +150 -0
- package/dist/obsidian/types.d.ts +15 -0
- package/dist/obsidian/types.js +1 -0
- package/dist/parser/discover.d.ts +2 -0
- package/dist/parser/discover.js +52 -0
- package/dist/parser/index.d.ts +5 -0
- package/dist/parser/index.js +4 -0
- package/dist/parser/openclaw-parser.d.ts +3 -0
- package/dist/parser/openclaw-parser.js +214 -0
- package/dist/parser/parse.d.ts +2 -0
- package/dist/parser/parse.js +210 -0
- package/dist/parser/types.d.ts +46 -0
- package/dist/parser/types.js +1 -0
- package/dist/parser/unified.d.ts +6 -0
- package/dist/parser/unified.js +37 -0
- package/dist/promptguard/advanced-rules.d.ts +16 -0
- package/dist/promptguard/advanced-rules.js +236 -0
- package/dist/promptguard/entropy.d.ts +57 -0
- package/dist/promptguard/entropy.js +256 -0
- package/dist/promptguard/evolution/auto-update.d.ts +82 -0
- package/dist/promptguard/evolution/auto-update.js +138 -0
- package/dist/promptguard/evolution/corpus.d.ts +83 -0
- package/dist/promptguard/evolution/corpus.js +127 -0
- package/dist/promptguard/evolution/index.d.ts +6 -0
- package/dist/promptguard/evolution/index.js +3 -0
- package/dist/promptguard/evolution/rule-evolver.d.ts +56 -0
- package/dist/promptguard/evolution/rule-evolver.js +264 -0
- package/dist/promptguard/index.js +8 -0
- package/dist/promptguard/multilingual-rules.d.ts +15 -0
- package/dist/promptguard/multilingual-rules.js +333 -0
- package/dist/promptguard/normalize.d.ts +35 -0
- package/dist/promptguard/normalize.js +198 -0
- package/dist/promptguard/rules.d.ts +13 -0
- package/dist/promptguard/rules.js +211 -0
- package/dist/promptguard/scanner.d.ts +44 -0
- package/dist/promptguard/scanner.js +217 -0
- package/dist/promptguard/semantic.d.ts +18 -0
- package/dist/promptguard/semantic.js +267 -0
- package/dist/promptguard/token-analysis.d.ts +27 -0
- package/dist/promptguard/token-analysis.js +236 -0
- package/dist/promptguard/types.d.ts +85 -0
- package/dist/promptguard/types.js +1 -0
- package/dist/review/analyzer.d.ts +2 -0
- package/dist/review/analyzer.js +554 -0
- package/dist/review/diff-reviewer.d.ts +2 -0
- package/dist/review/diff-reviewer.js +142 -0
- package/dist/review/index.d.ts +3 -0
- package/dist/review/index.js +2 -0
- package/dist/review/types.d.ts +24 -0
- package/dist/review/types.js +1 -0
- package/dist/shared/stop-words.d.ts +1 -0
- package/dist/shared/stop-words.js +21 -0
- package/dist/skills/index.d.ts +2 -0
- package/dist/skills/index.js +1 -0
- package/dist/skills/memory-skill-engine.d.ts +124 -0
- package/dist/skills/memory-skill-engine.js +832 -0
- package/dist/testing/health-check.d.ts +2 -0
- package/dist/testing/health-check.js +386 -0
- package/dist/testing/index.js +2 -0
- package/dist/testing/test-fixer.d.ts +2 -0
- package/dist/testing/test-fixer.js +103 -0
- package/dist/testing/types.d.ts +17 -0
- package/dist/testing/types.js +1 -0
- package/package.json +9 -4
- package/scripts/auto-skill.sh +0 -54
- package/scripts/auto-sync.sh +0 -11
- package/scripts/benchmark.ts +0 -444
- package/scripts/demo-hero.sh +0 -52
- package/scripts/demo-injection.sh +0 -25
- package/scripts/demo-map.sh +0 -19
- package/scripts/demo-review.sh +0 -40
- package/scripts/demo-sessions.sh +0 -19
- package/scripts/demo.sh +0 -101
- package/scripts/scan-tool-result.sh +0 -46
- package/src/cli/index.ts +0 -922
- package/src/codebase/mapper.ts +0 -485
- package/src/codebase/onboard.ts +0 -200
- package/src/codebase/types.ts +0 -43
- package/src/config/types.ts +0 -14
- package/src/config/validator.ts +0 -368
- package/src/mcp/server.ts +0 -309
- package/src/memory-engine/nexus-memory.test.ts +0 -437
- package/src/memory-engine/nexus-memory.ts +0 -631
- package/src/memory-engine/semantic.ts +0 -380
- package/src/obsidian/daily-note.ts +0 -84
- package/src/obsidian/exporter.ts +0 -310
- package/src/obsidian/moc.ts +0 -169
- package/src/obsidian/types.ts +0 -16
- package/src/parser/discover.ts +0 -57
- package/src/parser/index.ts +0 -15
- package/src/parser/openclaw-parser.ts +0 -275
- package/src/parser/parse.ts +0 -263
- package/src/parser/types.ts +0 -45
- package/src/parser/unified.ts +0 -61
- package/src/promptguard/advanced-rules.ts +0 -290
- package/src/promptguard/entropy.ts +0 -314
- package/src/promptguard/evolution/auto-update.ts +0 -192
- package/src/promptguard/evolution/corpus.ts +0 -224
- package/src/promptguard/evolution/index.ts +0 -23
- package/src/promptguard/evolution/rule-evolver.ts +0 -347
- package/src/promptguard/multilingual-rules.ts +0 -344
- package/src/promptguard/normalize.ts +0 -240
- package/src/promptguard/rules.ts +0 -257
- package/src/promptguard/scanner.test.ts +0 -262
- package/src/promptguard/scanner.ts +0 -285
- package/src/promptguard/semantic.ts +0 -317
- package/src/promptguard/token-analysis.ts +0 -304
- package/src/promptguard/types.ts +0 -83
- package/src/review/analyzer.test.ts +0 -279
- package/src/review/analyzer.ts +0 -944
- package/src/review/diff-reviewer.ts +0 -191
- package/src/review/index.ts +0 -10
- package/src/review/types.ts +0 -38
- package/src/shared/stop-words.ts +0 -21
- package/src/skills/index.ts +0 -13
- package/src/skills/memory-skill-engine.ts +0 -1044
- package/src/testing/health-check.ts +0 -404
- package/src/testing/test-fixer.ts +0 -129
- package/src/testing/types.ts +0 -18
- package/tsconfig.json +0 -16
- /package/{src/config/index.ts → dist/config/index.d.ts} +0 -0
- /package/{src/memory-engine/index.ts → dist/memory-engine/index.d.ts} +0 -0
- /package/{src/obsidian/index.ts → dist/obsidian/index.d.ts} +0 -0
- /package/{src/promptguard/index.ts → dist/promptguard/index.d.ts} +0 -0
- /package/{src/testing/index.ts → dist/testing/index.d.ts} +0 -0
|
@@ -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,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,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 {};
|