codecortex-ai 0.1.1 → 0.2.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/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
> Persistent codebase knowledge layer for AI agents. Your AI shouldn't re-learn your codebase every session.
|
|
4
4
|
|
|
5
|
+
[Website](https://codecortex-ai.vercel.app) · [npm](https://www.npmjs.com/package/codecortex-ai) · [GitHub](https://github.com/rushikeshmore/CodeCortex)
|
|
6
|
+
|
|
5
7
|
## The Problem
|
|
6
8
|
|
|
7
9
|
Every AI coding session starts from scratch. When context compacts or a new session begins, the AI re-scans the entire codebase. Same files, same tokens, same wasted time. It's like hiring a new developer every session who has to re-learn everything before writing a single line.
|
|
@@ -15,7 +17,7 @@ Every AI coding session starts from scratch. When context compacts or a new sess
|
|
|
15
17
|
|
|
16
18
|
CodeCortex pre-digests codebases into layered knowledge files and serves them to any AI agent via MCP. Instead of re-understanding your codebase every session, the AI starts with knowledge.
|
|
17
19
|
|
|
18
|
-
**Hybrid extraction:** tree-sitter native N-API for structure (symbols, imports, calls across
|
|
20
|
+
**Hybrid extraction:** tree-sitter native N-API for structure (symbols, imports, calls across 28 languages) + host LLM for semantics (what modules do, why they're built that way). Zero extra API keys.
|
|
19
21
|
|
|
20
22
|
## Quick Start
|
|
21
23
|
|
|
@@ -136,11 +138,11 @@ vs. raw scan of entire codebase: ~37,800 tokens
|
|
|
136
138
|
|
|
137
139
|
85-90% token reduction. 7-10x efficiency gain.
|
|
138
140
|
|
|
139
|
-
## Supported Languages (
|
|
141
|
+
## Supported Languages (28)
|
|
140
142
|
|
|
141
143
|
| Category | Languages |
|
|
142
144
|
|----------|-----------|
|
|
143
|
-
| Web | TypeScript, TSX, JavaScript |
|
|
145
|
+
| Web | TypeScript, TSX, JavaScript, Liquid |
|
|
144
146
|
| Systems | C, C++, Objective-C, Rust, Zig, Go |
|
|
145
147
|
| JVM | Java, Kotlin, Scala |
|
|
146
148
|
| .NET | C# |
|
|
@@ -152,7 +154,7 @@ vs. raw scan of entire codebase: ~37,800 tokens
|
|
|
152
154
|
## Tech Stack
|
|
153
155
|
|
|
154
156
|
- TypeScript ESM, Node.js 20+
|
|
155
|
-
- `tree-sitter` (native N-API) +
|
|
157
|
+
- `tree-sitter` (native N-API) + 28 language grammar packages
|
|
156
158
|
- `@modelcontextprotocol/sdk` - MCP server
|
|
157
159
|
- `commander` - CLI
|
|
158
160
|
- `simple-git` - git integration
|
|
@@ -947,16 +947,21 @@ export {
|
|
|
947
947
|
writeManifest,
|
|
948
948
|
createManifest,
|
|
949
949
|
updateManifest,
|
|
950
|
+
readGraph,
|
|
950
951
|
writeGraph,
|
|
951
952
|
buildGraph,
|
|
953
|
+
getModuleDependencies,
|
|
952
954
|
enrichCouplingWithImports,
|
|
955
|
+
readModuleDoc,
|
|
956
|
+
writeModuleDoc,
|
|
953
957
|
listModuleDocs,
|
|
954
958
|
listDecisions,
|
|
955
959
|
writeSession,
|
|
956
960
|
listSessions,
|
|
957
961
|
getLatestSession,
|
|
958
962
|
createSession,
|
|
963
|
+
searchKnowledge,
|
|
959
964
|
createServer,
|
|
960
965
|
startServer
|
|
961
966
|
};
|
|
962
|
-
//# sourceMappingURL=chunk-
|
|
967
|
+
//# sourceMappingURL=chunk-DWUIP3WA.js.map
|
package/dist/cli/index.js
CHANGED
|
@@ -7,21 +7,27 @@ import {
|
|
|
7
7
|
enrichCouplingWithImports,
|
|
8
8
|
ensureDir,
|
|
9
9
|
getLatestSession,
|
|
10
|
+
getModuleDependencies,
|
|
10
11
|
listDecisions,
|
|
11
12
|
listModuleDocs,
|
|
12
13
|
listSessions,
|
|
13
14
|
readFile,
|
|
15
|
+
readGraph,
|
|
14
16
|
readManifest,
|
|
17
|
+
readModuleDoc,
|
|
18
|
+
searchKnowledge,
|
|
15
19
|
startServer,
|
|
16
20
|
updateManifest,
|
|
17
21
|
writeFile,
|
|
18
22
|
writeGraph,
|
|
19
23
|
writeJsonStream,
|
|
20
24
|
writeManifest,
|
|
25
|
+
writeModuleDoc,
|
|
21
26
|
writeSession
|
|
22
|
-
} from "../chunk-
|
|
27
|
+
} from "../chunk-DWUIP3WA.js";
|
|
23
28
|
|
|
24
29
|
// src/cli/index.ts
|
|
30
|
+
import { createRequire as createRequire2 } from "module";
|
|
25
31
|
import { Command } from "commander";
|
|
26
32
|
|
|
27
33
|
// src/cli/commands/init.ts
|
|
@@ -74,9 +80,11 @@ var LANGUAGE_LOADERS = {
|
|
|
74
80
|
ocaml: () => require2("tree-sitter-ocaml").ocaml,
|
|
75
81
|
elm: () => require2("tree-sitter-elm"),
|
|
76
82
|
elisp: () => require2("tree-sitter-elisp"),
|
|
83
|
+
// Web / Templating
|
|
84
|
+
vue: () => require2("tree-sitter-vue"),
|
|
85
|
+
liquid: () => require2("tree-sitter-liquid"),
|
|
77
86
|
// Web3 / Other
|
|
78
87
|
solidity: () => require2("tree-sitter-solidity"),
|
|
79
|
-
vue: () => require2("tree-sitter-vue"),
|
|
80
88
|
ql: () => require2("tree-sitter-ql")
|
|
81
89
|
};
|
|
82
90
|
var EXTENSION_MAP = {
|
|
@@ -147,6 +155,8 @@ var EXTENSION_MAP = {
|
|
|
147
155
|
".sol": "solidity",
|
|
148
156
|
// Vue
|
|
149
157
|
".vue": "vue",
|
|
158
|
+
// Liquid
|
|
159
|
+
".liquid": "liquid",
|
|
150
160
|
// CodeQL
|
|
151
161
|
".ql": "ql"
|
|
152
162
|
};
|
|
@@ -1492,6 +1502,75 @@ function extractGenericCalls(tree, file) {
|
|
|
1492
1502
|
|
|
1493
1503
|
// src/cli/commands/init.ts
|
|
1494
1504
|
import { readFile as readFile2 } from "fs/promises";
|
|
1505
|
+
|
|
1506
|
+
// src/core/module-gen.ts
|
|
1507
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1508
|
+
async function generateStructuralModuleDocs(projectRoot, data) {
|
|
1509
|
+
let generated = 0;
|
|
1510
|
+
for (const mod of data.graph.modules) {
|
|
1511
|
+
const docPath = cortexPath(projectRoot, "modules", `${mod.name}.md`);
|
|
1512
|
+
if (existsSync3(docPath)) continue;
|
|
1513
|
+
const moduleFiles = new Set(mod.files);
|
|
1514
|
+
const exported = data.symbols.filter((s) => moduleFiles.has(s.file) && s.exported).map((s) => {
|
|
1515
|
+
const loc = s.endLine > s.startLine ? `${s.file}:${s.startLine}-${s.endLine}` : `${s.file}:${s.startLine}`;
|
|
1516
|
+
return `${s.name} (${s.kind}, ${loc})`;
|
|
1517
|
+
});
|
|
1518
|
+
const deps = getModuleDependencies(data.graph, mod.name);
|
|
1519
|
+
const importsFromModules = /* @__PURE__ */ new Set();
|
|
1520
|
+
const importedByModules = /* @__PURE__ */ new Set();
|
|
1521
|
+
for (const edge of deps.imports) {
|
|
1522
|
+
for (const other of data.graph.modules) {
|
|
1523
|
+
if (other.name !== mod.name && other.files.includes(edge.target)) {
|
|
1524
|
+
importsFromModules.add(other.name);
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
for (const edge of deps.importedBy) {
|
|
1529
|
+
for (const other of data.graph.modules) {
|
|
1530
|
+
if (other.name !== mod.name && other.files.includes(edge.source)) {
|
|
1531
|
+
importedByModules.add(other.name);
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
const depLines = [];
|
|
1536
|
+
if (importsFromModules.size > 0) depLines.push(`Imports from: ${[...importsFromModules].join(", ")}`);
|
|
1537
|
+
if (importedByModules.size > 0) depLines.push(`Imported by: ${[...importedByModules].join(", ")}`);
|
|
1538
|
+
let temporalSignals;
|
|
1539
|
+
if (data.temporal) {
|
|
1540
|
+
const hotspots = data.temporal.hotspots.filter((h) => moduleFiles.has(h.file));
|
|
1541
|
+
const topHotspot = hotspots[0];
|
|
1542
|
+
const couplings = data.temporal.coupling.filter(
|
|
1543
|
+
(c) => moduleFiles.has(c.fileA) || moduleFiles.has(c.fileB)
|
|
1544
|
+
);
|
|
1545
|
+
const coupledWith = couplings.map((c) => {
|
|
1546
|
+
const other = moduleFiles.has(c.fileA) ? c.fileB : c.fileA;
|
|
1547
|
+
return `${other} (${c.cochanges} co-changes, ${Math.round(c.strength * 100)}%)`;
|
|
1548
|
+
});
|
|
1549
|
+
const bugs = data.temporal.bugHistory.filter((b) => moduleFiles.has(b.file));
|
|
1550
|
+
temporalSignals = {
|
|
1551
|
+
churn: topHotspot ? `${topHotspot.changes} changes (${topHotspot.stability})` : "no hotspot data",
|
|
1552
|
+
coupledWith,
|
|
1553
|
+
stability: topHotspot?.stability ?? "unknown",
|
|
1554
|
+
bugHistory: bugs.flatMap((b) => b.lessons),
|
|
1555
|
+
lastChanged: topHotspot?.lastChanged ?? "unknown"
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1558
|
+
const analysis = {
|
|
1559
|
+
name: mod.name,
|
|
1560
|
+
purpose: `${mod.files.length} files, ${mod.lines} lines (${mod.language}). Auto-generated from code structure \u2014 use \`analyze_module\` MCP tool for semantic analysis.`,
|
|
1561
|
+
dataFlow: `Files: ${mod.files.join(", ")}`,
|
|
1562
|
+
publicApi: exported,
|
|
1563
|
+
gotchas: [],
|
|
1564
|
+
dependencies: depLines,
|
|
1565
|
+
temporalSignals
|
|
1566
|
+
};
|
|
1567
|
+
await writeModuleDoc(projectRoot, analysis);
|
|
1568
|
+
generated++;
|
|
1569
|
+
}
|
|
1570
|
+
return generated;
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
// src/cli/commands/init.ts
|
|
1495
1574
|
async function initCommand(opts) {
|
|
1496
1575
|
const root = resolve(opts.root);
|
|
1497
1576
|
const days = parseInt(opts.days, 10);
|
|
@@ -1619,7 +1698,12 @@ async function initCommand(opts) {
|
|
|
1619
1698
|
});
|
|
1620
1699
|
await writeManifest(root, manifest);
|
|
1621
1700
|
await writeFile(cortexPath(root, "patterns.md"), "# Coding Patterns\n\nNo patterns recorded yet. Use `update_patterns` to add patterns.\n");
|
|
1622
|
-
|
|
1701
|
+
const moduleDocsGenerated = await generateStructuralModuleDocs(root, {
|
|
1702
|
+
graph,
|
|
1703
|
+
symbols: allSymbols,
|
|
1704
|
+
temporal: temporalData
|
|
1705
|
+
});
|
|
1706
|
+
console.log(` Written: cortex.yaml, symbols.json, graph.json, temporal.json, overview.md, patterns.md, ${moduleDocsGenerated} module docs`);
|
|
1623
1707
|
console.log("");
|
|
1624
1708
|
console.log("Step 6/6: Generating constitution...");
|
|
1625
1709
|
await generateConstitution(root, {
|
|
@@ -1689,10 +1773,10 @@ function generateOverview(project) {
|
|
|
1689
1773
|
|
|
1690
1774
|
// src/cli/commands/serve.ts
|
|
1691
1775
|
import { resolve as resolve2 } from "path";
|
|
1692
|
-
import { existsSync as
|
|
1776
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1693
1777
|
async function serveCommand(opts) {
|
|
1694
1778
|
const root = resolve2(opts.root);
|
|
1695
|
-
if (!
|
|
1779
|
+
if (!existsSync4(cortexPath(root, "cortex.yaml"))) {
|
|
1696
1780
|
console.error("Error: No CodeCortex knowledge found.");
|
|
1697
1781
|
console.error(`Run 'codecortex init' first to analyze the codebase.`);
|
|
1698
1782
|
console.error(`Expected: ${cortexPath(root, "cortex.yaml")}`);
|
|
@@ -1703,7 +1787,7 @@ async function serveCommand(opts) {
|
|
|
1703
1787
|
|
|
1704
1788
|
// src/cli/commands/update.ts
|
|
1705
1789
|
import { resolve as resolve3 } from "path";
|
|
1706
|
-
import { existsSync as
|
|
1790
|
+
import { existsSync as existsSync5 } from "fs";
|
|
1707
1791
|
|
|
1708
1792
|
// src/git/diff.ts
|
|
1709
1793
|
import simpleGit2 from "simple-git";
|
|
@@ -1739,7 +1823,7 @@ import { readFile as fsRead3 } from "fs/promises";
|
|
|
1739
1823
|
async function updateCommand(opts) {
|
|
1740
1824
|
const root = resolve3(opts.root);
|
|
1741
1825
|
const days = parseInt(opts.days, 10);
|
|
1742
|
-
if (!
|
|
1826
|
+
if (!existsSync5(cortexPath(root, "cortex.yaml"))) {
|
|
1743
1827
|
console.error("Error: No CodeCortex knowledge found. Run `codecortex init` first.");
|
|
1744
1828
|
process.exit(1);
|
|
1745
1829
|
}
|
|
@@ -1821,6 +1905,11 @@ async function updateCommand(opts) {
|
|
|
1821
1905
|
if (temporalData) {
|
|
1822
1906
|
await writeFile(cortexPath(root, "temporal.json"), JSON.stringify(temporalData, null, 2));
|
|
1823
1907
|
}
|
|
1908
|
+
await generateStructuralModuleDocs(root, {
|
|
1909
|
+
graph,
|
|
1910
|
+
symbols: allSymbols,
|
|
1911
|
+
temporal: temporalData
|
|
1912
|
+
});
|
|
1824
1913
|
await updateManifest(root, {
|
|
1825
1914
|
totalFiles: project.files.length,
|
|
1826
1915
|
totalSymbols: allSymbols.length,
|
|
@@ -1856,10 +1945,10 @@ async function updateCommand(opts) {
|
|
|
1856
1945
|
|
|
1857
1946
|
// src/cli/commands/status.ts
|
|
1858
1947
|
import { resolve as resolve4 } from "path";
|
|
1859
|
-
import { existsSync as
|
|
1948
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1860
1949
|
async function statusCommand(opts) {
|
|
1861
1950
|
const root = resolve4(opts.root);
|
|
1862
|
-
if (!
|
|
1951
|
+
if (!existsSync6(cortexPath(root, "cortex.yaml"))) {
|
|
1863
1952
|
console.log("No CodeCortex knowledge found.");
|
|
1864
1953
|
console.log(`Run 'codecortex init' to analyze this codebase.`);
|
|
1865
1954
|
return;
|
|
@@ -1946,12 +2035,300 @@ async function statusCommand(opts) {
|
|
|
1946
2035
|
}
|
|
1947
2036
|
}
|
|
1948
2037
|
|
|
2038
|
+
// src/cli/commands/symbols.ts
|
|
2039
|
+
import { resolve as resolve5 } from "path";
|
|
2040
|
+
import { existsSync as existsSync7 } from "fs";
|
|
2041
|
+
async function symbolsCommand(query, opts) {
|
|
2042
|
+
const root = resolve5(opts.root);
|
|
2043
|
+
if (!existsSync7(cortexPath(root, "cortex.yaml"))) {
|
|
2044
|
+
console.log("No CodeCortex knowledge found.");
|
|
2045
|
+
console.log(`Run 'codecortex init' to analyze this codebase.`);
|
|
2046
|
+
return;
|
|
2047
|
+
}
|
|
2048
|
+
const content = await readFile(cortexPath(root, "symbols.json"));
|
|
2049
|
+
if (!content) {
|
|
2050
|
+
console.log("No symbol index found. Run `codecortex init` first.");
|
|
2051
|
+
return;
|
|
2052
|
+
}
|
|
2053
|
+
const index = JSON.parse(content);
|
|
2054
|
+
if (!query && !opts.kind && !opts.file && !opts.exported) {
|
|
2055
|
+
printSummary(index);
|
|
2056
|
+
return;
|
|
2057
|
+
}
|
|
2058
|
+
let matches = index.symbols;
|
|
2059
|
+
if (query) {
|
|
2060
|
+
const q = query.toLowerCase();
|
|
2061
|
+
matches = matches.filter((s) => s.name.toLowerCase().includes(q));
|
|
2062
|
+
}
|
|
2063
|
+
if (opts.kind) {
|
|
2064
|
+
matches = matches.filter((s) => s.kind === opts.kind);
|
|
2065
|
+
}
|
|
2066
|
+
if (opts.file) {
|
|
2067
|
+
matches = matches.filter((s) => s.file.includes(opts.file));
|
|
2068
|
+
}
|
|
2069
|
+
if (opts.exported) {
|
|
2070
|
+
matches = matches.filter((s) => s.exported);
|
|
2071
|
+
}
|
|
2072
|
+
const limit = parseInt(opts.limit ?? "30", 10);
|
|
2073
|
+
const display = matches.slice(0, limit);
|
|
2074
|
+
if (display.length === 0) {
|
|
2075
|
+
console.log(`No symbols found${query ? ` matching "${query}"` : ""}.`);
|
|
2076
|
+
return;
|
|
2077
|
+
}
|
|
2078
|
+
console.log("");
|
|
2079
|
+
for (const s of display) {
|
|
2080
|
+
const lines = s.endLine > s.startLine ? `${s.startLine}-${s.endLine}` : `${s.startLine}`;
|
|
2081
|
+
const exp = s.exported ? "exported" : "local";
|
|
2082
|
+
console.log(` ${pad(s.kind, 12)} ${pad(s.name, 30)} ${pad(s.file, 40)} ${pad(lines, 10)} ${exp}`);
|
|
2083
|
+
if (s.signature) {
|
|
2084
|
+
console.log(`${" ".repeat(14)}${s.signature}`);
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
console.log("");
|
|
2088
|
+
if (matches.length > limit) {
|
|
2089
|
+
console.log(`Showing ${limit} of ${matches.length} matches. Use -l to show more.`);
|
|
2090
|
+
} else {
|
|
2091
|
+
console.log(`${matches.length} symbol${matches.length === 1 ? "" : "s"} found.`);
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
function printSummary(index) {
|
|
2095
|
+
console.log("");
|
|
2096
|
+
console.log(`Symbol Index: ${index.total} symbols`);
|
|
2097
|
+
console.log("\u2500".repeat(50));
|
|
2098
|
+
const byKind = /* @__PURE__ */ new Map();
|
|
2099
|
+
for (const s of index.symbols) {
|
|
2100
|
+
byKind.set(s.kind, (byKind.get(s.kind) ?? 0) + 1);
|
|
2101
|
+
}
|
|
2102
|
+
console.log("");
|
|
2103
|
+
console.log("By Kind:");
|
|
2104
|
+
for (const [kind, count] of [...byKind.entries()].sort((a, b) => b[1] - a[1])) {
|
|
2105
|
+
console.log(` ${pad(kind, 14)} ${count}`);
|
|
2106
|
+
}
|
|
2107
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
2108
|
+
for (const s of index.symbols) {
|
|
2109
|
+
byFile.set(s.file, (byFile.get(s.file) ?? 0) + 1);
|
|
2110
|
+
}
|
|
2111
|
+
console.log("");
|
|
2112
|
+
console.log("Top Files:");
|
|
2113
|
+
for (const [file, count] of [...byFile.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10)) {
|
|
2114
|
+
console.log(` ${pad(file, 45)} ${count} symbols`);
|
|
2115
|
+
}
|
|
2116
|
+
const exported = index.symbols.filter((s) => s.exported).length;
|
|
2117
|
+
console.log("");
|
|
2118
|
+
console.log(`Exported: ${exported}/${index.total}`);
|
|
2119
|
+
console.log("");
|
|
2120
|
+
console.log("Run `codecortex symbols <query>` to search.");
|
|
2121
|
+
}
|
|
2122
|
+
function pad(str, len) {
|
|
2123
|
+
return str.length >= len ? str : str + " ".repeat(len - str.length);
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
// src/cli/commands/search.ts
|
|
2127
|
+
import { resolve as resolve6 } from "path";
|
|
2128
|
+
import { existsSync as existsSync8 } from "fs";
|
|
2129
|
+
async function searchCommand(query, opts) {
|
|
2130
|
+
const root = resolve6(opts.root);
|
|
2131
|
+
if (!existsSync8(cortexPath(root, "cortex.yaml"))) {
|
|
2132
|
+
console.log("No CodeCortex knowledge found.");
|
|
2133
|
+
console.log(`Run 'codecortex init' to analyze this codebase.`);
|
|
2134
|
+
return;
|
|
2135
|
+
}
|
|
2136
|
+
const results = await searchKnowledge(root, query);
|
|
2137
|
+
const limit = parseInt(opts.limit ?? "20", 10);
|
|
2138
|
+
const display = results.slice(0, limit);
|
|
2139
|
+
if (display.length === 0) {
|
|
2140
|
+
console.log(`No results for "${query}".`);
|
|
2141
|
+
return;
|
|
2142
|
+
}
|
|
2143
|
+
console.log("");
|
|
2144
|
+
console.log(`Search: "${query}"`);
|
|
2145
|
+
console.log("\u2500".repeat(50));
|
|
2146
|
+
for (let i = 0; i < display.length; i++) {
|
|
2147
|
+
const r = display[i];
|
|
2148
|
+
console.log("");
|
|
2149
|
+
console.log(` [${i + 1}] ${r.file}:${r.line}`);
|
|
2150
|
+
const ctxLines = r.context.split("\n");
|
|
2151
|
+
for (const line of ctxLines) {
|
|
2152
|
+
const trimmed = line.trim();
|
|
2153
|
+
if (!trimmed) continue;
|
|
2154
|
+
if (trimmed.toLowerCase().includes(query.toLowerCase())) {
|
|
2155
|
+
console.log(` > ${trimmed}`);
|
|
2156
|
+
} else {
|
|
2157
|
+
console.log(` ${trimmed}`);
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
console.log("");
|
|
2162
|
+
if (results.length > limit) {
|
|
2163
|
+
console.log(`Showing ${limit} of ${results.length} results. Use -l to show more.`);
|
|
2164
|
+
} else {
|
|
2165
|
+
console.log(`${results.length} result${results.length === 1 ? "" : "s"} found.`);
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
// src/cli/commands/modules.ts
|
|
2170
|
+
import { resolve as resolve7 } from "path";
|
|
2171
|
+
import { existsSync as existsSync9 } from "fs";
|
|
2172
|
+
async function modulesCommand(name, opts) {
|
|
2173
|
+
const root = resolve7(opts.root);
|
|
2174
|
+
if (!existsSync9(cortexPath(root, "cortex.yaml"))) {
|
|
2175
|
+
console.log("No CodeCortex knowledge found.");
|
|
2176
|
+
console.log(`Run 'codecortex init' to analyze this codebase.`);
|
|
2177
|
+
return;
|
|
2178
|
+
}
|
|
2179
|
+
const graph = await readGraph(root);
|
|
2180
|
+
if (!graph) {
|
|
2181
|
+
console.log("No dependency graph found. Run `codecortex init` first.");
|
|
2182
|
+
return;
|
|
2183
|
+
}
|
|
2184
|
+
if (!name) {
|
|
2185
|
+
await printModuleList(root, graph);
|
|
2186
|
+
return;
|
|
2187
|
+
}
|
|
2188
|
+
await printModuleDetail(root, name, graph);
|
|
2189
|
+
}
|
|
2190
|
+
async function printModuleList(root, graph) {
|
|
2191
|
+
const docsAvailable = new Set(await listModuleDocs(root));
|
|
2192
|
+
console.log("");
|
|
2193
|
+
console.log(`Modules: ${graph.modules.length}`);
|
|
2194
|
+
console.log("\u2500".repeat(70));
|
|
2195
|
+
console.log("");
|
|
2196
|
+
console.log(` ${pad2("MODULE", 18)} ${pad2("FILES", 6)} ${pad2("LINES", 7)} ${pad2("SYMBOLS", 8)} ${pad2("LANG", 14)} DOC`);
|
|
2197
|
+
for (const mod of [...graph.modules].sort((a, b) => a.name.localeCompare(b.name))) {
|
|
2198
|
+
const hasDoc = docsAvailable.has(mod.name) ? "yes" : "--";
|
|
2199
|
+
console.log(` ${pad2(mod.name, 18)} ${pad2(String(mod.files.length), 6)} ${pad2(String(mod.lines), 7)} ${pad2(String(mod.symbols), 8)} ${pad2(mod.language, 14)} ${hasDoc}`);
|
|
2200
|
+
}
|
|
2201
|
+
const withDocs = graph.modules.filter((m) => docsAvailable.has(m.name)).length;
|
|
2202
|
+
console.log("");
|
|
2203
|
+
console.log(`${graph.modules.length} modules, ${withDocs} with docs.`);
|
|
2204
|
+
console.log("");
|
|
2205
|
+
console.log("Run `codecortex modules <name>` to deep-dive into a module.");
|
|
2206
|
+
}
|
|
2207
|
+
async function printModuleDetail(root, name, graph) {
|
|
2208
|
+
const mod = graph.modules.find((m) => m.name === name);
|
|
2209
|
+
if (!mod) {
|
|
2210
|
+
const available = graph.modules.map((m) => m.name).join(", ");
|
|
2211
|
+
console.log(`Module "${name}" not found. Available: ${available}`);
|
|
2212
|
+
return;
|
|
2213
|
+
}
|
|
2214
|
+
console.log("");
|
|
2215
|
+
console.log(`Module: ${name}`);
|
|
2216
|
+
console.log("\u2550".repeat(50));
|
|
2217
|
+
const doc = await readModuleDoc(root, name);
|
|
2218
|
+
if (doc) {
|
|
2219
|
+
console.log("");
|
|
2220
|
+
console.log(doc);
|
|
2221
|
+
} else {
|
|
2222
|
+
console.log("");
|
|
2223
|
+
console.log(`No module doc for "${name}".`);
|
|
2224
|
+
console.log(`Run \`codecortex init\` to generate structural docs.`);
|
|
2225
|
+
}
|
|
2226
|
+
const deps = getModuleDependencies(graph, name);
|
|
2227
|
+
if (deps.imports.length > 0) {
|
|
2228
|
+
console.log("");
|
|
2229
|
+
console.log("Imports:");
|
|
2230
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2231
|
+
for (const edge of deps.imports) {
|
|
2232
|
+
const key = `${edge.source} -> ${edge.target}`;
|
|
2233
|
+
if (seen.has(key)) continue;
|
|
2234
|
+
seen.add(key);
|
|
2235
|
+
const specifiers = edge.specifiers.length > 0 ? ` [${edge.specifiers.join(", ")}]` : "";
|
|
2236
|
+
console.log(` ${edge.source} -> ${edge.target}${specifiers}`);
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
if (deps.importedBy.length > 0) {
|
|
2240
|
+
console.log("");
|
|
2241
|
+
console.log("Imported By:");
|
|
2242
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2243
|
+
for (const edge of deps.importedBy) {
|
|
2244
|
+
const key = `${edge.source} -> ${edge.target}`;
|
|
2245
|
+
if (seen.has(key)) continue;
|
|
2246
|
+
seen.add(key);
|
|
2247
|
+
const specifiers = edge.specifiers.length > 0 ? ` [${edge.specifiers.join(", ")}]` : "";
|
|
2248
|
+
console.log(` ${edge.source} -> ${edge.target}${specifiers}`);
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
console.log("");
|
|
2252
|
+
console.log(`Stats: ${mod.files.length} files, ${mod.lines} lines, ${mod.symbols} symbols (${mod.language})`);
|
|
2253
|
+
}
|
|
2254
|
+
function pad2(str, len) {
|
|
2255
|
+
return str.length >= len ? str : str + " ".repeat(len - str.length);
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
// src/cli/commands/hotspots.ts
|
|
2259
|
+
import { resolve as resolve8 } from "path";
|
|
2260
|
+
import { existsSync as existsSync10 } from "fs";
|
|
2261
|
+
async function hotspotsCommand(opts) {
|
|
2262
|
+
const root = resolve8(opts.root);
|
|
2263
|
+
if (!existsSync10(cortexPath(root, "cortex.yaml"))) {
|
|
2264
|
+
console.log("No CodeCortex knowledge found.");
|
|
2265
|
+
console.log(`Run 'codecortex init' to analyze this codebase.`);
|
|
2266
|
+
return;
|
|
2267
|
+
}
|
|
2268
|
+
const content = await readFile(cortexPath(root, "temporal.json"));
|
|
2269
|
+
if (!content) {
|
|
2270
|
+
console.log("No temporal data found. Run `codecortex init` in a git repository.");
|
|
2271
|
+
return;
|
|
2272
|
+
}
|
|
2273
|
+
const temporal = JSON.parse(content);
|
|
2274
|
+
const limit = parseInt(opts.limit ?? "15", 10);
|
|
2275
|
+
const riskMap = /* @__PURE__ */ new Map();
|
|
2276
|
+
for (const h of temporal.hotspots) {
|
|
2277
|
+
riskMap.set(h.file, { churn: h.changes, couplings: 0, bugs: 0, risk: h.changes });
|
|
2278
|
+
}
|
|
2279
|
+
for (const c of temporal.coupling) {
|
|
2280
|
+
for (const f of [c.fileA, c.fileB]) {
|
|
2281
|
+
const entry = riskMap.get(f) ?? { churn: 0, couplings: 0, bugs: 0, risk: 0 };
|
|
2282
|
+
entry.couplings++;
|
|
2283
|
+
entry.risk += c.strength * 2;
|
|
2284
|
+
riskMap.set(f, entry);
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
for (const b of temporal.bugHistory) {
|
|
2288
|
+
const entry = riskMap.get(b.file) ?? { churn: 0, couplings: 0, bugs: 0, risk: 0 };
|
|
2289
|
+
entry.bugs = b.fixCommits;
|
|
2290
|
+
entry.risk += b.fixCommits * 3;
|
|
2291
|
+
riskMap.set(b.file, entry);
|
|
2292
|
+
}
|
|
2293
|
+
const ranked = [...riskMap.entries()].sort((a, b) => b[1].risk - a[1].risk).slice(0, limit).map(([file, data]) => ({ file, ...data, risk: Math.round(data.risk * 100) / 100 }));
|
|
2294
|
+
console.log("");
|
|
2295
|
+
console.log(`Hotspots \u2014 ${temporal.periodDays} days, ${temporal.totalCommits} commits`);
|
|
2296
|
+
console.log("\u2500".repeat(70));
|
|
2297
|
+
console.log("");
|
|
2298
|
+
console.log(` ${pad3("#", 4)} ${pad3("FILE", 42)} ${pad3("CHURN", 6)} ${pad3("CPLS", 5)} ${pad3("BUGS", 5)} RISK`);
|
|
2299
|
+
for (let i = 0; i < ranked.length; i++) {
|
|
2300
|
+
const r = ranked[i];
|
|
2301
|
+
console.log(` ${pad3(String(i + 1), 4)} ${pad3(r.file, 42)} ${pad3(String(r.churn), 6)} ${pad3(String(r.couplings), 5)} ${pad3(String(r.bugs), 5)} ${r.risk.toFixed(2)}`);
|
|
2302
|
+
}
|
|
2303
|
+
const hidden = temporal.coupling.filter((c) => !c.hasImport && c.strength >= 0.3);
|
|
2304
|
+
if (hidden.length > 0) {
|
|
2305
|
+
console.log("");
|
|
2306
|
+
console.log("Hidden Dependencies (co-change but no import):");
|
|
2307
|
+
for (const h of hidden.slice(0, 10)) {
|
|
2308
|
+
console.log(` ${h.fileA} <-> ${h.fileB} (${h.cochanges} co-changes, ${Math.round(h.strength * 100)}%)`);
|
|
2309
|
+
}
|
|
2310
|
+
if (hidden.length > 10) {
|
|
2311
|
+
console.log(` ... and ${hidden.length - 10} more`);
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
console.log("");
|
|
2315
|
+
}
|
|
2316
|
+
function pad3(str, len) {
|
|
2317
|
+
return str.length >= len ? str : str + " ".repeat(len - str.length);
|
|
2318
|
+
}
|
|
2319
|
+
|
|
1949
2320
|
// src/cli/index.ts
|
|
2321
|
+
var require3 = createRequire2(import.meta.url);
|
|
2322
|
+
var { version } = require3("../../package.json");
|
|
1950
2323
|
var program = new Command();
|
|
1951
|
-
program.name("codecortex").description("Persistent, AI-powered codebase knowledge layer").version(
|
|
2324
|
+
program.name("codecortex").description("Persistent, AI-powered codebase knowledge layer").version(version);
|
|
1952
2325
|
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
2326
|
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
2327
|
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
2328
|
program.command("status").description("Show knowledge freshness, stale modules, and symbol counts").option("-r, --root <path>", "Project root directory", process.cwd()).action(statusCommand);
|
|
2329
|
+
program.command("symbols [query]").description("Browse and filter the symbol index").option("-r, --root <path>", "Project root directory", process.cwd()).option("-k, --kind <kind>", "Filter by kind: function, class, interface, type, const, enum, method, property, variable").option("-f, --file <path>", "Filter by file path (partial match)").option("-e, --exported", "Show only exported symbols").option("-l, --limit <number>", "Max results", "30").action((query, opts) => symbolsCommand(query, opts));
|
|
2330
|
+
program.command("search <query>").description("Search across all CodeCortex knowledge files").option("-r, --root <path>", "Project root directory", process.cwd()).option("-l, --limit <number>", "Max results", "20").action((query, opts) => searchCommand(query, opts));
|
|
2331
|
+
program.command("modules [name]").description("List modules or deep-dive into a specific module").option("-r, --root <path>", "Project root directory", process.cwd()).action((name, opts) => modulesCommand(name, opts));
|
|
2332
|
+
program.command("hotspots").description("Show files ranked by risk: churn + coupling + bug history").option("-r, --root <path>", "Project root directory", process.cwd()).option("-l, --limit <number>", "Number of files to show", "15").action(hotspotsCommand);
|
|
1956
2333
|
program.parse();
|
|
1957
2334
|
//# sourceMappingURL=index.js.map
|