Appearance
Architecture
nubos-pilot is split into a small, deliberately boring set of layers. Every layer has a single owner and a single failure mode.
Three trees, three lifecycles
ADR-0005 is the spine of the system. Three trees never overlap at runtime:
| Tree | Owner | Lifecycle | Location |
|---|---|---|---|
| Source | Contributors | Normal git on the nubos-pilot repo | tools/nubos-pilot/ |
| Install-Payload | End user (managed by installer) | Overwritten on npx nubos-pilot reinstall, manifest-tracked | .claude/nubos-pilot/, .opencode/nubos-pilot/ |
| Project-State | End user's project | Mutated only by workflows under a single-writer file lock | .nubos-pilot/ |
The orthogonality rule is hard: a file is source, payload or state — never two at once.
Source layout
tools/nubos-pilot/
├── bin/
│ ├── install.js # entry point exposed as `nubos-pilot` bin
│ ├── check-coverage.cjs
│ ├── check-workflows.cjs
│ └── np-tools/ # one .cjs per top-level/init subcommand
├── lib/ # parsers, runtime adapters, helpers
│ ├── core.cjs # NubosPilotError, atomicWriteFileSync, withFileLock
│ ├── phase.cjs, plan.cjs, tasks.cjs, roadmap.cjs, state.cjs
│ ├── agents.cjs, frontmatter.cjs, model-profiles.cjs
│ ├── git.cjs, metrics.cjs, checkpoint.cjs, undo.cjs, verify.cjs
│ ├── install/ # manifest, staging, backup, runtime-detect
│ └── runtime/ # claude / codex / gemini / opencode adapters
├── np-tools.cjs # single-entry CLI
├── agents/ # np-* role markdowns
├── workflows/ # np:* slash-command markdowns
├── templates/ # PROJECT.md, CONTEXT.md, PLAN.md scaffolds
└── docs/ # ADRs + canonical schemas (this site mirrors these)CLI entry point
np-tools.cjs is the single executable surface. It dispatches in two layers:
init <workflow>— for orchestrator workflows (plan-phase,discuss-phase,execute-phase,new-project, …). Either calls a registered handler inbin/np-tools/<name>.cjs, or composes a default phase payload viacomposeInit()and emits it to stdout.- Top-level commands — utility/leaf commands (
commit-task,checkpoint,metrics,doctor,dispatch,askuser, …) registered intopLevelCommands. Samebin/np-tools/<name>.cjsshape.
Output is JSON; payloads above 16 KB are written to .nubos-pilot/.tmp/init-<workflow>-<pid>-<rand>.json and the path is emitted as @file:<path> so workflow shell blocks can cat it.
Installer
bin/install.js is the only thing that mutates the Install-Payload tree. It:
- Resolves the project root and acquires
.nubos-pilot/.install.lock. - Detects
initvsre-installmode by checking for an existing.manifest.jsonin the payload. - On
init, runs the eleven-question interview throughlib/askuser.cjsand writes.nubos-pilot/config.json. - Stages a copy of
templates/claude/payload/into.nubos-pilot/.staging.tmp/, builds a SHA-256 manifest, diffs against the old manifest. - Backs up user-modified files to
.bak.<n>, writes new files atomically viafs.renameSync. - Optionally repeats for
templates/opencode/payload/→.opencode/nubos-pilot/. - Rewrites the managed block in
CLAUDE.md/AGENTS.md/GEMINI.md. - Writes the new manifest at
.claude/nubos-pilot/.manifest.json.
All filesystem mutations route through atomicWriteFileSync and withFileLock in lib/core.cjs — no partial writes, no double-writers.
Subagent model
Workflows orchestrate. Subagents do the actual reading, writing and reasoning. Every agent is a single markdown file in agents/ with the canonical D-09 frontmatter (reference):
yaml
---
name: np-planner
description: Creates executable phase plans …
tier: opus
tools: Read, Write, Bash, Glob, Grep
color: green
---lib/agents.cjs validates frontmatter on every load — there is no cache. The validator runs four gates in strict order: REQUIRED → FORBIDDEN → TIER_ENUM → name-match. First failure throws a NubosPilotError and the remaining gates are skipped.
The forbidden list (model, model_profile, hooks) keeps agents portable across runtimes. Concrete model selection happens out-of-band through np-tools.cjs resolve-model <agent> <tier>, which is consulted by the workflow at spawn time.
Runtime adapters
lib/runtime/ provides a thin abstraction over the four supported host CLIs (claude, codex, gemini, opencode). The adapter contract is enforced by _contract.test.cjs. getCurrent() resolves the active runtime by reading .nubos-pilot/config.json, falling back to env detection, and finally defaulting to codex.
Runtime-specific concerns (slash-command syntax, hook registration, MCP wiring) live exclusively here — never in agent frontmatter, never in workflow bodies. This is the seam that lets the same source tree install into four different CLIs.
Project-state mutations
.nubos-pilot/ is the only place workflows write user-data. The single-writer guarantee comes from withFileLock (built on O_EXCL lockfiles in lib/core.cjs); concurrent workflow runs queue rather than race. State files are append-only or atomic-write — never partial-update.
Key state files:
STATE.md— current phase, current task, session metadata.ROADMAP.md(rendered) +roadmap.yaml(source of truth, parsed byyaml@^2.8).phases/<NN>-<slug>/— phase artifacts, see Phase Directory Layout.checkpoints/<task-id>.json— crash-safety pointers managed vianp-tools.cjs checkpoint.metrics/*.jsonl— append-only metrics log.archive/v<X.Y>/— milestone archives written bynp:cleanup.
