Idea

Code review from facts about the compiled program

Ask concrete questions about a Bosatsu program: which inputs reach a result, which fields go unread, and what changed after an edit? We built tools that extract facts from the compiled program and test a reviewer’s claims against them.

Code & run Change a price calculation

Each result distinguishes a static fact from a bounded observation of seeded runs. Start with the pricing example below, or run the checks yourself. The tools analyze Bosatsu programs.

Worked example: four facts from a pricing module

Here is a small pricing module (it ships in the repository at demos/pricing, with the commands to reproduce these findings below). It contains deliberate input-ignoring examples and a function that stays constant in the sampled runs.

struct Order(id: Int, qty: Int, discount_code: Int)

def quote(o: Order) -> Int:
  _ = o          # the order is thrown away...
  42             # ...and the "computed" price is hard-coded

def shipping_fee(subtotal: Int, region: Int) -> Int:
  match cmp_Int(region, 0):
    case GT: add(subtotal, 5)
    case _:  add(subtotal, 5)   # both branches identical: region decides nothing

def surge_bonus(x: Int) -> Int:
  match cmp_Int(x, 1000):
    case GT: x        # looks input-dependent...
    case _:  7        # ...the sampled inputs all take this branch

One command, yichus explore --agenda --limit 500 demos/pricing/*.bosatsu (the “agenda” is the tool’s list of noteworthy facts, one card each), produces the excerpts below. The seeded observations use type-derived inputs for this pricing module; they do not establish behavior for every input. Read the complete captured agenda.

literal-result quote: “takes arguments, none of whose data reaches the produced value; every value the result can take originates in literals” The price is fake. The tool offers no opinion; the fact is checkable.
dead-field Order.discount_code: “no compiled extraction of the field exists in the supplied Bosatsu IR (reads: []); no declared API wire consumer was established; other engine or host consumers are outside this evidence; extraction absence alone does not justify removing the field” The supplied Bosatsu never extracts the discount code. Check engine and host consumers before removing a field.
guard-only-parameter shipping_fee(region): “the parameter only selects among branches; its data never reaches the produced value” Region picks a branch, but both branches compute the same thing.
constant-despite-reach surge_bonus: “across 8 seeded runs (seed 6321458022832390279), Demo/Pricing/surge_bonus always produced 7; bounded observation, not proof; parameter(s) x statically reach the produced value and no literal-result fact applies — the constancy is not statically explained” This static analysis does not establish the constancy, so seeded runs provide additional evidence.

Every fact is located and machine-checkable; the run-based ones carry the seed their inputs were generated from. None of them says “bug”. You decide which of these count as bugs. The tool provides a reproducible analysis result; its correctness still depends on the analyzer and its modeled semantics.

Agenda taxonomy: thirteen fact card types

There are thirteen kinds of fact. Each one comes with real code, or with a direct route to the checker that emits it. The taxonomy separates structural findings from seeded observations and declared-flow conformance. The first eight are computed from compiled structure. The next three summarize seeded, type-derived runs; absence or constancy observations are labeled “bounded observation, not proof.” The final two classify each mutation named by a FlowSpec as a structural instance or a located divergence. Run the taxonomy with the explore --agenda command below. The static and flow card emitters are in ExplorerAgenda.scala; seeded observation cards come from ExecutionEvidence.scala.

Dataflow semantics: “reaches”, “influences”, and negative guarantees

The static facts are computed on the compiled program’s dataflow structure: a value reaches a result if there is a chain of data edges from one to the other, and influences adds control edges (picking a branch counts). Structure over-approximates real influence, within the analyzer’s modeled operations. Negative results must retain their scope: “never reaches” excludes a data path to the result, while “influences nothing” also excludes a control path. A value can select a branch without contributing data to its result. A bare “reaches” only says a path exists on paper. So constant-despite-reach gets its own fact type: the structure says the input could matter, the observed runs say it didn’t, and the fact records that disagreement.

dead-field a field with no attributed extraction in the supplied Bosatsu IR
struct Order(id: Int, qty: Int, discount_code: Int)
# id and qty are read by accessors; discount_code is read by nothing
fact“no compiled extraction of the field exists in the supplied Bosatsu IR (reads: [])”

This raw fact does not justify removing the field. The overview and agenda also show declared API request/result consumers from the engine’s checked schema resolver. A response can be serialized without any Bosatsu field extraction. Empty consumer evidence does not exclude storage, archive or other host use.

No compiled field read was found in the analyzed program. Check the field’s intended use and any external consumers before removing it.

unused-parameter a parameter that influences nothing in its function
def quote(o: Order) -> Int:
  _ = o
  42
fact“the parameter influences nothing in the binding (neither data nor guards)”

The function does not use this parameter. Whether that violates the intended behavior depends on its specification.

guard-only-parameter a parameter that picks branches but never reaches the result
def shipping_fee(subtotal: Int, region: Int) -> Int:
  match cmp_Int(region, 0):
    case GT: add(subtotal, 5)
    case _:  add(subtotal, 5)
fact“the parameter only selects among branches; its data never reaches the produced value”

This is legitimate for a dispatch helper. It is suspicious when the spec says the value should flow into the answer.

discarded-argument a call that passes a value into a parameter the callee throws away
def summary(o: Order) -> Int:
  quote(o)      # quote discards o entirely
fact“a call passes a value into a callee parameter that influences nothing in the callee: the passed value is discarded there”

The caller passes data the callee never uses, and the fact points at the exact call.

literal-result a function whose every possible result is built from constants
def quote(o: Order) -> Int:
  _ = o
  42
fact“takes arguments, none of whose data reaches the produced value; every value the result can take originates in literals” (literals: 42)

This can expose a placeholder computation, including one routed through helpers. Predicates, lookup tables, and intentional constants can produce the same finding.

detached-read a state read whose value influences nothing
refresh: IO[Int] = (
  c <- cell.flat_map()
  _ <- c.read().flat_map()   # read the cell...
  pure(7)                    # ...ignore it, return 7
)
fact“the binding reads a state cell and the read value influences nothing (neither data nor guards)”

A state cell is a mutable variable managed by the runtime. This is code that looks like it consults state and doesn’t.

blind-write a state write with no read of that cell in the same code
reset: IO[Unit] = (
  c <- cell.flat_map()
  c.write(9)     # overwrites whatever was there, unconditionally
)
fact“the binding writes cell ‘cell’ without any read of that cell in this binding”

This is fine for a reset. It is a data-loss hazard in a read-modify-write that forgot the read.

package-reach one package can transitively reach another, with the import path
package Demo/Report
from Demo/Pricing import Order, quote
fact“Demo/Report → Demo/Pricing” (path: Demo/Report → Demo/Pricing)

These facts power layering rules like “the domain layer must never reach the notification layer”. When a rule is broken, you get the exact import chain that breaks it.

run-evidence what happened when the code ran on seeded inputs
def quote(o: Order) -> Int:
  _ = o
  42
fact“across 8 seeded runs (seed 6321458022832390279), Demo/Pricing/quote always produced 42; bounded observation, not proof”
fact“varying parameter ‘o’ of Demo/Pricing/quote across 5 values with the others fixed (seed 6321458022832390279), the result never changed; bounded observation, not proof”

The runner selects pure, monomorphic bindings whose input types it can generate, and reports skipped bindings. Run-based facts carry their input seed. A varying result supplies a concrete input-dependence witness: “runs 0 and 1 (seed 6321458022832390279) of Demo/Pricing/line_total produced 28 and -39 — the result depends on its inputs.”

unexecuted-reference a static reference whose target value was not forced in the sampled runs
def pick_quote(x: Int) -> Int:
  match cmp_Int(x, 999):
    case GT: fallback_quote(x)   # sampled inputs may miss this branch
    case _:  x

The runner records global values when evaluation forces them. A static referrer may have been evaluated only to a function value: registering a handler does not run its body. This card compares that value-forcing evidence with the typed reference graph. It does not report function-call coverage or prove dead code.

The stable card name and legacy executedStaticReferrers field remain available; referenceBasis: global-value-forcing states what they measure. Other inputs or an explicit handler invocation may force the missing values.

constant-despite-reach inputs reach the result on paper; observed constant in practice
def surge_bonus(x: Int) -> Int:
  match cmp_Int(x, 1000):
    case GT: x
    case _:  7
fact“across 8 seeded runs (seed 6321458022832390279), Demo/Pricing/surge_bonus always produced 7; bounded observation, not proof; parameter(s) x statically reach the produced value and no literal-result fact applies — the constancy is not statically explained”

The static story and the observed story disagree, which is the kind of divergence worth a human look.

flow-instance a declared mutation structurally matches its flow factory

The card names the flow, factory, mutation location, and every extracted part with its verified argument wiring. For example, Demo/Stock/restock is checked as an instance of retry-stock; the complete report and parts manifest are explained in the flow tutorial.

flow-divergence a declared mutation differs from its flow factory at a located structural path

The card records the first mismatch instead of treating all type-compatible mutations as conforming. The tutorial’s incorrectly wired error example shows the checker locating current where the flow requires deficit. Flow cards cover declared mutations only; an undeclared look-alike is outside this classification.

wire-result-field a field no Bosatsu code reads that an API route sends in its result
struct Quote(total: Int, currency: String)
def price(o: Order) -> IO[Quote]: ...   # served at /price; nothing in Bosatsu reads currency

The engine reads the field when it writes the response, so it is not dead. It gets its own card type, drained after every other card, so the fields nothing reads are not buried under a service’s response shapes. A request field the handler ignores stays a dead-field card: the client sends it and nothing uses it.

Claim verification: supported, refuted, and not-provable verdicts

A review (yours or an AI’s) is submitted as claims, and the verifier answers from the facts. Three claims about the module above:

{"claims": [
  {"kind": "literal-result",   "package": "Demo/Pricing", "binding": "quote"},
  {"kind": "no-detached-read", "package": "Demo/Report"},
  {"kind": "dead-field",       "package": "Demo/Pricing", "struct": "Order", "field": "qty"}
]}

Save the JSON above as claims.json. Running yichus verify-claims claims.json demos/pricing/*.bosatsu produces these verdict excerpts (claims, complete output):

supported for literal-result quote: “every value the result can take originates in literals”
supported for no-detached-read in Demo/Report: “zero detached reads in Demo/Report — 1 read site(s) examined program-wide, every read’s value has influence in scope”
refuted for dead-field Order.qty: “the field IS read, by: Demo/Pricing/line_total, Demo/Pricing/order_qty”

The exit code is 1 because one claim was refuted, so a wrong review fails the same way a failing test does. Look at the second verdict: the absence of detached reads in the stated scope is backed by an enumerated count of the read sites examined. Claim vocabulary is intentionally narrower than the card taxonomy. There is no generic no-<kind> constructor. Selected static facts have direct claims; five card families have general absence claims, package reach has its own negative claim, and flow conformance has flow-instance plus no-flow-divergence. Use a drained claim when a universal statement depends on processing every card in another family. The full vocabulary is in the agent tooling guide.

Fact diff: what changed between two versions

yichus fact-diff compares extracted facts for two source sets, reporting introduced and removed findings and downstream consumers. Replacing a computation with a literal can introduce a literal-result finding and leave callers passing arguments that no longer affect the result.

For a reproducible before/after example, use the fixtures in FactDiffCommandTest.scala. The command recipe below accepts your own two source versions.

Benchmark: fact-tool review versus source review on planted defects

The experiment generates Bosatsu codebases with planted defects (“needles”). Each round is a matched pair: one AI reviewer gets the fact tooling, one reads source, and both are scored against a hidden key. Every round’s raw record is committed under eval/haystack/results/ and rendered on the scoreboard, which defines the columns, the contract-accuracy gate, and the rounds the tools lost. The project builds the tools, generates the challenges, and scores the runs. Each cell is one run. Model labels are harness tiers, not exact model IDs. Round 008’s nominal 60k caps failed to bind.

Recorded outcomes on that scoreboard:

The honest limit is that with the caps removed on round 008, the strong source-reading agent found all 26 at 1.85× the tokens (253.2k vs 137.0k, where the fact-using reviewer stayed at 25 of 26). This result does not establish equal comprehension. It shows that the uncapped source reader recovered the remaining defect by spending more, while the fact-using reviewer used fewer tokens and still missed one. All the benchmark data, including the losses →

Build the CLI and run the pricing checks

Needs git, a JVM (17+), and sbt; sbt assembly writes yichus.jar under target/. The pricing module above ships in the repo, so this works end to end:

# one-time setup
git clone https://github.com/snoble/yichus && cd yichus
sbt assembly
alias yichus="java -jar $(pwd)/target/scala-*/yichus.jar"

yichus explore --agenda --limit 500 demos/pricing/*.bosatsu     # up to 500 fact cards
yichus explore --agenda --agenda-type dead-field \
  demos/pricing/*.bosatsu                           # one fact type

# save the three-claim JSON from the section above as claims.json:
yichus verify-claims claims.json demos/pricing/*.bosatsu

# fact-diff wants a "before": copy the module, then edit
# old/pricing.bosatsu to give quote a real computation.
# Compare that edited version with the shipped constant version:
cp -r demos/pricing old
yichus fact-diff --before old/pricing.bosatsu \
  --after demos/pricing/pricing.bosatsu

There is also yichus spec, which checks declared Bosatsu system properties with Yichus’s native bounded and structural methods and records results with counterexample traces. Use --output verdict.json to save its legacy 2.2 JSON artifact. Add --require-holds invariant-name when a named invariant must hold conclusively for the command to succeed. An inconclusive result otherwise remains visible in the artifact; TLC is not run by this command yet. The check chooser compares the available tools. See the spec-first verification demo.

Early UI fixtures and experiments live in the archive.

Where this fits