- TypeScript 91.7%
- Shell 8.3%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
All checks were successful
gate / gate (push) Successful in 20s
A four-inspector audit of florian/maproom and florian/homelab, re-run on claude-fable-5-1 with a Go toolchain actually on PATH, converged on four defects in this repository's own scaffold. All four were inherited shapes: patterns copied from maproom whose enforcing mechanism did not come with them. The command list moves into hack/gate.sh, which feedback-loop.yaml and .forgejo/workflows/gate.yml now both invoke. The previous shape was two literal copies plus a "change both in the same commit" rule, and the audit measured that rule in the repository it was borrowed from: maproom's two copies have never once been edited in the same commit across 215 commits, and their gofmt steps have already diverged. homelab solves it by extraction — one script, two callers, "cannot drift, because it is the same file" — and that is the half worth copying. The Node version gains a single owner. gate.yml claimed in a comment that package.json's engines.node owned it while hard-coding node-version: 22 three lines below; maproom's version of that sentence was true only because go-version-file: go.mod reads the owner and check-image.sh asserts the one other copy. The owner here is .nvmrc, not engines.node, because engines is a compatibility range and a range is not a version a runner can install. The feared breakage across the --experimental-strip-types boundary was measured and is not real: node 22.23.3 runs the typecheck and the full suite green. margin.ts's "Nothing here calls the network" becomes src/no-network.test.ts. In Go that claim is checkable — go list -deps ./internal/wayfinder returns no net package, and the audit verified it — but fetch is a global in Node, so a module reaches the network with no import and no dependency graph shows it. The test scans imports and globals separately, because module specifiers are string literals and identifiers are not: the first version stripped strings before scanning and therefore could not see a single import. Its own self-test caught that, which is the reason a scan that protects an unread claim must be tested against source it must reject. Verified by negative control: a fetch injected into margin.ts turns the suite red with the intended message. CLAUDE.md gains "What silently misleads here", the homelab section that names traps where a green result is false. It is the one pattern whose unit is a trap rather than a file, so it does not shrink with the repository, and this project's whole subject is failures that look like successes: a 200 that dropped the ZDR flag, a 0.45/0.40 split that reads as a winner, a null that is the correct answer, an offline suite that proves nothing about the wire. Three citations were wrong and are corrected. Root ADR-0037 is the tracker's self-built client and says nothing about typed decisions, so it cannot be the answer to "why any of it is shaped this way"; typed-decision-models is not among homelab's thirteen committed skills; and issue-tracker.md names a different label subset than the nine created here from maproom's FixedLabels, so the count leaves prose and the source of truth is named instead. AGENTS.md is added as a map, not a rulebook: Codex, OpenCode and Cursor read that filename and never CLAUDE.md, so without it every behavioural rule here is invisible to those sessions. Verified from a wiped node_modules: ./hack/gate.sh green, 10/10 tests. |
||
| .forgejo/workflows | ||
| hack | ||
| src | ||
| .env.example | ||
| .gitignore | ||
| .nvmrc | ||
| AGENTS.md | ||
| CLAUDE.md | ||
| feedback-loop.yaml | ||
| LICENSE | ||
| package-lock.json | ||
| package.json | ||
| PRODUCT.md | ||
| README.md | ||
| tsconfig.build.json | ||
| tsconfig.json | ||
ynajev
Typed decisions on the household budget: YNAB state judged by a System One evaluation model, never by prose.
The name is the two halves. yna is YNAB — the household's budget, and the state every question here is asked about. jev is the evaluation model reached through the Vercel AI Gateway, which answers typed questions — choice, score, boolean — with probabilities and no prose at all. ynajev is the layer between them: it projects budget state into a question, asks it, and turns the answer into a decision a script can act on.
It is deliberately not a chat over your finances. Nothing here generates text. A budget question that wants a paragraph is a question for an agent, and this repository is the wrong place for it.
| For | Read |
|---|---|
| How an agent behaves here — the rules that change behaviour | CLAUDE.md |
| Who this is for and what it must stay true to | PRODUCT.md |
| What is being planned, in full | the wayfinder map in this repository's issues |
| Why any of it is shaped this way | CLAUDE.md, and root ADR-0015 for why the gate sits at the pull request |
The two candidate jobs
Named here as the starting scope, and not yet decided — deciding them is what the map is for.
- Categorisation. An uncategorised transaction arrives from an import. Its payee, memo, amount and account project into a choice over the budget's own category list. A confident answer proposes a category; a thin one leaves the row exactly as a human would have found it.
- Triage. Which of today's budget facts deserve a human's attention at all — an overspent category, a scheduled charge a balance will not cover, an import that does not match. A score or boolean per fact, so the digest is ranked rather than exhaustive.
The three rules that outrank any feature
1. The margin is the gate, never the top probability. The Gateway sends
probabilities and no confidence field. A 0.45 / 0.40 split is a coin flip
wearing a confident-looking winner, so every decision thresholds on
top1 − top2 (or |p − 0.5| · 2 for a boolean). src/margin.ts is that
arithmetic and it is the first code in this repository for that reason.
2. Fail open to what the caller did before. Timeout, non-200, malformed body, thin margin → return nothing and let the caller behave exactly as it did without ynajev. A decision layer that can lose a transaction or suppress a row a human would have seen has failed in a way no accuracy number redeems.
3. Zero data retention, asserted rather than assumed. Every request carries
providerOptions.gateway.zeroDataRetention: true. Once per new endpoint, send
an invalid value and require HTTP 400 — a 200 means the flag was silently
dropped and household transaction text left the box without the retention term.
Eval before build
No question wording ships on taste. The order is: export real budget rows,
label them, run the question set, write the go/no-go threshold down, then
build. The house skill typed-decision-models holds the procedure and the
numbers it demands; this repository does not restate it.
Context
privat — the household budget. Nothing from beisel.works or from INTERSPORT enters this repository, its exports, or its eval sets.
Running it
npm ci
npm run typecheck
npm test
Those three are feedback-loop.yaml — a session's pre-flight — and
.forgejo/workflows/gate.yml, the gate at the pull request. They are the same
commands in the same order on purpose.