Skip to content

code-cartography/scalps

Repository files navigation

scalps

scalps is a local C++ definition search tool. It incrementally indexes functions and methods with Clang, then ranks whole definitions for short queries such as set line wrap mode or show an autocomplete list.

Why use it?

Normally, coding agents start a session by groping their way around the repository with grep, and then use sed to look at best-guess slices of code. Semantic search tools like grepai are supposed to help with that, but in practice they produce inconsistent results that might end up just adding more noise.

scalps searches definitions rather than arbitrary text windows. It uses C++ identifier structure, operation words, signatures, documentation, resolved relationships, bodies, and paths to distinguish nearby candidates.

scalps search returns a ranked list of functions and their exact line span. From there you can use scalps context to see the whole function and comment, and optionally the functions on either side for extra context.

scalps runs locally and does not require a model or remote service. It currently supports C++ function and method definitions. Indexing plans no-op, selective, or clean full updates and publishes only a complete validated generation when an update is needed.

Development story

scalps was designed and built with Codex GPT‑5.6 (“Sol”) in approximately three days. It is a local C++ definition-search tool for a specific but costly developer and coding-agent failure: finding the right function in a large codebase when the user knows what it does, but not its exact name.

The work began after local search tools such as grepai and cocoindex-code failed to locate relevant C++ functions, including after attempts to improve source discoverability through refactoring. Rather than adding another general-purpose search layer, the question posed to Sol was:

If we designed specifically for this case, with C++ as the only concern, should we modify an existing tool or build something new? What retrieval approach should we use?

Sol proposed a new, definition-first architecture: use Clang to identify written C++ function and method definitions; store their names, signatures, comments, bodies, source ranges, and compiler-resolved relationships; retrieve candidates with literal, identifier, and field-aware lexical search; then rank them with deterministic structural evidence. The result deliberately searches complete definitions rather than arbitrary source chunks, and returns exact source locations and context.

Before implementation, Sol was asked to conduct Deep Research on comparable projects. Its conclusion was that the proposed architecture remained “unusually specific in the current ecosystem,” in an area where permissively licensed tools appeared particularly sparse.

Sol then created a staged roadmap. Development proceeded through roughly 54 Zed sessions: roadmap phases were divided into focused sessions, and each session into a sequence of reviewable commits. Codex wrote the implementation at medium reasoning effort, including the Clang extraction pipeline, versioned SQLite index, incremental and atomic index updates, explainable ranking, definition context, JSON output, packaging, tests, and documentation.

Each commit was independently reviewed with Grok 4.5. Review findings were returned to Sol for correction, then verified through the project test suite.

The human directed the product and made key decisions throughout. In particular, they proposed the definition-context feature and required both concise default output and detailed verbose output. The human also set the constraint that the tool should remain local, deterministic, explainable, and narrowly focused on C++ definitions rather than becoming another broad natural-language code search system.

Requirements

Releases are built and tested on openSUSE Leap 16 x86-64 with official distribution packages. Other compatible configurations may build and run scalps, but they are not regularly tested or maintained by the project.

  • A C++ project with a usable compile_commands.json compilation database.
  • CMake 3.20 or newer.
  • A compiler with C++17 support.
  • LLVM and Clang 19.1.x development files, including Clang's builtin headers.
  • JsonCpp 1.9.6 development files (jsoncpp-devel on openSUSE; the installed executable uses the matching runtime library).
  • SQLite 3.35 or newer with FTS5 support and development files.

These are the current build requirements. See the supported-environment policy for the tested configuration and maintenance policy.

Build

cmake -S . -B build
cmake --build build

Run the test suite with:

ctest --test-dir build --output-on-failure

Install

The supported installation choices are a user-local CMake install and an openSUSE RPM. A direct system-wide CMake install is not supported because it would not be owned by Zypper.

Install for the current user with:

cmake --install build --prefix "$HOME/.local"

This installs scalps in $HOME/.local/bin. See Installation for prerequisites, PATH setup, removal, and building an RPM that Zypper can install and remove.

Quick start

Run these commands from the C++ project you want to search:

scalps index
scalps status
scalps search "set line wrap mode"
scalps context "Scintilla::Internal::Editor::SetWrapMode"

By default, indexing reads build/compile_commands.json, treats the current directory as the project root, and writes .scalps/index.sqlite. scalps status checks whether the compilation commands or files used by that index have changed. Search reads the index and displays at most 10 definitions.

To index a different checkout or use explicit paths:

scalps index --compile-commands ../scalpel-editor/build --project-root ../scalpel-editor --index /tmp/scalpel-editor.sqlite
scalps search --index /tmp/scalpel-editor.sqlite --limit 5 "set line wrap mode"

--compile-commands accepts either a compile_commands.json file or the directory containing it.

scalps index plans the update automatically. It reports Index unchanged when every input is current, otherwise it reuses unaffected translation units and extracts only added, changed, or otherwise affected units. The summary reports reused, re-extracted, added, and removed translation units, followed by extraction work and the complete generation totals.

scalps index --full is the stable comparison and recovery switch. It requests a clean rebuild and ignores reusable translation-unit artifacts. A normal run also falls back to a clean rebuild when reuse cannot be proved safe, including a missing prior index, incompatible storage or artifact data, missing or incompatible manifest data, incomplete current commands, unreadable dependencies, and precompiled-header or module command modes whose complete inputs cannot yet be tracked.

Reading a result

1. Scintilla::Internal::Editor::SetWrapMode  method  scintilla/src/EditorWrapping.cxx:48-55

The line contains the rank, qualified name, definition kind, project-relative file, and inclusive first and last source lines. A definition written on one line displays only that line.

Pass -v or --verbose to include the total score and every nonzero contribution:

1. Scintilla::Internal::Editor::SetWrapMode  method  scintilla/src/EditorWrapping.cxx:48-55
   score 27.23: identifier coverage 15.00, ordered identifier coverage 1.50, operation agreement 9.00, documentation 0.77, signature 0.58, referenced types 0.38

The explanation makes it possible to see whether a result won through its name, operation, signature, documentation, relationships, body, or path.

Reading definition context

Use the qualified name from a search result to print its exact written definition and attached documentation comment:

scalps context "Scintilla::Internal::Editor::SetWrapMode"

Add whole neighboring definitions from the same file when they are useful:

scalps context --before 1 --after 1 "Scintilla::Internal::Editor::SetWrapMode"

Overloaded names produce an ambiguity diagnostic instead of an arbitrary choice. Use --file and --line to select one location. Context checks the current source against the indexed content hash before printing, so changed source must be reindexed. See Definition-aware source context for the full command, output, and failure contract.

Machine-readable search output

Pass --format json to produce the stable version 1 search document for editors and automation:

scalps search --format json --limit 1 "set line wrap mode"
{"query":"set line wrap mode","results":[{"kind":"method","qualified_name":"Scintilla::Internal::Editor::SetWrapMode","range":{"begin":{"column":1,"line":48},"end":{"column":2,"line":55},"file":"scintilla/src/EditorWrapping.cxx"},"rank":1}],"schema_version":1}

A successful JSON search writes one complete document followed by one newline to standard output, including an empty results array when nothing matches. Diagnostics use standard error, and a failed search leaves standard output empty. Search returns 0 for success and 2 for argument, index, storage, or search failures, matching human search mode.

--verbose adds opaque result IDs, exact byte offsets, complete score contributions, and the identity of the index used for the search. Human output remains the default. --format applies only to search; index and status remain human-readable.

See Machine-readable search output for the complete required fields, position rules, examples, fixture, and compatibility policy.

Writing useful queries

  • Use the operation you want: set, get, show, hide, add, delete, or is can strongly distinguish sibling definitions.
  • Add the object or concern: set line wrap mode is more specific than set mode.
  • Exact and identifier-shaped names work too: SetWrapMode() is analyzed with the same identifier rules used during indexing.
  • Query words are lowercased, CamelCase and snake_case are split, and only a deliberately small vocabulary is normalized. Current examples include wrap with wrapping, find with search, and autocomplete with the AutoC identifier abbreviation.
  • Do not assume general synonym or natural-language understanding. If a descriptive query is weak, try a likely identifier word or rerun the search with --verbose to inspect the score explanation.

How it works

At a high level, scalps turns each written C++ function or method definition into one searchable record. Clang supplies definition boundaries and resolved C++ relationships, SQLite FTS5 supplies field-aware lexical candidates, and a deterministic ranker combines structural name evidence with lexical evidence.

Indexing definitions

The indexer reads the project's real compile commands and runs Clang with those options. It records definitions written inside the project root, including their Clang identity, names, identifier tokens, signature, leading documentation, body, exact source range, direct calls, referenced fields, and referenced types.

Repeated appearances of the same header definition are merged. A stable definition identity is derived from the Clang identity, project-relative path, and written begin offset; it does not depend on an internal SQLite row number. A completed index replaces the prior index atomically; an incomplete extraction, parse failure, merge conflict, or handled update failure leaves the previous usable index untouched. Temporary databases are created beside the destination for atomic installation and are removed after handled failures. Fully macro-generated functions are intentionally excluded because they do not have one contiguous written C++ definition range.

Each completed index also records a content hash for every translation-unit source, included header, system header, and response file observed while Clang processes it. scalps status compares those records with the current compilation database and files, reporting the affected compile commands when the index is stale.

See Indexing and index lifecycle for project boundaries, exclusions, diagnostics, exit statuses, and index compatibility.

Finding candidates

Candidate generation takes the deterministic union of exact qualified or unqualified name matches, identifier-token matches, and field-aware FTS5 matches. It fetches more candidates than the requested display count so closely related definitions can be compared by the final ranker.

Ranking candidates

The strongest structural evidence comes from the unqualified symbol name. The ranker rewards query-token coverage and order, penalizes extra symbol-name tokens, treats namespace and class matches as weaker evidence, rewards operation agreement, and applies a decisive penalty to explicit operation conflicts such as query set versus symbol GetWrapMode.

Lexical evidence is weighted by field: names, documentation, and signatures are stronger than resolved relationships, while bodies and paths are supporting evidence. Each field's BM25 strength is bounded and scaled by the fraction of distinct query tokens that field matched, preventing a rare or short lexical match from overwhelming fuller structural coverage.

Equal totals are ordered by stable definition identity, so the same index and query produce the same result order. See Ranking for the complete scoring pipeline and current configuration.

Indexing outcomes

scalps index returns 0 when a complete index is available, whether newly written or unchanged. It returns 1 when project command problems, failed translation units, skipped eligible definitions, or merge conflicts make the run incomplete. It returns 2 for command-line, loading, storage, or other indexing-operation failures. A nonzero result never publishes a partial replacement.

Commands for source files deliberately outside the project root are reported but do not make the index incomplete. Intentional exclusions, such as fully macro-generated test definitions, are also reported separately from unexpected skips.

Use scalps --help for the complete command syntax and exit-status summary.

Current limitations

  • Only C++ function and method definitions are indexed.
  • The project must provide usable compile commands, and relevant translation units must compile far enough for Clang extraction.
  • Freshness is unknown for compilation commands that use precompiled-header or module options because those inputs are not completely tracked yet.
  • Fully macro-generated definitions are excluded.
  • There is no editor integration, interactive interface, remote service, or learned retrieval.
  • Index storage is versioned. After extractor, schema, or ranking changes, an older index may be rejected and must be rebuilt.

Documentation

Support

Report ordinary bugs, indexing failures, and ranking problems in the GitHub issue tracker. The contribution guide lists the information needed for a useful report. Report suspected vulnerabilities privately according to the security policy.

License

scalps is licensed under the Blue Oak Model License 1.0.0.

Releases

Packages

Contributors

Languages