FDML is a local-first Rust CLI for working on a codebase with coding agents. It has three parts that work today:
- A code index (
fdml index,search,get,outline,impact): tree-sitter symbols, call graph and a per-symbol micro-map in one SQLite file, so an agent asks "where is X" once instead of grepping and reading files. Works for C/C++, Rust, TypeScript/JavaScript, Python, Go, Java, C#. - Project memory (
note,dossier,history): postmortems, invariants, playbooks and pending work anchored to symbols or features, restored in one command after a context reset. - A product model with proof (
vision.fdml,work,evidence,gap,research,serve): features and scenarios in YAML, an operations journal a human accepts, and a joinevidence → test → scenario → featurethat says PROVEN, FAILED, UNKNOWN or NOT VERIFIED.
No cloud, no daemon, no LLM in the core path. A model is optional and only ever proposes typed operations; it never writes the spec.
Keywords: code navigation for LLM agents, repository index, semantic code search, feature specification language, requirements traceability, BDD scenarios to test evidence, agent memory, Rust CLI, tree-sitter, SQLite.
git clone https://github.com/kolanski/FDML && cd FDML # tag v0.6.3 is the current release
cargo install --path . # the toolchain is pinned in rust-toolchain.toml (1.86)
fdml --versionEvery line below is real output on a two-file TypeScript repo.
$ fdml index .
Indexed …/shop: 2 parsed, 0 unchanged, 0 removed, 7 symbols
$ fdml search "charge the card" --limit 3
payments.ts:1: export function chargeCard(order) {
payments.ts:5: export function validateCard(card) {
checkout.ts:1: import { chargeCard } from "./payments";
$ fdml get chargeCard
payments.chargeCard payments.ts:1-4
export function chargeCard(order) {
validateCard(order.card);
return capture(order.total);
}
callers: checkout.checkout
$ fdml impact chargeCard
CALLERS
checkout.checkout
CALLEES / DEPENDS ON
payments.capture
payments.validateCardSearch is grep-shaped on purpose (path:line: source) so it drops into the same habit. Several phrasings go in one call: fdml search "charge card" "capture payment". Several symbols too: fdml get chargeCard validateCard --lines 80. A big function is read phase by phase with fdml outline <symbol>.
The index heals itself: if the code moved since the last run, any command reindexes first (about 15 ms when current, no watcher, no hook). When nothing useful is found the tool says so — ⚠ no useful result … fall back to grep — and you can teach it: fdml mark "<the query that failed>" <symbol>.
$ fdml note "card number missing" "Thrown by validateCard when the form sends an empty number; the fix is in the form, not here" --kind postmortem --at validateCard
Recorded postmortem "card number missing" @ validateCard
$ fdml search "card number missing" --limit 1
payments.validateCard [marked]
↳ [postmortem] Thrown by validateCard when the form sends an empty number; the fix is in the form, not here
$ fdml dossier
ANCHOR payments.validateCard
FLOW checkout.checkout → payments.chargeCard → payments.validateCard
SYMPTOMS «card number missing»Note kinds: postmortem, invariant, method, rejected, playbook, pending, link, domain, solution. A note anchored to a symbol is marked stale when that symbol's source changes. fdml dossier is the restore point: run it first when you pick up work, leave a --kind pending note when you stop mid-task.
fdml serve --v2 --no-open . # http://localhost:3000One page: the product's features traced to code, research history, the work queue. It is rebuilt on every load from the index and the spec; nothing is cached.
This is how FDML is developed with FDML. Six of its own features are PROVEN this way.
fdml propose --out - # 1. candidate features from the code's behavioral units
# write vision.fdml by hand: 5–10 features with value and scenarios (see below)
fdml validate vision.fdml # 2. ids resolve, closed enums, value graph
fdml gap # 3. what the vision declares vs what the scan observed
fdml work add '{"kind":"add_test_plan","scenario":"tool.research.s1",
"test":{"id":"t.research.citations","reference":"research::tests::citations_by_id_become_packet_numbers","runner":"cargo-test"}}' --accept
fdml work export # 4. the journal reaches the file; accepted ops are visible to every reader before that
fdml evidence run --runner cargo-test # 5. run the tests, translate the result into evidence
fdml evidence report # 6. the joinFeature: tool.research — Research: a question answered from the model
✓ proven Citations by id become packet numbers (then 1 · tests 1)
evidence: test research::tests::citations_by_id_become_packet_numbers… → verified
Feature status: PROVEN (1/1 scenarios with a passing linked test; then 1 · tests 1 across the feature)
Conditions: runner=cargo test · profile=release · suites=5
Runners today: cargo-test, make-check. A runner is a small adapter that turns a checker's output into verified / failed / unknown per reference; the spec does the rest.
The journal is the third writer of the model after the human and the scanner. Operations are a closed set — add_scenario, add_test_plan, link_code, add_rule, add_feature, set_feature, add_decision — proposed as JSON, validated and dry-run against the model, accepted by a human, overlaid on every load, exported when the file should catch up. A model may emit an operation; it may never emit a document.
fdml research "<how does X work>" answers a product question from the index and the spec: a numbered evidence packet, coverage by layer (feature · scenario · code · rule · test), gaps with draft operations, and optionally a model's answer citing packet numbers. Every answer records what it cost.
features:
- id: tool.journal
title: "The operations journal"
description: "Typed operations proposed, accepted by a human, overlaid on every load, exported to the file; via add_op, accept_op, export_products"
realizes: [tool]
scenarios:
- id: tool.journal.s1
title: "An accepted scenario is visible before export"
given: ["a pending add_scenario operation"]
when: ["a human accepts it"]
then: ["every reader sees the scenario", "the file is unchanged until export"]Root features carry a value block (beneficiary, statement, metric); every feature must reach a value-bearing root through realizes. The language is specified in FDML-1.3-en.md with the 1.4 extensions in fmdl-spec/.
- Working notes for agents live in CLAUDE.md: index first, grep second; Edit tool over heredoc patches;
dossierat the start of a session. Any agent that reads markdown can follow it. - Skills ship in .claude/skills/ (Claude Code skill format, plain markdown, portable to other agents):
fdml-nav— navigation:dossier → search → get/outline → impact, grep only after an honest miss,markafter a grep that found what search did not;fdml-cpo— what to build next, from telemetry, failed queries and the gap rather than opinion;fdml-visionary— judge an idea against the vision and the non-negotiables (deterministic core, local-first, spec is the product).
- Install the navigation skill into another repository:
fdml skill --install(or--global); check withfdml skill. - Every search is logged.
fdml logshows the hit rate and the failed queries;fdml candidatesshows the shell work an agent rebuilt by hand three or more times in this repo — the tool that is missing.
| command | what it does |
|---|---|
index, status |
build or update the SQLite index; statistics |
search, get, outline, impact |
find, read the smallest range, segment a big function, callers and callees |
mark, heal, log |
teach the index a query; turn failed queries into marks; telemetry |
note, dossier, history, candidates |
project memory; restore point; commits joined to the asks that caused them; repeated shell work |
validate, parse, trace |
the spec: parse, validate, traceability checks |
propose, gap |
reverse flow: candidate features from code; vision vs scan |
work |
the operations journal: add, list, accept, reject, export |
evidence |
`run --runner cargo-test |
research |
a product question answered from the model, with coverage and gaps |
serve --v2 |
the product page |
scan-platform, parse-code, link-code, facts |
scanning and linking an existing codebase; facts from external analysers |
generate, migrate, add, list, init |
legacy: code generation and file-level CRUD, scheduled for removal |
fdml <command> --help for flags. Most commands take --json and --path <root>.
Measured on this repository with fresh agents, frozen snapshots, five runs per cell, cost in input-token equivalents.
- The index cuts shell navigation: 22 grep/read actions became 8 on three tasks, with the same answers.
- It does not lower an agent's cost on a small, well-named repository: four of five comparisons were statistically indistinguishable, one was 18 % cheaper. A benchmark on a large unfamiliar codebase is the next experiment.
- Writing the feature draft or the verification plan before the code did not make implementation faster; real defects were found by running the result.
- Project memory after a context reset is the part agents consistently used.
- The journal cannot edit or remove a scenario.
- PROVEN means "the linked test passed", not "every
thenclause was checked". - Export rewrites the vision file in machine form.
- Code generation and migrations are legacy from an earlier direction and will be removed.
cargo test --release must pass (five suites). Navigate with the index, not grep — see CLAUDE.md. Design notes: docs/navigator.md, docs/evidence.md, docs/two-flows.md.
MIT — see LICENSE.