| name | mallard |
| description | Use Mallard to verify local code edits and scope refactor impact with a deterministic structural code index. Trigger after editing code; before renaming, removing, or changing public symbols; and when asked who calls a symbol, what tests exercise it, what changed between SHAs, or whether a refactor missed call sites. Mallard returns citation-grounded symbol records from per-SHA DuckDB indexes for Rust, Python, TypeScript, and JavaScript. |
| allowed-tools | ["Bash","Read"] |
Mallard — structural code-intelligence
Mallard is a CLI returning JSON over stdout. Invoke via Bash. Output is deterministic and citation-grounded.
Prerequisite — build an index first
Every query targets a built index (DuckDB file). Build once per commit SHA:
mallard index --sha "$(git rev-parse HEAD)" --out .mallard/head.duckdb .
Indexing takes ~10s per 100kloc. The .duckdb file is reproducible from the SHA. Re-build only when source changes.
If the user asks any of the questions in the description without an existing index, build one first. Default output: .mallard/head.duckdb.
Stable agent-facing commands
These four commands emit schema_version: "1.0". Always check it before parsing.
1. find — qname lookup
mallard query find --index .mallard/head.duckdb --qname auth_check
Returns symbols whose qualified_name equals (or suffix-matches) auth_check. Exact matches rank first. Use when the user gives a short name and you need the full symbol record (file, line, kind, signature).
2. blast-radius — composite impact
mallard query blast-radius --index .mallard/head.duckdb --qname auth_check
Returns {symbol, callers, callees, test_seams, other_qname_matches}. Call this BEFORE proposing a rename, removal, or signature change. It shows every site that breaks.
callers — inbound edges (who calls this symbol)
callees — outbound edges (what this symbol calls)
test_seams — subset of callers from test files
other_qname_matches — sibling symbols matching the qname (disambiguate via path + kind)
value: null if qname has no match.
3. test-seams — which tests exercise a symbol
mallard query test-seams --index .mallard/head.duckdb --qname auth_check
Returns just the test seams from blast-radius. Use to scope which tests to run after modifying a symbol.
4. symbol-diff — what changed between two SHAs
Needs two indexes — base and head. mallard index reads the working tree, not the SHA's actual file content. To compare real SHAs, check out each one (or use a git worktree) before indexing:
git checkout "$BASE_SHA"
mallard index --sha "$BASE_SHA" --out .mallard/base.duckdb .
git checkout "$HEAD_SHA"
mallard index --sha "$HEAD_SHA" --out .mallard/head.duckdb .
mallard symbol-diff --base-db .mallard/base.duckdb --head-db .mallard/head.duckdb
Or use worktrees to avoid mutating the user's checkout:
git worktree add /tmp/base "$BASE_SHA"
mallard index --sha "$BASE_SHA" --out .mallard/base.duckdb /tmp/base
git worktree remove /tmp/base
Returns {added, removed, modified}. Symbols match by (qualified_name, path, signature). modified = same key, different anchor (body changed). Use to verify what the agent actually changed structurally in the local working tree.
Composition patterns
Pre-refactor scoping
mallard query blast-radius --index .mallard/head.duckdb --qname process_request \
| jq '.value.callers[] | "\(.path):\(.anchor.start_line) \(.qualified_name)"'
Post-deletion verification
After agent removes a function, check for orphan callers:
mallard query unresolved-callers --index .mallard/head.duckdb --name deleted_fn
Local cross-SHA verification
mallard symbol-diff --base-db .mallard/base.duckdb --head-db .mallard/head.duckdb \
| jq '.removed[] | "\(.path):\(.anchor.start_line) \(.qualified_name)"'
Then for each removed symbol, check unresolved callers in HEAD to catch missed updates. unresolved-callers is part of the power-user surface below, so do not expect a schema_version envelope.
Untested-change detection (authoring time)
After modifying a symbol, flag it when no test exercises it — the structural form of "this behavior change ships without coverage." test-seams returns an empty value array for a symbol with no test callers:
mallard query test-seams --index .mallard/head.duckdb --qname process_request \
| jq -e '.value | length > 0' >/dev/null \
|| echo "⚠️ process_request has no test seams — modified without coverage"
Zero-seam is a structural fact. "Has tests but none were touched" is a staleness judgment mallard does NOT make — that's yours (or the LLM's) to decide.
When to refuse
- User asks about runtime behavior, types, generics resolution → mallard sees structure only. Use LSP / rust-analyzer / pyright.
- User asks about security patterns or CVEs → mallard is not SAST. Use Semgrep / CodeQL.
- User asks about a language outside Rust / Python / TypeScript / JavaScript → unsupported. Say so.
- Index not built and user is in read-only mode → ask before building.
Output format guarantees
| field | guarantee |
|---|
schema_version | always "1.0" on the 4 commands above. Refuse to parse other values |
kind (query results) | tagged enum discriminator |
value | empty array on no match, NOT null. Exception: blast_radius.value is null when qname unmatched |
| exit code | 0 = success with JSON on stdout; non-zero = error on stderr, no JSON |
| citations | every SymbolRecord includes stable id (content hash) + path + anchor.start_line |
Full schema reference: docs/cli-json-contract.md in the mallard repo.
Local change verification
For local agent-authored changes, prefer the stable agent-facing commands when they fit. Use power-user commands only for gaps such as post-deletion unresolved caller checks. The goal is to help the agent verify its own edit before handing work back to the user.
Recommended workflow:
- Build a base index before editing, or from the comparison SHA via a worktree.
- Make the code change.
- Build a head index from the updated working tree.
- Run
mallard symbol-diff to see added / removed / modified symbols.
- For removed or renamed symbols, run
mallard query unresolved-callers against the head index to catch missed call sites.
- For modified symbols, run
mallard query test-seams to identify tests that exercise the changed code.
- Report only citation-grounded facts with
path:line anchors.
Example local flow:
mallard index --sha "$(git rev-parse HEAD)" --out .mallard/base.duckdb .
mallard index --sha "working-tree" --out .mallard/head.duckdb .
mallard symbol-diff --base-db .mallard/base.duckdb --head-db .mallard/head.duckdb
mallard query unresolved-callers --index .mallard/head.duckdb --name deleted_fn
mallard query test-seams --index .mallard/head.duckdb --qname process_request
Use this local workflow when the user asks to verify an edit, check whether a refactor missed call sites, or understand the structural impact of changes.
Power-user surface (unversioned)
These commands exist but DON'T carry schema_version. Use only when the 4 versioned commands above don't fit:
query symbol, query neighbors, query expand, query findings, query symbols-in-file, query edges-by-file, query unresolved-callers, query importers-of, query files, query metadata, diff-hunks.
Their shapes are documented in docs/cli-json-contract.md but may evolve via SemVer of the binary.
Cardinal rules
- Never invent symbol names or file paths. If
find or blast-radius returns empty, the symbol doesn't exist in the index. Tell the user. Don't guess.
- Cite every claim. When reporting findings to the user, include
path:line from the mallard output. The user must be able to verify.
- Build once, query many. Don't re-index between queries unless source changed.
- Prefer
blast-radius over chaining find + expand — same result, one shell call, smaller token surface.