Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e6835b1
chore(deps): bump docker/login-action from 4.3.0 to 4.4.0
dependabot[bot] Jul 6, 2026
7cc191c
chore(deps): bump softprops/action-gh-release from 3.0.1 to 3.0.2
dependabot[bot] Jul 13, 2026
9cf6641
chore(deps): bump the go group across 1 directory with 5 updates
dependabot[bot] Jul 13, 2026
963503f
chore(deps): bump the npm group in /server/dashboard with 12 updates
dependabot[bot] Jul 13, 2026
5a0a7b2
Merge remote-tracking branch 'origin/dependabot/github_actions/develo…
dvcdsys Jul 15, 2026
0c901f8
Merge remote-tracking branch 'origin/dependabot/github_actions/develo…
dvcdsys Jul 15, 2026
5cf4565
Merge remote-tracking branch 'origin/dependabot/go_modules/server/dev…
dvcdsys Jul 15, 2026
3b73182
Merge remote-tracking branch 'origin/dependabot/npm_and_yarn/server/d…
dvcdsys Jul 15, 2026
2329a1e
feat(site): CodeIndeX marketing site — Vite build for Cloudflare Pages
dvcdsys Jul 15, 2026
23d7d65
docs: brand pass — canonical name CodeIndeX, link codeindex.app
dvcdsys Jul 15, 2026
37e20bd
feat(site): workspace demo — agent chat + live command pane
dvcdsys Jul 17, 2026
dedae4a
feat(site): deepen workspace demo — multi-step agent investigation
dvcdsys Jul 17, 2026
922a9f2
feat(site): workspace demo — DB discovery, dead-end step, SQL in the …
dvcdsys Jul 17, 2026
f0853e5
feat(site): workspace demo polish — one-search context, code bodies, …
dvcdsys Jul 17, 2026
1d79629
docs: workspaces are no longer experimental
dvcdsys Jul 17, 2026
ae8c1ec
fix(server): bundle CPU llama.cpp sidecar so semantic search works ou…
dvcdsys Jul 17, 2026
61bcaeb
chore(release): drop stale "upgrading from the Python backend" line f…
dvcdsys Jul 17, 2026
bd1f9fe
feat(site): workspace demo controls, agent-first quickstart, docs rec…
dvcdsys Jul 19, 2026
305e957
ci(site): gate site versions against the NEWEST release tag, not mere…
dvcdsys Jul 19, 2026
3a09076
ci(release): warn when the site still advertises the previous release
dvcdsys Jul 19, 2026
7c495c6
Merge pull request #147 from dvcdsys/feat/site
dvcdsys Jul 19, 2026
50e4a29
fix(server): bump go directive to 1.25.12 for GO-2026-5856
dvcdsys Jul 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions .github/workflows/ci-site.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
name: CI Site

on:
pull_request:
paths:
- "site/**"
- ".github/workflows/ci-site.yml"
push:
branches: [main, develop]
paths:
- "site/**"
- ".github/workflows/ci-site.yml"

permissions:
contents: read

jobs:
build:
name: Build site
runs-on: ubuntu-latest
defaults:
run:
working-directory: site
steps:
- uses: actions/checkout@v7

- uses: actions/setup-node@v5
with:
node-version: 22
cache: npm
cache-dependency-path: site/package-lock.json

- name: Install dependencies
run: npm ci

- name: Build
run: npm run build

# The site must never regress to the pre-audit claims or dev-grade
# tooling. Fails the build if a forbidden string appears in the output.
# CSS is included on purpose — a CDN font/@import regression would land
# there, not in HTML/JS. \b guards keep Rollup's base64 chunk hashes
# from false-positiving.
- name: Check for forbidden claims
run: |
FORBIDDEN='v0\.5|200\+ languages|21 MB|fsnotify|unpkg\.com|\bbabel\b'
if grep -rniE "$FORBIDDEN" dist/ --include='*.html' --include='*.js' --include='*.css'; then
echo "::error::Forbidden string found in built site (stale claim or dev-grade dependency)"
exit 1
fi
echo "OK: no forbidden strings in dist/"

# The site must never advertise a stale OR fake version: the plugin
# version must match plugin.json in this repo, and the server/CLI
# versions must equal the NEWEST release tags — not merely exist.
# "Exists" was the original rule and it was too weak: after
# server/v0.12.3 shipped, a versions.js still saying 0.12.2 passed
# (0.12.2 is a real tag) and the deployed site kept advertising the
# old release. Comparing against the newest tag closes that drift.
- name: Check version consistency
run: |
# Anchor on the full `export const <NAME> =` declaration: an
# unanchored grep for PLUGIN_VERSION also matches the
# COWORK_PLUGIN_VERSION line, which made $PLUGIN two-valued and
# the comparison below fail unconditionally.
v() { grep -oE "^export const $1 = '[^']+'" src/shared/versions.js | cut -d"'" -f2; }
SERVER=$(v SERVER_VERSION); CLI=$(v CLI_VERSION); PLUGIN=$(v PLUGIN_VERSION)
PLUGIN_JSON=$(node -p "require('../plugins/cix/.claude-plugin/plugin.json').version")
TAGS=$(git ls-remote --tags origin)
# Newest semver tag per release stream. The trailing `$` anchor skips
# the `^{}` peeled refs that annotated tags add to ls-remote output.
newest() { echo "$TAGS" | grep -oE "refs/tags/$1/v[0-9]+\.[0-9]+\.[0-9]+$" | sed "s#refs/tags/$1/v##" | sort -V | tail -n1; }
NEWEST_SERVER=$(newest server); NEWEST_CLI=$(newest cli)
echo "site: server=$SERVER cli=$CLI plugin=$PLUGIN"
echo "repo: server=$NEWEST_SERVER cli=$NEWEST_CLI plugin.json=$PLUGIN_JSON"
FAIL=0
if [ "$PLUGIN" != "$PLUGIN_JSON" ]; then
echo "::error::versions.js PLUGIN_VERSION ($PLUGIN) != plugins/cix plugin.json ($PLUGIN_JSON)"; FAIL=1
fi
if [ "$SERVER" != "$NEWEST_SERVER" ]; then
echo "::error::versions.js SERVER_VERSION ($SERVER) != newest release server/v$NEWEST_SERVER — bump it so the site stops advertising a stale version"; FAIL=1
fi
if [ "$CLI" != "$NEWEST_CLI" ]; then
echo "::error::versions.js CLI_VERSION ($CLI) != newest release cli/v$NEWEST_CLI — bump it so the site stops advertising a stale version"; FAIL=1
fi
exit $FAIL
2 changes: 1 addition & 1 deletion .github/workflows/prerelease-cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ jobs:
--repo "${{ github.repository }}" || true

- name: Create develop release
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
with:
tag_name: cli/develop
target_commitish: develop
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/prerelease-server.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
run: server/scripts/check-llama-pin.sh server/Dockerfile.cuda

- name: Login to Docker Hub
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4.3.0
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ jobs:
run: echo "version=${GITHUB_REF_NAME#cli/}" >> "$GITHUB_OUTPUT"

- name: Create GitHub release
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
with:
name: "CLI ${{ steps.ver.outputs.version }}"
files: |
Expand Down
51 changes: 47 additions & 4 deletions .github/workflows/release-server.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ jobs:
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0

- name: Login to Docker Hub
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4.3.0
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
Expand Down Expand Up @@ -134,7 +134,7 @@ jobs:
run: server/scripts/check-llama-pin.sh server/Dockerfile.cuda

- name: Login to Docker Hub
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4.3.0
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
Expand Down Expand Up @@ -192,7 +192,7 @@ jobs:
run: echo "version=${GITHUB_REF_NAME#server/}" >> "$GITHUB_OUTPUT"

- name: Create GitHub release
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
with:
name: "Server ${{ steps.ver.outputs.version }}"
generate_release_notes: true
Expand All @@ -204,4 +204,47 @@ jobs:
| CPU (multi-arch) | `dvcdsys/code-index:${{ steps.ver.outputs.version }}` |
| CUDA 12.8 | `dvcdsys/code-index:${{ steps.ver.outputs.version }}-cu128` |

See [doc/MIGRATION_FROM_PYTHON.md](doc/MIGRATION_FROM_PYTHON.md) if upgrading from the Python backend.
site-version:
name: Check the site advertises this release
needs: [docker-cpu, docker-cuda]
runs-on: ubuntu-latest
# The marketing site (site/) is a surface nothing else in a release
# touches, so it silently keeps advertising the previous version —
# versions.js still said 0.12.2 the moment server/v0.12.3 shipped.
# ci-site.yml hard-fails on a stale versions.js, but it only triggers on
# site/** changes, so a server release never runs it. This job closes that
# gap at release time.
#
# Deliberately NON-BLOCKING: versions.js may only name a version that is
# already tagged, so the bump legitimately lands *after* this tag exists.
# The warning is the reminder to go do it, not a failure.
if: github.event_name == 'push'
steps:
- name: Checkout
uses: actions/checkout@v7

- name: Compare site versions.js with the released version
run: |
# The site lives on its own branch until it merges; skip until then.
if [ ! -f site/src/shared/versions.js ]; then
echo "no site/src/shared/versions.js at this ref — nothing to check"
exit 0
fi
RELEASED="${GITHUB_REF_NAME#server/}"; RELEASED="${RELEASED#v}"
SITE=$(grep -oE "^export const SERVER_VERSION = '[^']+'" site/src/shared/versions.js | cut -d"'" -f2)
echo "released=$RELEASED site=$SITE"
if [ "$SITE" = "$RELEASED" ]; then
echo "site already advertises $RELEASED — nothing to do"
echo "Site already advertises \`$RELEASED\`." >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
echo "::warning title=Site version is stale::site/src/shared/versions.js still says $SITE — bump SERVER_VERSION to $RELEASED so the published site stops advertising the previous release."
{
echo "### ⚠️ Site still advertises \`$SITE\`"
echo ""
echo "\`server/v$RELEASED\` just shipped. Bump \`SERVER_VERSION\` in"
echo "\`site/src/shared/versions.js\` to \`$RELEASED\`, or the deployed"
echo "site keeps showing the previous release."
echo ""
echo "\`ci-site.yml\` fails until this is done."
} >> "$GITHUB_STEP_SUMMARY"
13 changes: 9 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,13 @@ cli/dist/
# Server build artifacts + runtime logs
server/dist/
server/exec.log
# Root scratch dir only — anchored: a bare "tmp" would silently ignore any
# nested file/dir named tmp (same pitfall as the docs/ pattern below).
/tmp/

# Marketing site (site/) build artifacts
site/node_modules/
site/dist/
# Dashboard build output — produced by `make dashboard-build`.
# A committed `.gitkeep` keeps dist/ non-empty so `//go:embed all:dist` works
# on a fresh clone (the embed.FS needs at least one entry). The real
Expand All @@ -62,11 +68,10 @@ server/internal/httpapi/dashboard/dist/*
.python-version

# Local docs (top-level docs/ is a notebook directory per CLAUDE.md;
# tracked project documentation lives in doc/). Exception below for the
# tracked project documentation lives in doc/). Anchored to the repo root —
# a bare "docs/" would also swallow nested dirs like site/docs/ and the
# embedded Swagger UI bundle inside the Go server package.
docs/
!server/internal/httpapi/docs/
!server/internal/httpapi/docs/**
/docs/

# Claude Code
.claude/
Expand Down
3 changes: 2 additions & 1 deletion CITATION.cff
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
cff-version: 1.2.0
title: "cix — Semantic Code Index"
title: "cix — CodeIndeX (semantic code index)"
message: "If you use this software, please cite it as below."
type: software
authors:
- alias: dvcdsys
repository-code: "https://github.com/dvcdsys/code-index"
url: "https://codeindex.app"
license: MIT
keywords:
- code-search
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
██║ ██║ ╚███╔╝
██║ ██║ ██╔██╗
╚██████╗██║██╔╝ ██╗
╚═════╝╚═╝╚═╝ ╚═╝ Code IndeX
╚═════╝╚═╝╚═╝ ╚═╝ CodeIndeX
```

[![Release: Server](https://github.com/dvcdsys/code-index/actions/workflows/release-server.yml/badge.svg)](https://github.com/dvcdsys/code-index/actions/workflows/release-server.yml)
[![Release: CLI](https://github.com/dvcdsys/code-index/actions/workflows/release-cli.yml/badge.svg)](https://github.com/dvcdsys/code-index/actions/workflows/release-cli.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Docker Hub](https://img.shields.io/docker/pulls/dvcdsys/code-index)](https://hub.docker.com/r/dvcdsys/code-index)

Search your codebase by meaning, not just text. Self-hosted, embeddings-based, works with any agent or terminal — with a full web dashboard and multi-repo workspace search.
**cix — CodeIndeX.** Search your codebase by meaning, not just text. Self-hosted, embeddings-based, works with any agent or terminal — with a full web dashboard and multi-repo workspace search. Website: [codeindex.app](https://codeindex.app)

```bash
cix search "authentication middleware"
Expand Down
2 changes: 1 addition & 1 deletion cli/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# `cix` — Code IndeX CLI
# `cix` — CodeIndeX CLI

A thin Go client for the `cix-server` semantic code index. Runs `init`,
`search`, `symbols`, `def`, `refs`, `files`, `summary`, `watch`,
Expand Down
65 changes: 57 additions & 8 deletions server/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# syntax=docker/dockerfile:1.7
# Phase 1 CPU-only multi-stage Dockerfile.
# Embeddings (CUDA / llama-server sidecar) land in Phase 3.
# CPU multi-stage Dockerfile (multi-arch: linux/amd64 + linux/arm64).
#
# Bundles the CPU llama.cpp embeddings sidecar (llama-server + the ggml .so
# set) so semantic search works out of the box. It is the SAME "ollama"
# provider the CUDA image (Dockerfile.cuda) runs — the only difference
# between the two images is the NVIDIA/CUDA layer; the sidecar ships in both.
# Without it cix-server still boots and serves symbol/keyword search, but
# every semantic query fails ("llama-server not found at /app/llama-server").
#
# The build context is `server/` but the OpenAPI spec lives at
# `<repo-root>/doc/openapi.yaml`. We expose it via a named build context so
Expand Down Expand Up @@ -80,15 +86,58 @@ RUN CGO_ENABLED=0 GOOS=linux go build \
-o /out/cix-server \
./cmd/cix-server

# Pre-create an empty /data tree so the final stage can COPY it in with
# nonroot (uid 65532) ownership. Without this, a fresh Docker named volume
# initialises root-owned and the distroless nonroot uid in the runtime
# stage cannot `mkdir /data/sqlite` on first boot.
RUN mkdir -p /out/data
# Pre-create the /data tree so the final stage can COPY it in with nonroot
# (uid 65532) ownership. Without this, a fresh Docker named volume initialises
# root-owned and the distroless nonroot uid in the runtime stage cannot
# `mkdir /data/sqlite` on first boot. /data/models is pre-created too so the
# separate `cix-models:/data/models` named volume in docker-compose.yml
# inherits 65532 ownership (a fresh volume copies the image dir's uid/gid) —
# otherwise the GGUF cache dir mounts root-owned and the model download fails
# with "mkdir /data/models: permission denied".
RUN mkdir -p /out/data/models

# ── Stage: CPU llama.cpp embeddings sidecar (multi-arch source) ────────────
# Pinned by digest for reproducible builds. The floating :server tag drifts on
# every upstream release and the shared-library layout periodically changes
# (the server logic now lives in libllama-server-impl.so) — an unpinned tag
# means a rebuild silently picks up a new layout and can break the runtime
# copy below. This is the CPU-only MULTI-ARCH index (linux/amd64 + linux/arm64
# + others), so buildx resolves the matching arch automatically for each
# target platform. Bump this digest deliberately and re-run `make scout-cpu`.
# Resolve a new digest with:
# docker buildx imagetools inspect ghcr.io/ggml-org/llama.cpp:server
FROM ghcr.io/ggml-org/llama.cpp:server@sha256:6bc9134e3278a0ecab23d7ef2f6a46b4595740014fe9bc2f67e8ba7dca8395b4 AS llama-source

FROM gcr.io/distroless/static-debian12:nonroot
# ── Stage: distroless runtime ──────────────────────────────────────────────
# gcr.io/distroless/cc-debian13:nonroot (Debian 13 trixie) — NOT static-debian12.
# The bundled llama-server is dynamically linked and needs glibc + libstdc++ +
# libgomp + libssl/libcrypto, none of which exist in the static image. Debian
# 13 is required because llama-server needs GLIBC_2.38 + GLIBCXX_3.4.32 (gcc 13+);
# Debian 12 ships only glibc 2.36 / gcc 12. cc-debian13:nonroot provides every
# system lib llama-server links (verified) + ca-certificates for the first-boot
# HF model download, and keeps the same nonroot uid 65532 as the old image.
FROM gcr.io/distroless/cc-debian13:nonroot
WORKDIR /
COPY --from=builder /out/cix-server /cix-server

# ── Embeddings sidecar: CPU llama.cpp (the "ollama" provider) ──────────────
# Copy the whole /app/*.so* set rather than a fixed list: upstream loads the
# CPU backend variants (libggml-cpu-*.so) via dlopen at runtime and the exact
# file set shifts between releases; a fixed list silently drops a now-required
# .so → llama-server exit 127. The extra unused variants add only a few MB.
COPY --from=llama-source /app/llama-server /app/llama-server
COPY --from=llama-source /app/*.so* /app/
# Distroless has no ldconfig — resolve llama-server's libs via LD_LIBRARY_PATH.
ENV LD_LIBRARY_PATH=/app
ENV CIX_LLAMA_BIN_DIR=/app
# CPU-only: no GPU offload. (The Go default is already 0 on linux; pinned here
# so `docker run` without compose is unambiguous.)
ENV CIX_N_GPU_LAYERS=0
ENV CIX_LLAMA_TRANSPORT=unix
ENV CIX_LLAMA_STARTUP_TIMEOUT=120
# GGUF cache lives on the /data volume (see the cix-models named volume in
# docker-compose.yml); /data/models is pre-owned 65532 in the builder stage.
ENV CIX_GGUF_CACHE_DIR=/data/models
# Managed Tunnels provider binaries (the feature is configured & enabled
# from the dashboard; both are exec'd directly — no shell needed).
COPY --from=cloudflared /cloudflared /cloudflared
Expand Down
7 changes: 6 additions & 1 deletion server/Dockerfile.cuda
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,12 @@ RUN mkdir -p /opt/cuda-runtime/usr/local/cuda/lib64 \
# Ubuntu-based image's `cix:cix` user) so existing prod named volumes
# keep working without a chown migration. Distroless has no /etc/passwd
# entry for 1001, but the kernel cares only about the numeric UID.
RUN mkdir -p /opt/cix-data && chown 1001:1001 /opt/cix-data
# /data/models is pre-created too so the separate `cix-models:/data/models`
# named volume in docker-compose.cuda.yml inherits 1001 ownership (a fresh
# volume copies the image dir's uid/gid) instead of mounting root-owned and
# failing the GGUF download with "mkdir /data/models: permission denied".
# Inert for the prod portainer stack, which mounts a single /data volume.
RUN mkdir -p /opt/cix-data/models && chown -R 1001:1001 /opt/cix-data

# ── Stage 4: distroless runtime ────────────────────────────────────────────
# gcr.io/distroless/cc-debian13:nonroot (Debian 13 trixie):
Expand Down
Loading
Loading