Structural verification

Turning on the Lumen code index, what each structural hint proves, querying the index, and Pyxis retrieval.

zurdo v1.36.0

A [grep:] hint proves a string exists, not that it's code: it passes on a comment as happily as on a real call. Structural hints check facts about named code symbols (a definition exists, a symbol is referenced, a function is actually called) by static analysis of the working tree. They resolve against Lumen, Zurdo's code index at .zurdo/lumen/.

Turning it on

[lumen]
enabled = true     # build the index, and allow [symbol:] / [references:] / [callers:]

It's off by default, and it's the only switch. A structural hint while Lumen is off is a validation error naming this key, in validate, analyze, run and --resume alike. The other [lumen] keys are on Configuration.

Lumen indexes Rust, Python, Go, TypeScript and JavaScript (TSX and JSX included).

What each hint proves

- [ ] limiter type is defined [symbol: struct RateLimiter in src/middleware/rate_limit.rs]
- [ ] app wires the limiter [callers: method RateLimiter::layer in src/middleware/rate_limit.rs within function build_router in src/app.rs]
- [ ] config reaches the limiter [references: struct AppConfig in src/config.rs within struct RateLimiter in src/middleware/rate_limit.rs]
HintPasses whenDoesn't count
[symbol:]A definition of that kind and name exists in exactly that fileImports, use sites
[references:]An occurrence inside the within symbol binds to the target through scope and static importsA same-named symbol from another module
[callers:]As [references:], and the occurrence is the callee of a callPassing the function as a value

Here a stub agent claims the wiring is done. The grep passes on a // TODO: comment; the [callers:] hint holds out until the real call lands:

zurdo run prds/limiter.md
─── task-wire: Wire the rate limiter ─── effort=low, deps=[]
  → iteration 1 of 3
  ✓ agent completed: exit=0, 272ms
    ✓ symbol: struct RateLimiter in src/middleware/rate_limit.rs
      already passed at pre-flight — proves nothing about this run
    ✓ grep: install_limiter\(&cfg\) in src/app.rs
      already passed at pre-flight — proves nothing about this run
    ✗ callers: function install_limiter in src/middleware/rate_limit.rs within function build_router in src/app.rs
      stderr tail: unbound: no call occurrence of `install_limiter` in `build_router` (in src/app.rs) binds to `install_limiter` in src/middleware/rate_limit.rs
  …
  → iteration 3 of 3
  ✓ agent completed: exit=0, 56ms
    ✓ callers: function install_limiter in src/middleware/rate_limit.rs within function build_router in src/app.rs
      evidence file modified by agent
  ✓ task-wire: passed in 3 iterations (464ms)
The grep matched a TODO comment. Only the call binding proved the wiring.

A failure is typed (symbol_unresolved or binding_unresolved) with a diagnostic (not found, wrong kind, ambiguous with every candidate listed), and the diagnostic goes into the agent's next prompt. A pass records the resolved symbol and its source span in prd.json.

Note

False negatives over false greens. What can't be bound deterministically (dynamic dispatch, trait objects, macro-generated names, method calls that need the receiver's type) fails rather than guesses. A structural hint can be strict; it can't be wrong in your favour.

The index during a run

How a run uses the Lumen index: structural hints trigger a pre-flight repair, then re-repair and typed verdicts on each verification passHow a run uses the Lumen index: structural hints trigger a pre-flight repair, then re-repair and typed verdicts on each verification pass
  • Lazy. A PRD with no structural hints never builds or reads the index.
  • Current. The index is repaired before each verification pass, so code the agent just wrote resolves at once.
  • Fails closed. No usable index means the hint fails, never passes.
  • Repo-scoped. One index serves every PRD. zurdo run --reset leaves it alone; zurdo lumen clear deletes it.

An optional watcher, Vela, can keep the index warm between runs. It never changes a verdict. Vela, and which constructs resolve in each language, are on Internals.

Asking the index a question

Structural hints assert; zurdo lumen query asks. Pass exactly one selector:

zurdo lumen query
$ zurdo lumen query --name load
segment  method  AppConfig::load  src/config.rs:6:5
$ zurdo lumen query --outline src/middleware/rate_limit.rs
module  rate_limit               src/middleware/rate_limit.rs:1:1
struct  RateLimiter              src/middleware/rate_limit.rs:3:1
field   RateLimiter::per_second  src/middleware/rate_limit.rs:4:5
method  RateLimiter::new         src/middleware/rate_limit.rs:8:5
method  RateLimiter::layer       src/middleware/rate_limit.rs:11:5
$ zurdo lumen query --callers new
RateLimiter::new  build_router  src/app.rs:6:19
$ zurdo lumen query --references AppConfig
AppConfig  build_router      src/app.rs:5:15
AppConfig  config            src/config.rs:5:6
AppConfig  RateLimiter::new  src/middleware/rate_limit.rs:8:22
Real output (tabs shown as spaces). Every row ends in a 1-based file:line:column.
SelectorRow
--namerank kind qualified-name file:line:col
--outlinekind qualified-name file:line:col, in source order
--callerscallee enclosing-symbol file:line:col
--referencesidentifier enclosing-symbol file:line:col

Results stop at 20 rows; --limit 0 lifts the cap. The query repairs the index first and writes nothing. Use --name before writing a hint: it prints the exact kind and qualified name the hint needs. The same four questions are served to agents by zurdo mcp serve.

CommandWhat it does
zurdo lumen statusRecord counts, languages and the last publish time
zurdo lumen rebuildRebuild the index from scratch. The fix for a non-ready index.
zurdo lumen clearDelete .zurdo/lumen/ (--yes skips the prompt)

Pyxis: finding code by meaning

zurdo lumen query needs an exact name, kind or path. Pyxis (since 1.26.0) answers a free-text question such as "the code that loads the config". It indexes one unit per Lumen definition and one per markdown section, and ranks them.

[lumen]
enabled = true     # required: symbol units come from Lumen

[pyxis]
enabled = true
zurdo pyxis query "the code that loads the config"
zurdo pyxis status
  • Lexical always. With no model, ranking is BM25: no network and nothing to download.
  • Hybrid with a model. zurdo pyxis install fetches the pinned embedding model and checks its digests. Rankings then fuse BM25 with embedding similarity, and each row says lexical or hybrid.
  • Retrieval, not proof. A row points at a file, never a line, and never satisfies a criterion. Use it to find where to look, then pin a structural hint.

With [pyxis] enabled = true, zurdo mcp serve also publishes find_related, the same query for agents.

Authoring with structural hints

  • Wiring criteria. "The middleware is installed" is exactly [callers:]. A grep for the registration line passes on commented-out code.
  • Existence with teeth. Prefer [symbol:] over grep when it must be a real definition in the right file.
  • Cost. Pricier to write than grep, cheaper than building [shell:] test infrastructure to prove a relationship.
SymptomFix
structural hint requires lumen.enabled = trueSet [lumen] enabled = true.
Pre-flight fails with a non-ready indexzurdo lumen rebuild, then re-run.
"wrong kind"Use the kind the diagnostic names (struct vs type is the usual one).
unknown symbol kind naming field, enum-variant, macro or aliasThose are recorded but not hint kinds. Target the enclosing type.
[callers:] fails binding_unresolved though the call is thereThe call site doesn't import the target, or the call needs receiver-type inference. Import it directly, or use [shell:].