From a236532cb2567b2ab26701e6faf055a41982adcf Mon Sep 17 00:00:00 2001 From: dvcdsys Date: Sat, 25 Jul 2026 09:29:23 +0100 Subject: [PATCH 1/3] feat(plugin): make the self-hosted server requirement explicit - plugin.json / marketplace.json: descriptions now lead with the self-hosted server requirement; add self-hosted keyword/tag; bump plugin to 0.3.1 - plugin README: "Requires a self-hosted cix server" section up top with the installer one-liner and connect-to-existing instructions - SessionStart hook: when NO cix server is configured on the machine (no CIX_API_URL, no url in ~/.cix/config.yaml), emit a one-line setup hint via additionalContext; configured-but-unindexed projects stay silent as before. Shared emit helper extracted. - site: PLUGIN_VERSION 0.3.1 Co-Authored-By: Claude Fable 5 --- .claude-plugin/marketplace.json | 8 +++--- plugins/cix/.claude-plugin/plugin.json | 5 ++-- plugins/cix/README.md | 17 ++++++++++++ plugins/cix/scripts/lib-cix-probe.sh | 14 ++++++++++ plugins/cix/scripts/session-start.sh | 38 ++++++++++++++++++++------ site/src/shared/versions.js | 2 +- 6 files changed, 69 insertions(+), 15 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 0e81dadf..4b6635f0 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -10,21 +10,21 @@ { "name": "cix", "source": "./plugins/cix", - "description": "Semantic code search and navigation for Claude Code. Bundles the cix CLI, slash commands, behavioral hooks, and the cix + cix-workspace skills (CLI-based) plus the cix-workspace-investigator sub-agent for cross-project research across cix workspaces.", + "description": "Semantic code search and navigation for Claude Code. Requires a self-hosted cix (CodeIndeX) server — free, MIT, ~10-minute install (https://codeindex.app); the plugin is the client. Bundles the cix CLI, slash commands, behavioral hooks, and the cix + cix-workspace skills (CLI-based) plus the cix-workspace-investigator sub-agent for cross-project research across cix workspaces.", "author": { "name": "dvcdsys" }, "homepage": "https://github.com/dvcdsys/code-index", "repository": "https://github.com/dvcdsys/code-index", "license": "MIT", - "keywords": ["search", "code-search", "semantic", "navigation", "indexing", "embeddings", "workspace", "cross-project", "sub-agent"], + "keywords": ["search", "self-hosted", "code-search", "semantic", "navigation", "indexing", "embeddings", "workspace", "cross-project", "sub-agent"], "category": "developer-tools", - "tags": ["search", "indexing", "ai", "embeddings", "workspace", "cross-project"] + "tags": ["search", "self-hosted", "indexing", "ai", "embeddings", "workspace", "cross-project"] }, { "name": "cix-cowork", "source": "./plugins/cix-cowork", - "description": "cix skills for Claude Desktop & Cowork: the cix and cix-workspace guidance adapted to the cix_* MCP tools. Install AFTER registering the cix MCP server (run `cix mcp install claude-desktop`). Skills only — no MCP server, no CLI.", + "description": "cix skills for Claude Desktop & Cowork: the cix and cix-workspace guidance adapted to the cix_* MCP tools. Requires a self-hosted cix (CodeIndeX) server (https://codeindex.app). Install AFTER registering the cix MCP server (run `cix mcp install claude-desktop`). Skills only — no MCP server, no CLI.", "author": { "name": "dvcdsys" }, diff --git a/plugins/cix/.claude-plugin/plugin.json b/plugins/cix/.claude-plugin/plugin.json index 293f9f30..4b29ccc7 100644 --- a/plugins/cix/.claude-plugin/plugin.json +++ b/plugins/cix/.claude-plugin/plugin.json @@ -1,8 +1,8 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "cix", - "version": "0.3.0", - "description": "Semantic code search and navigation for Claude Code via the cix index. Single-repo cix skill + commands (incl. cix file / cix tree for external projects), plus the experimental cix-workspace skill and cix-workspace-investigator sub-agent for cross-project research across cix workspaces.", + "version": "0.3.1", + "description": "Semantic code search for Claude Code. Requires a self-hosted cix (CodeIndeX) server — free, MIT, ~10-minute install (https://codeindex.app) on your machine, LAN box, or GPU server; the plugin is the client. Ships the cix CLI, slash commands (incl. cix file / cix tree for external projects), behavioral hooks, the cix skill, plus the experimental cix-workspace skill and cix-workspace-investigator sub-agent for cross-project research.", "author": { "name": "dvcdsys", "email": "dvcdsys@gmail.com" @@ -12,6 +12,7 @@ "license": "MIT", "keywords": [ "search", + "self-hosted", "code-search", "semantic", "navigation", diff --git a/plugins/cix/README.md b/plugins/cix/README.md index b0b0ee85..f7ab187e 100644 --- a/plugins/cix/README.md +++ b/plugins/cix/README.md @@ -3,6 +3,23 @@ Semantic code search and navigation for Claude Code, powered by the [cix](https://github.com/dvcdsys/code-index) index. +## Requires a self-hosted cix server + +This plugin is a **client**. All indexing and search happen on a +[cix (CodeIndeX)](https://codeindex.app) server that **you deploy +yourself** — on your laptop, a LAN box, or a GPU server. Free, MIT, +~10 minutes with the interactive installer: + +```bash +curl -fsSL https://raw.githubusercontent.com/dvcdsys/code-index/main/install-server.sh | bash +``` + +Already have a server on your network? Point the CLI at it instead: +`cix config set api.url ` and `cix config set api.key ` — or +paste the ready-made connect command from the dashboard's API-keys +dialog. Without a reachable server, the slash commands and skills have +nothing to talk to. + ## What you get - **`/cix:search`, `/cix:def`, `/cix:refs`, `/cix:init`, `/cix:status`, diff --git a/plugins/cix/scripts/lib-cix-probe.sh b/plugins/cix/scripts/lib-cix-probe.sh index a21abece..8b72a3c0 100644 --- a/plugins/cix/scripts/lib-cix-probe.sh +++ b/plugins/cix/scripts/lib-cix-probe.sh @@ -15,6 +15,20 @@ # which silenced the nudge for the ENTIRE session even if the cix server # came up seconds later. "unknown" lets the next Grep re-probe and recover. +# cix_server_configured — exit 0 when this machine has any cix server +# configured (env override or a url in ~/.cix/config.yaml), nonzero +# otherwise. Used by session-start.sh to tell "no server at all" (worth a +# one-time setup hint) apart from "server exists, project not indexed" +# (stay silent). A heuristic on purpose: any `url:` key counts, whether a +# named server entry or a legacy api.url — false positives just suppress +# the hint, which is the safe direction. +cix_server_configured() { + [ -n "${CIX_API_URL:-}" ] && return 0 + local cfg="${HOME:-}/.cix/config.yaml" + [ -f "$cfg" ] && grep -qE 'url:[[:space:]]*[^[:space:]#]' "$cfg" && return 0 + return 1 +} + # cix_resolve_bin — echo a usable cix binary path, or empty string. # Prefers the plugin-bundled wrapper so behavior matches the slash commands. cix_resolve_bin() { diff --git a/plugins/cix/scripts/session-start.sh b/plugins/cix/scripts/session-start.sh index ff810e0e..866247be 100755 --- a/plugins/cix/scripts/session-start.sh +++ b/plugins/cix/scripts/session-start.sh @@ -92,12 +92,35 @@ find "$CACHE_DIR" -maxdepth 1 -type f \ \( -name 'cix-aware-*' -o -name 'cix-grep-count-*' \) \ -mtime +30 -delete 2>/dev/null || true +# ── Emit additionalContext JSON for SessionStart ────────────────────────────── +emit_additional_context() { + local msg="$1" + if command -v jq >/dev/null 2>&1; then + jq -n --arg msg "$msg" \ + '{hookSpecificOutput: {hookEventName: "SessionStart", additionalContext: $msg}}' + else + local esc + esc=$(printf '%s' "$msg" | sed 's/\\/\\\\/g; s/"/\\"/g' | tr '\n' ' ') + printf '{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"%s"}}\n' "$esc" + fi +} + +# One-line setup hint for the "plugin installed but no server anywhere" +# state. Shown only when NO cix server is configured on this machine — +# a configured-but-unreachable server or a merely-unindexed project stays +# silent as before. Without this, the plugin fails with cryptic errors on +# first use and the user never learns the missing piece is the server. +NO_SERVER_MESSAGE='cix plugin: no cix server is configured on this machine. The plugin is a client for a self-hosted cix (CodeIndeX) server — without one, /cix commands and skills have nothing to talk to. Setup takes ~10 minutes: https://codeindex.app — or connect to an existing server with `cix config set api.url ` + `cix config set api.key `. If the user asks about cix or tries /cix commands, surface this. Otherwise do not bring it up on your own.' + # ── Resolve a working `cix` binary ──────────────────────────────────────────── CIX_BIN="$(cix_resolve_bin)" if [ -z "$CIX_BIN" ]; then # CLI not yet installed (would auto-bootstrap on first call). Mark off. printf '0' > "$CACHE_FILE" + if ! cix_server_configured; then + emit_additional_context "$NO_SERVER_MESSAGE" + fi exit 0 fi @@ -112,8 +135,13 @@ if [ "$VERDICT" = "unknown" ]; then fi if [ "$VERDICT" != "1" ]; then - # Definitive "not indexed". Stay silent for the session in this project. + # Definitive "not indexed". Stay silent for the session in this project — + # unless there is no server configured at all, which deserves the + # one-line setup hint (the plugin is unusable in every project then). printf '0' > "$CACHE_FILE" + if ! cix_server_configured; then + emit_additional_context "$NO_SERVER_MESSAGE" + fi exit 0 fi @@ -122,12 +150,6 @@ printf '1' > "$CACHE_FILE" MESSAGE='💡 This project has a cix semantic code index. For semantic queries — finding code by meaning, cross-file lookups, symbol navigation, "where is X used", "how does Y work" — use the CLI: `cix search`, `cix def`, `cix refs` (via Bash). Activate the /cix SKILL for guidance. Use Grep only for exact strings (error messages, config keys, import paths). Run `cix status` if results seem stale.' -if command -v jq >/dev/null 2>&1; then - jq -n --arg msg "$MESSAGE" \ - '{hookSpecificOutput: {hookEventName: "SessionStart", additionalContext: $msg}}' -else - ESC=$(printf '%s' "$MESSAGE" | sed 's/\\/\\\\/g; s/"/\\"/g' | tr '\n' ' ') - printf '{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"%s"}}\n' "$ESC" -fi +emit_additional_context "$MESSAGE" exit 0 diff --git a/site/src/shared/versions.js b/site/src/shared/versions.js index 990c663c..7ab9db15 100644 --- a/site/src/shared/versions.js +++ b/site/src/shared/versions.js @@ -3,7 +3,7 @@ // purpose: the site must never claim a version that is not actually tagged. export const SERVER_VERSION = '0.12.6'; export const CLI_VERSION = '0.10.2'; -export const PLUGIN_VERSION = '0.3.0'; +export const PLUGIN_VERSION = '0.3.1'; export const COWORK_PLUGIN_VERSION = '0.1.0'; export const GITHUB_URL = 'https://github.com/dvcdsys/code-index'; From 29a3e83c42e5902b2f5934549a876f99dd007608 Mon Sep 17 00:00:00 2001 From: dvcdsys Date: Sat, 25 Jul 2026 09:34:56 +0100 Subject: [PATCH 2/3] test(plugin): cover the no-server setup hint; isolate HOME in hook tests run_hook now points HOME at a per-test scratch dir so hook behavior never depends on the developer's real ~/.cix/config.yaml (the previous suite passed locally but failed in CI's clean HOME). make_server_config opts a test into the "server configured" state. Split the two silent tests into configured/unconfigured variants and assert the new setup hint fires only when no server is configured anywhere. Co-Authored-By: Claude Fable 5 --- plugins/cix/tests/helpers.bash | 21 ++++++++- plugins/cix/tests/session-start.bats | 66 ++++++++++++++++++++++++++-- 2 files changed, 83 insertions(+), 4 deletions(-) diff --git a/plugins/cix/tests/helpers.bash b/plugins/cix/tests/helpers.bash index ccee749a..a7ee8b14 100644 --- a/plugins/cix/tests/helpers.bash +++ b/plugins/cix/tests/helpers.bash @@ -19,6 +19,7 @@ TEST_CACHE_DIR="" TEST_MOCK_BIN="" TEST_LOG_FILE="" TEST_PROJECT_DIR="" +TEST_HOME="" setup_test_env() { # Plugin root = repo's plugins/cix directory, so scripts find their @@ -32,6 +33,12 @@ setup_test_env() { # Per-test scratch project dir. TEST_PROJECT_DIR="$(mktemp -d "${BATS_TMPDIR}/cix-proj-XXXXXX")" + # Per-test scratch HOME so hook behavior never depends on the real + # user's ~/.cix/config.yaml (session-start.sh consults it via + # cix_server_configured). Tests that need "a server is configured" + # call make_server_config to opt in. + TEST_HOME="$(mktemp -d "${BATS_TMPDIR}/cix-home-XXXXXX")" + # Per-test cix invocation log. TEST_LOG_FILE="$(mktemp "${BATS_TMPDIR}/cix-log-XXXXXX")" @@ -49,8 +56,17 @@ teardown_test_env() { [ -n "$TEST_CACHE_DIR" ] && [ -d "$TEST_CACHE_DIR" ] && rm -rf "$TEST_CACHE_DIR" [ -n "$TEST_PROJECT_DIR" ] && [ -d "$TEST_PROJECT_DIR" ] && rm -rf "$TEST_PROJECT_DIR" [ -n "$TEST_LOG_FILE" ] && [ -f "$TEST_LOG_FILE" ] && rm -f "$TEST_LOG_FILE" + [ -n "$TEST_HOME" ] && [ -d "$TEST_HOME" ] && rm -rf "$TEST_HOME" unset MOCK_CIX_EXIT MOCK_CIX_DELAY MOCK_CIX_LOG_FILE MOCK_CIX_STDOUT_LINES - unset TEST_PLUGIN_ROOT TEST_CACHE_DIR TEST_MOCK_BIN TEST_LOG_FILE TEST_PROJECT_DIR + unset TEST_PLUGIN_ROOT TEST_CACHE_DIR TEST_MOCK_BIN TEST_LOG_FILE TEST_PROJECT_DIR TEST_HOME +} + +# Write a minimal ~/.cix/config.yaml with a server url into TEST_HOME. +# Tests call this to simulate "a cix server IS configured on this machine". +make_server_config() { + mkdir -p "$TEST_HOME/.cix" + printf 'servers:\n local:\n url: http://localhost:21847\n' \ + > "$TEST_HOME/.cix/config.yaml" } # Run a hook script with controlled env. Usage: @@ -85,8 +101,11 @@ run_hook() { # We deliberately do NOT pre-empt CLAUDE_PLUGIN_ROOT/bin/cix here — # the wrapper has its own resolution logic that prefers it; for hook # tests we want the mock to win. + # HOME points at the per-test scratch home (see setup_test_env) so the + # real user's ~/.cix/config.yaml never leaks into hook behavior. run env \ PATH="$TEST_MOCK_BIN:$PATH" \ + HOME="$TEST_HOME" \ CLAUDE_PLUGIN_DATA="$TEST_CACHE_DIR" \ CLAUDE_PROJECT_DIR="$project_dir" \ bash "$TEST_PLUGIN_ROOT/scripts/$script" <<<"$payload" diff --git a/plugins/cix/tests/session-start.bats b/plugins/cix/tests/session-start.bats index a576a4e7..51339a9b 100644 --- a/plugins/cix/tests/session-start.bats +++ b/plugins/cix/tests/session-start.bats @@ -19,7 +19,8 @@ teardown() { teardown_test_env; } [ "$(read_cache 'sess-A' "$TEST_PROJECT_DIR")" = "1" ] } -@test "non-indexed project: writes cache=0 and stays silent" { +@test "non-indexed project, server configured: writes cache=0 and stays silent" { + make_server_config export MOCK_CIX_EXIT=1 run_hook session-start.sh "sess-B" "$TEST_PROJECT_DIR" @@ -28,6 +29,32 @@ teardown() { teardown_test_env; } [ "$(read_cache 'sess-B' "$TEST_PROJECT_DIR")" = "0" ] } +@test "non-indexed project, NO server configured: writes cache=0 and emits setup hint" { + # TEST_HOME has no .cix/config.yaml and CIX_API_URL is unset. + export MOCK_CIX_EXIT=1 + run_hook session-start.sh "sess-B2" "$TEST_PROJECT_DIR" + + [ "$status" -eq 0 ] + [[ "$output" == *"hookSpecificOutput"* ]] + [[ "$output" == *"no cix server is configured"* ]] + [ "$(read_cache 'sess-B2' "$TEST_PROJECT_DIR")" = "0" ] +} + +@test "non-indexed project, CIX_API_URL set: writes cache=0 and stays silent" { + export MOCK_CIX_EXIT=1 + run env \ + PATH="$TEST_MOCK_BIN:$PATH" \ + HOME="$TEST_HOME" \ + CIX_API_URL="http://localhost:21847" \ + CLAUDE_PLUGIN_DATA="$TEST_CACHE_DIR" \ + CLAUDE_PROJECT_DIR="$TEST_PROJECT_DIR" \ + bash "$TEST_PLUGIN_ROOT/scripts/session-start.sh" <<<"{\"session_id\":\"sess-B3\"}" + + [ "$status" -eq 0 ] + [ -z "$output" ] + [ "$(read_cache 'sess-B3' "$TEST_PROJECT_DIR")" = "0" ] +} + @test "missing session_id: silent, no cache written" { run env \ PATH="$TEST_MOCK_BIN:$PATH" \ @@ -41,7 +68,7 @@ teardown() { teardown_test_env; } [ -z "$(ls "$TEST_CACHE_DIR"/cix-aware-* 2>/dev/null)" ] } -@test "cix CLI not on PATH: writes cache=0, silent" { +@test "cix CLI not on PATH, server configured: writes cache=0, silent" { # Use /tmp as cache dir so the guard accepts it without TMPDIR set. # Use a PATH that excludes both our mock and any system-installed cix. local tmp_cache="/tmp/cix-test-noCix-$$" @@ -57,9 +84,11 @@ teardown() { teardown_test_env; } fi fi + make_server_config + run env -i \ PATH="/usr/bin:/bin" \ - HOME="$HOME" \ + HOME="$TEST_HOME" \ CLAUDE_PLUGIN_DATA="$tmp_cache" \ CLAUDE_PROJECT_DIR="$TEST_PROJECT_DIR" \ bash "$TEST_PLUGIN_ROOT/scripts/session-start.sh" <<<"{\"session_id\":\"sess-noCix\"}" @@ -74,6 +103,37 @@ teardown() { teardown_test_env; } rm -rf "$tmp_cache" } +@test "cix CLI not on PATH, NO server configured: writes cache=0 and emits setup hint" { + local tmp_cache="/tmp/cix-test-noCixHint-$$" + mkdir -p "$tmp_cache" + + if command -v cix >/dev/null 2>&1; then + local sys_cix + sys_cix=$(command -v cix) + if [[ "$sys_cix" == /usr/bin/* || "$sys_cix" == /bin/* || "$sys_cix" == /sbin/* || "$sys_cix" == /usr/sbin/* ]]; then + rm -rf "$tmp_cache" + skip "system cix is in $sys_cix — can't reliably remove from PATH for this test" + fi + fi + + # TEST_HOME has no .cix/config.yaml; env -i drops CIX_API_URL. + run env -i \ + PATH="/usr/bin:/bin" \ + HOME="$TEST_HOME" \ + CLAUDE_PLUGIN_DATA="$tmp_cache" \ + CLAUDE_PROJECT_DIR="$TEST_PROJECT_DIR" \ + bash "$TEST_PLUGIN_ROOT/scripts/session-start.sh" <<<"{\"session_id\":\"sess-noCixHint\"}" + + [ "$status" -eq 0 ] + [[ "$output" == *"hookSpecificOutput"* ]] + [[ "$output" == *"no cix server is configured"* ]] + local hash + hash=$(printf '%s' "$TEST_PROJECT_DIR" | shasum -a 256 | cut -c1-8) + [ "$(cat "$tmp_cache/cix-aware-sess-noCixHint-$hash" 2>/dev/null)" = "0" ] + + rm -rf "$tmp_cache" +} + @test "cix status timeout: kills child after 2s, writes cache=unknown" { export MOCK_CIX_EXIT=0 export MOCK_CIX_DELAY=10 # mock would hang for 10s; script must kill at 2s From ff18ac5c3ca91db2712e5872ab20d259573c4d8d Mon Sep 17 00:00:00 2001 From: dvcdsys Date: Sat, 25 Jul 2026 10:52:43 +0100 Subject: [PATCH 3/3] =?UTF-8?q?feat(plugin):=20address=20review=20?= =?UTF-8?q?=E2=80=94=20rate-limit=20hint,=20shared=20emit,=20cover=20all?= =?UTF-8?q?=20surfaces?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review follow-ups on the server-requirement change: - Setup hint is now genuinely rate-limited: cix-setup-hint marker in $CACHE_DIR, at most once per 7 days per machine; marker prefix added to the 30-day GC find so it cannot accumulate. - cix_emit_context(event, msg) moved into lib-cix-probe.sh and used by all three emitters (session-start, grep-nudge, post-compact); the jq/sed pair now exists once. post-compact.sh did not source the lib before — source added. - cix_server_configured: CIX_API_KEY alone now counts as configured (key + implicit localhost default is a working setup). - cix-cowork: requirement sentence + self-hosted keyword in its own plugin.json (0.1.0 → 0.1.1), README states the server dependency up top, not only in Requirements. - Site: the plugin card (landing) and the docs plugin section now state the client/server split with a link to Quick start / Install. - Onboarding texts teach the modern config form (server.main.url/key — first server added becomes the default, cli config.go migrateToServers) instead of the legacy api.* aliases. - semver: new user-visible hook behavior ships as 0.4.0, not a patch. - Tests: rate-limit second-run silence, config.yaml-without-url fires the hint, CIX_API_KEY suppression, indexed-never-hints assertion, GC reaps an old cix-setup-hint marker. Co-Authored-By: Claude Fable 5 --- plugins/cix-cowork/.claude-plugin/plugin.json | 5 +- plugins/cix-cowork/README.md | 5 +- plugins/cix/.claude-plugin/plugin.json | 2 +- plugins/cix/README.md | 9 ++-- plugins/cix/scripts/grep-nudge.sh | 8 +-- plugins/cix/scripts/lib-cix-probe.sh | 27 ++++++++-- plugins/cix/scripts/post-compact.sh | 12 ++--- plugins/cix/scripts/session-start.sh | 51 +++++++++---------- plugins/cix/tests/session-start.bats | 43 +++++++++++++++- site/src/docs/docs.jsx | 2 +- site/src/landing/sections.jsx | 2 +- site/src/shared/versions.js | 4 +- 12 files changed, 113 insertions(+), 57 deletions(-) diff --git a/plugins/cix-cowork/.claude-plugin/plugin.json b/plugins/cix-cowork/.claude-plugin/plugin.json index 8b176f25..d564f9cb 100644 --- a/plugins/cix-cowork/.claude-plugin/plugin.json +++ b/plugins/cix-cowork/.claude-plugin/plugin.json @@ -1,8 +1,8 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "cix-cowork", - "version": "0.1.0", - "description": "cix skills for Claude Desktop & Cowork — the cix (single-repo) and cix-workspace (cross-project) guidance adapted to the cix_* MCP tools. Skills only: no MCP server, no CLI, no hooks. Register the cix MCP server first with `cix mcp install claude-desktop`, then install this for the richer guidance.", + "version": "0.1.1", + "description": "cix skills for Claude Desktop & Cowork — the cix (single-repo) and cix-workspace (cross-project) guidance adapted to the cix_* MCP tools. Requires a self-hosted cix (CodeIndeX) server (https://codeindex.app). Skills only: no MCP server, no CLI, no hooks. Register the cix MCP server first with `cix mcp install claude-desktop`, then install this for the richer guidance.", "author": { "name": "dvcdsys", "email": "dvcdsys@gmail.com" @@ -12,6 +12,7 @@ "license": "MIT", "keywords": [ "search", + "self-hosted", "code-search", "semantic", "navigation", diff --git a/plugins/cix-cowork/README.md b/plugins/cix-cowork/README.md index 29ef6c77..bf24d92f 100644 --- a/plugins/cix-cowork/README.md +++ b/plugins/cix-cowork/README.md @@ -8,7 +8,10 @@ This plugin is **skills only**: no MCP server, no CLI, no hooks. It teaches the agent *how* to use the cix tools well (cix-vs-grep judgment, choosing the cheapest tool, query writing, the cross-project workflow, and the workspace trust rules). The tools themselves come from the cix MCP server, which you -register separately. +register separately — and everything ultimately talks to a +**self-hosted [cix (CodeIndeX) server](https://codeindex.app)** that you +deploy yourself (free, MIT, ~10-minute install). No server → nothing to +search. ## Two steps diff --git a/plugins/cix/.claude-plugin/plugin.json b/plugins/cix/.claude-plugin/plugin.json index 4b29ccc7..c26dab22 100644 --- a/plugins/cix/.claude-plugin/plugin.json +++ b/plugins/cix/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "cix", - "version": "0.3.1", + "version": "0.4.0", "description": "Semantic code search for Claude Code. Requires a self-hosted cix (CodeIndeX) server — free, MIT, ~10-minute install (https://codeindex.app) on your machine, LAN box, or GPU server; the plugin is the client. Ships the cix CLI, slash commands (incl. cix file / cix tree for external projects), behavioral hooks, the cix skill, plus the experimental cix-workspace skill and cix-workspace-investigator sub-agent for cross-project research.", "author": { "name": "dvcdsys", diff --git a/plugins/cix/README.md b/plugins/cix/README.md index f7ab187e..983220b9 100644 --- a/plugins/cix/README.md +++ b/plugins/cix/README.md @@ -15,10 +15,11 @@ curl -fsSL https://raw.githubusercontent.com/dvcdsys/code-index/main/install-ser ``` Already have a server on your network? Point the CLI at it instead: -`cix config set api.url ` and `cix config set api.key ` — or -paste the ready-made connect command from the dashboard's API-keys -dialog. Without a reachable server, the slash commands and skills have -nothing to talk to. +`cix config set server.main.url ` and +`cix config set server.main.key ` (the first server added becomes +the default) — or paste the ready-made connect command from the +dashboard's API-keys dialog. Without a reachable server, the slash +commands and skills have nothing to talk to. ## What you get diff --git a/plugins/cix/scripts/grep-nudge.sh b/plugins/cix/scripts/grep-nudge.sh index a7a6a8ce..f5c644c7 100755 --- a/plugins/cix/scripts/grep-nudge.sh +++ b/plugins/cix/scripts/grep-nudge.sh @@ -162,12 +162,6 @@ fi # ── Emit nudge ──────────────────────────────────────────────────────────────── MESSAGE="💡 You just ran a file/text search in this project (call #$COUNT this session). This project has a cix semantic index — next time, for queries by meaning (find by concept, cross-file lookups, symbol navigation, locating files by symbol name), the CLI commands \`cix search\` / \`cix def\` / \`cix refs\` outperform grep/find. grep and find are best for exact strings or filename patterns (error messages, config keys, import paths, glob extensions). Recommended to activate /cix SKILL to use cix effectively" -if command -v jq >/dev/null 2>&1; then - jq -n --arg msg "$MESSAGE" \ - '{hookSpecificOutput: {hookEventName: "PostToolUse", additionalContext: $msg}}' -else - ESC=$(printf '%s' "$MESSAGE" | sed 's/\\/\\\\/g; s/"/\\"/g' | tr '\n' ' ') - printf '{"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":"%s"}}\n' "$ESC" -fi +cix_emit_context "PostToolUse" "$MESSAGE" exit 0 diff --git a/plugins/cix/scripts/lib-cix-probe.sh b/plugins/cix/scripts/lib-cix-probe.sh index 8b72a3c0..77e4b13c 100644 --- a/plugins/cix/scripts/lib-cix-probe.sh +++ b/plugins/cix/scripts/lib-cix-probe.sh @@ -18,17 +18,36 @@ # cix_server_configured — exit 0 when this machine has any cix server # configured (env override or a url in ~/.cix/config.yaml), nonzero # otherwise. Used by session-start.sh to tell "no server at all" (worth a -# one-time setup hint) apart from "server exists, project not indexed" -# (stay silent). A heuristic on purpose: any `url:` key counts, whether a -# named server entry or a legacy api.url — false positives just suppress -# the hint, which is the safe direction. +# setup hint) apart from "server exists, project not indexed" (stay +# silent). A heuristic on purpose: any `url:` key counts, whether a named +# server entry or a legacy api.url — false positives just suppress the +# hint, which is the safe direction. CIX_API_KEY alone also counts: the +# CLI falls back to the implicit localhost default server, so a key with +# no config file is a working setup. cix_server_configured() { [ -n "${CIX_API_URL:-}" ] && return 0 + [ -n "${CIX_API_KEY:-}" ] && return 0 local cfg="${HOME:-}/.cix/config.yaml" [ -f "$cfg" ] && grep -qE 'url:[[:space:]]*[^[:space:]#]' "$cfg" && return 0 return 1 } +# cix_emit_context +# Emit the hookSpecificOutput JSON that feeds additionalContext back to +# Claude. Single implementation for every hook (SessionStart, PostToolUse, +# PostCompact); prefers jq, falls back to a sed escape for jq-less hosts. +cix_emit_context() { + local event="$1" msg="$2" + if command -v jq >/dev/null 2>&1; then + jq -n --arg ev "$event" --arg msg "$msg" \ + '{hookSpecificOutput: {hookEventName: $ev, additionalContext: $msg}}' + else + local esc + esc=$(printf '%s' "$msg" | sed 's/\\/\\\\/g; s/"/\\"/g' | tr '\n' ' ') + printf '{"hookSpecificOutput":{"hookEventName":"%s","additionalContext":"%s"}}\n' "$event" "$esc" + fi +} + # cix_resolve_bin — echo a usable cix binary path, or empty string. # Prefers the plugin-bundled wrapper so behavior matches the slash commands. cix_resolve_bin() { diff --git a/plugins/cix/scripts/post-compact.sh b/plugins/cix/scripts/post-compact.sh index 553fa4b4..ebad8852 100755 --- a/plugins/cix/scripts/post-compact.sh +++ b/plugins/cix/scripts/post-compact.sh @@ -20,6 +20,10 @@ set -euo pipefail +# Shared helpers (cix_emit_context). +# shellcheck source=lib-cix-probe.sh +. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib-cix-probe.sh" + INPUT=$(cat 2>/dev/null || echo "{}") if command -v jq >/dev/null 2>&1; then SESSION_ID=$(printf '%s' "$INPUT" | jq -r '.session_id // empty' 2>/dev/null || echo "") @@ -51,12 +55,6 @@ fi # ── Re-inject the SessionStart reminder ─────────────────────────────────────── MESSAGE='💡 (Post-compact reminder) This project has a cix semantic code index. For semantic queries — finding code by meaning, cross-file lookups, symbol navigation, "where is X used", "how does Y work" — use the CLI: `cix search`, `cix def`, `cix refs` (via Bash). Activate the /cix SKILL for guidance. Use Grep only for exact strings (error messages, config keys, import paths).' -if command -v jq >/dev/null 2>&1; then - jq -n --arg msg "$MESSAGE" \ - '{hookSpecificOutput: {hookEventName: "PostCompact", additionalContext: $msg}}' -else - ESC=$(printf '%s' "$MESSAGE" | sed 's/\\/\\\\/g; s/"/\\"/g' | tr '\n' ' ') - printf '{"hookSpecificOutput":{"hookEventName":"PostCompact","additionalContext":"%s"}}\n' "$ESC" -fi +cix_emit_context "PostCompact" "$MESSAGE" exit 0 diff --git a/plugins/cix/scripts/session-start.sh b/plugins/cix/scripts/session-start.sh index 866247be..f6e5a83e 100755 --- a/plugins/cix/scripts/session-start.sh +++ b/plugins/cix/scripts/session-start.sh @@ -89,28 +89,31 @@ CACHE_FILE="$CACHE_DIR/cix-aware-$SESSION_ID-$DIR_HASH" # A file outside this prefix is invisible to find — it's never even # considered for deletion, regardless of how the cache dir is configured. find "$CACHE_DIR" -maxdepth 1 -type f \ - \( -name 'cix-aware-*' -o -name 'cix-grep-count-*' \) \ + \( -name 'cix-aware-*' -o -name 'cix-grep-count-*' -o -name 'cix-setup-hint' \) \ -mtime +30 -delete 2>/dev/null || true -# ── Emit additionalContext JSON for SessionStart ────────────────────────────── -emit_additional_context() { - local msg="$1" - if command -v jq >/dev/null 2>&1; then - jq -n --arg msg "$msg" \ - '{hookSpecificOutput: {hookEventName: "SessionStart", additionalContext: $msg}}' - else - local esc - esc=$(printf '%s' "$msg" | sed 's/\\/\\\\/g; s/"/\\"/g' | tr '\n' ' ') - printf '{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"%s"}}\n' "$esc" - fi -} - -# One-line setup hint for the "plugin installed but no server anywhere" -# state. Shown only when NO cix server is configured on this machine — -# a configured-but-unreachable server or a merely-unindexed project stays +# Setup hint for the "plugin installed but no server anywhere" state. +# Shown only when NO cix server is configured on this machine — a +# configured-but-unreachable server or a merely-unindexed project stays # silent as before. Without this, the plugin fails with cryptic errors on # first use and the user never learns the missing piece is the server. -NO_SERVER_MESSAGE='cix plugin: no cix server is configured on this machine. The plugin is a client for a self-hosted cix (CodeIndeX) server — without one, /cix commands and skills have nothing to talk to. Setup takes ~10 minutes: https://codeindex.app — or connect to an existing server with `cix config set api.url ` + `cix config set api.key `. If the user asks about cix or tries /cix commands, surface this. Otherwise do not bring it up on your own.' +# Rate-limited to once per 7 days per machine via the cix-setup-hint +# marker's mtime (see maybe_emit_no_server_hint), so it is a pointer, +# not a nag; the 30-day GC above may reap the marker, which merely lets +# the hint reappear — same order of magnitude, no harm. +NO_SERVER_MESSAGE='cix plugin: no cix server is configured on this machine. The plugin is a client for a self-hosted cix (CodeIndeX) server — without one, /cix commands and skills have nothing to talk to. Setup takes ~10 minutes: https://codeindex.app — or connect to an existing server with `cix config set server.main.url ` + `cix config set server.main.key `. If the user asks about cix or tries /cix commands, surface this. Otherwise do not bring it up on your own.' + +# maybe_emit_no_server_hint — emit NO_SERVER_MESSAGE when no server is +# configured anywhere AND the hint has not fired in the last 7 days. +maybe_emit_no_server_hint() { + cix_server_configured && return 0 + local marker="$CACHE_DIR/cix-setup-hint" + if [ -n "$(find "$CACHE_DIR" -maxdepth 1 -type f -name 'cix-setup-hint' -mtime -7 2>/dev/null)" ]; then + return 0 + fi + touch "$marker" 2>/dev/null || true + cix_emit_context "SessionStart" "$NO_SERVER_MESSAGE" +} # ── Resolve a working `cix` binary ──────────────────────────────────────────── CIX_BIN="$(cix_resolve_bin)" @@ -118,9 +121,7 @@ CIX_BIN="$(cix_resolve_bin)" if [ -z "$CIX_BIN" ]; then # CLI not yet installed (would auto-bootstrap on first call). Mark off. printf '0' > "$CACHE_FILE" - if ! cix_server_configured; then - emit_additional_context "$NO_SERVER_MESSAGE" - fi + maybe_emit_no_server_hint exit 0 fi @@ -137,11 +138,9 @@ fi if [ "$VERDICT" != "1" ]; then # Definitive "not indexed". Stay silent for the session in this project — # unless there is no server configured at all, which deserves the - # one-line setup hint (the plugin is unusable in every project then). + # rate-limited setup hint (the plugin is unusable in every project then). printf '0' > "$CACHE_FILE" - if ! cix_server_configured; then - emit_additional_context "$NO_SERVER_MESSAGE" - fi + maybe_emit_no_server_hint exit 0 fi @@ -150,6 +149,6 @@ printf '1' > "$CACHE_FILE" MESSAGE='💡 This project has a cix semantic code index. For semantic queries — finding code by meaning, cross-file lookups, symbol navigation, "where is X used", "how does Y work" — use the CLI: `cix search`, `cix def`, `cix refs` (via Bash). Activate the /cix SKILL for guidance. Use Grep only for exact strings (error messages, config keys, import paths). Run `cix status` if results seem stale.' -emit_additional_context "$MESSAGE" +cix_emit_context "SessionStart" "$MESSAGE" exit 0 diff --git a/plugins/cix/tests/session-start.bats b/plugins/cix/tests/session-start.bats index 51339a9b..1b1d1685 100644 --- a/plugins/cix/tests/session-start.bats +++ b/plugins/cix/tests/session-start.bats @@ -8,7 +8,7 @@ teardown() { teardown_test_env; } # ─── Functional ─────────────────────────────────────────────────────────────── -@test "indexed project: writes cache=1 and emits reminder" { +@test "indexed project: writes cache=1 and emits reminder (never the setup hint)" { export MOCK_CIX_EXIT=0 run_hook session-start.sh "sess-A" "$TEST_PROJECT_DIR" @@ -16,6 +16,7 @@ teardown() { teardown_test_env; } [[ "$output" == *"hookSpecificOutput"* ]] [[ "$output" == *"SessionStart"* ]] [[ "$output" == *"semantic code index"* ]] + [[ "$output" != *"no cix server is configured"* ]] [ "$(read_cache 'sess-A' "$TEST_PROJECT_DIR")" = "1" ] } @@ -40,6 +41,44 @@ teardown() { teardown_test_env; } [ "$(read_cache 'sess-B2' "$TEST_PROJECT_DIR")" = "0" ] } +@test "setup hint is rate-limited: second session within 7 days stays silent" { + export MOCK_CIX_EXIT=1 + run_hook session-start.sh "sess-RL1" "$TEST_PROJECT_DIR" + [[ "$output" == *"no cix server is configured"* ]] + + # Same cache dir → marker exists and is fresh → no second hint. + run_hook session-start.sh "sess-RL2" "$TEST_PROJECT_DIR" + [ "$status" -eq 0 ] + [ -z "$output" ] + [ -f "$TEST_CACHE_DIR/cix-setup-hint" ] + [ "$(read_cache 'sess-RL2' "$TEST_PROJECT_DIR")" = "0" ] +} + +@test "config.yaml without any url: counts as NOT configured — hint fires" { + mkdir -p "$TEST_HOME/.cix" + printf 'watcher:\n enabled: true\n' > "$TEST_HOME/.cix/config.yaml" + export MOCK_CIX_EXIT=1 + run_hook session-start.sh "sess-noUrl" "$TEST_PROJECT_DIR" + + [ "$status" -eq 0 ] + [[ "$output" == *"no cix server is configured"* ]] +} + +@test "non-indexed project, CIX_API_KEY set (implicit localhost default): stays silent" { + export MOCK_CIX_EXIT=1 + run env \ + PATH="$TEST_MOCK_BIN:$PATH" \ + HOME="$TEST_HOME" \ + CIX_API_KEY="cix_testkey" \ + CLAUDE_PLUGIN_DATA="$TEST_CACHE_DIR" \ + CLAUDE_PROJECT_DIR="$TEST_PROJECT_DIR" \ + bash "$TEST_PLUGIN_ROOT/scripts/session-start.sh" <<<"{\"session_id\":\"sess-B4\"}" + + [ "$status" -eq 0 ] + [ -z "$output" ] + [ "$(read_cache 'sess-B4' "$TEST_PROJECT_DIR")" = "0" ] +} + @test "non-indexed project, CIX_API_URL set: writes cache=0 and stays silent" { export MOCK_CIX_EXIT=1 run env \ @@ -177,6 +216,7 @@ teardown() { teardown_test_env; } touch -t "$old_ts" "$TEST_CACHE_DIR/cix-aware-old-aaaa1111" touch -t "$old_ts" "$TEST_CACHE_DIR/cix-grep-count-old-bbbb2222" + touch -t "$old_ts" "$TEST_CACHE_DIR/cix-setup-hint" touch "$TEST_CACHE_DIR/cix-aware-new-cccc3333" # recent, must be kept export MOCK_CIX_EXIT=0 @@ -184,6 +224,7 @@ teardown() { teardown_test_env; } [ ! -f "$TEST_CACHE_DIR/cix-aware-old-aaaa1111" ] [ ! -f "$TEST_CACHE_DIR/cix-grep-count-old-bbbb2222" ] + [ ! -f "$TEST_CACHE_DIR/cix-setup-hint" ] [ -f "$TEST_CACHE_DIR/cix-aware-new-cccc3333" ] } diff --git a/site/src/docs/docs.jsx b/site/src/docs/docs.jsx index 3e1e6535..9a92c3ec 100644 --- a/site/src/docs/docs.jsx +++ b/site/src/docs/docs.jsx @@ -423,7 +423,7 @@ cix mcp uninstall claude-desktop`}
-

The official Claude Code plugin (v{PLUGIN_VERSION}) ships from the repo's marketplace. It bundles the CLI, eight slash commands, two lazy-loading skills (cix + cix-workspace), a cix-workspace-investigator sub-agent for parallel cross-repo research, and five behavioral hooks. The skills were distilled from dozens of recorded agent sessions on large codebases, scored retrospectively for grep-vs-semantic effectiveness.

+

The official Claude Code plugin (v{PLUGIN_VERSION}) ships from the repo's marketplace. It is a client for your self-hosted cix server — install the server first (see Install); without a reachable server the commands and skills have nothing to talk to. It bundles the CLI, eight slash commands, two lazy-loading skills (cix + cix-workspace), a cix-workspace-investigator sub-agent for parallel cross-repo research, and five behavioral hooks. The skills were distilled from dozens of recorded agent sessions on large codebases, scored retrospectively for grep-vs-semantic effectiveness.

Install

{`# Run in a terminal — NOT inside a Claude Code session. claude plugin marketplace add dvcdsys/code-index diff --git a/site/src/landing/sections.jsx b/site/src/landing/sections.jsx index d5ae2c66..cc103731 100644 --- a/site/src/landing/sections.jsx +++ b/site/src/landing/sections.jsx @@ -159,7 +159,7 @@ export function Agent() {
Claude Code plugin · v{PLUGIN_VERSION}

Two commands. Hooks register themselves.

-

The plugin ships the CLI, eight slash commands, two lazy-loading skills, a workspace-research sub-agent, and five behavioral hooks. SessionStart checks if your project is indexed; a PostToolUse hook nudges Claude — with exponential backoff — to reach for cix instead of Grep.

+

The plugin is a thin client for your self-hosted cix server — deploy that first (see Quick start); without it the commands have nothing to talk to. The plugin ships the CLI, eight slash commands, two lazy-loading skills, a workspace-research sub-agent, and five behavioral hooks. SessionStart checks if your project is indexed; a PostToolUse hook nudges Claude — with exponential backoff — to reach for cix instead of Grep.

The skills aren't guessed prompts. They were distilled from dozens of recorded agent sessions on large codebases — VS Code among them — where grep-vs-semantic outcomes were scored retrospectively and the winning strategies became the skill text.

               $ claude plugin marketplace add dvcdsys/code-index{'\n'}
diff --git a/site/src/shared/versions.js b/site/src/shared/versions.js
index 7ab9db15..75e393d8 100644
--- a/site/src/shared/versions.js
+++ b/site/src/shared/versions.js
@@ -3,8 +3,8 @@
 // purpose: the site must never claim a version that is not actually tagged.
 export const SERVER_VERSION = '0.12.6';
 export const CLI_VERSION = '0.10.2';
-export const PLUGIN_VERSION = '0.3.1';
-export const COWORK_PLUGIN_VERSION = '0.1.0';
+export const PLUGIN_VERSION = '0.4.0';
+export const COWORK_PLUGIN_VERSION = '0.1.1';
 
 export const GITHUB_URL = 'https://github.com/dvcdsys/code-index';
 export const DOCKER_HUB_URL = 'https://hub.docker.com/r/dvcdsys/code-index';