codecortex-ai 0.1.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.
- package/LICENSE +21 -0
- package/README.md +173 -0
- package/dist/chunk-F4WTE7R3.js +962 -0
- package/dist/chunk-F4WTE7R3.js.map +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +1957 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/mcp/server.d.ts +27 -0
- package/dist/mcp/server.js +9 -0
- package/dist/mcp/server.js.map +1 -0
- package/package.json +85 -0
|
@@ -0,0 +1,1957 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
buildGraph,
|
|
4
|
+
cortexPath,
|
|
5
|
+
createManifest,
|
|
6
|
+
createSession,
|
|
7
|
+
enrichCouplingWithImports,
|
|
8
|
+
ensureDir,
|
|
9
|
+
getLatestSession,
|
|
10
|
+
listDecisions,
|
|
11
|
+
listModuleDocs,
|
|
12
|
+
listSessions,
|
|
13
|
+
readFile,
|
|
14
|
+
readManifest,
|
|
15
|
+
startServer,
|
|
16
|
+
updateManifest,
|
|
17
|
+
writeFile,
|
|
18
|
+
writeGraph,
|
|
19
|
+
writeJsonStream,
|
|
20
|
+
writeManifest,
|
|
21
|
+
writeSession
|
|
22
|
+
} from "../chunk-F4WTE7R3.js";
|
|
23
|
+
|
|
24
|
+
// src/cli/index.ts
|
|
25
|
+
import { Command } from "commander";
|
|
26
|
+
|
|
27
|
+
// src/cli/commands/init.ts
|
|
28
|
+
import { resolve } from "path";
|
|
29
|
+
|
|
30
|
+
// src/core/discovery.ts
|
|
31
|
+
import { execSync } from "child_process";
|
|
32
|
+
import { readFile as fsRead2, stat } from "fs/promises";
|
|
33
|
+
import { join, relative, basename, extname } from "path";
|
|
34
|
+
import { existsSync } from "fs";
|
|
35
|
+
|
|
36
|
+
// src/extraction/parser.ts
|
|
37
|
+
import { createRequire } from "module";
|
|
38
|
+
import { readFile as fsRead } from "fs/promises";
|
|
39
|
+
var require2 = createRequire(import.meta.url);
|
|
40
|
+
var Parser = require2("tree-sitter");
|
|
41
|
+
var parser = new Parser();
|
|
42
|
+
var LANGUAGE_LOADERS = {
|
|
43
|
+
// TypeScript / JavaScript
|
|
44
|
+
typescript: () => require2("tree-sitter-typescript").typescript,
|
|
45
|
+
tsx: () => require2("tree-sitter-typescript").tsx,
|
|
46
|
+
javascript: () => require2("tree-sitter-javascript"),
|
|
47
|
+
// Python
|
|
48
|
+
python: () => require2("tree-sitter-python"),
|
|
49
|
+
// Go
|
|
50
|
+
go: () => require2("tree-sitter-go"),
|
|
51
|
+
// Rust
|
|
52
|
+
rust: () => require2("tree-sitter-rust"),
|
|
53
|
+
// Systems
|
|
54
|
+
c: () => require2("tree-sitter-c"),
|
|
55
|
+
cpp: () => require2("tree-sitter-cpp"),
|
|
56
|
+
objc: () => require2("tree-sitter-objc"),
|
|
57
|
+
zig: () => require2("tree-sitter-zig"),
|
|
58
|
+
// JVM
|
|
59
|
+
java: () => require2("tree-sitter-java"),
|
|
60
|
+
kotlin: () => require2("tree-sitter-kotlin"),
|
|
61
|
+
scala: () => require2("tree-sitter-scala"),
|
|
62
|
+
// .NET
|
|
63
|
+
c_sharp: () => require2("tree-sitter-c-sharp"),
|
|
64
|
+
// Mobile
|
|
65
|
+
swift: () => require2("tree-sitter-swift"),
|
|
66
|
+
dart: () => require2("tree-sitter-dart"),
|
|
67
|
+
// Scripting
|
|
68
|
+
ruby: () => require2("tree-sitter-ruby"),
|
|
69
|
+
php: () => require2("tree-sitter-php").php,
|
|
70
|
+
lua: () => require2("tree-sitter-lua"),
|
|
71
|
+
bash: () => require2("tree-sitter-bash"),
|
|
72
|
+
elixir: () => require2("tree-sitter-elixir"),
|
|
73
|
+
// Functional
|
|
74
|
+
ocaml: () => require2("tree-sitter-ocaml").ocaml,
|
|
75
|
+
elm: () => require2("tree-sitter-elm"),
|
|
76
|
+
elisp: () => require2("tree-sitter-elisp"),
|
|
77
|
+
// Web3 / Other
|
|
78
|
+
solidity: () => require2("tree-sitter-solidity"),
|
|
79
|
+
vue: () => require2("tree-sitter-vue"),
|
|
80
|
+
ql: () => require2("tree-sitter-ql")
|
|
81
|
+
};
|
|
82
|
+
var EXTENSION_MAP = {
|
|
83
|
+
// TypeScript / JavaScript
|
|
84
|
+
".ts": "typescript",
|
|
85
|
+
".tsx": "tsx",
|
|
86
|
+
".js": "javascript",
|
|
87
|
+
".jsx": "javascript",
|
|
88
|
+
".mjs": "javascript",
|
|
89
|
+
".cjs": "javascript",
|
|
90
|
+
// Python
|
|
91
|
+
".py": "python",
|
|
92
|
+
".pyw": "python",
|
|
93
|
+
".pyi": "python",
|
|
94
|
+
// Go
|
|
95
|
+
".go": "go",
|
|
96
|
+
// Rust
|
|
97
|
+
".rs": "rust",
|
|
98
|
+
// C / C++
|
|
99
|
+
".c": "c",
|
|
100
|
+
".h": "c",
|
|
101
|
+
".cpp": "cpp",
|
|
102
|
+
".cc": "cpp",
|
|
103
|
+
".cxx": "cpp",
|
|
104
|
+
".hpp": "cpp",
|
|
105
|
+
".hxx": "cpp",
|
|
106
|
+
".hh": "cpp",
|
|
107
|
+
// Objective-C
|
|
108
|
+
".m": "objc",
|
|
109
|
+
".mm": "objc",
|
|
110
|
+
// Zig
|
|
111
|
+
".zig": "zig",
|
|
112
|
+
// Java
|
|
113
|
+
".java": "java",
|
|
114
|
+
// Kotlin
|
|
115
|
+
".kt": "kotlin",
|
|
116
|
+
".kts": "kotlin",
|
|
117
|
+
// Scala
|
|
118
|
+
".scala": "scala",
|
|
119
|
+
".sc": "scala",
|
|
120
|
+
// C#
|
|
121
|
+
".cs": "c_sharp",
|
|
122
|
+
// Swift
|
|
123
|
+
".swift": "swift",
|
|
124
|
+
// Dart
|
|
125
|
+
".dart": "dart",
|
|
126
|
+
// Ruby
|
|
127
|
+
".rb": "ruby",
|
|
128
|
+
".rake": "ruby",
|
|
129
|
+
// PHP
|
|
130
|
+
".php": "php",
|
|
131
|
+
// Lua
|
|
132
|
+
".lua": "lua",
|
|
133
|
+
// Bash / Shell
|
|
134
|
+
".sh": "bash",
|
|
135
|
+
".bash": "bash",
|
|
136
|
+
// Elixir
|
|
137
|
+
".ex": "elixir",
|
|
138
|
+
".exs": "elixir",
|
|
139
|
+
// OCaml
|
|
140
|
+
".ml": "ocaml",
|
|
141
|
+
".mli": "ocaml",
|
|
142
|
+
// Elm
|
|
143
|
+
".elm": "elm",
|
|
144
|
+
// Emacs Lisp
|
|
145
|
+
".el": "elisp",
|
|
146
|
+
// Solidity
|
|
147
|
+
".sol": "solidity",
|
|
148
|
+
// Vue
|
|
149
|
+
".vue": "vue",
|
|
150
|
+
// CodeQL
|
|
151
|
+
".ql": "ql"
|
|
152
|
+
};
|
|
153
|
+
var languageCache = /* @__PURE__ */ new Map();
|
|
154
|
+
function loadLanguage(lang) {
|
|
155
|
+
const cached = languageCache.get(lang);
|
|
156
|
+
if (cached) return cached;
|
|
157
|
+
const loader = LANGUAGE_LOADERS[lang];
|
|
158
|
+
if (!loader) throw new Error(`Unsupported language: ${lang}`);
|
|
159
|
+
const language = loader();
|
|
160
|
+
languageCache.set(lang, language);
|
|
161
|
+
return language;
|
|
162
|
+
}
|
|
163
|
+
async function initParser() {
|
|
164
|
+
}
|
|
165
|
+
async function parseFile(filePath, language) {
|
|
166
|
+
const lang = loadLanguage(language);
|
|
167
|
+
parser.setLanguage(lang);
|
|
168
|
+
const source = await fsRead(filePath, "utf-8");
|
|
169
|
+
return parser.parse(source);
|
|
170
|
+
}
|
|
171
|
+
function languageFromPath(filePath) {
|
|
172
|
+
const ext = filePath.substring(filePath.lastIndexOf("."));
|
|
173
|
+
return EXTENSION_MAP[ext] || null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// src/core/discovery.ts
|
|
177
|
+
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
178
|
+
"node_modules",
|
|
179
|
+
".git",
|
|
180
|
+
"dist",
|
|
181
|
+
"build",
|
|
182
|
+
"out",
|
|
183
|
+
".next",
|
|
184
|
+
".nuxt",
|
|
185
|
+
".output",
|
|
186
|
+
"coverage",
|
|
187
|
+
"__pycache__",
|
|
188
|
+
".mypy_cache",
|
|
189
|
+
".pytest_cache",
|
|
190
|
+
"vendor",
|
|
191
|
+
"target",
|
|
192
|
+
".codecortex"
|
|
193
|
+
]);
|
|
194
|
+
var IGNORED_FILES = /* @__PURE__ */ new Set([
|
|
195
|
+
"package-lock.json",
|
|
196
|
+
"yarn.lock",
|
|
197
|
+
"pnpm-lock.yaml",
|
|
198
|
+
".DS_Store",
|
|
199
|
+
"Thumbs.db"
|
|
200
|
+
]);
|
|
201
|
+
async function discoverProject(root) {
|
|
202
|
+
const name = detectProjectName(root);
|
|
203
|
+
const type = detectProjectType(root);
|
|
204
|
+
const files = await discoverFiles(root);
|
|
205
|
+
const modules = detectModules(root, files);
|
|
206
|
+
const entryPoints = detectEntryPoints(root, files, type);
|
|
207
|
+
const languages = [...new Set(files.map((f) => f.language).filter(Boolean))];
|
|
208
|
+
return { name, root, type, files, modules, entryPoints, languages };
|
|
209
|
+
}
|
|
210
|
+
function detectProjectName(root) {
|
|
211
|
+
const pkgPath = join(root, "package.json");
|
|
212
|
+
if (existsSync(pkgPath)) {
|
|
213
|
+
try {
|
|
214
|
+
const pkg = JSON.parse(execSync(`cat "${pkgPath}"`, { encoding: "utf-8" }));
|
|
215
|
+
if (pkg.name) return pkg.name;
|
|
216
|
+
} catch {
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const cargoPath = join(root, "Cargo.toml");
|
|
220
|
+
if (existsSync(cargoPath)) {
|
|
221
|
+
try {
|
|
222
|
+
const cargo = execSync(`cat "${cargoPath}"`, { encoding: "utf-8" });
|
|
223
|
+
const match = cargo.match(/name\s*=\s*"(.+?)"/);
|
|
224
|
+
if (match?.[1]) return match[1];
|
|
225
|
+
} catch {
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return basename(root);
|
|
229
|
+
}
|
|
230
|
+
function detectProjectType(root) {
|
|
231
|
+
if (existsSync(join(root, "package.json"))) return "node";
|
|
232
|
+
if (existsSync(join(root, "pyproject.toml")) || existsSync(join(root, "setup.py")) || existsSync(join(root, "requirements.txt"))) return "python";
|
|
233
|
+
if (existsSync(join(root, "go.mod"))) return "go";
|
|
234
|
+
if (existsSync(join(root, "Cargo.toml"))) return "rust";
|
|
235
|
+
return "unknown";
|
|
236
|
+
}
|
|
237
|
+
async function discoverFiles(root) {
|
|
238
|
+
const files = [];
|
|
239
|
+
let filePaths;
|
|
240
|
+
try {
|
|
241
|
+
const output = execSync("git ls-files --cached --others --exclude-standard", {
|
|
242
|
+
cwd: root,
|
|
243
|
+
encoding: "utf-8",
|
|
244
|
+
maxBuffer: 10 * 1024 * 1024
|
|
245
|
+
});
|
|
246
|
+
filePaths = output.trim().split("\n").filter(Boolean);
|
|
247
|
+
} catch {
|
|
248
|
+
filePaths = await walkDirectory(root, root);
|
|
249
|
+
}
|
|
250
|
+
for (const relPath of filePaths) {
|
|
251
|
+
const ext = extname(relPath);
|
|
252
|
+
const language = EXTENSION_MAP[ext];
|
|
253
|
+
if (!language) continue;
|
|
254
|
+
const parts = relPath.split("/");
|
|
255
|
+
if (parts.some((p) => IGNORED_DIRS.has(p))) continue;
|
|
256
|
+
if (IGNORED_FILES.has(basename(relPath))) continue;
|
|
257
|
+
const absPath = join(root, relPath);
|
|
258
|
+
try {
|
|
259
|
+
const s = await stat(absPath);
|
|
260
|
+
const content = await fsRead2(absPath, "utf-8");
|
|
261
|
+
const lines = content.split("\n").length;
|
|
262
|
+
files.push({
|
|
263
|
+
path: relPath,
|
|
264
|
+
absolutePath: absPath,
|
|
265
|
+
language,
|
|
266
|
+
lines,
|
|
267
|
+
bytes: s.size
|
|
268
|
+
});
|
|
269
|
+
} catch {
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return files;
|
|
273
|
+
}
|
|
274
|
+
async function walkDirectory(root, baseRoot) {
|
|
275
|
+
const { readdir } = await import("fs/promises");
|
|
276
|
+
const paths = [];
|
|
277
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
278
|
+
for (const entry of entries) {
|
|
279
|
+
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
280
|
+
if (IGNORED_FILES.has(entry.name)) continue;
|
|
281
|
+
const fullPath = join(root, entry.name);
|
|
282
|
+
if (entry.isDirectory()) {
|
|
283
|
+
const sub = await walkDirectory(fullPath, baseRoot);
|
|
284
|
+
paths.push(...sub);
|
|
285
|
+
} else if (entry.isFile()) {
|
|
286
|
+
paths.push(relative(baseRoot, fullPath));
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return paths;
|
|
290
|
+
}
|
|
291
|
+
function detectModules(root, files) {
|
|
292
|
+
const MODULE_ROOTS = /* @__PURE__ */ new Set(["src", "lib", "pkg", "packages", "apps", "extensions", "crates", "internal", "cmd", "scripts", "tools", "rust"]);
|
|
293
|
+
const srcDirs = /* @__PURE__ */ new Set();
|
|
294
|
+
for (const file of files) {
|
|
295
|
+
const parts = file.path.split("/");
|
|
296
|
+
if (parts.length >= 3) {
|
|
297
|
+
const topDir = parts[0];
|
|
298
|
+
const dir = parts[1];
|
|
299
|
+
if (!topDir || !dir) continue;
|
|
300
|
+
if (MODULE_ROOTS.has(topDir)) {
|
|
301
|
+
srcDirs.add(dir);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return [...srcDirs].sort();
|
|
306
|
+
}
|
|
307
|
+
function detectEntryPoints(root, files, type) {
|
|
308
|
+
const entryPoints = [];
|
|
309
|
+
if (type === "node") {
|
|
310
|
+
const pkgPath = join(root, "package.json");
|
|
311
|
+
if (existsSync(pkgPath)) {
|
|
312
|
+
try {
|
|
313
|
+
const pkg = JSON.parse(execSync(`cat "${pkgPath}"`, { encoding: "utf-8" }));
|
|
314
|
+
if (pkg.main) entryPoints.push(pkg.main);
|
|
315
|
+
if (pkg.bin) {
|
|
316
|
+
const bins = typeof pkg.bin === "string" ? [pkg.bin] : Object.values(pkg.bin);
|
|
317
|
+
entryPoints.push(...bins);
|
|
318
|
+
}
|
|
319
|
+
} catch {
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
const commonEntries = ["src/index.ts", "src/main.ts", "src/app.ts", "index.ts", "main.ts", "main.go", "src/main.rs", "src/lib.rs"];
|
|
324
|
+
for (const entry of commonEntries) {
|
|
325
|
+
if (files.some((f) => f.path === entry)) {
|
|
326
|
+
if (!entryPoints.includes(entry)) entryPoints.push(entry);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return entryPoints;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// src/core/constitution.ts
|
|
333
|
+
async function generateConstitution(projectRoot, data) {
|
|
334
|
+
const manifest = await readManifest(projectRoot);
|
|
335
|
+
const modules = await listModuleDocs(projectRoot);
|
|
336
|
+
const decisions = await listDecisions(projectRoot);
|
|
337
|
+
let graphModules = data?.modules;
|
|
338
|
+
let entryPoints = data?.entryPoints;
|
|
339
|
+
let externalDeps = data?.externalDeps;
|
|
340
|
+
let temporal = data?.temporal ?? null;
|
|
341
|
+
if (!graphModules) {
|
|
342
|
+
const graphContent = await readFile(cortexPath(projectRoot, "graph.json"));
|
|
343
|
+
if (graphContent) {
|
|
344
|
+
try {
|
|
345
|
+
const graph = JSON.parse(graphContent);
|
|
346
|
+
graphModules = graph.modules;
|
|
347
|
+
entryPoints = graph.entryPoints;
|
|
348
|
+
externalDeps = graph.externalDeps;
|
|
349
|
+
} catch {
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (temporal === null && !data) {
|
|
354
|
+
const temporalContent = await readFile(cortexPath(projectRoot, "temporal.json"));
|
|
355
|
+
if (temporalContent) {
|
|
356
|
+
try {
|
|
357
|
+
temporal = JSON.parse(temporalContent);
|
|
358
|
+
} catch {
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
const lines = [
|
|
363
|
+
`# ${manifest?.project || "Project"} \u2014 Constitution`,
|
|
364
|
+
"",
|
|
365
|
+
`> Auto-generated by CodeCortex. This is the AI's starting knowledge for this codebase.`,
|
|
366
|
+
""
|
|
367
|
+
];
|
|
368
|
+
if (manifest) {
|
|
369
|
+
lines.push(
|
|
370
|
+
`## Project`,
|
|
371
|
+
`- **Name:** ${manifest.project}`,
|
|
372
|
+
`- **Languages:** ${manifest.languages.join(", ")}`,
|
|
373
|
+
`- **Files:** ${manifest.totalFiles}`,
|
|
374
|
+
`- **Symbols:** ${manifest.totalSymbols}`,
|
|
375
|
+
`- **Modules:** ${manifest.totalModules}`,
|
|
376
|
+
`- **Last updated:** ${manifest.lastUpdated}`,
|
|
377
|
+
""
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
if (graphModules) {
|
|
381
|
+
lines.push(`## Architecture`, "");
|
|
382
|
+
if (entryPoints && entryPoints.length > 0) {
|
|
383
|
+
lines.push(`**Entry points:** ${entryPoints.map((e) => `\`${e}\``).join(", ")}`, "");
|
|
384
|
+
}
|
|
385
|
+
lines.push(`**Modules:**`);
|
|
386
|
+
for (const mod of graphModules) {
|
|
387
|
+
lines.push(`- **${mod.name}** (${mod.files.length} files, ${mod.lines} lines) \u2014 ${mod.language}`);
|
|
388
|
+
}
|
|
389
|
+
lines.push("");
|
|
390
|
+
if (externalDeps) {
|
|
391
|
+
const extDeps = Object.keys(externalDeps);
|
|
392
|
+
if (extDeps.length > 0) {
|
|
393
|
+
lines.push(`**External dependencies:** ${extDeps.map((d) => `\`${d}\``).join(", ")}`, "");
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
if (temporal) {
|
|
398
|
+
lines.push(`## Risk Map`, "");
|
|
399
|
+
const topHotspots = temporal.hotspots.slice(0, 5);
|
|
400
|
+
if (topHotspots.length > 0) {
|
|
401
|
+
lines.push(`**Hottest files (most changes):**`);
|
|
402
|
+
for (const h of topHotspots) {
|
|
403
|
+
lines.push(`- \`${h.file}\` \u2014 ${h.changes} changes, ${h.stability.toUpperCase()}`);
|
|
404
|
+
}
|
|
405
|
+
lines.push("");
|
|
406
|
+
}
|
|
407
|
+
const hiddenCouplings = temporal.coupling.filter((c) => !c.hasImport && c.strength >= 0.5);
|
|
408
|
+
if (hiddenCouplings.length > 0) {
|
|
409
|
+
lines.push(`**Hidden dependencies (co-change but no import):**`);
|
|
410
|
+
for (const c of hiddenCouplings.slice(0, 5)) {
|
|
411
|
+
lines.push(`- \`${c.fileA}\` \u2194 \`${c.fileB}\` \u2014 ${c.cochanges} co-changes (${Math.round(c.strength * 100)}%)`);
|
|
412
|
+
}
|
|
413
|
+
lines.push("");
|
|
414
|
+
}
|
|
415
|
+
const buggy = temporal.bugHistory.filter((b) => b.fixCommits >= 2);
|
|
416
|
+
if (buggy.length > 0) {
|
|
417
|
+
lines.push(`**Bug-prone files:**`);
|
|
418
|
+
for (const b of buggy.slice(0, 5)) {
|
|
419
|
+
lines.push(`- \`${b.file}\` \u2014 ${b.fixCommits} fix commits`);
|
|
420
|
+
for (const lesson of b.lessons.slice(0, 3)) {
|
|
421
|
+
lines.push(` - ${lesson}`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
lines.push("");
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
lines.push(`## Available Knowledge`, "");
|
|
428
|
+
if (modules.length > 0) {
|
|
429
|
+
lines.push(`**Module docs:** ${modules.map((m) => `\`${m}\``).join(", ")}`);
|
|
430
|
+
}
|
|
431
|
+
if (decisions.length > 0) {
|
|
432
|
+
lines.push(`**Decision records:** ${decisions.length}`);
|
|
433
|
+
}
|
|
434
|
+
lines.push(
|
|
435
|
+
``,
|
|
436
|
+
`Use \`get_module_context\` to deep-dive into any module.`,
|
|
437
|
+
`Use \`get_change_coupling\` before editing a file to check what else must change.`,
|
|
438
|
+
`Use \`lookup_symbol\` to find any function, type, or class.`
|
|
439
|
+
);
|
|
440
|
+
const content = lines.join("\n") + "\n";
|
|
441
|
+
await writeFile(cortexPath(projectRoot, "constitution.md"), content);
|
|
442
|
+
return content;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// src/git/history.ts
|
|
446
|
+
import simpleGit from "simple-git";
|
|
447
|
+
async function getCommitHistory(root, days = 90) {
|
|
448
|
+
const git = simpleGit(root);
|
|
449
|
+
const since = new Date(Date.now() - days * 24 * 60 * 60 * 1e3).toISOString().split("T")[0];
|
|
450
|
+
const log = await git.log({
|
|
451
|
+
"--since": since,
|
|
452
|
+
"--stat": null,
|
|
453
|
+
maxCount: 500
|
|
454
|
+
});
|
|
455
|
+
return log.all.map((commit) => ({
|
|
456
|
+
hash: commit.hash,
|
|
457
|
+
date: commit.date,
|
|
458
|
+
message: commit.message,
|
|
459
|
+
author: commit.author_name,
|
|
460
|
+
filesChanged: parseStatFiles(commit.diff)
|
|
461
|
+
}));
|
|
462
|
+
}
|
|
463
|
+
function parseStatFiles(diff) {
|
|
464
|
+
if (!diff || !diff.files) return [];
|
|
465
|
+
return diff.files.map((f) => f.file);
|
|
466
|
+
}
|
|
467
|
+
async function isGitRepo(root) {
|
|
468
|
+
const git = simpleGit(root);
|
|
469
|
+
try {
|
|
470
|
+
await git.status();
|
|
471
|
+
return true;
|
|
472
|
+
} catch {
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
async function getHeadCommit(root) {
|
|
477
|
+
const git = simpleGit(root);
|
|
478
|
+
try {
|
|
479
|
+
const log = await git.log({ maxCount: 1 });
|
|
480
|
+
return log.latest?.hash || null;
|
|
481
|
+
} catch {
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// src/git/temporal.ts
|
|
487
|
+
var TEMPORAL_NOISE_FILES = /* @__PURE__ */ new Set([
|
|
488
|
+
"CHANGELOG.md",
|
|
489
|
+
"CHANGES.md",
|
|
490
|
+
"HISTORY.md",
|
|
491
|
+
"NEWS.md",
|
|
492
|
+
"package.json",
|
|
493
|
+
"package-lock.json",
|
|
494
|
+
"yarn.lock",
|
|
495
|
+
"pnpm-lock.yaml",
|
|
496
|
+
"Cargo.lock",
|
|
497
|
+
"go.sum",
|
|
498
|
+
"poetry.lock",
|
|
499
|
+
"Pipfile.lock"
|
|
500
|
+
]);
|
|
501
|
+
function isTemporalNoise(file) {
|
|
502
|
+
const basename2 = file.split("/").pop() ?? "";
|
|
503
|
+
return TEMPORAL_NOISE_FILES.has(basename2);
|
|
504
|
+
}
|
|
505
|
+
async function analyzeTemporalData(root, days = 90) {
|
|
506
|
+
const commits = await getCommitHistory(root, days);
|
|
507
|
+
const hotspots = getHotspots(commits, days);
|
|
508
|
+
const coupling = getChangeCoupling(commits);
|
|
509
|
+
const bugHistory = getBugArchaeology(commits);
|
|
510
|
+
return {
|
|
511
|
+
generated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
512
|
+
periodDays: days,
|
|
513
|
+
totalCommits: commits.length,
|
|
514
|
+
hotspots,
|
|
515
|
+
coupling,
|
|
516
|
+
bugHistory
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
function getHotspots(commits, days) {
|
|
520
|
+
const fileChanges = /* @__PURE__ */ new Map();
|
|
521
|
+
for (const commit of commits) {
|
|
522
|
+
for (const file of commit.filesChanged) {
|
|
523
|
+
if (isTemporalNoise(file)) continue;
|
|
524
|
+
const existing = fileChanges.get(file) || { count: 0, lastDate: "" };
|
|
525
|
+
existing.count++;
|
|
526
|
+
if (commit.date > existing.lastDate) {
|
|
527
|
+
existing.lastDate = commit.date;
|
|
528
|
+
}
|
|
529
|
+
fileChanges.set(file, existing);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
const now = /* @__PURE__ */ new Date();
|
|
533
|
+
const results = [];
|
|
534
|
+
for (const [file, data] of fileChanges) {
|
|
535
|
+
const lastChanged = new Date(data.lastDate);
|
|
536
|
+
const daysSinceChange = Math.floor((now.getTime() - lastChanged.getTime()) / (1e3 * 60 * 60 * 24));
|
|
537
|
+
let stability;
|
|
538
|
+
if (data.count >= 8 && daysSinceChange < 7) stability = "volatile";
|
|
539
|
+
else if (data.count >= 5 && daysSinceChange < 14) stability = "stabilizing";
|
|
540
|
+
else if (data.count >= 3) stability = "moderate";
|
|
541
|
+
else if (daysSinceChange > 30) stability = "very_stable";
|
|
542
|
+
else stability = "stable";
|
|
543
|
+
results.push({
|
|
544
|
+
file,
|
|
545
|
+
changes: data.count,
|
|
546
|
+
stability,
|
|
547
|
+
lastChanged: data.lastDate,
|
|
548
|
+
daysSinceChange
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
return results.sort((a, b) => b.changes - a.changes);
|
|
552
|
+
}
|
|
553
|
+
function getChangeCoupling(commits) {
|
|
554
|
+
const pairCounts = /* @__PURE__ */ new Map();
|
|
555
|
+
const fileCounts = /* @__PURE__ */ new Map();
|
|
556
|
+
for (const commit of commits) {
|
|
557
|
+
const files = commit.filesChanged.filter(
|
|
558
|
+
(f) => !f.endsWith(".md") && !f.endsWith(".json") && !f.endsWith(".lock") && !f.endsWith(".yaml") && !f.endsWith(".yml")
|
|
559
|
+
);
|
|
560
|
+
for (const file of files) {
|
|
561
|
+
fileCounts.set(file, (fileCounts.get(file) || 0) + 1);
|
|
562
|
+
}
|
|
563
|
+
if (files.length > 50) continue;
|
|
564
|
+
for (let i = 0; i < files.length; i++) {
|
|
565
|
+
for (let j = i + 1; j < files.length; j++) {
|
|
566
|
+
const key = [files[i], files[j]].sort().join("|");
|
|
567
|
+
pairCounts.set(key, (pairCounts.get(key) || 0) + 1);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
const results = [];
|
|
572
|
+
for (const [key, cochanges] of pairCounts) {
|
|
573
|
+
if (cochanges < 3) continue;
|
|
574
|
+
const parts = key.split("|");
|
|
575
|
+
const fileA = parts[0] ?? "";
|
|
576
|
+
const fileB = parts[1] ?? "";
|
|
577
|
+
const maxChanges = Math.max(fileCounts.get(fileA) || 0, fileCounts.get(fileB) || 0);
|
|
578
|
+
const strength = maxChanges > 0 ? cochanges / maxChanges : 0;
|
|
579
|
+
const coupling = {
|
|
580
|
+
fileA,
|
|
581
|
+
fileB,
|
|
582
|
+
cochanges,
|
|
583
|
+
strength: Math.round(strength * 100) / 100,
|
|
584
|
+
hasImport: false
|
|
585
|
+
// Will be enriched by graph analysis
|
|
586
|
+
};
|
|
587
|
+
if (strength >= 0.7 && !coupling.hasImport) {
|
|
588
|
+
coupling.warning = `HIDDEN DEPENDENCY \u2014 ${Math.round(strength * 100)}% co-change rate`;
|
|
589
|
+
}
|
|
590
|
+
results.push(coupling);
|
|
591
|
+
}
|
|
592
|
+
return results.sort((a, b) => b.cochanges - a.cochanges);
|
|
593
|
+
}
|
|
594
|
+
function getBugArchaeology(commits) {
|
|
595
|
+
const bugPatterns = /^(fix|bug|hotfix|patch)[\s:(]/i;
|
|
596
|
+
const fileRecords = /* @__PURE__ */ new Map();
|
|
597
|
+
for (const commit of commits) {
|
|
598
|
+
if (!bugPatterns.test(commit.message)) continue;
|
|
599
|
+
const lesson = commit.message.replace(/^(fix|bug|hotfix|patch)[\s:(]+/i, "").replace(/\s*\(#\d+\)$/, "").trim();
|
|
600
|
+
for (const file of commit.filesChanged) {
|
|
601
|
+
if (isTemporalNoise(file)) continue;
|
|
602
|
+
const existing = fileRecords.get(file) || { fixCommits: 0, lessons: /* @__PURE__ */ new Set() };
|
|
603
|
+
existing.fixCommits++;
|
|
604
|
+
if (lesson) existing.lessons.add(lesson);
|
|
605
|
+
fileRecords.set(file, existing);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
const results = [];
|
|
609
|
+
for (const [file, data] of fileRecords) {
|
|
610
|
+
results.push({
|
|
611
|
+
file,
|
|
612
|
+
fixCommits: data.fixCommits,
|
|
613
|
+
lessons: [...data.lessons]
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
return results.sort((a, b) => b.fixCommits - a.fixCommits);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// src/extraction/symbols.ts
|
|
620
|
+
var TS_JS_QUERY = {
|
|
621
|
+
nodeTypes: /* @__PURE__ */ new Set([
|
|
622
|
+
"function_declaration",
|
|
623
|
+
"class_declaration",
|
|
624
|
+
"interface_declaration",
|
|
625
|
+
"type_alias_declaration",
|
|
626
|
+
"enum_declaration",
|
|
627
|
+
"lexical_declaration",
|
|
628
|
+
"variable_declaration",
|
|
629
|
+
"method_definition",
|
|
630
|
+
"public_field_definition",
|
|
631
|
+
"export_statement"
|
|
632
|
+
]),
|
|
633
|
+
getKind(type) {
|
|
634
|
+
const map = {
|
|
635
|
+
function_declaration: "function",
|
|
636
|
+
class_declaration: "class",
|
|
637
|
+
interface_declaration: "interface",
|
|
638
|
+
type_alias_declaration: "type",
|
|
639
|
+
enum_declaration: "enum",
|
|
640
|
+
lexical_declaration: "const",
|
|
641
|
+
variable_declaration: "variable",
|
|
642
|
+
method_definition: "method",
|
|
643
|
+
public_field_definition: "property"
|
|
644
|
+
};
|
|
645
|
+
return map[type] || "variable";
|
|
646
|
+
},
|
|
647
|
+
getName(node) {
|
|
648
|
+
const nameNode = node.childForFieldName("name");
|
|
649
|
+
if (nameNode) return nameNode.text;
|
|
650
|
+
if (node.type === "lexical_declaration" || node.type === "variable_declaration") {
|
|
651
|
+
const declarator = node.namedChildren.find(
|
|
652
|
+
(c) => c.type === "variable_declarator"
|
|
653
|
+
);
|
|
654
|
+
if (declarator) {
|
|
655
|
+
const name = declarator.childForFieldName("name");
|
|
656
|
+
return name?.text || null;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
return null;
|
|
660
|
+
},
|
|
661
|
+
getSignature(node, source) {
|
|
662
|
+
const startLine = node.startPosition.row;
|
|
663
|
+
const lines = source.split("\n");
|
|
664
|
+
const line = lines[startLine];
|
|
665
|
+
if (!line) return void 0;
|
|
666
|
+
const sig = line.trim();
|
|
667
|
+
return sig.length > 200 ? sig.slice(0, 200) + "..." : sig;
|
|
668
|
+
},
|
|
669
|
+
isExported(node) {
|
|
670
|
+
const parent = node.parent;
|
|
671
|
+
if (!parent) return false;
|
|
672
|
+
if (parent.type === "export_statement") return true;
|
|
673
|
+
if (node.type === "lexical_declaration") {
|
|
674
|
+
return parent.type === "export_statement";
|
|
675
|
+
}
|
|
676
|
+
return false;
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
var PYTHON_QUERY = {
|
|
680
|
+
nodeTypes: /* @__PURE__ */ new Set([
|
|
681
|
+
"function_definition",
|
|
682
|
+
"class_definition",
|
|
683
|
+
"assignment"
|
|
684
|
+
]),
|
|
685
|
+
getKind(type) {
|
|
686
|
+
const map = {
|
|
687
|
+
function_definition: "function",
|
|
688
|
+
class_definition: "class",
|
|
689
|
+
assignment: "variable"
|
|
690
|
+
};
|
|
691
|
+
return map[type] || "variable";
|
|
692
|
+
},
|
|
693
|
+
getName(node) {
|
|
694
|
+
const nameNode = node.childForFieldName("name");
|
|
695
|
+
if (nameNode) return nameNode.text;
|
|
696
|
+
if (node.type === "assignment") {
|
|
697
|
+
const left = node.childForFieldName("left");
|
|
698
|
+
return left?.text || null;
|
|
699
|
+
}
|
|
700
|
+
return null;
|
|
701
|
+
},
|
|
702
|
+
getSignature(node, source) {
|
|
703
|
+
const startLine = node.startPosition.row;
|
|
704
|
+
const lines = source.split("\n");
|
|
705
|
+
return lines[startLine]?.trim();
|
|
706
|
+
},
|
|
707
|
+
isExported(node) {
|
|
708
|
+
const name = PYTHON_QUERY.getName(node);
|
|
709
|
+
return name ? !name.startsWith("_") : false;
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
var GO_QUERY = {
|
|
713
|
+
nodeTypes: /* @__PURE__ */ new Set([
|
|
714
|
+
"function_declaration",
|
|
715
|
+
"method_declaration",
|
|
716
|
+
"type_declaration",
|
|
717
|
+
"const_declaration",
|
|
718
|
+
"var_declaration"
|
|
719
|
+
]),
|
|
720
|
+
getKind(type) {
|
|
721
|
+
const map = {
|
|
722
|
+
function_declaration: "function",
|
|
723
|
+
method_declaration: "method",
|
|
724
|
+
type_declaration: "type",
|
|
725
|
+
const_declaration: "const",
|
|
726
|
+
var_declaration: "variable"
|
|
727
|
+
};
|
|
728
|
+
return map[type] || "variable";
|
|
729
|
+
},
|
|
730
|
+
getName(node) {
|
|
731
|
+
const nameNode = node.childForFieldName("name");
|
|
732
|
+
if (nameNode) return nameNode.text;
|
|
733
|
+
if (node.type === "type_declaration" || node.type === "const_declaration" || node.type === "var_declaration") {
|
|
734
|
+
for (const child of node.namedChildren) {
|
|
735
|
+
if (child.type === "type_spec" || child.type === "const_spec" || child.type === "var_spec") {
|
|
736
|
+
const specName = child.childForFieldName("name");
|
|
737
|
+
if (specName) return specName.text;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
return null;
|
|
742
|
+
},
|
|
743
|
+
getSignature(node, source) {
|
|
744
|
+
const startLine = node.startPosition.row;
|
|
745
|
+
const lines = source.split("\n");
|
|
746
|
+
return lines[startLine]?.trim();
|
|
747
|
+
},
|
|
748
|
+
isExported(node) {
|
|
749
|
+
const name = GO_QUERY.getName(node);
|
|
750
|
+
return name ? /^[A-Z]/.test(name) : false;
|
|
751
|
+
}
|
|
752
|
+
};
|
|
753
|
+
var RUST_QUERY = {
|
|
754
|
+
nodeTypes: /* @__PURE__ */ new Set([
|
|
755
|
+
"function_item",
|
|
756
|
+
"struct_item",
|
|
757
|
+
"enum_item",
|
|
758
|
+
"impl_item",
|
|
759
|
+
"trait_item",
|
|
760
|
+
"type_item",
|
|
761
|
+
"const_item",
|
|
762
|
+
"static_item"
|
|
763
|
+
]),
|
|
764
|
+
getKind(type) {
|
|
765
|
+
const map = {
|
|
766
|
+
function_item: "function",
|
|
767
|
+
struct_item: "class",
|
|
768
|
+
enum_item: "enum",
|
|
769
|
+
impl_item: "class",
|
|
770
|
+
trait_item: "interface",
|
|
771
|
+
type_item: "type",
|
|
772
|
+
const_item: "const",
|
|
773
|
+
static_item: "variable"
|
|
774
|
+
};
|
|
775
|
+
return map[type] || "variable";
|
|
776
|
+
},
|
|
777
|
+
getName(node) {
|
|
778
|
+
const nameNode = node.childForFieldName("name");
|
|
779
|
+
return nameNode?.text || null;
|
|
780
|
+
},
|
|
781
|
+
getSignature(node, source) {
|
|
782
|
+
const startLine = node.startPosition.row;
|
|
783
|
+
const lines = source.split("\n");
|
|
784
|
+
return lines[startLine]?.trim();
|
|
785
|
+
},
|
|
786
|
+
isExported(node) {
|
|
787
|
+
const text = node.text;
|
|
788
|
+
return text.startsWith("pub ");
|
|
789
|
+
}
|
|
790
|
+
};
|
|
791
|
+
var C_CPP_QUERY = {
|
|
792
|
+
nodeTypes: /* @__PURE__ */ new Set([
|
|
793
|
+
"function_definition",
|
|
794
|
+
"declaration",
|
|
795
|
+
"struct_specifier",
|
|
796
|
+
"enum_specifier",
|
|
797
|
+
"union_specifier",
|
|
798
|
+
"type_definition",
|
|
799
|
+
"preproc_function_def",
|
|
800
|
+
// C++ additions
|
|
801
|
+
"class_specifier",
|
|
802
|
+
"namespace_definition"
|
|
803
|
+
]),
|
|
804
|
+
getKind(type) {
|
|
805
|
+
const map = {
|
|
806
|
+
function_definition: "function",
|
|
807
|
+
declaration: "variable",
|
|
808
|
+
struct_specifier: "class",
|
|
809
|
+
enum_specifier: "enum",
|
|
810
|
+
union_specifier: "class",
|
|
811
|
+
type_definition: "type",
|
|
812
|
+
preproc_function_def: "function",
|
|
813
|
+
class_specifier: "class",
|
|
814
|
+
namespace_definition: "class"
|
|
815
|
+
};
|
|
816
|
+
return map[type] || "variable";
|
|
817
|
+
},
|
|
818
|
+
getName(node) {
|
|
819
|
+
const nameNode = node.childForFieldName("name");
|
|
820
|
+
if (nameNode) return nameNode.text;
|
|
821
|
+
let declarator = node.childForFieldName("declarator");
|
|
822
|
+
while (declarator) {
|
|
823
|
+
if (declarator.type === "identifier" || declarator.type === "type_identifier" || declarator.type === "field_identifier") {
|
|
824
|
+
return declarator.text;
|
|
825
|
+
}
|
|
826
|
+
const inner = declarator.childForFieldName("declarator");
|
|
827
|
+
if (inner) {
|
|
828
|
+
declarator = inner;
|
|
829
|
+
continue;
|
|
830
|
+
}
|
|
831
|
+
const declName = declarator.childForFieldName("name");
|
|
832
|
+
if (declName) return declName.text;
|
|
833
|
+
break;
|
|
834
|
+
}
|
|
835
|
+
return null;
|
|
836
|
+
},
|
|
837
|
+
getSignature(node, source) {
|
|
838
|
+
const startLine = node.startPosition.row;
|
|
839
|
+
const lines = source.split("\n");
|
|
840
|
+
const line = lines[startLine];
|
|
841
|
+
if (!line) return void 0;
|
|
842
|
+
const sig = line.trim();
|
|
843
|
+
return sig.length > 200 ? sig.slice(0, 200) + "..." : sig;
|
|
844
|
+
},
|
|
845
|
+
isExported(node) {
|
|
846
|
+
if (node.text.startsWith("static ")) return false;
|
|
847
|
+
if (node.text.startsWith("public ")) return true;
|
|
848
|
+
return node.parent?.type === "translation_unit" || false;
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
var JAVA_QUERY = {
|
|
852
|
+
nodeTypes: /* @__PURE__ */ new Set([
|
|
853
|
+
"class_declaration",
|
|
854
|
+
"interface_declaration",
|
|
855
|
+
"enum_declaration",
|
|
856
|
+
"method_declaration",
|
|
857
|
+
"constructor_declaration",
|
|
858
|
+
"field_declaration",
|
|
859
|
+
"annotation_type_declaration",
|
|
860
|
+
// Kotlin
|
|
861
|
+
"function_declaration",
|
|
862
|
+
"object_declaration"
|
|
863
|
+
]),
|
|
864
|
+
getKind(type) {
|
|
865
|
+
const map = {
|
|
866
|
+
class_declaration: "class",
|
|
867
|
+
interface_declaration: "interface",
|
|
868
|
+
enum_declaration: "enum",
|
|
869
|
+
method_declaration: "method",
|
|
870
|
+
constructor_declaration: "method",
|
|
871
|
+
field_declaration: "property",
|
|
872
|
+
annotation_type_declaration: "interface",
|
|
873
|
+
function_declaration: "function",
|
|
874
|
+
object_declaration: "class"
|
|
875
|
+
};
|
|
876
|
+
return map[type] || "variable";
|
|
877
|
+
},
|
|
878
|
+
getName(node) {
|
|
879
|
+
const nameNode = node.childForFieldName("name");
|
|
880
|
+
if (nameNode) return nameNode.text;
|
|
881
|
+
for (const child of node.namedChildren) {
|
|
882
|
+
if (child.type === "variable_declarator") {
|
|
883
|
+
const name = child.childForFieldName("name");
|
|
884
|
+
if (name) return name.text;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
return null;
|
|
888
|
+
},
|
|
889
|
+
getSignature(node, source) {
|
|
890
|
+
const startLine = node.startPosition.row;
|
|
891
|
+
const lines = source.split("\n");
|
|
892
|
+
const line = lines[startLine];
|
|
893
|
+
if (!line) return void 0;
|
|
894
|
+
const sig = line.trim();
|
|
895
|
+
return sig.length > 200 ? sig.slice(0, 200) + "..." : sig;
|
|
896
|
+
},
|
|
897
|
+
isExported(node) {
|
|
898
|
+
return node.text.startsWith("public ") || node.text.startsWith("protected ");
|
|
899
|
+
}
|
|
900
|
+
};
|
|
901
|
+
var GENERIC_QUERY = {
|
|
902
|
+
nodeTypes: /* @__PURE__ */ new Set([
|
|
903
|
+
// Functions
|
|
904
|
+
"function_declaration",
|
|
905
|
+
"function_definition",
|
|
906
|
+
"function_item",
|
|
907
|
+
"method_declaration",
|
|
908
|
+
"method_definition",
|
|
909
|
+
"method",
|
|
910
|
+
// Classes / structs
|
|
911
|
+
"class_declaration",
|
|
912
|
+
"class_definition",
|
|
913
|
+
"class_specifier",
|
|
914
|
+
"struct_specifier",
|
|
915
|
+
"struct_item",
|
|
916
|
+
"struct_declaration",
|
|
917
|
+
"module_definition",
|
|
918
|
+
"module",
|
|
919
|
+
// Interfaces / traits / protocols
|
|
920
|
+
"interface_declaration",
|
|
921
|
+
"trait_item",
|
|
922
|
+
"protocol_declaration",
|
|
923
|
+
// Enums
|
|
924
|
+
"enum_declaration",
|
|
925
|
+
"enum_item",
|
|
926
|
+
"enum_specifier",
|
|
927
|
+
"enum_definition",
|
|
928
|
+
// Types
|
|
929
|
+
"type_declaration",
|
|
930
|
+
"type_alias_declaration",
|
|
931
|
+
"type_item",
|
|
932
|
+
"type_definition",
|
|
933
|
+
// Constants
|
|
934
|
+
"const_declaration",
|
|
935
|
+
"const_item"
|
|
936
|
+
]),
|
|
937
|
+
getKind(type) {
|
|
938
|
+
if (type.includes("function") || type.includes("method")) return "function";
|
|
939
|
+
if (type.includes("class") || type.includes("struct") || type.includes("module")) return "class";
|
|
940
|
+
if (type.includes("interface") || type.includes("trait") || type.includes("protocol")) return "interface";
|
|
941
|
+
if (type.includes("enum")) return "enum";
|
|
942
|
+
if (type.includes("type")) return "type";
|
|
943
|
+
if (type.includes("const")) return "const";
|
|
944
|
+
return "variable";
|
|
945
|
+
},
|
|
946
|
+
getName(node) {
|
|
947
|
+
const nameNode = node.childForFieldName("name");
|
|
948
|
+
if (nameNode) return nameNode.text;
|
|
949
|
+
let declarator = node.childForFieldName("declarator");
|
|
950
|
+
while (declarator) {
|
|
951
|
+
if (declarator.type === "identifier" || declarator.type === "type_identifier") return declarator.text;
|
|
952
|
+
const inner = declarator.childForFieldName("declarator") || declarator.childForFieldName("name");
|
|
953
|
+
if (inner) {
|
|
954
|
+
if (inner.type === "identifier" || inner.type === "type_identifier") return inner.text;
|
|
955
|
+
declarator = inner;
|
|
956
|
+
continue;
|
|
957
|
+
}
|
|
958
|
+
break;
|
|
959
|
+
}
|
|
960
|
+
for (const child of node.namedChildren) {
|
|
961
|
+
if (child.type === "identifier" || child.type === "type_identifier") return child.text;
|
|
962
|
+
}
|
|
963
|
+
return null;
|
|
964
|
+
},
|
|
965
|
+
getSignature(node, source) {
|
|
966
|
+
const startLine = node.startPosition.row;
|
|
967
|
+
const lines = source.split("\n");
|
|
968
|
+
const line = lines[startLine];
|
|
969
|
+
if (!line) return void 0;
|
|
970
|
+
const sig = line.trim();
|
|
971
|
+
return sig.length > 200 ? sig.slice(0, 200) + "..." : sig;
|
|
972
|
+
},
|
|
973
|
+
isExported(node) {
|
|
974
|
+
const text = node.text;
|
|
975
|
+
if (text.startsWith("pub ") || text.startsWith("public ") || text.startsWith("export ")) return true;
|
|
976
|
+
const parent = node.parent;
|
|
977
|
+
if (parent?.type === "export_statement") return true;
|
|
978
|
+
return false;
|
|
979
|
+
}
|
|
980
|
+
};
|
|
981
|
+
function getQuery(language) {
|
|
982
|
+
switch (language) {
|
|
983
|
+
case "typescript":
|
|
984
|
+
case "tsx":
|
|
985
|
+
case "javascript":
|
|
986
|
+
return TS_JS_QUERY;
|
|
987
|
+
case "python":
|
|
988
|
+
return PYTHON_QUERY;
|
|
989
|
+
case "go":
|
|
990
|
+
return GO_QUERY;
|
|
991
|
+
case "rust":
|
|
992
|
+
return RUST_QUERY;
|
|
993
|
+
case "c":
|
|
994
|
+
case "cpp":
|
|
995
|
+
case "objc":
|
|
996
|
+
return C_CPP_QUERY;
|
|
997
|
+
case "java":
|
|
998
|
+
case "kotlin":
|
|
999
|
+
case "c_sharp":
|
|
1000
|
+
case "scala":
|
|
1001
|
+
case "dart":
|
|
1002
|
+
return JAVA_QUERY;
|
|
1003
|
+
default:
|
|
1004
|
+
return GENERIC_QUERY;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
function extractSymbols(tree, file, language, source) {
|
|
1008
|
+
const query = getQuery(language);
|
|
1009
|
+
const symbols = [];
|
|
1010
|
+
function walk(node, parentName) {
|
|
1011
|
+
if (query.nodeTypes.has(node.type)) {
|
|
1012
|
+
if (node.type === "export_statement") {
|
|
1013
|
+
const declaration = node.namedChildren.find((c) => query.nodeTypes.has(c.type));
|
|
1014
|
+
if (declaration) {
|
|
1015
|
+
const name2 = query.getName(declaration);
|
|
1016
|
+
if (name2) {
|
|
1017
|
+
symbols.push({
|
|
1018
|
+
name: name2,
|
|
1019
|
+
kind: query.getKind(declaration.type),
|
|
1020
|
+
file,
|
|
1021
|
+
startLine: declaration.startPosition.row + 1,
|
|
1022
|
+
endLine: declaration.endPosition.row + 1,
|
|
1023
|
+
signature: query.getSignature(declaration, source),
|
|
1024
|
+
exported: true,
|
|
1025
|
+
parentName
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
for (const child of declaration.namedChildren) {
|
|
1029
|
+
walk(child, name2 || parentName);
|
|
1030
|
+
}
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
const name = query.getName(node);
|
|
1036
|
+
if (name) {
|
|
1037
|
+
symbols.push({
|
|
1038
|
+
name,
|
|
1039
|
+
kind: query.getKind(node.type),
|
|
1040
|
+
file,
|
|
1041
|
+
startLine: node.startPosition.row + 1,
|
|
1042
|
+
endLine: node.endPosition.row + 1,
|
|
1043
|
+
signature: query.getSignature(node, source),
|
|
1044
|
+
exported: query.isExported(node),
|
|
1045
|
+
parentName
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
for (const child of node.namedChildren) {
|
|
1049
|
+
walk(child, name || parentName);
|
|
1050
|
+
}
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
for (const child of node.namedChildren) {
|
|
1054
|
+
walk(child, parentName);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
walk(tree.rootNode);
|
|
1058
|
+
return symbols;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
// src/extraction/imports.ts
|
|
1062
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
1063
|
+
import { existsSync as existsSync2 } from "fs";
|
|
1064
|
+
function extractImports(tree, file, language) {
|
|
1065
|
+
switch (language) {
|
|
1066
|
+
case "typescript":
|
|
1067
|
+
case "tsx":
|
|
1068
|
+
case "javascript":
|
|
1069
|
+
return extractTsImports(tree, file);
|
|
1070
|
+
case "python":
|
|
1071
|
+
return extractPythonImports(tree, file);
|
|
1072
|
+
case "go":
|
|
1073
|
+
return extractGoImports(tree, file);
|
|
1074
|
+
case "rust":
|
|
1075
|
+
return extractRustImports(tree, file);
|
|
1076
|
+
case "c":
|
|
1077
|
+
case "cpp":
|
|
1078
|
+
case "objc":
|
|
1079
|
+
return extractCIncludes(tree, file);
|
|
1080
|
+
case "java":
|
|
1081
|
+
case "kotlin":
|
|
1082
|
+
case "c_sharp":
|
|
1083
|
+
case "scala":
|
|
1084
|
+
case "dart":
|
|
1085
|
+
return extractJavaImports(tree, file);
|
|
1086
|
+
default:
|
|
1087
|
+
return extractGenericImports(tree, file);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
function extractTsImports(tree, file) {
|
|
1091
|
+
const imports = [];
|
|
1092
|
+
function walk(node) {
|
|
1093
|
+
if (node.type === "import_statement") {
|
|
1094
|
+
const sourceNode = node.childForFieldName("source");
|
|
1095
|
+
if (!sourceNode) {
|
|
1096
|
+
for (const child of node.namedChildren) {
|
|
1097
|
+
walk(child);
|
|
1098
|
+
}
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
const rawPath = sourceNode.text.replace(/['"]/g, "");
|
|
1102
|
+
if (!rawPath.startsWith(".")) {
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
const resolved = resolveImportPath(file, rawPath);
|
|
1106
|
+
const specifiers = extractSpecifiers(node);
|
|
1107
|
+
imports.push({
|
|
1108
|
+
source: file,
|
|
1109
|
+
target: resolved,
|
|
1110
|
+
specifiers
|
|
1111
|
+
});
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
for (const child of node.namedChildren) {
|
|
1115
|
+
walk(child);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
walk(tree.rootNode);
|
|
1119
|
+
return imports;
|
|
1120
|
+
}
|
|
1121
|
+
function extractSpecifiers(importNode) {
|
|
1122
|
+
const specifiers = [];
|
|
1123
|
+
function walk(node) {
|
|
1124
|
+
if (node.type === "import_specifier") {
|
|
1125
|
+
const name = node.childForFieldName("name");
|
|
1126
|
+
if (name) specifiers.push(name.text);
|
|
1127
|
+
} else if (node.type === "namespace_import") {
|
|
1128
|
+
specifiers.push("*");
|
|
1129
|
+
} else if (node.type === "identifier" && node.parent?.type === "import_clause") {
|
|
1130
|
+
specifiers.push(node.text);
|
|
1131
|
+
}
|
|
1132
|
+
for (const child of node.namedChildren) {
|
|
1133
|
+
walk(child);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
walk(importNode);
|
|
1137
|
+
return specifiers;
|
|
1138
|
+
}
|
|
1139
|
+
function resolveImportPath(fromFile, importPath) {
|
|
1140
|
+
const dir = dirname2(fromFile);
|
|
1141
|
+
let resolved = join2(dir, importPath);
|
|
1142
|
+
resolved = resolved.replace(/\.(js|ts|tsx|jsx|mjs)$/, "");
|
|
1143
|
+
const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", "/index.ts", "/index.js"];
|
|
1144
|
+
for (const ext of extensions) {
|
|
1145
|
+
if (existsSync2(resolved + ext)) {
|
|
1146
|
+
return resolved + ext;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
return resolved + ".ts";
|
|
1150
|
+
}
|
|
1151
|
+
function extractPythonImports(tree, file) {
|
|
1152
|
+
const imports = [];
|
|
1153
|
+
function walk(node) {
|
|
1154
|
+
if (node.type === "import_from_statement") {
|
|
1155
|
+
const moduleNode = node.childForFieldName("module_name");
|
|
1156
|
+
if (moduleNode) {
|
|
1157
|
+
const modulePath = moduleNode.text;
|
|
1158
|
+
if (modulePath.startsWith(".")) {
|
|
1159
|
+
const specifiers = [];
|
|
1160
|
+
for (const child of node.namedChildren) {
|
|
1161
|
+
if (child.type === "dotted_name" && child !== moduleNode) {
|
|
1162
|
+
specifiers.push(child.text);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
imports.push({
|
|
1166
|
+
source: file,
|
|
1167
|
+
target: resolvePythonImport(file, modulePath),
|
|
1168
|
+
specifiers
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
for (const child of node.namedChildren) {
|
|
1174
|
+
walk(child);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
walk(tree.rootNode);
|
|
1178
|
+
return imports;
|
|
1179
|
+
}
|
|
1180
|
+
function resolvePythonImport(fromFile, importPath) {
|
|
1181
|
+
const dir = dirname2(fromFile);
|
|
1182
|
+
const parts = importPath.replace(/^\.+/, "");
|
|
1183
|
+
const dots = importPath.match(/^\.+/)?.[0].length || 1;
|
|
1184
|
+
let base = dir;
|
|
1185
|
+
for (let i = 1; i < dots; i++) base = dirname2(base);
|
|
1186
|
+
return join2(base, parts.replace(/\./g, "/") + ".py");
|
|
1187
|
+
}
|
|
1188
|
+
function extractGoImports(tree, file) {
|
|
1189
|
+
const imports = [];
|
|
1190
|
+
function walk(node) {
|
|
1191
|
+
if (node.type === "import_spec") {
|
|
1192
|
+
const pathNode = node.childForFieldName("path");
|
|
1193
|
+
if (pathNode) {
|
|
1194
|
+
imports.push({
|
|
1195
|
+
source: file,
|
|
1196
|
+
target: pathNode.text.replace(/"/g, ""),
|
|
1197
|
+
specifiers: ["*"]
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
for (const child of node.namedChildren) {
|
|
1202
|
+
walk(child);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
walk(tree.rootNode);
|
|
1206
|
+
return imports;
|
|
1207
|
+
}
|
|
1208
|
+
function extractRustImports(tree, file) {
|
|
1209
|
+
const imports = [];
|
|
1210
|
+
function walk(node) {
|
|
1211
|
+
if (node.type === "use_declaration") {
|
|
1212
|
+
const path = node.namedChildren.find((c) => c.type === "scoped_identifier" || c.type === "use_wildcard" || c.type === "use_list");
|
|
1213
|
+
if (path) {
|
|
1214
|
+
imports.push({
|
|
1215
|
+
source: file,
|
|
1216
|
+
target: path.text,
|
|
1217
|
+
specifiers: ["*"]
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
for (const child of node.namedChildren) {
|
|
1222
|
+
walk(child);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
walk(tree.rootNode);
|
|
1226
|
+
return imports;
|
|
1227
|
+
}
|
|
1228
|
+
function extractCIncludes(tree, file) {
|
|
1229
|
+
const imports = [];
|
|
1230
|
+
function walk(node) {
|
|
1231
|
+
if (node.type === "preproc_include") {
|
|
1232
|
+
const pathNode = node.childForFieldName("path");
|
|
1233
|
+
if (pathNode) {
|
|
1234
|
+
const raw = pathNode.text.replace(/['"<>]/g, "");
|
|
1235
|
+
imports.push({ source: file, target: raw, specifiers: ["*"] });
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
for (const child of node.namedChildren) walk(child);
|
|
1239
|
+
}
|
|
1240
|
+
walk(tree.rootNode);
|
|
1241
|
+
return imports;
|
|
1242
|
+
}
|
|
1243
|
+
function extractJavaImports(tree, file) {
|
|
1244
|
+
const imports = [];
|
|
1245
|
+
function walk(node) {
|
|
1246
|
+
if (node.type === "import_declaration") {
|
|
1247
|
+
for (const child of node.namedChildren) {
|
|
1248
|
+
if (child.type === "scoped_identifier" || child.type === "identifier") {
|
|
1249
|
+
imports.push({ source: file, target: child.text, specifiers: ["*"] });
|
|
1250
|
+
break;
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
for (const child of node.namedChildren) walk(child);
|
|
1255
|
+
}
|
|
1256
|
+
walk(tree.rootNode);
|
|
1257
|
+
return imports;
|
|
1258
|
+
}
|
|
1259
|
+
function extractGenericImports(tree, file) {
|
|
1260
|
+
const imports = [];
|
|
1261
|
+
const importNodeTypes = /* @__PURE__ */ new Set([
|
|
1262
|
+
"preproc_include",
|
|
1263
|
+
"import_declaration",
|
|
1264
|
+
"import_statement",
|
|
1265
|
+
"import_from_statement",
|
|
1266
|
+
"use_declaration",
|
|
1267
|
+
"namespace_use_declaration"
|
|
1268
|
+
]);
|
|
1269
|
+
function walk(node) {
|
|
1270
|
+
if (importNodeTypes.has(node.type)) {
|
|
1271
|
+
const pathNode = node.childForFieldName("path") || node.childForFieldName("source") || node.childForFieldName("module_name");
|
|
1272
|
+
if (pathNode) {
|
|
1273
|
+
const raw = pathNode.text.replace(/['"<>]/g, "");
|
|
1274
|
+
imports.push({ source: file, target: raw, specifiers: ["*"] });
|
|
1275
|
+
} else {
|
|
1276
|
+
for (const child of node.namedChildren) {
|
|
1277
|
+
if (child.type === "scoped_identifier" || child.type === "dotted_name" || child.type === "string_literal" || child.type === "string") {
|
|
1278
|
+
imports.push({ source: file, target: child.text.replace(/['"]/g, ""), specifiers: ["*"] });
|
|
1279
|
+
break;
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
for (const child of node.namedChildren) walk(child);
|
|
1286
|
+
}
|
|
1287
|
+
walk(tree.rootNode);
|
|
1288
|
+
return imports;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
// src/extraction/calls.ts
|
|
1292
|
+
function extractCalls(tree, file, language) {
|
|
1293
|
+
switch (language) {
|
|
1294
|
+
case "typescript":
|
|
1295
|
+
case "tsx":
|
|
1296
|
+
case "javascript":
|
|
1297
|
+
return extractTsCalls(tree, file);
|
|
1298
|
+
case "python":
|
|
1299
|
+
return extractPythonCalls(tree, file);
|
|
1300
|
+
case "go":
|
|
1301
|
+
return extractGoCalls(tree, file);
|
|
1302
|
+
case "rust":
|
|
1303
|
+
return extractRustCalls(tree, file);
|
|
1304
|
+
default:
|
|
1305
|
+
return extractGenericCalls(tree, file);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
function extractTsCalls(tree, file) {
|
|
1309
|
+
const calls = [];
|
|
1310
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1311
|
+
function findEnclosingFunction(node) {
|
|
1312
|
+
let current = node.parent;
|
|
1313
|
+
while (current) {
|
|
1314
|
+
if (current.type === "function_declaration" || current.type === "method_definition" || current.type === "arrow_function") {
|
|
1315
|
+
const name = current.childForFieldName("name");
|
|
1316
|
+
if (name) return name.text;
|
|
1317
|
+
if (current.parent?.type === "variable_declarator") {
|
|
1318
|
+
const varName = current.parent.childForFieldName("name");
|
|
1319
|
+
if (varName) return varName.text;
|
|
1320
|
+
}
|
|
1321
|
+
return "<anonymous>";
|
|
1322
|
+
}
|
|
1323
|
+
current = current.parent;
|
|
1324
|
+
}
|
|
1325
|
+
return "<module>";
|
|
1326
|
+
}
|
|
1327
|
+
function walk(node) {
|
|
1328
|
+
if (node.type === "call_expression") {
|
|
1329
|
+
const funcNode = node.childForFieldName("function");
|
|
1330
|
+
if (funcNode) {
|
|
1331
|
+
let callee;
|
|
1332
|
+
if (funcNode.type === "member_expression") {
|
|
1333
|
+
const prop = funcNode.childForFieldName("property");
|
|
1334
|
+
callee = prop?.text || funcNode.text;
|
|
1335
|
+
} else {
|
|
1336
|
+
callee = funcNode.text;
|
|
1337
|
+
}
|
|
1338
|
+
const caller = findEnclosingFunction(node);
|
|
1339
|
+
const key = `${caller}:${callee}:${node.startPosition.row}`;
|
|
1340
|
+
if (!seen.has(key)) {
|
|
1341
|
+
seen.add(key);
|
|
1342
|
+
calls.push({
|
|
1343
|
+
caller: `${file}:${caller}`,
|
|
1344
|
+
callee,
|
|
1345
|
+
file,
|
|
1346
|
+
line: node.startPosition.row + 1
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
for (const child of node.namedChildren) {
|
|
1352
|
+
walk(child);
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
walk(tree.rootNode);
|
|
1356
|
+
return calls;
|
|
1357
|
+
}
|
|
1358
|
+
function extractPythonCalls(tree, file) {
|
|
1359
|
+
const calls = [];
|
|
1360
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1361
|
+
function findEnclosing(node) {
|
|
1362
|
+
let current = node.parent;
|
|
1363
|
+
while (current) {
|
|
1364
|
+
if (current.type === "function_definition") {
|
|
1365
|
+
const name = current.childForFieldName("name");
|
|
1366
|
+
if (name) return name.text;
|
|
1367
|
+
}
|
|
1368
|
+
current = current.parent;
|
|
1369
|
+
}
|
|
1370
|
+
return "<module>";
|
|
1371
|
+
}
|
|
1372
|
+
function walk(node) {
|
|
1373
|
+
if (node.type === "call") {
|
|
1374
|
+
const funcNode = node.childForFieldName("function");
|
|
1375
|
+
if (funcNode) {
|
|
1376
|
+
const callee = funcNode.type === "attribute" ? funcNode.childForFieldName("attribute")?.text || funcNode.text : funcNode.text;
|
|
1377
|
+
const caller = findEnclosing(node);
|
|
1378
|
+
const key = `${caller}:${callee}:${node.startPosition.row}`;
|
|
1379
|
+
if (!seen.has(key)) {
|
|
1380
|
+
seen.add(key);
|
|
1381
|
+
calls.push({ caller: `${file}:${caller}`, callee, file, line: node.startPosition.row + 1 });
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
for (const child of node.namedChildren) walk(child);
|
|
1386
|
+
}
|
|
1387
|
+
walk(tree.rootNode);
|
|
1388
|
+
return calls;
|
|
1389
|
+
}
|
|
1390
|
+
function extractGoCalls(tree, file) {
|
|
1391
|
+
const calls = [];
|
|
1392
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1393
|
+
function findEnclosing(node) {
|
|
1394
|
+
let current = node.parent;
|
|
1395
|
+
while (current) {
|
|
1396
|
+
if (current.type === "function_declaration" || current.type === "method_declaration") {
|
|
1397
|
+
const name = current.childForFieldName("name");
|
|
1398
|
+
if (name) return name.text;
|
|
1399
|
+
}
|
|
1400
|
+
current = current.parent;
|
|
1401
|
+
}
|
|
1402
|
+
return "<module>";
|
|
1403
|
+
}
|
|
1404
|
+
function walk(node) {
|
|
1405
|
+
if (node.type === "call_expression") {
|
|
1406
|
+
const funcNode = node.childForFieldName("function");
|
|
1407
|
+
if (funcNode) {
|
|
1408
|
+
const callee = funcNode.type === "selector_expression" ? funcNode.childForFieldName("field")?.text || funcNode.text : funcNode.text;
|
|
1409
|
+
const caller = findEnclosing(node);
|
|
1410
|
+
const key = `${caller}:${callee}:${node.startPosition.row}`;
|
|
1411
|
+
if (!seen.has(key)) {
|
|
1412
|
+
seen.add(key);
|
|
1413
|
+
calls.push({ caller: `${file}:${caller}`, callee, file, line: node.startPosition.row + 1 });
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
for (const child of node.namedChildren) walk(child);
|
|
1418
|
+
}
|
|
1419
|
+
walk(tree.rootNode);
|
|
1420
|
+
return calls;
|
|
1421
|
+
}
|
|
1422
|
+
function extractRustCalls(tree, file) {
|
|
1423
|
+
const calls = [];
|
|
1424
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1425
|
+
function findEnclosing(node) {
|
|
1426
|
+
let current = node.parent;
|
|
1427
|
+
while (current) {
|
|
1428
|
+
if (current.type === "function_item") {
|
|
1429
|
+
const name = current.childForFieldName("name");
|
|
1430
|
+
if (name) return name.text;
|
|
1431
|
+
}
|
|
1432
|
+
current = current.parent;
|
|
1433
|
+
}
|
|
1434
|
+
return "<module>";
|
|
1435
|
+
}
|
|
1436
|
+
function walk(node) {
|
|
1437
|
+
if (node.type === "call_expression") {
|
|
1438
|
+
const funcNode = node.childForFieldName("function");
|
|
1439
|
+
if (funcNode) {
|
|
1440
|
+
const callee = funcNode.text.split("::").pop() || funcNode.text;
|
|
1441
|
+
const caller = findEnclosing(node);
|
|
1442
|
+
const key = `${caller}:${callee}:${node.startPosition.row}`;
|
|
1443
|
+
if (!seen.has(key)) {
|
|
1444
|
+
seen.add(key);
|
|
1445
|
+
calls.push({ caller: `${file}:${caller}`, callee, file, line: node.startPosition.row + 1 });
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
for (const child of node.namedChildren) walk(child);
|
|
1450
|
+
}
|
|
1451
|
+
walk(tree.rootNode);
|
|
1452
|
+
return calls;
|
|
1453
|
+
}
|
|
1454
|
+
function extractGenericCalls(tree, file) {
|
|
1455
|
+
const calls = [];
|
|
1456
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1457
|
+
function findEnclosing(node) {
|
|
1458
|
+
let current = node.parent;
|
|
1459
|
+
while (current) {
|
|
1460
|
+
if (current.type.includes("function") || current.type.includes("method")) {
|
|
1461
|
+
const name = current.childForFieldName("name");
|
|
1462
|
+
if (name) return name.text;
|
|
1463
|
+
}
|
|
1464
|
+
current = current.parent;
|
|
1465
|
+
}
|
|
1466
|
+
return "<module>";
|
|
1467
|
+
}
|
|
1468
|
+
function walk(node) {
|
|
1469
|
+
if (node.type === "call_expression" || node.type === "call") {
|
|
1470
|
+
const funcNode = node.childForFieldName("function") || node.childForFieldName("method");
|
|
1471
|
+
if (funcNode) {
|
|
1472
|
+
let callee;
|
|
1473
|
+
if (funcNode.type.includes("member") || funcNode.type.includes("selector") || funcNode.type === "attribute") {
|
|
1474
|
+
const prop = funcNode.childForFieldName("property") || funcNode.childForFieldName("field") || funcNode.childForFieldName("attribute");
|
|
1475
|
+
callee = prop?.text || funcNode.text;
|
|
1476
|
+
} else {
|
|
1477
|
+
callee = funcNode.text;
|
|
1478
|
+
}
|
|
1479
|
+
const caller = findEnclosing(node);
|
|
1480
|
+
const key = `${caller}:${callee}:${node.startPosition.row}`;
|
|
1481
|
+
if (!seen.has(key)) {
|
|
1482
|
+
seen.add(key);
|
|
1483
|
+
calls.push({ caller: `${file}:${caller}`, callee, file, line: node.startPosition.row + 1 });
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
for (const child of node.namedChildren) walk(child);
|
|
1488
|
+
}
|
|
1489
|
+
walk(tree.rootNode);
|
|
1490
|
+
return calls;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
// src/cli/commands/init.ts
|
|
1494
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
1495
|
+
async function initCommand(opts) {
|
|
1496
|
+
const root = resolve(opts.root);
|
|
1497
|
+
const days = parseInt(opts.days, 10);
|
|
1498
|
+
console.log(`CodeCortex init \u2014 analyzing ${root}`);
|
|
1499
|
+
console.log("");
|
|
1500
|
+
console.log("Step 1/6: Discovering project structure...");
|
|
1501
|
+
const project = await discoverProject(root);
|
|
1502
|
+
console.log(` Found ${project.files.length} files in ${project.modules.length} modules`);
|
|
1503
|
+
console.log(` Languages: ${project.languages.join(", ")}`);
|
|
1504
|
+
console.log(` Type: ${project.type}`);
|
|
1505
|
+
console.log("");
|
|
1506
|
+
console.log("Step 2/6: Extracting symbols with tree-sitter...");
|
|
1507
|
+
await initParser();
|
|
1508
|
+
const allSymbols = [];
|
|
1509
|
+
const allImports = [];
|
|
1510
|
+
const allCalls = [];
|
|
1511
|
+
let extractionErrors = 0;
|
|
1512
|
+
let parsed = 0;
|
|
1513
|
+
const parseable = project.files.filter((f) => languageFromPath(f.path)).length;
|
|
1514
|
+
const showProgress = parseable > 500;
|
|
1515
|
+
for (const file of project.files) {
|
|
1516
|
+
const lang = languageFromPath(file.path);
|
|
1517
|
+
if (!lang) continue;
|
|
1518
|
+
try {
|
|
1519
|
+
const tree = await parseFile(file.absolutePath, lang);
|
|
1520
|
+
const source = await readFile2(file.absolutePath, "utf-8");
|
|
1521
|
+
const symbols = extractSymbols(tree, file.path, lang, source);
|
|
1522
|
+
const imports = extractImports(tree, file.path, lang);
|
|
1523
|
+
const calls = extractCalls(tree, file.path, lang);
|
|
1524
|
+
allSymbols.push(...symbols);
|
|
1525
|
+
allImports.push(...imports);
|
|
1526
|
+
allCalls.push(...calls);
|
|
1527
|
+
} catch {
|
|
1528
|
+
extractionErrors++;
|
|
1529
|
+
}
|
|
1530
|
+
parsed++;
|
|
1531
|
+
if (showProgress && parsed % 5e3 === 0) {
|
|
1532
|
+
process.stdout.write(`\r Progress: ${parsed}/${parseable} files (${allSymbols.length} symbols)`);
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
if (showProgress) process.stdout.write("\r" + " ".repeat(70) + "\r");
|
|
1536
|
+
console.log(` Extracted ${allSymbols.length} symbols, ${allImports.length} imports, ${allCalls.length} call edges`);
|
|
1537
|
+
if (extractionErrors > 0) {
|
|
1538
|
+
console.log(` (${extractionErrors} files skipped due to parse errors)`);
|
|
1539
|
+
}
|
|
1540
|
+
console.log("");
|
|
1541
|
+
console.log("Step 3/6: Building dependency graph...");
|
|
1542
|
+
const MODULE_ROOTS = /* @__PURE__ */ new Set(["src", "lib", "pkg", "packages", "apps", "extensions", "crates", "internal", "cmd", "scripts", "tools", "rust"]);
|
|
1543
|
+
const moduleNodes = project.modules.map((modName) => {
|
|
1544
|
+
const modFiles = project.files.filter((f) => {
|
|
1545
|
+
const parts = f.path.split("/");
|
|
1546
|
+
const topDir2 = parts[0] ?? "";
|
|
1547
|
+
return MODULE_ROOTS.has(topDir2) && parts[1] === modName || parts[0] === modName;
|
|
1548
|
+
});
|
|
1549
|
+
const topDir = modFiles[0]?.path.split("/")[0] ?? "src";
|
|
1550
|
+
return {
|
|
1551
|
+
path: `${topDir}/${modName}`,
|
|
1552
|
+
name: modName,
|
|
1553
|
+
files: modFiles.map((f) => f.path),
|
|
1554
|
+
language: modFiles[0]?.language || "unknown",
|
|
1555
|
+
lines: modFiles.reduce((sum, f) => sum + f.lines, 0),
|
|
1556
|
+
symbols: allSymbols.filter((s) => modFiles.some((f) => f.path === s.file)).length
|
|
1557
|
+
};
|
|
1558
|
+
});
|
|
1559
|
+
const externalDeps = {};
|
|
1560
|
+
for (const file of project.files) {
|
|
1561
|
+
try {
|
|
1562
|
+
const content = await readFile2(file.absolutePath, "utf-8");
|
|
1563
|
+
const importMatches = content.matchAll(/from\s+['"]([^.\/][^'"]*)['"]/g);
|
|
1564
|
+
for (const match of importMatches) {
|
|
1565
|
+
const raw = match[1];
|
|
1566
|
+
if (!raw) continue;
|
|
1567
|
+
const pkg = raw.startsWith("@") ? raw.split("/").slice(0, 2).join("/") : raw.split("/")[0] ?? raw;
|
|
1568
|
+
if (!externalDeps[pkg]) externalDeps[pkg] = [];
|
|
1569
|
+
externalDeps[pkg].push(file.path);
|
|
1570
|
+
}
|
|
1571
|
+
} catch {
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
const graph = buildGraph({
|
|
1575
|
+
modules: moduleNodes,
|
|
1576
|
+
imports: allImports,
|
|
1577
|
+
calls: allCalls,
|
|
1578
|
+
entryPoints: project.entryPoints,
|
|
1579
|
+
externalDeps
|
|
1580
|
+
});
|
|
1581
|
+
console.log(` ${moduleNodes.length} modules, ${Object.keys(externalDeps).length} external deps`);
|
|
1582
|
+
console.log("");
|
|
1583
|
+
console.log("Step 4/6: Analyzing git history...");
|
|
1584
|
+
let temporalData = null;
|
|
1585
|
+
const hasGit = await isGitRepo(root);
|
|
1586
|
+
if (hasGit) {
|
|
1587
|
+
temporalData = await analyzeTemporalData(root, days);
|
|
1588
|
+
enrichCouplingWithImports(graph, temporalData.coupling);
|
|
1589
|
+
console.log(` ${temporalData.totalCommits} commits analyzed over ${days} days`);
|
|
1590
|
+
console.log(` ${temporalData.hotspots.length} hotspots, ${temporalData.coupling.length} coupling pairs, ${temporalData.bugHistory.length} bug records`);
|
|
1591
|
+
} else {
|
|
1592
|
+
console.log(" No git repository found, skipping temporal analysis");
|
|
1593
|
+
}
|
|
1594
|
+
console.log("");
|
|
1595
|
+
console.log("Step 5/6: Writing knowledge files...");
|
|
1596
|
+
await ensureDir(cortexPath(root));
|
|
1597
|
+
await ensureDir(cortexPath(root, "modules"));
|
|
1598
|
+
await ensureDir(cortexPath(root, "decisions"));
|
|
1599
|
+
await ensureDir(cortexPath(root, "sessions"));
|
|
1600
|
+
const symbolIndex = {
|
|
1601
|
+
generated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1602
|
+
total: allSymbols.length,
|
|
1603
|
+
symbols: allSymbols
|
|
1604
|
+
};
|
|
1605
|
+
await writeJsonStream(cortexPath(root, "symbols.json"), symbolIndex, "symbols");
|
|
1606
|
+
await writeGraph(root, graph);
|
|
1607
|
+
if (temporalData) {
|
|
1608
|
+
await writeFile(cortexPath(root, "temporal.json"), JSON.stringify(temporalData, null, 2));
|
|
1609
|
+
}
|
|
1610
|
+
const overview = generateOverview(project);
|
|
1611
|
+
await writeFile(cortexPath(root, "overview.md"), overview);
|
|
1612
|
+
const manifest = createManifest({
|
|
1613
|
+
project: project.name,
|
|
1614
|
+
root,
|
|
1615
|
+
languages: project.languages,
|
|
1616
|
+
totalFiles: project.files.length,
|
|
1617
|
+
totalSymbols: allSymbols.length,
|
|
1618
|
+
totalModules: project.modules.length
|
|
1619
|
+
});
|
|
1620
|
+
await writeManifest(root, manifest);
|
|
1621
|
+
await writeFile(cortexPath(root, "patterns.md"), "# Coding Patterns\n\nNo patterns recorded yet. Use `update_patterns` to add patterns.\n");
|
|
1622
|
+
console.log(" Written: cortex.yaml, symbols.json, graph.json, temporal.json, overview.md, patterns.md");
|
|
1623
|
+
console.log("");
|
|
1624
|
+
console.log("Step 6/6: Generating constitution...");
|
|
1625
|
+
await generateConstitution(root, {
|
|
1626
|
+
modules: moduleNodes,
|
|
1627
|
+
entryPoints: project.entryPoints,
|
|
1628
|
+
externalDeps,
|
|
1629
|
+
temporal: temporalData
|
|
1630
|
+
});
|
|
1631
|
+
console.log(" Written: constitution.md");
|
|
1632
|
+
console.log("");
|
|
1633
|
+
const head = await getHeadCommit(root);
|
|
1634
|
+
console.log("\u2500".repeat(50));
|
|
1635
|
+
console.log("CodeCortex initialized successfully!");
|
|
1636
|
+
console.log("");
|
|
1637
|
+
console.log(` Project: ${project.name}`);
|
|
1638
|
+
console.log(` Files: ${project.files.length}`);
|
|
1639
|
+
console.log(` Symbols: ${allSymbols.length}`);
|
|
1640
|
+
console.log(` Modules: ${project.modules.length}`);
|
|
1641
|
+
if (temporalData) {
|
|
1642
|
+
console.log(` Commits: ${temporalData.totalCommits} (last ${days} days)`);
|
|
1643
|
+
const hidden = temporalData.coupling.filter((c) => !c.hasImport && c.strength >= 0.5);
|
|
1644
|
+
if (hidden.length > 0) {
|
|
1645
|
+
console.log(` Hidden deps: ${hidden.length} (files that co-change but don't import each other)`);
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
if (head) {
|
|
1649
|
+
console.log(` Git HEAD: ${head.slice(0, 7)}`);
|
|
1650
|
+
}
|
|
1651
|
+
console.log("");
|
|
1652
|
+
console.log(`Knowledge stored in: ${cortexPath(root)}`);
|
|
1653
|
+
console.log("Run `codecortex serve` to start the MCP server.");
|
|
1654
|
+
}
|
|
1655
|
+
function generateOverview(project) {
|
|
1656
|
+
const lines = [
|
|
1657
|
+
`# ${project.name} \u2014 Overview`,
|
|
1658
|
+
"",
|
|
1659
|
+
`**Type:** ${project.type}`,
|
|
1660
|
+
`**Languages:** ${project.languages.join(", ")}`,
|
|
1661
|
+
`**Files:** ${project.files.length}`,
|
|
1662
|
+
"",
|
|
1663
|
+
`## Entry Points`,
|
|
1664
|
+
...project.entryPoints.map((e) => `- \`${e}\``),
|
|
1665
|
+
"",
|
|
1666
|
+
`## Modules`,
|
|
1667
|
+
...project.modules.map((m) => `- **${m}**`),
|
|
1668
|
+
"",
|
|
1669
|
+
`## File Map`
|
|
1670
|
+
];
|
|
1671
|
+
const dirs = /* @__PURE__ */ new Map();
|
|
1672
|
+
for (const file of project.files) {
|
|
1673
|
+
const parts = file.path.split("/");
|
|
1674
|
+
const dir = parts.length > 1 ? parts.slice(0, -1).join("/") : ".";
|
|
1675
|
+
const existing = dirs.get(dir) || [];
|
|
1676
|
+
const fileName = parts[parts.length - 1];
|
|
1677
|
+
if (fileName) existing.push(fileName);
|
|
1678
|
+
dirs.set(dir, existing);
|
|
1679
|
+
}
|
|
1680
|
+
for (const [dir, files] of [...dirs.entries()].sort()) {
|
|
1681
|
+
lines.push(`
|
|
1682
|
+
### ${dir}/`);
|
|
1683
|
+
for (const file of files.sort()) {
|
|
1684
|
+
lines.push(`- ${file}`);
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
return lines.join("\n") + "\n";
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
// src/cli/commands/serve.ts
|
|
1691
|
+
import { resolve as resolve2 } from "path";
|
|
1692
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1693
|
+
async function serveCommand(opts) {
|
|
1694
|
+
const root = resolve2(opts.root);
|
|
1695
|
+
if (!existsSync3(cortexPath(root, "cortex.yaml"))) {
|
|
1696
|
+
console.error("Error: No CodeCortex knowledge found.");
|
|
1697
|
+
console.error(`Run 'codecortex init' first to analyze the codebase.`);
|
|
1698
|
+
console.error(`Expected: ${cortexPath(root, "cortex.yaml")}`);
|
|
1699
|
+
process.exit(1);
|
|
1700
|
+
}
|
|
1701
|
+
await startServer(root);
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
// src/cli/commands/update.ts
|
|
1705
|
+
import { resolve as resolve3 } from "path";
|
|
1706
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1707
|
+
|
|
1708
|
+
// src/git/diff.ts
|
|
1709
|
+
import simpleGit2 from "simple-git";
|
|
1710
|
+
async function getUncommittedDiff(root) {
|
|
1711
|
+
const git = simpleGit2(root);
|
|
1712
|
+
const diff = await git.diffSummary();
|
|
1713
|
+
return {
|
|
1714
|
+
filesChanged: diff.files.map((f) => f.file),
|
|
1715
|
+
insertions: diff.insertions,
|
|
1716
|
+
deletions: diff.deletions,
|
|
1717
|
+
summary: `${diff.files.length} files changed, +${diff.insertions} -${diff.deletions}`
|
|
1718
|
+
};
|
|
1719
|
+
}
|
|
1720
|
+
function mapFilesToModules(files) {
|
|
1721
|
+
const moduleMap = /* @__PURE__ */ new Map();
|
|
1722
|
+
for (const file of files) {
|
|
1723
|
+
const parts = file.split("/");
|
|
1724
|
+
let module = "root";
|
|
1725
|
+
if (parts[0] === "src" && parts.length >= 3 && parts[1]) {
|
|
1726
|
+
module = parts[1];
|
|
1727
|
+
} else if (parts[0] === "lib" && parts.length >= 3 && parts[1]) {
|
|
1728
|
+
module = parts[1];
|
|
1729
|
+
}
|
|
1730
|
+
const existing = moduleMap.get(module) || [];
|
|
1731
|
+
existing.push(file);
|
|
1732
|
+
moduleMap.set(module, existing);
|
|
1733
|
+
}
|
|
1734
|
+
return moduleMap;
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
// src/cli/commands/update.ts
|
|
1738
|
+
import { readFile as fsRead3 } from "fs/promises";
|
|
1739
|
+
async function updateCommand(opts) {
|
|
1740
|
+
const root = resolve3(opts.root);
|
|
1741
|
+
const days = parseInt(opts.days, 10);
|
|
1742
|
+
if (!existsSync4(cortexPath(root, "cortex.yaml"))) {
|
|
1743
|
+
console.error("Error: No CodeCortex knowledge found. Run `codecortex init` first.");
|
|
1744
|
+
process.exit(1);
|
|
1745
|
+
}
|
|
1746
|
+
console.log("CodeCortex update \u2014 refreshing knowledge...");
|
|
1747
|
+
console.log("");
|
|
1748
|
+
console.log("Discovering changes...");
|
|
1749
|
+
const project = await discoverProject(root);
|
|
1750
|
+
console.log("Re-extracting symbols...");
|
|
1751
|
+
await initParser();
|
|
1752
|
+
const allSymbols = [];
|
|
1753
|
+
const allImports = [];
|
|
1754
|
+
const allCalls = [];
|
|
1755
|
+
for (const file of project.files) {
|
|
1756
|
+
const lang = languageFromPath(file.path);
|
|
1757
|
+
if (!lang) continue;
|
|
1758
|
+
try {
|
|
1759
|
+
const tree = await parseFile(file.absolutePath, lang);
|
|
1760
|
+
const source = await fsRead3(file.absolutePath, "utf-8");
|
|
1761
|
+
allSymbols.push(...extractSymbols(tree, file.path, lang, source));
|
|
1762
|
+
allImports.push(...extractImports(tree, file.path, lang));
|
|
1763
|
+
allCalls.push(...extractCalls(tree, file.path, lang));
|
|
1764
|
+
} catch {
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
console.log("Rebuilding dependency graph...");
|
|
1768
|
+
const MODULE_ROOTS = /* @__PURE__ */ new Set(["src", "lib", "pkg", "packages", "apps", "extensions", "crates", "internal", "cmd", "scripts", "tools", "rust"]);
|
|
1769
|
+
const moduleNodes = project.modules.map((modName) => {
|
|
1770
|
+
const modFiles = project.files.filter((f) => {
|
|
1771
|
+
const parts = f.path.split("/");
|
|
1772
|
+
const topDir2 = parts[0] ?? "";
|
|
1773
|
+
return MODULE_ROOTS.has(topDir2) && parts[1] === modName || parts[0] === modName;
|
|
1774
|
+
});
|
|
1775
|
+
const topDir = modFiles[0]?.path.split("/")[0] ?? "src";
|
|
1776
|
+
return {
|
|
1777
|
+
path: `${topDir}/${modName}`,
|
|
1778
|
+
name: modName,
|
|
1779
|
+
files: modFiles.map((f) => f.path),
|
|
1780
|
+
language: modFiles[0]?.language || "unknown",
|
|
1781
|
+
lines: modFiles.reduce((sum, f) => sum + f.lines, 0),
|
|
1782
|
+
symbols: allSymbols.filter((s) => modFiles.some((f) => f.path === s.file)).length
|
|
1783
|
+
};
|
|
1784
|
+
});
|
|
1785
|
+
const externalDeps = {};
|
|
1786
|
+
for (const file of project.files) {
|
|
1787
|
+
try {
|
|
1788
|
+
const content = await fsRead3(file.absolutePath, "utf-8");
|
|
1789
|
+
const importMatches = content.matchAll(/from\s+['"]([^.\/][^'"]*)['"]/g);
|
|
1790
|
+
for (const match of importMatches) {
|
|
1791
|
+
const raw = match[1];
|
|
1792
|
+
if (!raw) continue;
|
|
1793
|
+
const pkg = raw.startsWith("@") ? raw.split("/").slice(0, 2).join("/") : raw.split("/")[0] ?? raw;
|
|
1794
|
+
if (!externalDeps[pkg]) externalDeps[pkg] = [];
|
|
1795
|
+
externalDeps[pkg].push(file.path);
|
|
1796
|
+
}
|
|
1797
|
+
} catch {
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
const graph = buildGraph({
|
|
1801
|
+
modules: moduleNodes,
|
|
1802
|
+
imports: allImports,
|
|
1803
|
+
calls: allCalls,
|
|
1804
|
+
entryPoints: project.entryPoints,
|
|
1805
|
+
externalDeps
|
|
1806
|
+
});
|
|
1807
|
+
console.log("Re-analyzing git history...");
|
|
1808
|
+
let temporalData = null;
|
|
1809
|
+
if (await isGitRepo(root)) {
|
|
1810
|
+
temporalData = await analyzeTemporalData(root, days);
|
|
1811
|
+
enrichCouplingWithImports(graph, temporalData.coupling);
|
|
1812
|
+
}
|
|
1813
|
+
console.log("Writing updated knowledge...");
|
|
1814
|
+
const symbolIndex = {
|
|
1815
|
+
generated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1816
|
+
total: allSymbols.length,
|
|
1817
|
+
symbols: allSymbols
|
|
1818
|
+
};
|
|
1819
|
+
await writeJsonStream(cortexPath(root, "symbols.json"), symbolIndex, "symbols");
|
|
1820
|
+
await writeGraph(root, graph);
|
|
1821
|
+
if (temporalData) {
|
|
1822
|
+
await writeFile(cortexPath(root, "temporal.json"), JSON.stringify(temporalData, null, 2));
|
|
1823
|
+
}
|
|
1824
|
+
await updateManifest(root, {
|
|
1825
|
+
totalFiles: project.files.length,
|
|
1826
|
+
totalSymbols: allSymbols.length,
|
|
1827
|
+
totalModules: project.modules.length,
|
|
1828
|
+
languages: project.languages
|
|
1829
|
+
});
|
|
1830
|
+
await generateConstitution(root, {
|
|
1831
|
+
modules: moduleNodes,
|
|
1832
|
+
entryPoints: project.entryPoints,
|
|
1833
|
+
externalDeps,
|
|
1834
|
+
temporal: temporalData
|
|
1835
|
+
});
|
|
1836
|
+
const diff = await getUncommittedDiff(root).catch(() => ({ filesChanged: [], summary: "no changes" }));
|
|
1837
|
+
const previousSession = await getLatestSession(root);
|
|
1838
|
+
const affectedModules = [...mapFilesToModules(diff.filesChanged).keys()];
|
|
1839
|
+
const session = createSession({
|
|
1840
|
+
filesChanged: diff.filesChanged,
|
|
1841
|
+
modulesAffected: affectedModules,
|
|
1842
|
+
summary: `Updated knowledge. ${allSymbols.length} symbols, ${project.modules.length} modules.`,
|
|
1843
|
+
previousSession: previousSession || void 0
|
|
1844
|
+
});
|
|
1845
|
+
await writeSession(root, session);
|
|
1846
|
+
console.log("");
|
|
1847
|
+
console.log("\u2500".repeat(50));
|
|
1848
|
+
console.log("Update complete!");
|
|
1849
|
+
console.log(` Symbols: ${allSymbols.length}`);
|
|
1850
|
+
console.log(` Modules: ${project.modules.length}`);
|
|
1851
|
+
if (temporalData) {
|
|
1852
|
+
console.log(` Commits: ${temporalData.totalCommits}`);
|
|
1853
|
+
}
|
|
1854
|
+
console.log(` Session: ${session.id}`);
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
// src/cli/commands/status.ts
|
|
1858
|
+
import { resolve as resolve4 } from "path";
|
|
1859
|
+
import { existsSync as existsSync5 } from "fs";
|
|
1860
|
+
async function statusCommand(opts) {
|
|
1861
|
+
const root = resolve4(opts.root);
|
|
1862
|
+
if (!existsSync5(cortexPath(root, "cortex.yaml"))) {
|
|
1863
|
+
console.log("No CodeCortex knowledge found.");
|
|
1864
|
+
console.log(`Run 'codecortex init' to analyze this codebase.`);
|
|
1865
|
+
return;
|
|
1866
|
+
}
|
|
1867
|
+
const manifest = await readManifest(root);
|
|
1868
|
+
if (!manifest) {
|
|
1869
|
+
console.log("Error reading cortex.yaml");
|
|
1870
|
+
return;
|
|
1871
|
+
}
|
|
1872
|
+
console.log(`CodeCortex Status \u2014 ${manifest.project}`);
|
|
1873
|
+
console.log("\u2500".repeat(50));
|
|
1874
|
+
console.log("");
|
|
1875
|
+
console.log("Knowledge Store:");
|
|
1876
|
+
console.log(` Version: ${manifest.version}`);
|
|
1877
|
+
console.log(` Languages: ${manifest.languages.join(", ")}`);
|
|
1878
|
+
console.log(` Files: ${manifest.totalFiles}`);
|
|
1879
|
+
console.log(` Symbols: ${manifest.totalSymbols}`);
|
|
1880
|
+
console.log(` Modules: ${manifest.totalModules}`);
|
|
1881
|
+
console.log(` Generated: ${manifest.generated}`);
|
|
1882
|
+
console.log(` Last updated: ${manifest.lastUpdated}`);
|
|
1883
|
+
console.log("");
|
|
1884
|
+
const lastUpdated = new Date(manifest.lastUpdated);
|
|
1885
|
+
const ageHours = Math.floor((Date.now() - lastUpdated.getTime()) / (1e3 * 60 * 60));
|
|
1886
|
+
const ageLabel = ageHours < 1 ? "just now" : ageHours < 24 ? `${ageHours} hours ago` : `${Math.floor(ageHours / 24)} days ago`;
|
|
1887
|
+
console.log(`Freshness: ${ageLabel}`);
|
|
1888
|
+
if (ageHours > 24) {
|
|
1889
|
+
console.log(" Consider running `codecortex update` to refresh.");
|
|
1890
|
+
}
|
|
1891
|
+
console.log("");
|
|
1892
|
+
const modules = await listModuleDocs(root);
|
|
1893
|
+
const decisions = await listDecisions(root);
|
|
1894
|
+
const sessions = await listSessions(root);
|
|
1895
|
+
console.log("Knowledge Breakdown:");
|
|
1896
|
+
console.log(` Module docs: ${modules.length}${modules.length > 0 ? ` (${modules.join(", ")})` : ""}`);
|
|
1897
|
+
console.log(` Decision records: ${decisions.length}`);
|
|
1898
|
+
console.log(` Session logs: ${sessions.length}`);
|
|
1899
|
+
const patterns = await readFile(cortexPath(root, "patterns.md"));
|
|
1900
|
+
const patternCount = patterns ? (patterns.match(/^### /gm) || []).length : 0;
|
|
1901
|
+
console.log(` Coding patterns: ${patternCount}`);
|
|
1902
|
+
console.log("");
|
|
1903
|
+
const symbolContent = await readFile(cortexPath(root, "symbols.json"));
|
|
1904
|
+
if (symbolContent) {
|
|
1905
|
+
const index = JSON.parse(symbolContent);
|
|
1906
|
+
const byKind = /* @__PURE__ */ new Map();
|
|
1907
|
+
for (const s of index.symbols) {
|
|
1908
|
+
byKind.set(s.kind, (byKind.get(s.kind) || 0) + 1);
|
|
1909
|
+
}
|
|
1910
|
+
console.log("Symbol Index:");
|
|
1911
|
+
for (const [kind, count] of [...byKind.entries()].sort((a, b) => b[1] - a[1])) {
|
|
1912
|
+
console.log(` ${kind}: ${count}`);
|
|
1913
|
+
}
|
|
1914
|
+
const exported = index.symbols.filter((s) => s.exported).length;
|
|
1915
|
+
console.log(` Total exported: ${exported}/${index.total}`);
|
|
1916
|
+
console.log("");
|
|
1917
|
+
}
|
|
1918
|
+
const temporalContent = await readFile(cortexPath(root, "temporal.json"));
|
|
1919
|
+
if (temporalContent) {
|
|
1920
|
+
const temporal = JSON.parse(temporalContent);
|
|
1921
|
+
console.log("Temporal Analysis:");
|
|
1922
|
+
console.log(` Period: ${temporal.periodDays} days, ${temporal.totalCommits} commits`);
|
|
1923
|
+
console.log(` Hotspots: ${temporal.hotspots.filter((h) => h.stability === "volatile").length} volatile, ${temporal.hotspots.filter((h) => h.stability === "stabilizing").length} stabilizing`);
|
|
1924
|
+
console.log(` Couplings: ${temporal.coupling.length} pairs (${temporal.coupling.filter((c) => !c.hasImport).length} hidden)`);
|
|
1925
|
+
console.log(` Bug records: ${temporal.bugHistory.length} files with fix history`);
|
|
1926
|
+
const top = temporal.hotspots.slice(0, 3);
|
|
1927
|
+
if (top.length > 0) {
|
|
1928
|
+
console.log(` Top hotspots:`);
|
|
1929
|
+
for (const h of top) {
|
|
1930
|
+
console.log(` ${h.file} \u2014 ${h.changes} changes (${h.stability})`);
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
const feedbackContent = await readFile(cortexPath(root, "feedback", "log.json"));
|
|
1935
|
+
if (feedbackContent) {
|
|
1936
|
+
const feedback = JSON.parse(feedbackContent);
|
|
1937
|
+
if (feedback.length > 0) {
|
|
1938
|
+
console.log("");
|
|
1939
|
+
console.log(`Pending feedback: ${feedback.length} reports (will be addressed on next update)`);
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
const latestSession = await getLatestSession(root);
|
|
1943
|
+
if (latestSession) {
|
|
1944
|
+
console.log("");
|
|
1945
|
+
console.log(`Latest session: ${latestSession}`);
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
// src/cli/index.ts
|
|
1950
|
+
var program = new Command();
|
|
1951
|
+
program.name("codecortex").description("Persistent, AI-powered codebase knowledge layer").version("0.1.0");
|
|
1952
|
+
program.command("init").description("Initialize codebase knowledge: discover files, extract symbols, analyze git history").option("-r, --root <path>", "Project root directory", process.cwd()).option("-d, --days <number>", "Days of git history to analyze", "90").action(initCommand);
|
|
1953
|
+
program.command("serve").description("Start MCP server (stdio transport) for AI agent access").option("-r, --root <path>", "Project root directory", process.cwd()).action(serveCommand);
|
|
1954
|
+
program.command("update").description("Update knowledge for changed files since last session").option("-r, --root <path>", "Project root directory", process.cwd()).option("-d, --days <number>", "Days of git history to re-analyze", "90").action(updateCommand);
|
|
1955
|
+
program.command("status").description("Show knowledge freshness, stale modules, and symbol counts").option("-r, --root <path>", "Project root directory", process.cwd()).action(statusCommand);
|
|
1956
|
+
program.parse();
|
|
1957
|
+
//# sourceMappingURL=index.js.map
|