@ulpi/codemap 0.3.2 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +44 -1
  2. package/dist/index.js +315 -9
  3. 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
@@ -5758,8 +5758,7 @@ function registerInit(program2) {
5758
5758
  console.log(` Model: ${chalk3.cyan(config.embedding.model)}`);
5759
5759
  console.log(` Dimensions: ${chalk3.cyan(String(config.embedding.dimensions))}`);
5760
5760
  if (config.embedding.apiKey) {
5761
- const masked = config.embedding.apiKey.slice(0, 8) + "...";
5762
- console.log(` API key: ${chalk3.cyan(masked)}`);
5761
+ console.log(` API key: ${chalk3.green("configured")}`);
5763
5762
  }
5764
5763
  console.log();
5765
5764
  const answer = await ask("Reconfigure? (y/N): ");
@@ -5794,11 +5793,30 @@ function registerInit(program2) {
5794
5793
  console.log();
5795
5794
  const hasAccount = await ask("Do you have a ULPI account? (y/n): ");
5796
5795
  if (hasAccount.toLowerCase() === "y") {
5797
- const apiKey = await ask("API key: ");
5798
- if (apiKey) {
5799
- config.embedding.apiKey = apiKey;
5800
- } else {
5801
- console.log(chalk3.yellow("No API key provided. Set it later with:"));
5796
+ console.log();
5797
+ const email = await ask("Email: ");
5798
+ const password = await askSecret("Password: ");
5799
+ try {
5800
+ console.log();
5801
+ console.log(chalk3.dim("Logging in..."));
5802
+ const token = await ulpiLogin(email, password);
5803
+ console.log(chalk3.green("Logged in."));
5804
+ console.log(chalk3.dim("Fetching API keys..."));
5805
+ const existingKey = await ulpiGetKey(token);
5806
+ if (existingKey) {
5807
+ config.embedding.apiKey = existingKey;
5808
+ console.log(chalk3.green("API key configured."));
5809
+ } else {
5810
+ console.log(chalk3.dim("No existing key found. Creating one..."));
5811
+ const apiKey = await ulpiCreateKey(token);
5812
+ config.embedding.apiKey = apiKey;
5813
+ console.log(chalk3.green("API key configured."));
5814
+ }
5815
+ } catch (err) {
5816
+ const msg = err instanceof Error ? err.message : String(err);
5817
+ console.error(chalk3.red(`Login failed: ${msg}`));
5818
+ console.log();
5819
+ console.log(chalk3.yellow("You can set the API key manually later:"));
5802
5820
  console.log(chalk3.dim(" codemap config set embedding.apiKey <key>"));
5803
5821
  }
5804
5822
  } else {
@@ -5821,7 +5839,7 @@ function registerInit(program2) {
5821
5839
  console.log(chalk3.dim("Creating API key..."));
5822
5840
  const apiKey = await ulpiCreateKey(token);
5823
5841
  config.embedding.apiKey = apiKey;
5824
- console.log(chalk3.green("API key: ") + chalk3.cyan(apiKey));
5842
+ console.log(chalk3.green("API key configured."));
5825
5843
  } catch (err) {
5826
5844
  const msg = err instanceof Error ? err.message : String(err);
5827
5845
  console.error(chalk3.red(`Registration failed: ${msg}`));
@@ -5892,8 +5910,64 @@ function registerInit(program2) {
5892
5910
  console.log(` ${chalk3.cyan("codemap index")} Index your project`);
5893
5911
  console.log(` ${chalk3.cyan("codemap search")} Search your code`);
5894
5912
  console.log(` ${chalk3.cyan("codemap serve")} Start MCP server`);
5913
+ console.log();
5914
+ printAgentSnippet();
5895
5915
  });
5896
5916
  }
5917
+ function printAgentSnippet() {
5918
+ console.log(chalk3.bold("Add to your CLAUDE.md / agents.md / rules:"));
5919
+ console.log(chalk3.dim("\u2500".repeat(60)));
5920
+ console.log(`
5921
+ ## Code Intelligence (codemap)
5922
+
5923
+ This project is indexed with codemap for semantic code search,
5924
+ symbol lookup, dependency analysis, and PageRank file ranking.
5925
+
5926
+ ### MCP Tools
5927
+
5928
+ | Tool | Description |
5929
+ |------|-------------|
5930
+ | \`mcp__codemap__search_code\` | Hybrid vector + BM25 semantic code search |
5931
+ | \`mcp__codemap__search_symbols\` | Find functions, classes, types by name |
5932
+ | \`mcp__codemap__get_file_summary\` | File overview with symbols and line count |
5933
+ | \`mcp__codemap__get_index_stats\` | Index statistics (files, chunks, size) |
5934
+ | \`mcp__codemap__get_dependencies\` | Files this file imports (outgoing) |
5935
+ | \`mcp__codemap__get_dependents\` | Files that import this file (incoming) |
5936
+ | \`mcp__codemap__get_file_rank\` | PageRank importance score |
5937
+ | \`mcp__codemap__find_cycles\` | Detect circular dependencies |
5938
+ | \`mcp__codemap__get_coupling_metrics\` | Afferent/efferent coupling analysis |
5939
+ | \`mcp__codemap__get_depgraph_stats\` | Aggregate dependency graph statistics |
5940
+ | \`mcp__codemap__reindex\` | Re-index the codebase |
5941
+ | \`mcp__codemap__rebuild_depgraph\` | Rebuild dependency graph |
5942
+
5943
+ ### CLI Commands
5944
+
5945
+ | Command | Description |
5946
+ |---------|-------------|
5947
+ | \`codemap search <query>\` | Hybrid vector + BM25 semantic code search |
5948
+ | \`codemap symbols <query>\` | Find functions, classes, types by name |
5949
+ | \`codemap summary <file>\` | File overview with symbols and line count |
5950
+ | \`codemap status\` | Index statistics (files, chunks, size) |
5951
+ | \`codemap deps <file>\` | Files this file imports (outgoing) |
5952
+ | \`codemap dependents <file>\` | Files that import this file (incoming) |
5953
+ | \`codemap rank [file]\` | PageRank importance score |
5954
+ | \`codemap cycles\` | Detect circular dependencies |
5955
+ | \`codemap coupling\` | Afferent/efferent coupling analysis |
5956
+ | \`codemap graph-stats\` | Aggregate dependency graph statistics |
5957
+ | \`codemap index\` | Re-index the codebase |
5958
+ | \`codemap rebuild-depgraph\` | Rebuild dependency graph |
5959
+
5960
+ ### When to Use
5961
+
5962
+ - **Before editing**: \`search_code\` or \`search_symbols\` to find relevant files
5963
+ - **Understanding impact**: \`get_dependents\` to see what breaks if you change a file
5964
+ - **Architecture review**: \`get_coupling_metrics\` and \`find_cycles\` for code health
5965
+ - **Navigation**: \`get_file_rank\` to identify the most important files
5966
+ `.trim());
5967
+ console.log();
5968
+ console.log(chalk3.dim("\u2500".repeat(60)));
5969
+ console.log(chalk3.dim("Copy the above into your CLAUDE.md, .cursorrules, or agent config."));
5970
+ }
5897
5971
  var ULPI_API_BASE = "https://codemap.ulpi.io";
5898
5972
  var ULPI_AUTH_BASE = "https://codemap.ulpi.io/api/v1";
5899
5973
  function ask(prompt) {
@@ -5965,6 +6039,34 @@ async function ulpiRegister(name, email, password) {
5965
6039
  }
5966
6040
  return json2.token;
5967
6041
  }
6042
+ async function ulpiLogin(email, password) {
6043
+ const form = new URLSearchParams();
6044
+ form.set("email", email);
6045
+ form.set("password", password);
6046
+ const res = await fetch(`${ULPI_AUTH_BASE}/login`, {
6047
+ method: "POST",
6048
+ headers: { "Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json" },
6049
+ body: form
6050
+ });
6051
+ const json2 = await res.json();
6052
+ if (!res.ok || !json2.token) {
6053
+ if (json2.errors) {
6054
+ const msgs = Object.values(json2.errors).flat();
6055
+ throw new Error(msgs.join(", "));
6056
+ }
6057
+ throw new Error(json2.message ?? `HTTP ${res.status}`);
6058
+ }
6059
+ return json2.token;
6060
+ }
6061
+ async function ulpiGetKey(token) {
6062
+ const res = await fetch(`${ULPI_AUTH_BASE}/keys`, {
6063
+ method: "GET",
6064
+ headers: { "Accept": "application/json", "Authorization": `Bearer ${token}` }
6065
+ });
6066
+ const json2 = await res.json();
6067
+ if (!res.ok || !json2.data?.length) return null;
6068
+ return json2.data[0].key ?? null;
6069
+ }
5968
6070
  async function ulpiCreateKey(token) {
5969
6071
  const form = new URLSearchParams();
5970
6072
  form.set("name", "codemap-cli");
@@ -6708,12 +6810,212 @@ function registerIgnore(program2) {
6708
6810
  });
6709
6811
  }
6710
6812
 
6813
+ // src/commands/summary.ts
6814
+ import chalk13 from "chalk";
6815
+ import fs17 from "fs";
6816
+ function registerSummary(program2) {
6817
+ program2.command("summary <file>").description("Show file overview with symbols and size").option("--json", "Output as JSON").action(async (file, opts) => {
6818
+ const projectDir = program2.opts().cwd || process.cwd();
6819
+ const branch = getCurrentBranch(projectDir);
6820
+ const fullPath = `${projectDir}/${file}`;
6821
+ if (!fs17.existsSync(fullPath)) {
6822
+ console.error(chalk13.red(`File not found: ${file}`));
6823
+ process.exit(1);
6824
+ return;
6825
+ }
6826
+ const content = fs17.readFileSync(fullPath, "utf-8");
6827
+ const lines = content.split("\n");
6828
+ const metaDir = codemapMetadataDir(projectDir, branch, getDataDir2());
6829
+ const symbolIndex = loadSymbolIndex(metaDir);
6830
+ const fileSymbols = symbolIndex.filter((s) => s.filePath === file);
6831
+ const result = {
6832
+ filePath: file,
6833
+ sizeBytes: Buffer.byteLength(content, "utf-8"),
6834
+ lineCount: lines.length,
6835
+ symbols: fileSymbols.map((s) => ({
6836
+ name: s.name,
6837
+ type: s.symbolType,
6838
+ line: s.startLine
6839
+ }))
6840
+ };
6841
+ if (opts.json) {
6842
+ console.log(JSON.stringify(result, null, 2));
6843
+ return;
6844
+ }
6845
+ console.log(chalk13.bold(`File Summary: ${chalk13.cyan(file)}`));
6846
+ console.log(chalk13.dim("-".repeat(40)));
6847
+ console.log(`Size: ${formatBytes2(result.sizeBytes)}`);
6848
+ console.log(`Lines: ${result.lineCount}`);
6849
+ console.log(`Symbols: ${result.symbols.length}`);
6850
+ console.log();
6851
+ if (result.symbols.length > 0) {
6852
+ console.log(chalk13.bold("Symbols:"));
6853
+ for (const sym of result.symbols) {
6854
+ console.log(` ${chalk13.dim(sym.type.padEnd(10))} ${chalk13.cyan(sym.name)} ${chalk13.dim(`L${sym.line}`)}`);
6855
+ }
6856
+ }
6857
+ });
6858
+ }
6859
+ function formatBytes2(bytes) {
6860
+ if (bytes < 1024) return `${bytes}B`;
6861
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
6862
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
6863
+ }
6864
+
6865
+ // src/commands/coupling.ts
6866
+ import chalk14 from "chalk";
6867
+ function registerCoupling(program2) {
6868
+ 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) => {
6869
+ const projectDir = program2.opts().cwd || process.cwd();
6870
+ const branch = getCurrentBranch(projectDir);
6871
+ const graphFile = depgraphGraphFile(projectDir, branch, getDataDir2());
6872
+ let graph;
6873
+ try {
6874
+ graph = loadGraph(graphFile);
6875
+ } catch {
6876
+ console.error(chalk14.red("Dependency graph not available. Run `codemap index` first."));
6877
+ process.exit(1);
6878
+ return;
6879
+ }
6880
+ let coupling = computeCoupling(graph);
6881
+ if (opts.module) {
6882
+ coupling = coupling.filter((c2) => c2.filePath.startsWith(opts.module));
6883
+ }
6884
+ coupling.sort((a, b) => b.instability - a.instability);
6885
+ coupling = coupling.slice(0, parseInt(opts.limit, 10));
6886
+ const results = coupling.map((c2) => ({
6887
+ file: c2.filePath,
6888
+ fanIn: c2.afferentCoupling,
6889
+ fanOut: c2.efferentCoupling,
6890
+ instability: Math.round(c2.instability * 1e3) / 1e3
6891
+ }));
6892
+ if (opts.json) {
6893
+ console.log(JSON.stringify(results, null, 2));
6894
+ return;
6895
+ }
6896
+ console.log(chalk14.bold("Coupling Metrics (sorted by instability)"));
6897
+ console.log(chalk14.dim("-".repeat(60)));
6898
+ console.log(
6899
+ `${"File".padEnd(40)} ${"In".padStart(4)} ${"Out".padStart(4)} ${"Inst".padStart(6)}`
6900
+ );
6901
+ console.log(chalk14.dim("-".repeat(60)));
6902
+ for (const r2 of results) {
6903
+ const instColor = r2.instability >= 0.8 ? chalk14.red : r2.instability >= 0.5 ? chalk14.yellow : chalk14.green;
6904
+ console.log(
6905
+ `${chalk14.cyan(r2.file.slice(-38).padEnd(40))} ${String(r2.fanIn).padStart(4)} ${String(r2.fanOut).padStart(4)} ${instColor(String(r2.instability).padStart(6))}`
6906
+ );
6907
+ }
6908
+ });
6909
+ }
6910
+
6911
+ // src/commands/graph-stats.ts
6912
+ import chalk15 from "chalk";
6913
+ function registerGraphStats(program2) {
6914
+ program2.command("graph-stats").description("Show aggregate dependency graph statistics").option("--json", "Output as JSON").action(async (opts) => {
6915
+ const projectDir = program2.opts().cwd || process.cwd();
6916
+ const branch = getCurrentBranch(projectDir);
6917
+ let metrics;
6918
+ try {
6919
+ metrics = loadMetrics(depgraphMetricsFile(projectDir, branch, getDataDir2()));
6920
+ } catch {
6921
+ console.error(chalk15.red("Dependency metrics not available. Run `codemap index` first."));
6922
+ process.exit(1);
6923
+ return;
6924
+ }
6925
+ const result = {
6926
+ totalFiles: metrics.totalFiles,
6927
+ totalEdges: metrics.totalEdges,
6928
+ totalDefinitions: metrics.totalDefinitions,
6929
+ totalReferences: metrics.totalReferences,
6930
+ cycleCount: metrics.cycles.length,
6931
+ avgFanIn: Math.round(metrics.avgFanIn * 100) / 100,
6932
+ avgFanOut: Math.round(metrics.avgFanOut * 100) / 100,
6933
+ maxFanIn: metrics.maxFanIn,
6934
+ maxFanOut: metrics.maxFanOut,
6935
+ hotspots: metrics.hotspots.slice(0, 10).map((h) => ({
6936
+ file: h.filePath,
6937
+ connectivity: h.connectivity,
6938
+ pageRank: Math.round(h.pageRank * 1e4) / 1e4
6939
+ }))
6940
+ };
6941
+ if (opts.json) {
6942
+ console.log(JSON.stringify(result, null, 2));
6943
+ return;
6944
+ }
6945
+ console.log(chalk15.bold("Dependency Graph Statistics"));
6946
+ console.log(chalk15.dim("-".repeat(40)));
6947
+ console.log(`Files: ${result.totalFiles}`);
6948
+ console.log(`Edges: ${result.totalEdges}`);
6949
+ console.log(`Definitions: ${result.totalDefinitions}`);
6950
+ console.log(`References: ${result.totalReferences}`);
6951
+ console.log(`Cycles: ${result.cycleCount}`);
6952
+ console.log(`Avg fan-in: ${result.avgFanIn}`);
6953
+ console.log(`Avg fan-out: ${result.avgFanOut}`);
6954
+ console.log(`Max fan-in: ${result.maxFanIn}`);
6955
+ console.log(`Max fan-out: ${result.maxFanOut}`);
6956
+ if (result.hotspots.length > 0) {
6957
+ console.log();
6958
+ console.log(chalk15.bold("Top Hotspots:"));
6959
+ for (const h of result.hotspots) {
6960
+ console.log(
6961
+ ` ${chalk15.cyan(h.file)} ${chalk15.dim(`connectivity=${h.connectivity} rank=${h.pageRank}`)}`
6962
+ );
6963
+ }
6964
+ }
6965
+ });
6966
+ }
6967
+
6968
+ // src/commands/rebuild.ts
6969
+ import chalk16 from "chalk";
6970
+ function registerRebuild(program2) {
6971
+ program2.command("rebuild-depgraph").description("Rebuild dependency graph from scratch using tree-sitter tag extraction").option("--json", "Output as JSON").action(async (opts) => {
6972
+ const projectDir = program2.opts().cwd || process.cwd();
6973
+ const config = loadCliConfig(projectDir);
6974
+ bridgeEnvVars(config);
6975
+ const branch = getCurrentBranch(projectDir);
6976
+ const dataDir = getDataDir2();
6977
+ try {
6978
+ const result = await rebuildDepgraph(projectDir, { branch, dataDir });
6979
+ if (result.taggedFiles === 0) {
6980
+ console.error(chalk16.yellow("No tags extracted \u2014 check that the project contains supported language files."));
6981
+ return;
6982
+ }
6983
+ if (opts.json) {
6984
+ console.log(JSON.stringify({
6985
+ message: "Dependency graph rebuilt",
6986
+ totalFiles: result.nodeCount,
6987
+ totalEdges: result.edgeCount,
6988
+ definitions: result.definitionCount,
6989
+ references: result.referenceCount,
6990
+ cycleCount: result.cycleCount,
6991
+ taggedFiles: result.taggedFiles,
6992
+ durationMs: result.durationMs
6993
+ }, null, 2));
6994
+ return;
6995
+ }
6996
+ console.log(chalk16.green("Dependency graph rebuilt"));
6997
+ console.log(chalk16.dim("-".repeat(40)));
6998
+ console.log(`Tagged files: ${result.taggedFiles}/${result.totalFiles}`);
6999
+ console.log(`Nodes: ${result.nodeCount}`);
7000
+ console.log(`Edges: ${result.edgeCount}`);
7001
+ console.log(`Definitions: ${result.definitionCount}`);
7002
+ console.log(`References: ${result.referenceCount}`);
7003
+ console.log(`Cycles: ${result.cycleCount}`);
7004
+ console.log(`Duration: ${(result.durationMs / 1e3).toFixed(1)}s`);
7005
+ } catch (err) {
7006
+ const msg = err instanceof Error ? err.message : String(err);
7007
+ console.error(chalk16.red(`Depgraph rebuild failed: ${msg}`));
7008
+ process.exit(1);
7009
+ }
7010
+ });
7011
+ }
7012
+
6711
7013
  // src/index.ts
6712
7014
  var __dirname = path13.dirname(fileURLToPath(import.meta.url));
6713
7015
  var grammarsDir = path13.join(__dirname, "grammars");
6714
7016
  setGrammarDir(grammarsDir);
6715
7017
  var program = new Command();
6716
- program.name("codemap").description("Code intelligence CLI \u2014 hybrid search, dependency analysis, PageRank").version("0.3.2").option("--cwd <dir>", "Project directory (default: cwd)");
7018
+ program.name("codemap").description("Code intelligence CLI \u2014 hybrid search, dependency analysis, PageRank").version("0.3.4").option("--cwd <dir>", "Project directory (default: cwd)");
6717
7019
  registerSearch(program);
6718
7020
  registerSymbols(program);
6719
7021
  registerIndex(program);
@@ -6727,4 +7029,8 @@ registerCycles(program);
6727
7029
  registerConfig(program);
6728
7030
  registerInit(program);
6729
7031
  registerIgnore(program);
7032
+ registerSummary(program);
7033
+ registerCoupling(program);
7034
+ registerGraphStats(program);
7035
+ registerRebuild(program);
6730
7036
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ulpi/codemap",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "type": "module",
5
5
  "description": "Standalone code intelligence CLI — hybrid vector + BM25 search, dependency analysis, PageRank",
6
6
  "bin": {