Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
83 changes: 83 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ mcp = ["rmcp", "schemars", "async-trait"]
[dev-dependencies]
tempfile = "3"
tokio-test = "0.4"
assert_cmd = "2"
predicates = "3"

[profile.release]
lto = true
Expand Down
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,37 @@ ctx query stats
ctx query files
```

### `ctx sql`
Run read-only SQL against the code intelligence index through DuckDB, over a
stable `v1` view layer (`v1.symbols`, `v1.edges`, `v1.files`, `v1.meta`). Reach
for `ctx sql` instead of the canned `ctx query` subcommands whenever you need an
aggregation, a join, or a custom `WHERE` condition. Query `v1.*` only — anything
else (e.g. `code.*`) is internal and unstable. For agents and scripts, use
`--json` with `--max-rows`. Run `ctx sql --schema` for the full column reference.

```bash
# Ten most complex symbols
ctx sql "SELECT name, file, complexity FROM v1.symbols ORDER BY complexity DESC LIMIT 10;"

# Symbol counts by kind
ctx sql "SELECT kind, COUNT(*) AS n FROM v1.symbols GROUP BY kind ORDER BY n DESC;"

# Public functions that nothing calls (dead-code candidates)
ctx sql "SELECT name, file FROM v1.symbols WHERE kind IN ('function', 'method') AND is_public AND fan_in = 0 ORDER BY file, name;"
```

Use `--fail-on-rows` to turn a query into a gate: any returned row is a
violation, and the command exits `1`.

```bash
# Fails (exit 1) if the query returns any row
ctx sql --fail-on-rows --file .ctx/gates/no-utils-imports.sql
```

Access is read-only and engine-hardened (filesystem access, extension loading,
and file-based `ATTACH` are disabled; the index cannot be modified), so
`Bash(ctx sql *)` is safe to add to a Claude Code allow-list.

### `ctx explain <symbol>`
Get detailed information about a symbol including its relationships.

Expand Down Expand Up @@ -684,6 +715,7 @@ USAGE:
COMMANDS:
index Build or update the code intelligence index
query Query the code intelligence database
sql Run read-only SQL against the index (v1 schema)
search Search for symbols using keyword matching
semantic Search using embeddings (natural language)
embed Generate embeddings for semantic search
Expand Down
157 changes: 157 additions & 0 deletions docs/website/docs/commands/sql.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
---
id: sql
title: ctx sql
sidebar_position: 6
---

# ctx sql

Run read-only SQL against the code-intelligence index through DuckDB, over a stable `v1` view layer.

## Synopsis

```bash
ctx sql [QUERY] [OPTIONS]
```

## Description

`ctx sql` gives you raw SQL access to the code-intelligence index. Where the
canned `ctx query` subcommands answer fixed questions (callers, deps, impact),
`ctx sql` lets you ask arbitrary ones: aggregations, joins across symbols and
edges, and custom `WHERE` conditions. **Prefer `ctx sql` whenever you need a
grouping, a join, or a filter the canned commands do not expose.**

The query surface is the versioned **`v1`** schema — a set of stable views
(`v1.symbols`, `v1.edges`, `v1.files`, `v1.meta`). See the full
[SQL Schema (v1)](../sql-schema.md) reference for every column.

- **Query `v1.*` only.** It is the compatibility contract: columns and views may
be added within `schema_version` 1, but nothing is renamed or removed without
bumping `v1.meta.schema_version`.
- **Anything outside `v1.*` is internal and unstable.** The raw index is
reachable as `code.*`, but its shape can change at any time. Do not depend on it.

The query is read from the first of: the `[QUERY]` argument, `--file`, or stdin
(when `QUERY` is `-` or omitted and stdin is piped).

### Programmatic use

For agents and scripts, pass `--json` for machine-readable rows and
`--max-rows` to bound the result set:

```bash
ctx sql --json --max-rows 50 "SELECT name, complexity FROM v1.symbols ORDER BY complexity DESC"
```

An index must exist first (`ctx index`); querying without one exits with code 2.

## Options

| Option | Description | Default |
|--------|-------------|---------|
| `[QUERY]` | SQL text. If `-` or omitted while stdin is piped, read from stdin. | — |
| `--file <PATH>` | Read the query from a file (mutually exclusive with `QUERY`). | — |
| `--output <F>` | Output format: `table`, `csv`, or `json`. | `table` |
| `--json` | Alias for `--output json`. | — |
| `--max-rows <N>` | Cap returned rows (`0` = unlimited). | 1000 |
| `--timeout <SECS>` | Abort the query after N seconds. | 10 |
| `--fail-on-rows` | Exit `1` if the query returns `>= 1` row (for gate usage). | false |
| `--schema` | Print the `v1` schema reference and exit `0`. | — |

> **Note:** the flag is `--output`, not `--format` — `ctx` already has a global
> `-f/--format` for context output. `--json` is the convenient alias for
> `--output json`.

### Exit codes

| Code | Meaning |
|------|---------|
| `0` | Query ran successfully (with `--fail-on-rows`: zero rows returned). |
| `1` | `--fail-on-rows` was set and the query returned `>= 1` row. |
| `2` | Any error: SQL error, timeout, missing index, invalid flags, or a build without the `duckdb` feature. |

## Examples

### Ten most complex symbols

```bash
ctx sql "SELECT name, file, complexity
FROM v1.symbols
ORDER BY complexity DESC
LIMIT 10;"
```

### Symbol counts by kind

```bash
ctx sql "SELECT kind, COUNT(*) AS n
FROM v1.symbols
GROUP BY kind
ORDER BY n DESC;"
```

### Public functions that nothing calls (dead-code candidates)

```bash
ctx sql "SELECT name, file
FROM v1.symbols
WHERE kind IN ('function', 'method') AND is_public AND fan_in = 0
ORDER BY file, name;"
```

### Print the schema

```bash
ctx sql --schema
```

## Gates

`--fail-on-rows` turns a query into a CI or pre-commit gate: write SQL that
selects the *violations*, and any returned row fails the check. Keep gate
queries in files under `.ctx/gates/` and run them by path:

```bash
ctx sql --fail-on-rows --file .ctx/gates/no-utils-imports.sql
```

If the query returns any row, `ctx sql` exits `1` and the gate fails; zero rows
exits `0`. For example, a `.ctx/gates/no-utils-imports.sql` that flags forbidden
imports:

```sql
-- Fail if anything imports the legacy utils module
SELECT source_file, line
FROM v1.edges
WHERE kind = 'imports' AND target_name = 'utils';
```

Wire it into a pre-commit hook:

```bash
#!/usr/bin/env bash
# .git/hooks/pre-commit
ctx index >/dev/null
for gate in .ctx/gates/*.sql; do
ctx sql --fail-on-rows --file "$gate" || {
echo "Gate failed: $gate" >&2
exit 1
}
done
```

## Security

Access is **read-only and engine-hardened**. Filesystem access, extension
loading, and file-based `ATTACH` are disabled, and the index cannot be
modified — a query can only read the `v1` (and internal `code`) views. Because
`ctx sql` cannot write to disk, mutate the index, or reach the network, it is
safe to add `Bash(ctx sql *)` to a Claude Code (or other harness) plugin
allow-list.

## See Also

- [SQL Schema (v1)](../sql-schema.md) - Full column reference for the `v1` views
- [Code Intelligence](../code-intelligence.md) - Indexing and the canned `ctx query` commands
- [ctx audit](./audit.md) - Automated quality gates
Loading
Loading