Appearance
ADR-0005: Three Orthogonal File-Trees
- Status: Accepted
- Date: 2026-04-14
- Supersedes: None
Context and Problem Statement
Three distinct concerns have historically been conflated in similar tools:
- What we (
nubos-pilotcontributors) author and commit — the source repository. - What we ship to end users via
npx nubos-pilot init— the install payload landing at the user's.claude/nubos-pilot/. - What an end user's project accumulates as they plan and execute with
nubos-pilot—STATE.md,ROADMAP.md, phase directories, todos, checkpoints, metrics.
Mixing these three concerns produces two well-known failure modes:
- Install bit-rot — the "copy-paste N blocks to remove stale X" problem. When installer files and state files live in the same tree, "clean install" and "clean state" cannot be distinguished.
- Contributor confusion — authors cannot tell, for a given file, whether they are editing something that ships to users, something that only contributors see, or something users will mutate at runtime.
Decision Drivers
- Install correctness — the installer's manifest needs a distinct payload subtree to manifest against.
- Contributor clarity — every file answers unambiguously: "do I edit this, does it ship, or does a user's project produce it?"
- Removability — end-user uninstall must operate on one well-defined tree without sweeping scattered files.
- Avoiding bit-rot — stale-install cleanup only works deterministically if payload boundaries are precisely defined.
Considered Options
- Three orthogonal file-trees — Source / Install-Payload / Project-State, defined below. (CHOSEN)
- Single tree with everything intermixed.
- Two trees — either merge Install-Payload into Source, or merge Project-State into Install-Payload.
Decision Outcome
Chosen: "Three orthogonal file-trees". The three trees are enumerated below; each has a distinct owner, lifecycle, and runtime location.
The three trees
Source tree — what lives inside the
nubos-pilotgit repository (this monorepo subdirectory attools/nubos-pilot/). Owned by contributors. Lifecycle: normal git. Contains abin/subdir for the installer and the CLI helper, adocs/subdir, a staging subdirectory whose contents become the install-payload afternpx nubos-pilot initruns, a.planning/subdir with authoring-time planning artifacts,CLAUDE.md,package.json, and related author-facing artifacts. Committed.Install-Payload tree — the subset of the Source tree that is copied onto an end user's machine when they run
npx nubos-pilot init. Owned by the end user after install, but managed by the installer via a manifest. Lifecycle: overwritten onnpx nubos-pilotreinstall; cleaned up vianp:doctor --fixagainst the manifest. Runtime location on the end user's machine:.claude/nubos-pilot/for Claude Code (primary),.opencode/nubos-pilot/for OpenCode.Project-State tree — state that end users' projects accumulate as they plan and execute:
STATE.md,ROADMAP.md,phases/<NN>-<slug>/directories,todos/pending/,backlog/,checkpoints/,metrics/. Owned by the end user's project. Lifecycle: mutated only throughnubos-pilotworkflows under a single-writer lock. Runtime location:.nubos-pilot/.
Orthogonality rule
No file lives in two trees simultaneously at runtime. A file is either source (in the contributor's repo), or payload (at the end user's install location), or state (in the end user's project) — never two at once. Workflow commands operate on exactly one tree per invocation; a single commit touches files in one tree at a time (see ADR-0004).
Source-vs-Install-Payload overlap nuance
The install-payload content ORIGINATES in the Source tree — it is authored by contributors in a staging subdirectory of the repo. At install time, the installer copies those files into a distinct tree at the end user's .claude/nubos-pilot/ — a different filesystem location entirely. "Orthogonality" applies to runtime locations, not to pre-install staging. On the contributor's machine, only the Source tree is populated; on the end user's machine, only the Install-Payload and Project-State trees are populated.
Consequences
- Good — the installer has a well-defined source-of-truth for manifest generation.
- Good — uninstall and stale-cleanup are mechanical — operate on the Install-Payload tree only; Project-State is never touched.
- Good —
git logon thenubos-pilotrepo contains contributor work only — never end-user state churn. - Good — users can delete
.nubos-pilot/to reset their state without disturbing the installed tool, or delete.claude/nubos-pilot/to uninstall without losing their plans. - Bad — contributors must remember the staging-subtree-vs-installed-payload distinction.
- Neutral — project-state schema evolves over time; ADR-0005 is agnostic to the schema, only asserts the tree boundary.
Pros and Cons of the Options
Three orthogonal file-trees — chosen
- Good — maps cleanly to the three distinct owners (contributor / installed-tool / user-project) without overlap.
- Good — each tree has a single, well-defined lifecycle operation (git-commit / npx-reinstall / workflow-mutate).
- Good — makes manifest-based install implementable without heuristic boundaries.
- Bad — contributors must mentally track the staging-vs-installed distinction.
Single tree with everything intermixed — rejected
- Good — "simpler" in the sense of having fewer rules.
- Bad — reproduces the "copy-paste N blocks to remove stale X" bit-rot pattern.
- Bad — a user who wants to reset state deletes things that look like state but may be installed-tooling.
- Bad — manifest-based install becomes undecidable.
Two trees — rejected
- Good — reduces the tree count, superficially simpler.
- Bad — merge source+payload: ships contributor-only artifacts (planning docs, ADRs, internal notes) to end users.
- Bad — merge payload+state: reintroduces the bit-rot failure mode exactly.
