ADR-0039: Repository topology — split the monorepo into seven focused repos
Context
Section titled “Context”The non-docs, non-language codebase is a monolith. Everything that is not documentation (kn86-docs) or the language (kec-lisp) lives in the kinoshita repo (github.com/jschairb/kn86-deckline) under kn86-emulator/ plus a root tools/ Rust workspace:
kn86-emulator/— the nOSh runtime substrate (native framebuffer renderer, FFI bridge, event loop, input, audio, OLED, save/economy cores), the Fe system userland, the SDL3 desktop host, and the cart-loader path — all in one tree.tools/— the Rust workspace for cart packaging (kn86cart) and device/firmware tooling (kn86fw,flasher,sd-provision).
Two structural facts make the monolith increasingly costly:
- The runtime is mid-inversion. Per
kec-lisp-runtime-architecture.md§1 and §8, nOSh is being inverted from “all C” to C substrate ← Fe userland: the Fe system libraries (render/ board/ deck/ nemacs/ repl/ sys/ ui/and the supportinglib/) land underkn86-emulator/system-image/lib/, driving a small C substrate (the native renderer per ADR-0036, the FFI bridge, the event loop, the integrity cores). This re-architects the runtime into a clean library + host shape that the monolith’s single-tree layout obscures. - Three concerns ship on three different cadences. The language (kec-lisp), the runtime, the UI kit, the SDK, the launch carts, and the device image each want to release independently. Bundling them forces lockstep versioning where none is warranted.
The precedent already exists. ADR-0037 carved the language out as its own org repo (Kinoshita-Electronics-Consortium/kec-lisp), vendored back into the consumer via copy + a sync.sh that records the source commit (kn86-emulator/vendor/kec-lisp/sync.sh). That pattern — separate org repo → gitignored sibling checkout → vendored into consumers via copy + sync.sh, recording the source commit — is the same pattern kn86-docs itself uses as a gitignored sibling of kinoshita. This ADR extends that proven pattern across the whole codebase.
This ADR records the target repository topology, the dependency direction, the vendoring + versioning contract, the extraction mechanics, and the extraction sequencing. It is a planning decision — no code moves in this ADR or its PR.
Decision
Section titled “Decision”Split the monolith into seven focused repositories.
The seven repos
Section titled “The seven repos”- kec-lisp (exists) — the language: Fe kernel + KEC Core + host primitives. Per ADR-0037. Source of the language standard.
- nOSh — the runtime library (
libnosh). The base of everything below. It contains:- The C substrate: the native framebuffer renderer (
render.c+phosphor.c+composite.c), the cart cell-API tier (cell_api.c), the system render tier (sys_render.c/sys_context.c), the FFI bridge (nosh_lisp_bridge.c), the event loop, the input classifier, the audio path (PSG / coproc), the OLED driver, and the save / economy integrity cores. - The Fe system userland:
render/ board/ deck/ nemacs/ repl/ sys/ cipher/ attract/plus the runtime-tier launch baselines. - A headless test backend so nOSh runs its own
ctestwithout a graphical host. - The L2 API contract it publishes (see Dependency direction below).
- The C substrate: the native framebuffer renderer (
- kn86-emulator — the thin SDL3 desktop host. Links nOSh; owns the SDL surface/input backend +
main.c. The existing monolith is reduced to exactly this. - kn86-ui — the Fe component kit (
frames lists data input glyphs compositing). Targets the render + cell-API contract nOSh publishes. Its spec isui-design-language.md. - kn86-sdk — cart authoring: the
.kn86format + grammar + the Rustkn86cartpackager + thekn86-sdkcrate. Pins NoshAPI v1. - kn86-carts — the launch titles (snake, icebreaker, depthcharge, blackledger, neongrid). Built by kn86-sdk, run on nOSh.
- kn86-device — the device / OS host: the
/dev/fb0backend linking nOSh + the system image + the Rustkn86fw/packaging/sd-provisiontooling + device config (systemd units, device-tree overlays, kiosk setup).
Dependency direction
Section titled “Dependency direction” ┌───────────┐ │ kec-lisp │ (language: Fe kernel + KEC Core) └─────┬─────┘ │ vendored (sync.sh) ▼ ┌───────────┐ │ nOSh │ libnosh — C substrate + Fe userland │ (libnosh) │ publishes L2 contract: │ │ • render/* (privileged system tier) │ │ • cell-* (constrained cart tier) │ │ • NoshAPI v1 (cart-facing FFI, ADR-0005) └─────┬─────┘ code dep (one-way down) ───────────────┐ ┌──────────────┬───────────┬─────────────┤ ▼ ▼ ▼ ▼ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌───────────────┐ │ kn86-ui │ │ kn86-sdk │ │kn86-carts│ │ two hosts: │ │ (Fe kit) │ │ (cart │ │ (launch │ │ kn86-emulator │ │ │ │ author) │ │ titles) │ │ (SDL3 backend)│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ kn86-device │ │ │ │ │ (/dev/fb0) │ │ ui vendored back (sync.sh) into └───────┬───────┘ └──────────► nOSh, kn86-sdk, kn86-carts │ link libnosh beside nOSh as surface/input backends- Code dependency is one-way down:
kec-lisp → nOSh → {kn86-ui, kn86-sdk, kn86-carts}. - nOSh is the base. It publishes the L2 contract: the privileged
render/*system tier, the constrainedcell-*cart tier (ADR-0027, preserved verbatim by ADR-0036), and NoshAPI v1 — the cart-facing FFI surface (ADR-0005). - kn86-ui is pure Fe built on that contract, and is vendored back into nOSh, kn86-sdk, and kn86-carts (copy +
sync.sh). The code dependency points down; the packaging is the reverse (consumers bundle the ui kit). This is not a cycle: kn86-ui depends only on the published contract, never on nOSh’s implementation. - The two hosts (kn86-emulator, kn86-device) sit beside nOSh as surface/input backend implementations of the host seam. nOSh ships only a headless backend for its own
ctest; the SDL3 backend lives in kn86-emulator and the/dev/fb0backend lives in kn86-device.
No dependency cycle exists.
Vendoring + versioning contract
Section titled “Vendoring + versioning contract”- Every consumer vendors its dependency via a
sync.shthat copies the dependency tree and records the source commit. The template is the existingkn86-emulator/vendor/kec-lisp/sync.sh(in thekinoshitarepo) — a copy step plus a recorded upstream commit hash, never an edit-in-place mirror. - Bridge during extraction. Until nOSh is carved out (phase 4), the satellite repos (kn86-ui, kn86-sdk, kn86-carts) vendor a frozen substrate/contract shim from the monorepo, then flip their
sync.shsource to the real nOSh repo once it lands. - Versioning. kn86-sdk pins NoshAPI v1. Cross-repo compatibility is governed by the NoshAPI version plus the render-API surface version — these two surface versions are the compatibility coordinates between nOSh and its consumers.
Extraction mechanics (locked)
Section titled “Extraction mechanics (locked)”Clean curated move — each repo is seeded with a single curated initial-extraction commit (the kec-lisp style), no git history preservation. History-preserving extraction (git filter-repo) was considered and rejected (see Options Considered).
Sequencing (locked)
Section titled “Sequencing (locked)”| Phase | Repo / change | Notes |
|---|---|---|
| P0 | This ADR | Topology decision recorded. No code moves. |
| P1 | kn86-ui | Carve the Fe component kit out first (pure Fe, against the published contract; lowest coupling). |
| P2 | kn86-sdk | Cart format + grammar + kn86cart + kn86-sdk crate; pins NoshAPI v1. |
| P3 | kn86-carts | Launch titles; built by kn86-sdk, vendoring ui. |
| P4 | nOSh carve + kn86-emulator reduced to thin host | Folded into the in-flight nOSh re-flow: native renderer landed, the SDL display path + termbox2 spike retired, and the types.h legacy 80×25 constants removed. The library/host split (the backend seam) lands with this re-flow; satellite repos flip their sync.sh source to nOSh. |
| P5 | kn86-device | The /dev/fb0 backend + system image + kn86fw / packaging / sd-provision + device config. |
Green-tree invariant. Each step keeps the monorepo green — ctest passes and the kn86cart-built *_cart artifacts build — until that step’s extraction lands. Extraction is incremental, never a flag-day cutover.
Options Considered
Section titled “Options Considered”Option A: 3 repos (fold carts into sdk, fold device/firmware into runtime)
Section titled “Option A: 3 repos (fold carts into sdk, fold device/firmware into runtime)”A coarser split: one runtime+device repo, one sdk+carts repo, plus the language. Rejected. Josh chose maximal decomposition so each concern releases on its own cadence. Folding carts into the SDK couples title releases to tooling releases; folding device into the runtime couples the system-image cadence to the library cadence.
Option B: nOSh as a single application with two backends (no separate emulator repo)
Section titled “Option B: nOSh as a single application with two backends (no separate emulator repo)”nOSh stays an app (not a library), compiling in both an SDL3 and a /dev/fb0 backend; no standalone host repos.
Rejected. Josh chose nOSh-as-library + thin hosts. The library shape makes the host seam explicit, lets the two hosts (desktop, device) evolve independently, and gives nOSh a clean headless test target.
Option C: history-preserving extraction (git filter-repo)
Section titled “Option C: history-preserving extraction (git filter-repo)”Extract each repo carrying its full monorepo history.
Rejected in favor of the clean curated move. History preservation drags the entire monolith’s churn into each focused repo, complicates the curated seed, and offers little value given the kec-lisp precedent already established a clean-seed pattern. Pre-extraction history stays addressable in the kinoshita monorepo.
Consequences
Section titled “Consequences”Positive
- Focused repos: each has a single concern, a single owner surface, and a readable tree.
- Independent CI and release cadence per concern — language, runtime, ui, sdk, carts, device image each ship when ready.
- Clear contracts: nOSh’s L2 surface (render tier + cell tier + NoshAPI v1) becomes an explicit, versioned API rather than an implicit in-tree coupling.
Costs / follow-ons
- N vendoring edges to keep synced. Each
sync.shedge (kec-lisp→nOSh, ui→{nOSh, sdk, carts}, …) is a sync surface that must be re-vendored and its recorded commit bumped on dependency updates. - The nOSh carve is a real lib/host re-architecture. Introducing the backend seam (headless / SDL3 /
/dev/fb0) is non-trivial and is gated on the in-flight re-flow (P4) — it is not a mechanical move. - Cross-repo version coordination via NoshAPI v1 + the render-API surface version: a contract change now ripples across repos and must be versioned and re-vendored deliberately.
Documentation Updates (REQUIRED — Spec Hygiene Rule 3)
Section titled “Documentation Updates (REQUIRED — Spec Hygiene Rule 3)”-
docs/adr/ADR-0039-repo-topology.md— this file. -
docs/adr/README.md— index entry added at the top of the active list (ADR-0039, title, one-line).
Topology references found by the grep across docs/ — fix-now vs. land-with-phase.
The code has not moved yet (P0 is decision-only), so references that accurately describe the current monorepo layout remain accurate until their extraction phase lands. Each is a tracked follow-on landing with its extraction phase, not a fix-now error:
- Cart tooling under
tools/kn86cart/— ADR-0006 (thekn86cartpackager +tools/kn86cart/format/kn86cart.has the on-disk-format source of truth). Accurate today; updates land with P2 (kn86-sdk carve), whenkn86cart+ the cart format move to kn86-sdk. - Device / firmware tooling under
tools/— ADR-0011 (tools/kn86fw/,tools/flasher/,tools/sd-provision/) and the two DOS-easter-egg research memos underdocs/research/(tools/sd-provision/pi-gen-*). Accurate today; updates land with P5 (kn86-device carve), when this tooling moves to kn86-device. - The ui kit as an in-tree
system-image/lib/ui/row —kec-lisp-runtime-architecture.md§5 andui-design-language.mddescribeui/as a monorepo Fe library. Accurate today; the “kn86-ui is its own repo, vendored back” framing lands with P1. - nOSh ≡ the emulator tree — the runtime-architecture draft treats
kn86-emulator/as the home of both the substrate and the userland. Accurate today; the library/host split framing lands with P4.
No fix-now stale references were found in docs/ — every topology-describing reference correctly describes the pre-extraction monorepo and stays correct until its phase lands. This section is the tracking ledger for those phase-gated updates.
Out of scope for this PR (kinoshita-repo change): the root CLAUDE.md “Two Compile Targets” and “Emulator Reference” sections describe kn86-emulator/ as the runtime+host monolith. Those update when the nOSh / emulator split lands (P4) — that is a change in the kinoshita repo, not this kn86-docs PR. Noted here as a follow-on.