@ulpi/codemap 0.3.11 → 0.3.13

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 +29 -0
  2. package/dist/index.js +181 -60
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -111,6 +111,35 @@ codemap watch --debounce 500
111
111
  |--------|-------------|---------|
112
112
  | `--debounce <ms>` | Debounce interval | `300` |
113
113
 
114
+ ### `codemap statusline`
115
+
116
+ Install a CodeMap status indicator into Claude Code's statusline. It extends your existing statusline (if any) and appends CodeMap info.
117
+
118
+ ```bash
119
+ codemap statusline # Install
120
+ codemap statusline --uninstall # Remove and restore previous statusline
121
+ ```
122
+
123
+ The statusline shows at a glance:
124
+
125
+ ```
126
+ MyProject | git:main | Opus (1M context) | ctx:8%
127
+ CodeMap: ● 842 files · ◆12.4k chunks | 23m ago
128
+ ```
129
+
130
+ With multiple codemap MCP servers registered in `.mcp.json`, each gets its own line:
131
+
132
+ ```
133
+ MyProject | git:main | Opus (1M context) | ctx:8%
134
+ CodeMap: ● 842 files · ◆12.4k chunks | 23m ago
135
+ CodeMap (frontend): ○ 360 files · ◆1.9k chunks · ⚠3 stale | 2h ago
136
+ ```
137
+
138
+ - **●/○** — `codemap watch` running or not
139
+ - **◆** — indexed chunks (search units)
140
+ - **⚠N stale** — files modified since last index
141
+ - **IDX** — indexing in progress
142
+
114
143
  ### `codemap deps <file>`
115
144
 
116
145
  Show files this file depends on (outgoing imports).
package/dist/index.js CHANGED
@@ -5924,13 +5924,10 @@ function registerInit(program2) {
5924
5924
  console.log(` ${chalk3.cyan("codemap search")} Search your code`);
5925
5925
  console.log(` ${chalk3.cyan("codemap serve")} Start MCP server`);
5926
5926
  console.log();
5927
- printAgentSnippet();
5927
+ await showAgentSnippet(program2.opts().cwd || process.cwd());
5928
5928
  });
5929
5929
  }
5930
- function printAgentSnippet() {
5931
- console.log(chalk3.bold("Add to your CLAUDE.md / agents.md / rules:"));
5932
- console.log(chalk3.dim("\u2500".repeat(60)));
5933
- console.log(`
5930
+ var AGENT_SNIPPET = `
5934
5931
  ## Code Intelligence (codemap)
5935
5932
 
5936
5933
  This project is indexed with codemap for semantic code search,
@@ -5976,10 +5973,34 @@ symbol lookup, dependency analysis, and PageRank file ranking.
5976
5973
  - **Understanding impact**: \`get_dependents\` to see what breaks if you change a file
5977
5974
  - **Architecture review**: \`get_coupling_metrics\` and \`find_cycles\` for code health
5978
5975
  - **Navigation**: \`get_file_rank\` to identify the most important files
5979
- `.trim());
5976
+ - **Cross-project**: Any \`codemap-<name>\` MCP servers (e.g. \`codemap-frontend\`) are connected projects \u2014 use their tools to search and trace dependencies across your stack
5977
+ `.trim();
5978
+ async function showAgentSnippet(projectDir) {
5979
+ console.log(chalk3.bold("Agent configuration snippet:"));
5980
+ console.log(chalk3.dim("\u2500".repeat(60)));
5981
+ console.log(AGENT_SNIPPET);
5980
5982
  console.log();
5981
5983
  console.log(chalk3.dim("\u2500".repeat(60)));
5982
- console.log(chalk3.dim("Copy the above into your CLAUDE.md, .cursorrules, or agent config."));
5984
+ const claudeMdPath = path11.join(projectDir, "CLAUDE.md");
5985
+ const exists = fs13.existsSync(claudeMdPath);
5986
+ const prompt = exists ? `Append this to ${chalk3.cyan("CLAUDE.md")}? (Y/n): ` : `Create ${chalk3.cyan("CLAUDE.md")} with this content? (Y/n): `;
5987
+ const answer = await ask(prompt);
5988
+ if (answer.toLowerCase() !== "n") {
5989
+ if (exists) {
5990
+ const current = fs13.readFileSync(claudeMdPath, "utf-8");
5991
+ if (current.includes("## Code Intelligence (codemap)")) {
5992
+ console.log(chalk3.yellow("CLAUDE.md already contains a codemap section. Skipping."));
5993
+ } else {
5994
+ fs13.appendFileSync(claudeMdPath, "\n\n" + AGENT_SNIPPET + "\n");
5995
+ console.log(chalk3.green("Appended to ") + chalk3.cyan("CLAUDE.md"));
5996
+ }
5997
+ } else {
5998
+ fs13.writeFileSync(claudeMdPath, AGENT_SNIPPET + "\n");
5999
+ console.log(chalk3.green("Created ") + chalk3.cyan("CLAUDE.md"));
6000
+ }
6001
+ } else {
6002
+ console.log(chalk3.dim("Skipped. You can copy the snippet above manually."));
6003
+ }
5983
6004
  console.log();
5984
6005
  console.log(chalk3.bold("Multi-project setup"));
5985
6006
  console.log();
@@ -6137,14 +6158,24 @@ function registerIndex(program2) {
6137
6158
  console.log(chalk4.bold("Indexing ") + chalk4.cyan(projectDir));
6138
6159
  console.log(chalk4.dim(`Branch: ${branch} | Provider: ${config.embedding.provider} | Model: ${config.embedding.model}`));
6139
6160
  console.log();
6140
- const stats = await runInitPipeline(projectDir, config, {
6141
- branch,
6142
- dataDir: getDataDir2(),
6143
- onProgress: (progress) => {
6144
- const pct = progress.total ? ` (${progress.current ?? 0}/${progress.total})` : "";
6145
- console.log(chalk4.dim(`[${progress.phase}]`) + ` ${progress.message}${pct}`);
6161
+ let stats;
6162
+ try {
6163
+ stats = await runInitPipeline(projectDir, config, {
6164
+ branch,
6165
+ dataDir: getDataDir2(),
6166
+ onProgress: (progress) => {
6167
+ const pct = progress.total ? ` (${progress.current ?? 0}/${progress.total})` : "";
6168
+ console.log(chalk4.dim(`[${progress.phase}]`) + ` ${progress.message}${pct}`);
6169
+ }
6170
+ });
6171
+ } catch (err) {
6172
+ if (err instanceof Error && err.message.includes("Failed to acquire codemap lock")) {
6173
+ console.log(chalk4.yellow("Another codemap process is already indexing this project."));
6174
+ console.log(chalk4.dim("Wait for it to finish, or run `codemap status` to check."));
6175
+ return;
6146
6176
  }
6147
- });
6177
+ throw err;
6178
+ }
6148
6179
  console.log();
6149
6180
  console.log(chalk4.green("Index complete:"));
6150
6181
  console.log(` Files: ${stats.totalFiles}`);
@@ -7051,7 +7082,11 @@ import { homedir as homedir3 } from "os";
7051
7082
  function buildStatuslineScript(originalCommand) {
7052
7083
  const escapedCmd = originalCommand.replace(/'/g, "'\\''");
7053
7084
  const originalSection = originalCommand ? `ORIGINAL_CMD='${escapedCmd}'
7054
- echo "$SESSION_JSON" | eval "$ORIGINAL_CMD" 2>/dev/null || true` : "# (none)";
7085
+ original_out=$(echo "$SESSION_JSON" | eval "$ORIGINAL_CMD" 2>/dev/null || true)
7086
+ if [ -n "$original_out" ]; then
7087
+ echo "$original_out"
7088
+ has_line1=true
7089
+ fi` : "";
7055
7090
  return `#!/bin/bash
7056
7091
  # CodeMap statusline for Claude Code
7057
7092
  # Generated by: codemap statusline
@@ -7062,34 +7097,49 @@ set -euo pipefail
7062
7097
  # Read session JSON from stdin (Claude Code pipes it)
7063
7098
  SESSION_JSON=$(cat)
7064
7099
 
7065
- # --- Original statusline ---
7100
+ has_line1=false
7101
+
7102
+ # --- Original statusline command (if any) ---
7066
7103
  ${originalSection}
7067
7104
 
7068
- # --- CodeMap status ---
7069
- # Resolve the working directory from session JSON
7105
+ # --- Reconstruct Claude Code default status (line 1) ---
7070
7106
  current_dir=$(echo "$SESSION_JSON" | ${jsonExtract("cwd")} 2>/dev/null || pwd)
7071
7107
 
7072
- # Run codemap status --json for the project directory
7073
- cm_json=$(codemap status --json --cwd "$current_dir" 2>/dev/null || true)
7108
+ if [ "$has_line1" = "false" ]; then
7109
+ project_name=$(basename "$current_dir")
7110
+ git_branch=$(cd "$current_dir" && git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
7111
+ model_display=$(echo "$SESSION_JSON" | ${jsonExtract("display_name")} 2>/dev/null || echo "")
7112
+ ctx_size=$(echo "$SESSION_JSON" | ${jsonExtract("context_window_size")} 2>/dev/null || echo "")
7113
+ ctx_label=""
7114
+ if [ -n "$ctx_size" ]; then
7115
+ if [ "$ctx_size" -ge 1000000 ] 2>/dev/null; then
7116
+ ctx_label="1M context"
7117
+ elif [ "$ctx_size" -ge 100000 ] 2>/dev/null; then
7118
+ ctx_label="200k context"
7119
+ fi
7120
+ fi
7121
+ ctx_pct=$(echo "$SESSION_JSON" | ${jsonExtract("used_percentage")} 2>/dev/null || echo "")
7074
7122
 
7075
- if [ -z "$cm_json" ]; then
7076
- exit 0
7077
- fi
7123
+ line1="$project_name"
7124
+ if [ -n "$git_branch" ]; then
7125
+ line1="\${line1} | git:$git_branch"
7126
+ fi
7127
+ if [ -n "$model_display" ]; then
7128
+ if [ -n "$ctx_label" ]; then
7129
+ line1="\${line1} | \${model_display} (\${ctx_label})"
7130
+ else
7131
+ line1="\${line1} | \${model_display}"
7132
+ fi
7133
+ fi
7134
+ if [ -n "$ctx_pct" ]; then
7135
+ line1="\${line1} | ctx:\${ctx_pct}%"
7136
+ fi
7078
7137
 
7079
- # Parse fields
7080
- stats_null=$(echo "$cm_json" | ${jsonExtract("stats")} 2>/dev/null || echo "null")
7081
- if [ "$stats_null" = "null" ] || [ -z "$stats_null" ]; then
7082
- echo "CM: \u2014 no index (run: codemap index)"
7083
- exit 0
7138
+ echo "$line1"
7084
7139
  fi
7085
7140
 
7086
- total_files=$(echo "$cm_json" | ${jsonPath("stats", "totalFiles")} 2>/dev/null || echo "0")
7087
- total_chunks=$(echo "$cm_json" | ${jsonPath("stats", "totalChunks")} 2>/dev/null || echo "0")
7088
- stale_files=$(echo "$cm_json" | ${jsonPath("stats", "staleFiles")} 2>/dev/null || echo "0")
7089
- last_updated=$(echo "$cm_json" | ${jsonPath("stats", "lastUpdated")} 2>/dev/null || echo "")
7090
- indexing=$(echo "$cm_json" | ${jsonExtract("indexing")} 2>/dev/null || echo "false")
7141
+ # --- Helper functions ---
7091
7142
 
7092
- # Format chunks (e.g. 12400 \u2192 12.4k)
7093
7143
  format_chunks() {
7094
7144
  local n=$1
7095
7145
  if [ "$n" -ge 1000 ]; then
@@ -7102,7 +7152,6 @@ format_chunks() {
7102
7152
  fi
7103
7153
  }
7104
7154
 
7105
- # Calculate time ago
7106
7155
  time_ago() {
7107
7156
  local updated="$1"
7108
7157
  if [ -z "$updated" ]; then
@@ -7112,7 +7161,6 @@ time_ago() {
7112
7161
  local now
7113
7162
  now=$(date +%s)
7114
7163
  local then_ts
7115
- # macOS date -j vs GNU date
7116
7164
  if date -j -f "%Y-%m-%dT%H:%M:%S" "$(echo "$updated" | cut -c1-19)" +%s >/dev/null 2>&1; then
7117
7165
  then_ts=$(date -j -f "%Y-%m-%dT%H:%M:%S" "$(echo "$updated" | cut -c1-19)" +%s 2>/dev/null || echo "$now")
7118
7166
  else
@@ -7130,36 +7178,104 @@ time_ago() {
7130
7178
  fi
7131
7179
  }
7132
7180
 
7133
- # Watcher detection
7134
- watcher_icon="\u25CB"
7135
- if pgrep -f "codemap watch" >/dev/null 2>&1; then
7136
- watcher_icon="\u25CF"
7137
- fi
7181
+ # Print one CodeMap status line for a given project directory and label
7182
+ print_codemap_status() {
7183
+ local label="$1"
7184
+ local target_dir="$2"
7138
7185
 
7139
- # Indexing indicator
7140
- idx_icon=""
7141
- if [ "$indexing" = "true" ]; then
7142
- idx_icon=" IDX"
7143
- fi
7186
+ local cm_json
7187
+ cm_json=$(codemap status --json --cwd "$target_dir" 2>/dev/null || true)
7188
+ if [ -z "$cm_json" ]; then
7189
+ return
7190
+ fi
7144
7191
 
7145
- chunks_fmt=$(format_chunks "$total_chunks")
7192
+ local stats_null
7193
+ stats_null=$(echo "$cm_json" | ${jsonExtract("stats")} 2>/dev/null || echo "null")
7194
+ if [ "$stats_null" = "null" ] || [ -z "$stats_null" ]; then
7195
+ echo "\${label} \u2014 no index (run: codemap index)"
7196
+ return
7197
+ fi
7146
7198
 
7147
- # Build status line
7148
- line="CM: \${watcher_icon} \${total_files}f \${chunks_fmt}\u25C6"
7199
+ local total_files total_chunks stale_files last_updated indexing
7200
+ total_files=$(echo "$cm_json" | ${jsonPath("stats", "totalFiles")} 2>/dev/null || echo "0")
7201
+ total_chunks=$(echo "$cm_json" | ${jsonPath("stats", "totalChunks")} 2>/dev/null || echo "0")
7202
+ stale_files=$(echo "$cm_json" | ${jsonPath("stats", "staleFiles")} 2>/dev/null || echo "0")
7203
+ last_updated=$(echo "$cm_json" | ${jsonPath("stats", "lastUpdated")} 2>/dev/null || echo "")
7204
+ indexing=$(echo "$cm_json" | ${jsonExtract("indexing")} 2>/dev/null || echo "false")
7149
7205
 
7150
- if [ "$stale_files" -gt 0 ] 2>/dev/null; then
7151
- line="\${line} \u26A0\${stale_files}"
7152
- fi
7206
+ local watcher_icon="\u25CB"
7207
+ if pgrep -f "codemap watch" >/dev/null 2>&1; then
7208
+ watcher_icon="\u25CF"
7209
+ fi
7210
+
7211
+ local idx_icon=""
7212
+ if [ "$indexing" = "true" ]; then
7213
+ idx_icon=" \xB7 IDX"
7214
+ fi
7215
+
7216
+ local chunks_fmt
7217
+ chunks_fmt=$(format_chunks "$total_chunks")
7218
+
7219
+ local line="\${label} \${watcher_icon} \${total_files} files \xB7 \u25C6\${chunks_fmt} chunks"
7220
+
7221
+ if [ "$stale_files" -gt 0 ] 2>/dev/null; then
7222
+ line="\${line} \xB7 \u26A0\${stale_files} stale"
7223
+ fi
7153
7224
 
7154
- line="\${line}\${idx_icon} | $(time_ago "$last_updated")"
7225
+ line="\${line}\${idx_icon} | $(time_ago "$last_updated")"
7155
7226
 
7156
- echo "$line"
7227
+ echo "$line"
7228
+ }
7229
+
7230
+ # --- CodeMap status lines ---
7231
+
7232
+ # Discover codemap MCP servers from .mcp.json
7233
+ mcp_file="$current_dir/.mcp.json"
7234
+ found_servers=false
7235
+
7236
+ if [ -f "$mcp_file" ]; then
7237
+ # Use node to reliably parse JSON \u2014 extract "name|cwd" pairs for codemap servers
7238
+ codemap_servers=$(node -e "
7239
+ const fs = require('fs');
7240
+ try {
7241
+ const mcp = JSON.parse(fs.readFileSync(process.argv[1], 'utf-8'));
7242
+ const servers = mcp.mcpServers || {};
7243
+ for (const [name, config] of Object.entries(servers)) {
7244
+ if (config.command !== 'codemap') continue;
7245
+ const args = config.args || [];
7246
+ const cwdIdx = args.indexOf('--cwd');
7247
+ const cwd = cwdIdx >= 0 && cwdIdx + 1 < args.length ? args[cwdIdx + 1] : '';
7248
+ console.log(name + '|' + cwd);
7249
+ }
7250
+ } catch {}
7251
+ " "$mcp_file" 2>/dev/null || true)
7252
+
7253
+ if [ -n "$codemap_servers" ]; then
7254
+ found_servers=true
7255
+ while IFS='|' read -r server_name server_cwd; do
7256
+ target_dir="\${server_cwd:-$current_dir}"
7257
+ # Derive label: "codemap" \u2192 "CodeMap:", "codemap-frontend" \u2192 "CodeMap (frontend):"
7258
+ suffix=$(echo "$server_name" | sed 's/^codemap-//' | sed 's/^codemap$//')
7259
+ if [ -n "$suffix" ]; then
7260
+ label="CodeMap (\${suffix}):"
7261
+ else
7262
+ label="CodeMap:"
7263
+ fi
7264
+ print_codemap_status "$label" "$target_dir"
7265
+ done <<< "$codemap_servers"
7266
+ fi
7267
+ fi
7268
+
7269
+ # Fallback: no .mcp.json or no codemap entries \u2014 show status for current dir
7270
+ if [ "$found_servers" = "false" ]; then
7271
+ print_codemap_status "CodeMap:" "$current_dir"
7272
+ fi
7157
7273
  `;
7158
7274
  }
7159
7275
  function jsonExtract(field) {
7160
7276
  return `grep -o '"${field}"[[:space:]]*:[[:space:]]*[^,}]*' | sed 's/.*:[[:space:]]*//' | sed 's/"//g' | head -1`;
7161
7277
  }
7162
- function jsonPath(parent, child) {
7278
+ function jsonPath(_parent, child) {
7163
7279
  return `grep -o '"${child}"[[:space:]]*:[[:space:]]*[^,}]*' | sed 's/.*:[[:space:]]*//' | sed 's/"//g' | head -1`;
7164
7280
  }
7165
7281
 
@@ -7221,11 +7337,16 @@ function install() {
7221
7337
  console.log(chalk17.dim("Your existing statusline is preserved \u2014 CodeMap appends to it."));
7222
7338
  }
7223
7339
  console.log();
7224
- console.log("CodeMap will append to the statusline:");
7225
- console.log(chalk17.cyan(" CM: \u25CF 842f 12.4k\u25C6 | 23m ago") + chalk17.dim(" \u2014 watcher running, index fresh"));
7226
- console.log(chalk17.cyan(" CM: \u25CB 842f 12.4k\u25C6 \u26A03 | 2h ago") + chalk17.dim(" \u2014 no watcher, 3 stale files"));
7227
- console.log(chalk17.cyan(" CM: \u25CB 842f 12.4k\u25C6 IDX | 5s ago") + chalk17.dim(" \u2014 indexing in progress"));
7228
- console.log(chalk17.cyan(" CM: \u2014 no index (run: codemap index)") + chalk17.dim(" \u2014 not yet indexed"));
7340
+ console.log("The statusline will show:");
7341
+ console.log();
7342
+ console.log(" " + chalk17.cyan("MyProject | git:main | Opus (1M context) | ctx:8%"));
7343
+ console.log(" " + chalk17.cyan("CodeMap: \u25CF 842 files \xB7 \u25C612.4k chunks | 23m ago"));
7344
+ console.log();
7345
+ console.log(" With multiple codemap MCP servers:");
7346
+ console.log();
7347
+ console.log(" " + chalk17.cyan("MyProject | git:main | Opus (1M context) | ctx:8%"));
7348
+ console.log(" " + chalk17.cyan("CodeMap: \u25CF 842 files \xB7 \u25C612.4k chunks | 23m ago"));
7349
+ console.log(" " + chalk17.cyan("CodeMap (frontend): \u25CB 360 files \xB7 \u25C61.9k chunks \xB7 \u26A03 stale | 2h ago"));
7229
7350
  console.log();
7230
7351
  console.log(chalk17.dim("Restart Claude Code to see the statusline."));
7231
7352
  console.log(chalk17.dim("Run `codemap statusline --uninstall` to remove."));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ulpi/codemap",
3
- "version": "0.3.11",
3
+ "version": "0.3.13",
4
4
  "type": "module",
5
5
  "description": "Standalone code intelligence CLI — hybrid vector + BM25 search, dependency analysis, PageRank",
6
6
  "bin": {