Skip to content

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:

  1. What we (nubos-pilot contributors) author and commit — the source repository.
  2. What we ship to end users via npx nubos-pilot init — the install payload landing at the user's .claude/nubos-pilot/.
  3. What an end user's project accumulates as they plan and execute with nubos-pilotSTATE.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

  1. Source tree — what lives inside the nubos-pilot git repository (this monorepo subdirectory at tools/nubos-pilot/). Owned by contributors. Lifecycle: normal git. Contains a bin/ subdir for the installer and the CLI helper, a docs/ subdir, a staging subdirectory whose contents become the install-payload after npx nubos-pilot init runs, a .planning/ subdir with authoring-time planning artifacts, CLAUDE.md, package.json, and related author-facing artifacts. Committed.

  2. 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 on npx nubos-pilot reinstall; cleaned up via np:doctor --fix against the manifest. Runtime location on the end user's machine: .claude/nubos-pilot/ for Claude Code (primary), .opencode/nubos-pilot/ for OpenCode.

  3. 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 through nubos-pilot workflows 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.
  • Goodgit log on the nubos-pilot repo 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.

More Information

  • Related ADR: ADR-0002 — the Install-Payload tree contains only .cjs + Markdown.
  • Related ADR: ADR-0004 — commits touch files in a single tree at a time.