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
13 changes: 7 additions & 6 deletions Cargo.lock

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

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ path = "src/lib.rs"

[dependencies]
clap = { version = "4", features = ["derive"] }
ignore = "0.4"
ignore = "0.4.33"
anyhow = "1"
chrono = "0.4"
tree-sitter = "0.25"
Expand All @@ -41,6 +41,7 @@ tree-sitter-c = "0.24"
tree-sitter-c-sharp = "0.23"
tree-sitter-zig = "1.1"
tree-sitter-odin = "1.3"
crossbeam-epoch = "0.9.20"

[profile.release]
opt-level = 2
116 changes: 18 additions & 98 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Supports: `C99`, `C++ (20 except modules)`, `C#`, `Rust`, `Go`, `Python`, `Zig`,
- [Quick-Start](#quick-start)
- [Usage](#usage)
- [Insights](#insights)
- [VS Code Extension](#extension)
- [Extension](#extension)
- [Dependency Map](#dependencymap)
- [Cross-Repository Calls](#externals)
- [Agents.md](#agentsmd)
Expand Down Expand Up @@ -84,18 +84,18 @@ Supports: `C99`, `C++ (20 except modules)`, `C#`, `Rust`, `Go`, `Python`, `Zig`,
## Usage

```sh
ccc scan [PATH] # regen PATH/.ccc (PATH defaults to ".")
ccc scan [PATH] --tokens # also pre-encode the cache into a token stream
ccc check [PATH] # exit non-zero if .ccc is stale - for CI
ccc check [PATH] --format json # same, but print changed cache files as JSON
ccc tokenize [PATH] # pre-encode an existing .ccc into tokens.bin + tokens.json
ccc changes [PATH] # what changed vs the base branch + which services to test (JSON)
ccc serve [PATH] # MCP server: agents query the in-memory map (REST + MCP)
ccc serve [PATH] --html # render the insights UI at /insights
ccc export [PATH] # publish what this project serves/calls, for other repos
ccc insights [PATH] # the insights analysis as JSON (call graph, triggers, lints)
ccc insights [PATH] --html F # as one self-contained page
ccc install [--dir DIR] # install the ccc binary onto your PATH (Linux)
ccc changes [PATH] --telemetry # changes vs the base branch: services to test, dependencies, otel
ccc check [PATH] --format json # exit non-zero if .ccc is stale - for CI
ccc tokenize [PATH] # pre-encode an existing .ccc into tokens.bin + tokens.json
ccc deps [PATH] # just the dependency delta of that report, for CI (JSON)
ccc prompts [PATH] # which claude/copilot request produced each change (JSON)
ccc serve [PATH] --html # MCP server and optional insights UI: agents query the in-memory map
ccc export [PATH] # publish what this project serves/calls, for other repos
ccc insights [PATH] --html <File> # the insights analysis as JSON (call graph, triggers, lints)
ccc sast [PATH] # security findings; defaults to non-zero on a high finding
ccc audit [PATH] # resolve lockfiles and check against the OSV advisory db
ccc install [--dir] <DIR> # install the ccc binary onto your PATH (Linux)
ccc scan [PATH] --tokens # regen PATH/.ccc (PATH defaults to ".") (opt: output token stream)
```

## Insights
Expand All @@ -104,11 +104,10 @@ The command `(ccc serve --html)` starts the MCP server with the insights UI on `
`/insights.json` from the running server, so it tracks the in-memory ccc map at runtime.

```sh
ccc serve --html # then open http://127.0.0.1:6767/insights
curl -s localhost:6767/insights.json # the same data, for scripting

ccc insights # the same analysis as JSON, no server
ccc insights --html page.html # ...as one self-contained page, for static hosting
ccc serve --html # then open http://127.0.0.1:6767/insights
curl -s localhost:6767/insights.json # the same data, for scripting
ccc insights # the same analysis as JSON, no server
ccc insights --html page.html # insights as a single page, for static hosting
```

## Extension
Expand All @@ -117,86 +116,7 @@ ccc insights --html page.html # ...as one self-contained page, for static host
`ccc serve` in the background for each workspace folder and reads it over loopback HTTP, so nothing
leaves the machine and no configuration is needed to get started.

### Install

`cargo build` packages the extension alongside the binary:

```sh
cargo build --release # -> dist/ccc-codecache.vsix
code --install-extension dist/ccc-codecache.vsix
```

The packaging step is best-effort: it is skipped without `npm`, under `CI`, or with `CCC_SKIP_VSIX`
set, and never fails the Rust build. To build the extension on its own, `cd extensions/vscode` and
run `npm run package` (or `npm run watch` and press F5 for an Extension Development Host).

### Using it

Open a file with work in progress. Each changed function carries a CodeLens above it, and every lens
is clickable:

| lens | what it means |
|---|---|
| `3 tests` | Tests cover this change - opens them, nearest call hop first. |
| `no smoke test` | Nothing covers this change, and a smoke test is the kind worth writing. |
| `calls billing` | This call crosses a service boundary - opens the handler, in a peer checkout if that is where it lives. |
| `called by gateway` | Another service calls this function - opens the callers. |
| `37 callers`, `cycle of 3` | A hot path, or a call cycle. From the call graph, so these show on files nobody has touched. |
| `billing.v1.Charge unanswered` | A `ccc:calls` whose key nothing serves - a typo at one end, or a peer missing from `externals`. |

Every function the analyser parsed also carries its complexity as a filled circled number between
its name and its signature - `fn parse ❸ (s: &str)`. It is a cyclomatic-style count (one path, plus
one per decision point and loop) banded onto 1-10, and the colour runs grey, plain, green, blue,
purple, yellow, brown, amber, orange, red as it climbs. Unlike the hints above it is not diff-driven:
it describes the code as written, so it shows on files nobody has touched. Hover it for the raw
count, the branches and the loop depth behind the band. The `⚠` (no test covers this) and `🔥`
(hot path) verdicts sit inline in the same spot, right after the band, rather than out in the
gutter; the other hint kinds keep their gutter icons. `ccc.complexity.minScore` raises the floor if
you only want to see the functions worth a second look, and `ccc.complexity.enabled` turns it off;
the ten colours are contributed theme colours, so a theme or a `workbench.colorCustomizations` entry
can restyle any of them.

Click the **CodeCaChe** mark in the activity bar to open the panel, and again to close it. It has
two views. **Triggers** is the tests your changes invoke: a triggered test usually lives in a
different file from the change that triggered it, so this is the only place that shows the whole set
- **Run these** (click to open; the tooltip carries how many call hops it sits from the change and
why), **No test covers**, and **Commands** - the suggested command for running exactly that set,
click to run it in a terminal. The badge is the number of tests worth running before you push.

**Complexity** is every measured function grouped by band, worst first, with the count per band on
the group row. The title-bar buttons filter it: by name (substring), by parameter count (niladic,
monadic, dyadic, variadic - pick several), and by band (a 1-10 range). Test functions are measured
but hidden by default; the beaker button shows them, and the clear button appears whenever any
filter is active. The view's subtitle always says how many of the measured functions you are
looking at, so a filtered list can never pass itself off as the whole map.

The status bar entry on the right is the summary - counts, the base ref being compared, and, when a
file has no marks at all, which of the two reasons applies: nothing in it changed, or it is not in
the ccc map.

Everything runs against the **working tree**, so untracked and uncommitted files count. Hints reflect
the last *saved* state, since the analyser reads files rather than editor buffers; they fade while a
file is dirty and refresh on save.

Coverage and boundary hints are diff-driven, so a file identical to the base ref has no changed
functions and therefore no hints - that is the design, not a fault. Hot paths come from the call
graph alone and appear regardless.

### Worth knowing

- Cross-service hints need a `services` block in [`.ccc/map.json`](#dependencymap); cross-repository
hints need [`externals` and `ccc:` comments](#externals). With no map, ccc groups by directory and
the hints still mean something; where even that degenerates to one unit per file, it stays quiet
rather than calling every import a service call.
- Coverage is matched through the static call graph by name - not by running anything. A same-named
function elsewhere can produce a false positive, and a test that reaches code only through a
framework is invisible.
- Useful settings: `ccc.baseRef` (what to diff against), `ccc.binaryPath`, `ccc.hints.crossServiceMode`,
and `ccc.hints.codeLens` - set that to `false` with `ccc.decorations.style` as `badge+gutter` for
end-of-line badges instead of lenses. Commands are under **ccc:** in the palette.

Full details, every setting, and the troubleshooting list are in
[`extensions/vscode/README.md`](extensions/vscode/README.md).
See ['EXTENSION.md'](docs/EXTENSION.md) for more information.

## DependencyMap

Expand Down
86 changes: 86 additions & 0 deletions docs/EXTENSION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Extension

[`extensions/vscode`](extensions/vscode) is an editor client for the same analysis. It runs
`ccc serve` in the background for each workspace folder and reads it over loopback HTTP, so nothing
leaves the machine and no configuration is needed to get started.

## Install

`cargo build` packages the extension alongside the binary:

```sh
cargo build --release # -> dist/ccc-codecache.vsix
code --install-extension dist/ccc-codecache.vsix
```

The packaging step is best-effort: it is skipped without `npm`, under `CI`, or with `CCC_SKIP_VSIX`
set, and never fails the Rust build. To build the extension on its own, `cd extensions/vscode` and
run `npm run package` (or `npm run watch` and press F5 for an Extension Development Host).

## Using the extension

Open a file with work in progress. Each changed function carries a CodeLens above it, and every lens
is clickable:

| lens | what it means |
|---|---|
| `3 tests` | Tests cover this change - opens them, nearest call hop first. |
| `no smoke test` | Nothing covers this change, and a smoke test is the kind worth writing. |
| `calls billing` | This call crosses a service boundary - opens the handler, in a peer checkout if that is where it lives. |
| `called by gateway` | Another service calls this function - opens the callers. |
| `37 callers`, `cycle of 3` | A hot path, or a call cycle. From the call graph, so these show on files nobody has touched. |
| `billing.v1.Charge unanswered` | A `ccc:calls` whose key nothing serves - a typo at one end, or a peer missing from `externals`. |

Every function the analyser parsed also carries its complexity as a filled circled number between
its name and its signature - `fn parse ❸ (s: &str)`. It is a cyclomatic-style count (one path, plus
one per decision point and loop) banded onto 1-10, and the colour runs grey, plain, green, blue,
purple, yellow, brown, amber, orange, red as it climbs. Unlike the hints above it is not diff-driven:
it describes the code as written, so it shows on files nobody has touched. Hover it for the raw
count, the branches and the loop depth behind the band. The `⚠` (no test covers this) and `🔥`
(hot path) verdicts sit inline in the same spot, right after the band, rather than out in the
gutter; the other hint kinds keep their gutter icons. `ccc.complexity.minScore` raises the floor if
you only want to see the functions worth a second look, and `ccc.complexity.enabled` turns it off;
the ten colours are contributed theme colours, so a theme or a `workbench.colorCustomizations` entry
can restyle any of them.

Click the **CodeCaChe** mark in the activity bar to open the panel, and again to close it. It has
two views. **Triggers** is the tests your changes invoke: a triggered test usually lives in a
different file from the change that triggered it, so this is the only place that shows the whole set
- **Run these** (click to open; the tooltip carries how many call hops it sits from the change and
why), **No test covers**, and **Commands** - the suggested command for running exactly that set,
click to run it in a terminal. The badge is the number of tests worth running before you push.

**Complexity** is every measured function grouped by band, worst first, with the count per band on
the group row. The title-bar buttons filter it: by name (substring), by parameter count (niladic,
monadic, dyadic, variadic - pick several), and by band (a 1-10 range). Test functions are measured
but hidden by default; the beaker button shows them, and the clear button appears whenever any
filter is active. The view's subtitle always says how many of the measured functions you are
looking at, so a filtered list can never pass itself off as the whole map.

The status bar entry on the right is the summary - counts, the base ref being compared, and, when a
file has no marks at all, which of the two reasons applies: nothing in it changed, or it is not in
the ccc map.

Everything runs against the **working tree**, so untracked and uncommitted files count. Hints reflect
the last *saved* state, since the analyser reads files rather than editor buffers; they fade while a
file is dirty and refresh on save.

Coverage and boundary hints are diff-driven, so a file identical to the base ref has no changed
functions and therefore no hints - that is the design, not a fault. Hot paths come from the call
graph alone and appear regardless.

## Worth knowing

- Cross-service hints need a `services` block in [`.ccc/map.json`](#dependencymap); cross-repository
hints need [`externals` and `ccc:` comments](#externals). With no map, ccc groups by directory and
the hints still mean something; where even that degenerates to one unit per file, it stays quiet
rather than calling every import a service call.
- Coverage is matched through the static call graph by name - not by running anything. A same-named
function elsewhere can produce a false positive, and a test that reaches code only through a
framework is invisible.
- Useful settings: `ccc.baseRef` (what to diff against), `ccc.binaryPath`, `ccc.hints.crossServiceMode`,
and `ccc.hints.codeLens` - set that to `false` with `ccc.decorations.style` as `badge+gutter` for
end-of-line badges instead of lenses. Commands are under **ccc:** in the palette.

Full details, every setting, and the troubleshooting list are in
[`extensions/vscode/README.md`](extensions/vscode/README.md).
19 changes: 13 additions & 6 deletions extensions/vscode/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,13 @@ The panel badge is the number of tests worth running before you push.

## Requirements

**The `ccc` binary.** Install it with `./install.sh` from the repo root, or `cargo build --release`
and point `ccc.binaryPath` at `target/release/ccc`. The extension searches, in order:
`ccc.binaryPath`, your `PATH`, `<folder>/target/release/ccc`, `<folder>/target/debug/ccc`.
**The `ccc` binary.** The extension installs it for you. When it activates it looks for a working
`ccc` — `ccc.binaryPath`, your `PATH`, `<folder>/target/release/ccc`, `<folder>/target/debug/ccc`,
then a copy it installed earlier — and, finding none, downloads the release matching your platform
into its own storage before the first analyser starts. Nothing on your `PATH` is modified, and
`ccc.autoInstall: false` turns the download off. To manage the binary yourself, install it with
`./install.sh` from the repo root, or `cargo build --release` and point `ccc.binaryPath` at
`target/release/ccc`; a binary you provide is always preferred to the one the extension installs.

**A git repo with a resolvable base ref**, for the coverage hints. The analyser diffs against the
first of `origin/main`, `main`, `origin/master`, `master` that exists. On a shallow clone or a repo
Expand Down Expand Up @@ -211,8 +215,10 @@ carries the analyser's own explanation.
**Everything looks like a cross-service call.** Your project has no `.ccc/map.json`, so boundaries
were inferred from directories. Add a `services` block.

**"could not find the ccc binary".** Build it (`cargo build --release`) or set `ccc.binaryPath`. The
message lists every path that was searched.
**"could not find the ccc binary".** The download failed, or `ccc.autoInstall` is off and nothing is
installed. The message lists every path that was searched; **Retry Install** tries the download
again, and `ccc: Show Log` says why the last one failed. Failing that, build it
(`cargo build --release`) or set `ccc.binaryPath`.

**The analyser keeps restarting.** It is restarted with backoff and gives up after five failures in
five minutes. `ccc: Show Log` has the last 20 lines of its stderr for each crash.
Expand All @@ -223,7 +229,8 @@ same data the hints are built from.
## Performance

One analyser process per workspace folder per window, started lazily the first time you open a file
in that folder. It runs with `--no-watch` by default and rescans on save and on window focus, so it
in that folder. Activation itself only probes for the binary — a single `ccc --version` — and costs
a download once, when there is no ccc to find. It runs with `--no-watch` by default and rescans on save and on window focus, so it
is idle between edits. Two windows on the same folder run two analysers, each on its own free port;
a `ccc serve` you started yourself is neither used nor disturbed.

Expand Down
Loading
Loading