Skip to content

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:

TreeOwnerLifecycleLocation
SourceContributorsNormal git on the nubos-pilot repotools/nubos-pilot/
Install-PayloadEnd user (managed by installer)Overwritten on npx nubos-pilot reinstall, manifest-tracked.claude/nubos-pilot/, .opencode/nubos-pilot/
Project-StateEnd user's projectMutated 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 in bin/np-tools/<name>.cjs, or composes a default phase payload via composeInit() and emits it to stdout.
  • Top-level commands — utility/leaf commands (commit-task, checkpoint, metrics, doctor, dispatch, askuser, …) registered in topLevelCommands. Same bin/np-tools/<name>.cjs shape.

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:

  1. Resolves the project root and acquires .nubos-pilot/.install.lock.
  2. Detects init vs re-install mode by checking for an existing .manifest.json in the payload.
  3. On init, runs the eleven-question interview through lib/askuser.cjs and writes .nubos-pilot/config.json.
  4. Stages a copy of templates/claude/payload/ into .nubos-pilot/.staging.tmp/, builds a SHA-256 manifest, diffs against the old manifest.
  5. Backs up user-modified files to .bak.<n>, writes new files atomically via fs.renameSync.
  6. Optionally repeats for templates/opencode/payload/.opencode/nubos-pilot/.
  7. Rewrites the managed block in CLAUDE.md / AGENTS.md / GEMINI.md.
  8. 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 by yaml@^2.8).
  • phases/<NN>-<slug>/ — phase artifacts, see Phase Directory Layout.
  • checkpoints/<task-id>.json — crash-safety pointers managed via np-tools.cjs checkpoint.
  • metrics/*.jsonl — append-only metrics log.
  • archive/v<X.Y>/ — milestone archives written by np:cleanup.