@ulpi/codemap 0.3.1 → 0.3.3
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 +44 -1
- package/dist/index.js +365 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ Code intelligence CLI — hybrid vector + BM25 semantic search, dependency analy
|
|
|
13
13
|
- **Cycle detection** — find circular dependencies automatically
|
|
14
14
|
- **Coupling metrics** — measure afferent/efferent coupling and instability per file
|
|
15
15
|
- **Real-time watching** — incremental re-indexing on file changes
|
|
16
|
-
- **MCP server** — 12 tools for AI agent integration (Claude, Cursor, VS Code, etc.)
|
|
16
|
+
- **MCP server** — 12 tools for AI agent integration (Claude, Cursor, VS Code, Kiro, etc.)
|
|
17
17
|
- **Per-branch indexing** — separate indexes for each git branch
|
|
18
18
|
- **Multiple providers** — works with Ollama (free, local), OpenAI, or ULPI Cloud embeddings
|
|
19
19
|
|
|
@@ -154,6 +154,49 @@ codemap cycles
|
|
|
154
154
|
codemap cycles --json
|
|
155
155
|
```
|
|
156
156
|
|
|
157
|
+
### `codemap summary <file>`
|
|
158
|
+
|
|
159
|
+
Show file overview with symbols and size.
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
codemap summary src/routes/auth.ts
|
|
163
|
+
codemap summary src/index.ts --json
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### `codemap coupling`
|
|
167
|
+
|
|
168
|
+
Analyze afferent/efferent coupling and instability.
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
codemap coupling
|
|
172
|
+
codemap coupling --module src/routes/ --limit 20
|
|
173
|
+
codemap coupling --json
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
| Option | Description | Default |
|
|
177
|
+
|--------|-------------|---------|
|
|
178
|
+
| `--module <path>` | Filter to a directory prefix | — |
|
|
179
|
+
| `-l, --limit <n>` | Max results | `50` |
|
|
180
|
+
| `--json` | Output as JSON | — |
|
|
181
|
+
|
|
182
|
+
### `codemap graph-stats`
|
|
183
|
+
|
|
184
|
+
Show aggregate dependency graph statistics.
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
codemap graph-stats
|
|
188
|
+
codemap graph-stats --json
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### `codemap rebuild-depgraph`
|
|
192
|
+
|
|
193
|
+
Rebuild dependency graph from scratch using tree-sitter tag extraction.
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
codemap rebuild-depgraph
|
|
197
|
+
codemap rebuild-depgraph --json
|
|
198
|
+
```
|
|
199
|
+
|
|
157
200
|
### `codemap ignore`
|
|
158
201
|
|
|
159
202
|
Generate or reset `.codemapignore` with default exclusion patterns.
|
package/dist/index.js
CHANGED
|
@@ -4098,6 +4098,59 @@ function extractSymbolsFromChunks(chunks) {
|
|
|
4098
4098
|
}
|
|
4099
4099
|
return allSymbols;
|
|
4100
4100
|
}
|
|
4101
|
+
var TAG_TYPE_MAP = {
|
|
4102
|
+
class: "class",
|
|
4103
|
+
function: "function",
|
|
4104
|
+
method: "method",
|
|
4105
|
+
interface: "interface",
|
|
4106
|
+
type: "type",
|
|
4107
|
+
enum: "enum",
|
|
4108
|
+
module: "class",
|
|
4109
|
+
trait: "interface",
|
|
4110
|
+
struct: "class",
|
|
4111
|
+
property: "variable",
|
|
4112
|
+
constant: "const",
|
|
4113
|
+
variable: "variable",
|
|
4114
|
+
mixin: "class",
|
|
4115
|
+
extension: "class",
|
|
4116
|
+
union: "enum"
|
|
4117
|
+
};
|
|
4118
|
+
function symbolsFromTags(allTags, chunks) {
|
|
4119
|
+
const chunksByFile = /* @__PURE__ */ new Map();
|
|
4120
|
+
for (const chunk of chunks) {
|
|
4121
|
+
let arr = chunksByFile.get(chunk.filePath);
|
|
4122
|
+
if (!arr) {
|
|
4123
|
+
arr = [];
|
|
4124
|
+
chunksByFile.set(chunk.filePath, arr);
|
|
4125
|
+
}
|
|
4126
|
+
arr.push(chunk);
|
|
4127
|
+
}
|
|
4128
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4129
|
+
const symbols = [];
|
|
4130
|
+
for (const [filePath, tags] of allTags) {
|
|
4131
|
+
const fileChunks = chunksByFile.get(filePath);
|
|
4132
|
+
for (const tag of tags) {
|
|
4133
|
+
if (tag.kind !== "def") continue;
|
|
4134
|
+
if (!tag.name || tag.name.length < 2) continue;
|
|
4135
|
+
const symbolType = TAG_TYPE_MAP[tag.symbolType ?? ""] ?? "unknown";
|
|
4136
|
+
const key = `${tag.name}:${filePath}:${tag.line}`;
|
|
4137
|
+
if (seen.has(key)) continue;
|
|
4138
|
+
seen.add(key);
|
|
4139
|
+
const chunk = fileChunks?.find(
|
|
4140
|
+
(c2) => tag.line >= c2.startLine && tag.line <= c2.endLine
|
|
4141
|
+
);
|
|
4142
|
+
symbols.push({
|
|
4143
|
+
name: tag.name,
|
|
4144
|
+
symbolType,
|
|
4145
|
+
filePath,
|
|
4146
|
+
startLine: tag.line,
|
|
4147
|
+
endLine: chunk?.endLine ?? tag.line,
|
|
4148
|
+
chunkId: chunk?.id ?? ""
|
|
4149
|
+
});
|
|
4150
|
+
}
|
|
4151
|
+
}
|
|
4152
|
+
return symbols;
|
|
4153
|
+
}
|
|
4101
4154
|
function searchSymbols(symbols, query, topK = 20) {
|
|
4102
4155
|
const q = query.toLowerCase();
|
|
4103
4156
|
const results = [];
|
|
@@ -4512,11 +4565,13 @@ async function runInitPipelineInner(projectDir, config, branch, onProgress, data
|
|
|
4512
4565
|
await store.dropTable();
|
|
4513
4566
|
await embedAndStore(embedder, store, allChunks, onProgress);
|
|
4514
4567
|
onProgress?.({ phase: "symbols", message: "Extracting symbols..." });
|
|
4515
|
-
const
|
|
4568
|
+
const regexSymbols = extractSymbolsFromChunks(allChunks);
|
|
4569
|
+
const tagSymbols = allTags.size > 0 ? symbolsFromTags(allTags, allChunks) : [];
|
|
4570
|
+
const symbols = mergeSymbols(regexSymbols, tagSymbols);
|
|
4516
4571
|
saveSymbolIndex(metadataDir, symbols);
|
|
4517
4572
|
onProgress?.({
|
|
4518
4573
|
phase: "symbols",
|
|
4519
|
-
message: `Extracted ${symbols.length} symbols`,
|
|
4574
|
+
message: `Extracted ${symbols.length} symbols (${tagSymbols.length} from tags, ${regexSymbols.length} from regex)`,
|
|
4520
4575
|
current: symbols.length,
|
|
4521
4576
|
total: symbols.length
|
|
4522
4577
|
});
|
|
@@ -4631,7 +4686,10 @@ async function runIncrementalInner(projectDir, config, branch, manifest, changed
|
|
|
4631
4686
|
saveBM25Index(metadataDir, bm25Index);
|
|
4632
4687
|
const allFilePaths2 = scannedFiles.map((f) => f.filePath);
|
|
4633
4688
|
const allChunks = chunkFiles(projectDir, allFilePaths2, config.chunking);
|
|
4634
|
-
const
|
|
4689
|
+
const regexSymbols = extractSymbolsFromChunks(allChunks);
|
|
4690
|
+
const allTags2 = await extractAllTags(projectDir, allFilePaths2, onProgress);
|
|
4691
|
+
const tagSymbols = allTags2.size > 0 ? symbolsFromTags(allTags2, allChunks) : [];
|
|
4692
|
+
const symbols = mergeSymbols(regexSymbols, tagSymbols);
|
|
4635
4693
|
saveSymbolIndex(metadataDir, symbols);
|
|
4636
4694
|
} else {
|
|
4637
4695
|
const bm25Index = loadBM25Index(metadataDir);
|
|
@@ -4703,6 +4761,23 @@ function buildEmbeddingText(chunk) {
|
|
|
4703
4761
|
return `${header}
|
|
4704
4762
|
${chunk.content}`;
|
|
4705
4763
|
}
|
|
4764
|
+
function mergeSymbols(regexSymbols, tagSymbols) {
|
|
4765
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4766
|
+
const merged = [];
|
|
4767
|
+
for (const sym of tagSymbols) {
|
|
4768
|
+
const key = `${sym.name}:${sym.filePath}:${sym.startLine}`;
|
|
4769
|
+
if (seen.has(key)) continue;
|
|
4770
|
+
seen.add(key);
|
|
4771
|
+
merged.push(sym);
|
|
4772
|
+
}
|
|
4773
|
+
for (const sym of regexSymbols) {
|
|
4774
|
+
const key = `${sym.name}:${sym.filePath}:${sym.startLine}`;
|
|
4775
|
+
if (seen.has(key)) continue;
|
|
4776
|
+
seen.add(key);
|
|
4777
|
+
merged.push(sym);
|
|
4778
|
+
}
|
|
4779
|
+
return merged;
|
|
4780
|
+
}
|
|
4706
4781
|
async function extractAllTags(projectDir, filePaths, onProgress) {
|
|
4707
4782
|
const allTags = /* @__PURE__ */ new Map();
|
|
4708
4783
|
try {
|
|
@@ -5000,6 +5075,7 @@ async function searchCode(projectDir, query, options) {
|
|
|
5000
5075
|
const hybridConfig = config.hybrid;
|
|
5001
5076
|
if (hybridConfig.enabled) {
|
|
5002
5077
|
return await hybridSearch(
|
|
5078
|
+
projectDir,
|
|
5003
5079
|
store,
|
|
5004
5080
|
metadataDir,
|
|
5005
5081
|
queryVector,
|
|
@@ -5027,7 +5103,7 @@ async function searchCode(projectDir, query, options) {
|
|
|
5027
5103
|
await store.close();
|
|
5028
5104
|
}
|
|
5029
5105
|
}
|
|
5030
|
-
async function hybridSearch(store, metadataDir, queryVector, query, weights, vectorK, bm25K, limit, threshold, pathPrefix, includeTests, includeDocs) {
|
|
5106
|
+
async function hybridSearch(projectDir, store, metadataDir, queryVector, query, weights, vectorK, bm25K, limit, threshold, pathPrefix, includeTests, includeDocs) {
|
|
5031
5107
|
const vectorResults = await store.query(queryVector, vectorK);
|
|
5032
5108
|
const bm25Index = loadBM25Index(metadataDir);
|
|
5033
5109
|
const bm25Results = bm25Index ? queryBM25(bm25Index, query, bm25K) : [];
|
|
@@ -5051,6 +5127,7 @@ async function hybridSearch(store, metadataDir, queryVector, query, weights, vec
|
|
|
5051
5127
|
weights,
|
|
5052
5128
|
Math.max(limit * 3, 80)
|
|
5053
5129
|
);
|
|
5130
|
+
enrichBM25Candidates(projectDir, fused);
|
|
5054
5131
|
return applyFilters(fused, limit, threshold, pathPrefix, includeTests, includeDocs);
|
|
5055
5132
|
}
|
|
5056
5133
|
async function vectorOnlySearch(store, queryVector, limit, threshold, pathPrefix, includeTests, includeDocs) {
|
|
@@ -5089,6 +5166,28 @@ function applyFilters(candidates, limit, threshold, pathPrefix, includeTests, in
|
|
|
5089
5166
|
symbolScore: c2.symbolScore
|
|
5090
5167
|
}));
|
|
5091
5168
|
}
|
|
5169
|
+
function enrichBM25Candidates(projectDir, candidates) {
|
|
5170
|
+
for (const c2 of candidates) {
|
|
5171
|
+
if (c2.snippet !== "" || c2.startLine !== 0) continue;
|
|
5172
|
+
const lastColon = c2.id.lastIndexOf(":");
|
|
5173
|
+
if (lastColon === -1) continue;
|
|
5174
|
+
const beforeHash = c2.id.slice(0, lastColon);
|
|
5175
|
+
const secondLastColon = beforeHash.lastIndexOf(":");
|
|
5176
|
+
if (secondLastColon === -1) continue;
|
|
5177
|
+
const lineRange = beforeHash.slice(secondLastColon + 1);
|
|
5178
|
+
const match = lineRange.match(/^(\d+)-(\d+)$/);
|
|
5179
|
+
if (!match) continue;
|
|
5180
|
+
c2.startLine = parseInt(match[1], 10);
|
|
5181
|
+
c2.endLine = parseInt(match[2], 10);
|
|
5182
|
+
try {
|
|
5183
|
+
const absPath = path10.join(projectDir, c2.filePath);
|
|
5184
|
+
const content = fs10.readFileSync(absPath, "utf-8");
|
|
5185
|
+
const lines = content.split("\n");
|
|
5186
|
+
c2.snippet = lines.slice(c2.startLine - 1, c2.endLine).join("\n").slice(0, 200);
|
|
5187
|
+
} catch {
|
|
5188
|
+
}
|
|
5189
|
+
}
|
|
5190
|
+
}
|
|
5092
5191
|
function loadGraphRanks(metadataDir) {
|
|
5093
5192
|
try {
|
|
5094
5193
|
const file = path10.join(metadataDir, "depgraph", "pagerank.json");
|
|
@@ -5793,8 +5892,64 @@ function registerInit(program2) {
|
|
|
5793
5892
|
console.log(` ${chalk3.cyan("codemap index")} Index your project`);
|
|
5794
5893
|
console.log(` ${chalk3.cyan("codemap search")} Search your code`);
|
|
5795
5894
|
console.log(` ${chalk3.cyan("codemap serve")} Start MCP server`);
|
|
5895
|
+
console.log();
|
|
5896
|
+
printAgentSnippet();
|
|
5796
5897
|
});
|
|
5797
5898
|
}
|
|
5899
|
+
function printAgentSnippet() {
|
|
5900
|
+
console.log(chalk3.bold("Add to your CLAUDE.md / agents.md / rules:"));
|
|
5901
|
+
console.log(chalk3.dim("\u2500".repeat(60)));
|
|
5902
|
+
console.log(`
|
|
5903
|
+
## Code Intelligence (codemap)
|
|
5904
|
+
|
|
5905
|
+
This project is indexed with codemap for semantic code search,
|
|
5906
|
+
symbol lookup, dependency analysis, and PageRank file ranking.
|
|
5907
|
+
|
|
5908
|
+
### MCP Tools
|
|
5909
|
+
|
|
5910
|
+
| Tool | Description |
|
|
5911
|
+
|------|-------------|
|
|
5912
|
+
| \`mcp__codemap__search_code\` | Hybrid vector + BM25 semantic code search |
|
|
5913
|
+
| \`mcp__codemap__search_symbols\` | Find functions, classes, types by name |
|
|
5914
|
+
| \`mcp__codemap__get_file_summary\` | File overview with symbols and line count |
|
|
5915
|
+
| \`mcp__codemap__get_index_stats\` | Index statistics (files, chunks, size) |
|
|
5916
|
+
| \`mcp__codemap__get_dependencies\` | Files this file imports (outgoing) |
|
|
5917
|
+
| \`mcp__codemap__get_dependents\` | Files that import this file (incoming) |
|
|
5918
|
+
| \`mcp__codemap__get_file_rank\` | PageRank importance score |
|
|
5919
|
+
| \`mcp__codemap__find_cycles\` | Detect circular dependencies |
|
|
5920
|
+
| \`mcp__codemap__get_coupling_metrics\` | Afferent/efferent coupling analysis |
|
|
5921
|
+
| \`mcp__codemap__get_depgraph_stats\` | Aggregate dependency graph statistics |
|
|
5922
|
+
| \`mcp__codemap__reindex\` | Re-index the codebase |
|
|
5923
|
+
| \`mcp__codemap__rebuild_depgraph\` | Rebuild dependency graph |
|
|
5924
|
+
|
|
5925
|
+
### CLI Commands
|
|
5926
|
+
|
|
5927
|
+
| Command | Description |
|
|
5928
|
+
|---------|-------------|
|
|
5929
|
+
| \`codemap search <query>\` | Hybrid vector + BM25 semantic code search |
|
|
5930
|
+
| \`codemap symbols <query>\` | Find functions, classes, types by name |
|
|
5931
|
+
| \`codemap summary <file>\` | File overview with symbols and line count |
|
|
5932
|
+
| \`codemap status\` | Index statistics (files, chunks, size) |
|
|
5933
|
+
| \`codemap deps <file>\` | Files this file imports (outgoing) |
|
|
5934
|
+
| \`codemap dependents <file>\` | Files that import this file (incoming) |
|
|
5935
|
+
| \`codemap rank [file]\` | PageRank importance score |
|
|
5936
|
+
| \`codemap cycles\` | Detect circular dependencies |
|
|
5937
|
+
| \`codemap coupling\` | Afferent/efferent coupling analysis |
|
|
5938
|
+
| \`codemap graph-stats\` | Aggregate dependency graph statistics |
|
|
5939
|
+
| \`codemap index\` | Re-index the codebase |
|
|
5940
|
+
| \`codemap rebuild-depgraph\` | Rebuild dependency graph |
|
|
5941
|
+
|
|
5942
|
+
### When to Use
|
|
5943
|
+
|
|
5944
|
+
- **Before editing**: \`search_code\` or \`search_symbols\` to find relevant files
|
|
5945
|
+
- **Understanding impact**: \`get_dependents\` to see what breaks if you change a file
|
|
5946
|
+
- **Architecture review**: \`get_coupling_metrics\` and \`find_cycles\` for code health
|
|
5947
|
+
- **Navigation**: \`get_file_rank\` to identify the most important files
|
|
5948
|
+
`.trim());
|
|
5949
|
+
console.log();
|
|
5950
|
+
console.log(chalk3.dim("\u2500".repeat(60)));
|
|
5951
|
+
console.log(chalk3.dim("Copy the above into your CLAUDE.md, .cursorrules, or agent config."));
|
|
5952
|
+
}
|
|
5798
5953
|
var ULPI_API_BASE = "https://codemap.ulpi.io";
|
|
5799
5954
|
var ULPI_AUTH_BASE = "https://codemap.ulpi.io/api/v1";
|
|
5800
5955
|
function ask(prompt) {
|
|
@@ -6038,7 +6193,7 @@ function loadMetricsSafe(projectDir, branch) {
|
|
|
6038
6193
|
function createCodemapMcpServer() {
|
|
6039
6194
|
const server = new McpServer({
|
|
6040
6195
|
name: "codemap",
|
|
6041
|
-
version: "0.3.
|
|
6196
|
+
version: "0.3.2"
|
|
6042
6197
|
});
|
|
6043
6198
|
const dataDir = getDataDir2();
|
|
6044
6199
|
server.tool(
|
|
@@ -6609,12 +6764,212 @@ function registerIgnore(program2) {
|
|
|
6609
6764
|
});
|
|
6610
6765
|
}
|
|
6611
6766
|
|
|
6767
|
+
// src/commands/summary.ts
|
|
6768
|
+
import chalk13 from "chalk";
|
|
6769
|
+
import fs17 from "fs";
|
|
6770
|
+
function registerSummary(program2) {
|
|
6771
|
+
program2.command("summary <file>").description("Show file overview with symbols and size").option("--json", "Output as JSON").action(async (file, opts) => {
|
|
6772
|
+
const projectDir = program2.opts().cwd || process.cwd();
|
|
6773
|
+
const branch = getCurrentBranch(projectDir);
|
|
6774
|
+
const fullPath = `${projectDir}/${file}`;
|
|
6775
|
+
if (!fs17.existsSync(fullPath)) {
|
|
6776
|
+
console.error(chalk13.red(`File not found: ${file}`));
|
|
6777
|
+
process.exit(1);
|
|
6778
|
+
return;
|
|
6779
|
+
}
|
|
6780
|
+
const content = fs17.readFileSync(fullPath, "utf-8");
|
|
6781
|
+
const lines = content.split("\n");
|
|
6782
|
+
const metaDir = codemapMetadataDir(projectDir, branch, getDataDir2());
|
|
6783
|
+
const symbolIndex = loadSymbolIndex(metaDir);
|
|
6784
|
+
const fileSymbols = symbolIndex.filter((s) => s.filePath === file);
|
|
6785
|
+
const result = {
|
|
6786
|
+
filePath: file,
|
|
6787
|
+
sizeBytes: Buffer.byteLength(content, "utf-8"),
|
|
6788
|
+
lineCount: lines.length,
|
|
6789
|
+
symbols: fileSymbols.map((s) => ({
|
|
6790
|
+
name: s.name,
|
|
6791
|
+
type: s.symbolType,
|
|
6792
|
+
line: s.startLine
|
|
6793
|
+
}))
|
|
6794
|
+
};
|
|
6795
|
+
if (opts.json) {
|
|
6796
|
+
console.log(JSON.stringify(result, null, 2));
|
|
6797
|
+
return;
|
|
6798
|
+
}
|
|
6799
|
+
console.log(chalk13.bold(`File Summary: ${chalk13.cyan(file)}`));
|
|
6800
|
+
console.log(chalk13.dim("-".repeat(40)));
|
|
6801
|
+
console.log(`Size: ${formatBytes2(result.sizeBytes)}`);
|
|
6802
|
+
console.log(`Lines: ${result.lineCount}`);
|
|
6803
|
+
console.log(`Symbols: ${result.symbols.length}`);
|
|
6804
|
+
console.log();
|
|
6805
|
+
if (result.symbols.length > 0) {
|
|
6806
|
+
console.log(chalk13.bold("Symbols:"));
|
|
6807
|
+
for (const sym of result.symbols) {
|
|
6808
|
+
console.log(` ${chalk13.dim(sym.type.padEnd(10))} ${chalk13.cyan(sym.name)} ${chalk13.dim(`L${sym.line}`)}`);
|
|
6809
|
+
}
|
|
6810
|
+
}
|
|
6811
|
+
});
|
|
6812
|
+
}
|
|
6813
|
+
function formatBytes2(bytes) {
|
|
6814
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
6815
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
6816
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
6817
|
+
}
|
|
6818
|
+
|
|
6819
|
+
// src/commands/coupling.ts
|
|
6820
|
+
import chalk14 from "chalk";
|
|
6821
|
+
function registerCoupling(program2) {
|
|
6822
|
+
program2.command("coupling").description("Analyze afferent/efferent coupling and instability").option("--module <path>", "Filter to a directory prefix (e.g. src/routes/)").option("-l, --limit <n>", "Max results", "50").option("--json", "Output as JSON").action(async (opts) => {
|
|
6823
|
+
const projectDir = program2.opts().cwd || process.cwd();
|
|
6824
|
+
const branch = getCurrentBranch(projectDir);
|
|
6825
|
+
const graphFile = depgraphGraphFile(projectDir, branch, getDataDir2());
|
|
6826
|
+
let graph;
|
|
6827
|
+
try {
|
|
6828
|
+
graph = loadGraph(graphFile);
|
|
6829
|
+
} catch {
|
|
6830
|
+
console.error(chalk14.red("Dependency graph not available. Run `codemap index` first."));
|
|
6831
|
+
process.exit(1);
|
|
6832
|
+
return;
|
|
6833
|
+
}
|
|
6834
|
+
let coupling = computeCoupling(graph);
|
|
6835
|
+
if (opts.module) {
|
|
6836
|
+
coupling = coupling.filter((c2) => c2.filePath.startsWith(opts.module));
|
|
6837
|
+
}
|
|
6838
|
+
coupling.sort((a, b) => b.instability - a.instability);
|
|
6839
|
+
coupling = coupling.slice(0, parseInt(opts.limit, 10));
|
|
6840
|
+
const results = coupling.map((c2) => ({
|
|
6841
|
+
file: c2.filePath,
|
|
6842
|
+
fanIn: c2.afferentCoupling,
|
|
6843
|
+
fanOut: c2.efferentCoupling,
|
|
6844
|
+
instability: Math.round(c2.instability * 1e3) / 1e3
|
|
6845
|
+
}));
|
|
6846
|
+
if (opts.json) {
|
|
6847
|
+
console.log(JSON.stringify(results, null, 2));
|
|
6848
|
+
return;
|
|
6849
|
+
}
|
|
6850
|
+
console.log(chalk14.bold("Coupling Metrics (sorted by instability)"));
|
|
6851
|
+
console.log(chalk14.dim("-".repeat(60)));
|
|
6852
|
+
console.log(
|
|
6853
|
+
`${"File".padEnd(40)} ${"In".padStart(4)} ${"Out".padStart(4)} ${"Inst".padStart(6)}`
|
|
6854
|
+
);
|
|
6855
|
+
console.log(chalk14.dim("-".repeat(60)));
|
|
6856
|
+
for (const r2 of results) {
|
|
6857
|
+
const instColor = r2.instability >= 0.8 ? chalk14.red : r2.instability >= 0.5 ? chalk14.yellow : chalk14.green;
|
|
6858
|
+
console.log(
|
|
6859
|
+
`${chalk14.cyan(r2.file.slice(-38).padEnd(40))} ${String(r2.fanIn).padStart(4)} ${String(r2.fanOut).padStart(4)} ${instColor(String(r2.instability).padStart(6))}`
|
|
6860
|
+
);
|
|
6861
|
+
}
|
|
6862
|
+
});
|
|
6863
|
+
}
|
|
6864
|
+
|
|
6865
|
+
// src/commands/graph-stats.ts
|
|
6866
|
+
import chalk15 from "chalk";
|
|
6867
|
+
function registerGraphStats(program2) {
|
|
6868
|
+
program2.command("graph-stats").description("Show aggregate dependency graph statistics").option("--json", "Output as JSON").action(async (opts) => {
|
|
6869
|
+
const projectDir = program2.opts().cwd || process.cwd();
|
|
6870
|
+
const branch = getCurrentBranch(projectDir);
|
|
6871
|
+
let metrics;
|
|
6872
|
+
try {
|
|
6873
|
+
metrics = loadMetrics(depgraphMetricsFile(projectDir, branch, getDataDir2()));
|
|
6874
|
+
} catch {
|
|
6875
|
+
console.error(chalk15.red("Dependency metrics not available. Run `codemap index` first."));
|
|
6876
|
+
process.exit(1);
|
|
6877
|
+
return;
|
|
6878
|
+
}
|
|
6879
|
+
const result = {
|
|
6880
|
+
totalFiles: metrics.totalFiles,
|
|
6881
|
+
totalEdges: metrics.totalEdges,
|
|
6882
|
+
totalDefinitions: metrics.totalDefinitions,
|
|
6883
|
+
totalReferences: metrics.totalReferences,
|
|
6884
|
+
cycleCount: metrics.cycles.length,
|
|
6885
|
+
avgFanIn: Math.round(metrics.avgFanIn * 100) / 100,
|
|
6886
|
+
avgFanOut: Math.round(metrics.avgFanOut * 100) / 100,
|
|
6887
|
+
maxFanIn: metrics.maxFanIn,
|
|
6888
|
+
maxFanOut: metrics.maxFanOut,
|
|
6889
|
+
hotspots: metrics.hotspots.slice(0, 10).map((h) => ({
|
|
6890
|
+
file: h.filePath,
|
|
6891
|
+
connectivity: h.connectivity,
|
|
6892
|
+
pageRank: Math.round(h.pageRank * 1e4) / 1e4
|
|
6893
|
+
}))
|
|
6894
|
+
};
|
|
6895
|
+
if (opts.json) {
|
|
6896
|
+
console.log(JSON.stringify(result, null, 2));
|
|
6897
|
+
return;
|
|
6898
|
+
}
|
|
6899
|
+
console.log(chalk15.bold("Dependency Graph Statistics"));
|
|
6900
|
+
console.log(chalk15.dim("-".repeat(40)));
|
|
6901
|
+
console.log(`Files: ${result.totalFiles}`);
|
|
6902
|
+
console.log(`Edges: ${result.totalEdges}`);
|
|
6903
|
+
console.log(`Definitions: ${result.totalDefinitions}`);
|
|
6904
|
+
console.log(`References: ${result.totalReferences}`);
|
|
6905
|
+
console.log(`Cycles: ${result.cycleCount}`);
|
|
6906
|
+
console.log(`Avg fan-in: ${result.avgFanIn}`);
|
|
6907
|
+
console.log(`Avg fan-out: ${result.avgFanOut}`);
|
|
6908
|
+
console.log(`Max fan-in: ${result.maxFanIn}`);
|
|
6909
|
+
console.log(`Max fan-out: ${result.maxFanOut}`);
|
|
6910
|
+
if (result.hotspots.length > 0) {
|
|
6911
|
+
console.log();
|
|
6912
|
+
console.log(chalk15.bold("Top Hotspots:"));
|
|
6913
|
+
for (const h of result.hotspots) {
|
|
6914
|
+
console.log(
|
|
6915
|
+
` ${chalk15.cyan(h.file)} ${chalk15.dim(`connectivity=${h.connectivity} rank=${h.pageRank}`)}`
|
|
6916
|
+
);
|
|
6917
|
+
}
|
|
6918
|
+
}
|
|
6919
|
+
});
|
|
6920
|
+
}
|
|
6921
|
+
|
|
6922
|
+
// src/commands/rebuild.ts
|
|
6923
|
+
import chalk16 from "chalk";
|
|
6924
|
+
function registerRebuild(program2) {
|
|
6925
|
+
program2.command("rebuild-depgraph").description("Rebuild dependency graph from scratch using tree-sitter tag extraction").option("--json", "Output as JSON").action(async (opts) => {
|
|
6926
|
+
const projectDir = program2.opts().cwd || process.cwd();
|
|
6927
|
+
const config = loadCliConfig(projectDir);
|
|
6928
|
+
bridgeEnvVars(config);
|
|
6929
|
+
const branch = getCurrentBranch(projectDir);
|
|
6930
|
+
const dataDir = getDataDir2();
|
|
6931
|
+
try {
|
|
6932
|
+
const result = await rebuildDepgraph(projectDir, { branch, dataDir });
|
|
6933
|
+
if (result.taggedFiles === 0) {
|
|
6934
|
+
console.error(chalk16.yellow("No tags extracted \u2014 check that the project contains supported language files."));
|
|
6935
|
+
return;
|
|
6936
|
+
}
|
|
6937
|
+
if (opts.json) {
|
|
6938
|
+
console.log(JSON.stringify({
|
|
6939
|
+
message: "Dependency graph rebuilt",
|
|
6940
|
+
totalFiles: result.nodeCount,
|
|
6941
|
+
totalEdges: result.edgeCount,
|
|
6942
|
+
definitions: result.definitionCount,
|
|
6943
|
+
references: result.referenceCount,
|
|
6944
|
+
cycleCount: result.cycleCount,
|
|
6945
|
+
taggedFiles: result.taggedFiles,
|
|
6946
|
+
durationMs: result.durationMs
|
|
6947
|
+
}, null, 2));
|
|
6948
|
+
return;
|
|
6949
|
+
}
|
|
6950
|
+
console.log(chalk16.green("Dependency graph rebuilt"));
|
|
6951
|
+
console.log(chalk16.dim("-".repeat(40)));
|
|
6952
|
+
console.log(`Tagged files: ${result.taggedFiles}/${result.totalFiles}`);
|
|
6953
|
+
console.log(`Nodes: ${result.nodeCount}`);
|
|
6954
|
+
console.log(`Edges: ${result.edgeCount}`);
|
|
6955
|
+
console.log(`Definitions: ${result.definitionCount}`);
|
|
6956
|
+
console.log(`References: ${result.referenceCount}`);
|
|
6957
|
+
console.log(`Cycles: ${result.cycleCount}`);
|
|
6958
|
+
console.log(`Duration: ${(result.durationMs / 1e3).toFixed(1)}s`);
|
|
6959
|
+
} catch (err) {
|
|
6960
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6961
|
+
console.error(chalk16.red(`Depgraph rebuild failed: ${msg}`));
|
|
6962
|
+
process.exit(1);
|
|
6963
|
+
}
|
|
6964
|
+
});
|
|
6965
|
+
}
|
|
6966
|
+
|
|
6612
6967
|
// src/index.ts
|
|
6613
6968
|
var __dirname = path13.dirname(fileURLToPath(import.meta.url));
|
|
6614
6969
|
var grammarsDir = path13.join(__dirname, "grammars");
|
|
6615
6970
|
setGrammarDir(grammarsDir);
|
|
6616
6971
|
var program = new Command();
|
|
6617
|
-
program.name("codemap").description("Code intelligence CLI \u2014 hybrid search, dependency analysis, PageRank").version("0.3.
|
|
6972
|
+
program.name("codemap").description("Code intelligence CLI \u2014 hybrid search, dependency analysis, PageRank").version("0.3.3").option("--cwd <dir>", "Project directory (default: cwd)");
|
|
6618
6973
|
registerSearch(program);
|
|
6619
6974
|
registerSymbols(program);
|
|
6620
6975
|
registerIndex(program);
|
|
@@ -6628,4 +6983,8 @@ registerCycles(program);
|
|
|
6628
6983
|
registerConfig(program);
|
|
6629
6984
|
registerInit(program);
|
|
6630
6985
|
registerIgnore(program);
|
|
6986
|
+
registerSummary(program);
|
|
6987
|
+
registerCoupling(program);
|
|
6988
|
+
registerGraphStats(program);
|
|
6989
|
+
registerRebuild(program);
|
|
6631
6990
|
program.parse();
|