Facts, decisions and the contract
hiiu splits what it can read from a repo, what it has to be told, and what it compiles from the two, and keeps the three in separate documents.
Three documents sit under everything hiiu does, and the split between them is the reason the pipeline can be automated at all.
Facts are what the repo says
hiiu analyze reads a directory and reports what it is:
the framework, the router, the routes, the API handlers. Nothing in that
document is a judgement. It touches no network, runs no model, and two runs of
one input are byte-identical, so it can be cached against a revision and
trusted without a staleness check.
The layer's contract is that it never holds an opinion. That is a stronger constraint than it sounds, and it is enforced by one deliberate absence: the analyzer never picks an app. The path you give it is the app, even inside a monorepo with exactly one candidate. Which app to build is a decision, and analysis is the layer whose whole job is not to make one.
The workspace readers exist only to make the refusal actionable. They take the
union of every workspace declaration they can find, deliberately without a
precedence rule, because ranking package.json workspaces against a
pnpm-workspace.yaml is only meaningful if the answer selects something.
Nothing branches on that list. Over-listing costs a glance; under-listing costs
the answer.
The IR is what the facts compile to
hiiu ir turns those facts into the app's declared API surface
as a single document: every endpoint hiiu believes the app can call.
Two sources feed it. The router's own claims, and any MSW handlers the repo
already has, which state templates of their own and arrive with
msw-handler provenance. Where both name one template they merge.
An endpoint is identified by a path template, not by a URL. That
distinction is what stops the discovery loop running forever: if
/invoices/inv_9f2c1b were treated as a literal path, every rebuild would seed
different ids, every crawl would find fresh gaps, and the loop would never go
dry. The recogniser that decides a segment is an id and the generator that
mints ids are one contract, and neither changes alone.
A repo with no declared API surface compiles to zero endpoints and exits 0.
That is a true answer, not an error.
Decisions are what analysis cannot compute
Some things are not in the repository at any depth. Which app in the monorepo. Who the persona is. Which routes matter enough that a denied request on one should fail the build.
hiiu decisions proposes answers, and that verb is
load-bearing: a proposal is not a confirmation. Given --decisions <file> it
does the opposite job and checks an existing document against the repo,
exiting 3 and naming the contradiction if the two disagree. That is how a
decisions file that was right last quarter fails loudly when the repo moves
under it, rather than quietly building the wrong app.
Why the split matters
Because it is what lets the automated parts be automated. Facts regenerate from the repo on every build with no human in the loop, and cost nothing to recompute. Decisions are asked once, written down, and checked from then on. The maintenance agent can redo the first freely, and knows it must never silently redo the second.
Read next: the interception overlay, which is what the contract is compiled into.