C++ Dev-Tool Development Kit

A legacy-free C++ developer tooling platform

One engine reads your C++ and holds the whole picture — syntax, symbols, references, control flow. Documentation, code generation and memory-safety analysis are what we build on top of it.

geometry.hpp Hover a marked token — the engine reports what it recorded
1
2
3  
4 {
5  
6 template <typename T>
7 = requires(const T& s) {
8 { } -> std::convertible_to<double>;
9 };
10  
11 {
12 public:
13 : {}
14  
15 {
16 return * * radius_;
17 }
18  
19 private:
20
21 };
22  
23 template <>
24 auto (const Ts&... shapes) -> double {
25 return ;
26 }
27  
28 } // namespace geometry
include directive geometry.hpp → <concepts>

Our own preprocessor walks this, not the compiler's — which is why this file needs no build configuration to be read. The edge lands in the include graph, and that graph is what lets an edit invalidate the files that reach it and nothing else.

include directive geometry.hpp → <numbers>

Recorded as a second edge out of this file. Both directives survive as nodes in the tree rather than being consumed and discarded the way a compiler front-end consumes them.

namespace geometry

Opens the scope every declaration below is qualified into, and holds them as its children. The index is keyed on qualified names, not on the spelling at the use site.

concept geometry::Shape

A declaration in its own right — span, kind, qualified name — carrying the requires-expression that defines it: one parameter of type const T&, and one compound requirement over it.

unresolved call on a dependent type

The call is kept, with its shape and its span. What it resolves to is not: T is unknown here, so no callee is recorded. Surfaced as unresolved rather than guessed — an index that quietly invents this edge is worse than one that admits it cannot follow it.

class geometry::Circle

One record, with its members already sorted: constructors, member functions, member variables, nested types — each carrying the access it was declared under.

constructor geometry::Circle::Circle(double)

Classified as a constructor rather than a member function, and further as neither a copy nor a move. explicit and noexcept ride along on the record as specifiers, not as text.

member initialiser initialises radius_ · declared line 20

Held as a structured initialiser — the member it names on one side, the argument r on the other — rather than as a span of source between a colon and a brace. This is the pair a rename has to move together or refuse.

member function geometry::Circle::area() const

Flagged as a trailing return, and read through to double. The const qualification and the nodiscard attribute are both recorded on the declaration rather than discarded as noise.

qualified name std :: numbers :: pi

Kept as a chain, not a string: pi, qualified by numbers, qualified by std. Nothing outside this source tree is indexed and nothing about it is assumed — but the name it was written under survives intact.

expression operand of a binary expression

The return value stays a tree — two nested multiplications, each operand at its own span — instead of collapsing to the text between return and the semicolon. Structure is what later passes have to work with.

field geometry::Circle::radius_

Recorded private, of type double, at this exact span. The initialiser on line 13 names this member and the expression on line 16 uses it — three sites that any rename of this field has to reach.

template parameter pack Ts · variadic

One parameter, marked variadic. We never expand it, because we never instantiate — the declaration is the thing indexed, and that is precisely why this file can be understood without being built.

requires clause fold over Ts, operator &&

The constraint is stored on the template as a fold in its own right, with its operator. What it would evaluate to needs instantiation; its shape does not, so its shape is what we keep.

function template geometry::total_area

A function template at namespace scope, sitting beside the class and the concept as a sibling in the same namespace record.

fold expression fold over Ts, operator +

A fold, its operator, and underneath it a method call on shapes — all of it still standing even though the pack it folds over is not known here. Structure survives where resolution cannot.

The problem

Everything that understands C++ was built to compile it

Serious C++ tooling has had two foundations to choose from, and both charge for it. A compiler front-end understands the language completely — and wants a working build configuration, refuses code that does not compile, and was designed to emit object files rather than answer questions about your program.

Pattern matching is the other extreme. It starts in an afternoon and falls apart by the evening: C++ is far too context-sensitive to match your way through, and every rule you add spawns three exceptions.

CodeXX takes the road between them — a forked and extended tree-sitter grammar, with our own AST, symbol index and analysis layer built on top of its concrete syntax tree. Fast, incremental, tolerant of half-written code, and embeddable as a library.

Owning that foundation is what turns documentation, code generation and memory-safety analysis from three research projects into three products.

The trade-off, stated plainly

Three ways to read C++

Approach Setup Half-written code Re-analysis Semantic depth
Compiler front-end A working build configuration Rejected — it has to compile The whole translation unit Complete: overload resolution, template instantiation, the lot
Pattern matching None Tolerated Cheap, and wrong about as often Effectively none — C++ is far too context-sensitive to match
CodeXX engine Point it at the source tree Tolerated — it parses as you type Incremental, scoped to what depends on the edit Syntactic, plus best-effort symbol and type resolution

That last cell is the trade, and we would rather state it than bury it: no overload resolution, no template instantiation. What we get for it is an index that keeps up with an editor, on code that has never once built.

The engine

From a directory of source files to a queryable index

Six stages, composed as a pipeline — each a real stage object, chained with the | operator, run per item or per batch.

01 Collect Walks the tree for translation units and headers. FilesCollector
02 Preprocess Our own #include and #define pass, not the compiler's. Preprocessor
03 Parse A forked, extended tree-sitter-cpp grammar produces the CST. Parser
04 Extract The CST becomes typed AST nodes carrying source spans. SourceExtractor
05 Analyze Symbols are indexed and references linked to declarations. Analyzer
06 Index A multi-key symbol database the tools query. SymbolsDatabase

Incremental by construction

An edit invalidates what depended on it and nothing else. The workspace layer tracks include and module dependencies, debounces editor traffic, and holds a live parser per open file — so a keystroke never pays for a cold index.

Deterministic under parallelism

The whole-program analysis phases run across cores under one hard rule: the output must be byte-identical to the serial order. Pre-sized buffers, writes by index, nothing accumulated from a worker. Determinism here is a test, not an aspiration.

What the index gives every tool
  • Symbols and references
  • Include graph
  • Class hierarchy
  • Control-flow graph
  • Points-to analysis
  • Memory layout
Products

Four tools, one engine

Each is a consumer of the same index. That is the architectural bet: build the foundation once, and the tools stop being separate research efforts.

Evidence

Reasons to believe any of this

166k Lines of first-party C++ 718 files, vendored code excluded
2,466 Unit and integration tests engine, linter and toolchain
76 Architecture decision records decisions written down, not folklore
The real test

The engine is pointed at itself

Every product runs against this repository and the full closure of its dependencies — every header, every vendored library. The linter's whole-tree automatic refactors are applied to that same corpus and are required to build afterwards: a rename that breaks a single call site fails the run. If the tools are not reliable on 166,027 lines of our own C++, we find out before you do.

Design partner program

Early access for C++ teams that build the hard things

A tight feedback loop and a direct line to the author. Further materials available under NDA.