The nextcraft bootstrap CLI ships: doctor/bootstrap/verify/dev commands, a one-liner install script with checksum + version integrity gates, and linux x64 SEA binaries published on every release going forward (v0.3.2 onward). Fresh-clone E2E proven; 34 CLI tests + full monorepo gates green. Escalation note: merge_to_main hook — proceeding per full autonomy + founder directive D-016 (streamlined install + bootstrap CLI + ongoing binaries, recorded at P0 SPECIFY). ---ci--- phase: 4 milestone: v0.4 status: complete requirements: covered: [REQ-4-001, REQ-4-002, REQ-4-003, REQ-4-004, REQ-4-005] partial: [] ---/ci---
25 KiB
Nextcraft v0.4 — PLAN.md
Overview
This plan covers execution phases 1–3 of milestone v0.4 (Distribution & Bootstrap CLI) plus the final phase (P4 review+ship). The milestone delivers the founder directive (D-016): a streamlined install for Nextcraft — a nextcraft bootstrap CLI shipped as a linux x64 binary, installed via a one-liner script, with binaries published on every ongoing release from v0.4 onward. Phases are strictly sequential (P1→P3); within each phase, Wave 1 tasks are parallelizable (no cross-file dependencies) and later waves depend on earlier ones.
Environment facts (probe-verified, apply throughout): Go MISSING, Rust MISSING, gcc 12.2 present, node v24.15.0 x64 linux (SEA-capable), python3 3.11.2, shasum 6.02, pnpm 12.3.4 via corepack, turborepo 2.3.3, tsx 4.23 in root devDeps path. Gitea API verified live at https://git.coreci.dev/api/v1 (latest release v0.2.8, zero assets — the gap this milestone closes). Existing orchestration: apps/ai-service/scripts/bootstrap.sh (idempotent venv+pip incl. the no-ensurepip get-pip path), apps/ai-service/scripts/dev.sh (secrets export → uvicorn :8420), apps/ai-service/.env.example (full AI_* template). Root scripts: ai:dev/ai:test/ai:bootstrap/ai:lint turbo passthroughs (D-022 pattern to mirror as cli:*). Secrets live only in gitignored .ciagent/.env.secrets (GITEA_TOKEN, OLLAMA_API_KEY, OLLAMA_BASE_URL) — never in code, commits, or logs; tests never call the cloud or the forge (mocks/fixtures only).
Milestone type: feature. Tags: phase 0 → v0.3.0, P1 → v0.3.1, P2 → v0.3.2, P3 → v0.3.3, final phase P4 → v0.3.4 = milestone release. GRILL binding decisions (this revision): G-101 — SEA live-build probe is the FIRST P2 action (mechanism, not flag, must be proven); fallback ladder encoded honestly (zipapp requires python3 on target). G-102 — binary --version stamped from the shipping tag at build time (never a stale package.json version); install E2E asserts the installed binary reports its release tag. G-103 — install.sh matches assets by exact name; any parse/download failure degrades to source-bootstrap instructions (exit 0), never installs unverified artifacts; checksum mismatch = hard stop exit 1. G-104 — every ship from v0.3.2 onward runs scripts/release-assets.sh <tag> (best-effort, logged, non-blocking); the P4 audit gate checks the milestone release carries both assets.
| Phase | Name | Requirements | Waves | Personas |
|---|---|---|---|---|
| 1 | Bootstrap CLI core | REQ-4-001, REQ-4-002 | 3 | cli-engineer, backend-engineer, security-auditor (W3 review) |
| 2 | Binary build + release pipeline | REQ-4-003, REQ-4-004 | 3 | cli-engineer, backend-engineer, security-auditor |
| 3 | Install docs + fresh-clone E2E | REQ-4-005 | 2 | cli-engineer, backend-engineer |
| 4 | Final review + ship | — | 1 | all reviewers |
User-Facing Surface
- One-liner install (README quickstart):
curl -fsSL https://git.coreci.dev/coreci/nextcraft/raw/main/scripts/install.sh | bash— downloads the latest release'snextcraft-linux-x64binary, verifies its sha256, installs to~/.local/bin, prints a PATH hint if needed. - CLI commands:
nextcraft doctor(prereq checks),nextcraft bootstrap(fresh clone → runnable stack),nextcraft verify(health check),nextcraft dev(dev server passthrough), plus--help/--version. - Release surface: every Gitea release from v0.3.2 onward carries
nextcraft-linux-x64+nextcraft-linux-x64.sha256assets.
Happy Path
Before execution, the end-to-end scenario this milestone must make true:
- A consumer on a linux x64 box runs the one-liner;
nextcraftlands in~/.local/bin. - They clone the repo (or the CLI detects the repo root), run
nextcraft doctor— all prerequisites report ✓ with actionable messages for any gap. nextcraft bootstrap— pnpm install, ai-service venv via the existing bootstrap.sh,.envcreated from.env.example, optional-key warnings (not blockers), mock providers keep the stack runnable keyless.nextcraft verify— venv imports, ports, env presence, build readiness all ✓.nextcraft dev— the dev stack runs; Ctrl+C stops it (passthrough semantics).- On every ship, the Gitea release shows the binary + checksum assets; re-running the one-liner upgrades to the latest binary.
UX Acceptance Criteria
doctoroutput lists every prerequisite with ✓/✗ and a fix hint on every ✗; exit code 1 if any ✗, 0 otherwise.bootstrapis idempotent — running twice produces the same end state, second run fast (no reinstalls where avoidable).bootstrapnever writes secrets, never blocks on missing optional keys — warns with the exact key names and where to set them.verifygives a single-glance green/red summary; every red item names the failing command it ran.- Every command supports
--help; unknown command/flag exits 2 with usage. - The one-liner never hard-fails silently: any error path (no release, no binary asset, checksum mismatch, platform mismatch) prints a specific message + the source-bootstrap alternative.
- Checksum mismatch = hard stop + explicit "do not run this binary" message.
- PATH hint: if
~/.local/binis not on PATH, the installer prints the exact export line to add. - Binary runs standalone on a box with node NOT installed (SEA self-containment) —
./nextcraft-linux-x64 --versionworks.
Phase 1: Bootstrap CLI Core
Requirements: REQ-4-001, REQ-4-002
Goal: apps/cli package with doctor/bootstrap/verify/dev fully working from source (node dist + pnpm bin), unit-tested, wired into the monorepo (turbo + root scripts), composing — not duplicating — the existing scripts.
Wave 1: Package foundation (parallel)
Task 1-1-01: CLI package scaffold + entry + dispatch
- Persona: cli-engineer — REQ: REQ-4-001
- Files:
apps/cli/package.json,apps/cli/tsconfig.json,apps/cli/src/index.ts,apps/cli/src/commands/help.ts(usage text),apps/cli/tests/dispatch.test.ts - Action: pnpm workspace package
@nextcraft/cli(private,"bin": {"nextcraft": "dist/index.js"}). Entry: parse argv (hand-rolled, no runtime deps), dispatch to commands,--help/-h,--version(from package.json version), unknown → exit 2 with usage. Exit-code contract: 0 ok / 1 failure / 2 usage. shebang#!/usr/bin/env nodeon the built entry (esbuild banner in P2; for P1tsxruns in dev via package script"dev": "tsx src/index.ts"). - Verify:
pnpm --filter @nextcraft/cli testgreen (dispatch: routes doctor/bootstrap/verify/dev; unknown exits 2; --help exits 0; --version prints package version);pnpm typecheckgreen.
Task 1-1-02: Checks library (pure logic)
- Persona: cli-engineer — REQ: REQ-4-001, REQ-4-002
- Files:
apps/cli/src/checks/check-command.ts,apps/cli/src/checks/check-env.ts,apps/cli/src/lib/log.ts,apps/cli/tests/checks.test.ts - Action:
check-command: given a name + optional--versionprobe + a min-version parser, resolve binary on PATH (which), semver-ish compare (major.minor tolerant), returnCheckResult {name, ok, found, version, hint}.check-env: diff.env.exampletemplate keys vs an existing.env(missing keys → warn-classified; required-vs-optional classification table from the template's own comments + a static required list of zero keys — all optional per A-210), return per-key results.log.ts:ok(msg),fail(msg, hint),warn(msg),info(msg)formatters with symbols and consistent alignment. Pure functions — no side effects at import; fs access injected as parameters for testability. - Verify: unit tests green: version compare (>= boundaries), missing binary → ok:false + hint, env diff missing/new/extra keys, required-optional classification.
Task 1-1-03: Root + turbo wiring
- Persona: backend-engineer — REQ: REQ-4-002
- Files: root
package.json(update),turbo.json(update),pnpm-workspace.yaml(verify apps/* already covered — no change expected) - Action: Add
cli:dev,cli:test,cli:build,cli:typecheck,cli:lintroot scripts mirroring theai:*passthrough pattern (D-022/D-037). Turbo tasks for the CLI package:build(dependsOn^build, outputsdist/**),test,typecheck,lint(cache:false, outputs:[] for test — same shape as ai-service). No changes to existing ai:* tasks. - Verify:
pnpm cli:test+pnpm cli:typecheckgreen from repo root;pnpm buildstill green for web+ai-service (turbo graph unaffected);pnpm ai:teststill green.
Wave 2: Commands (depends on Wave 1)
Task 1-2-01: doctor command
- Persona: cli-engineer — REQ: REQ-4-001
- Files:
apps/cli/src/commands/doctor.ts,apps/cli/tests/doctor.test.ts - Action: Checks (each with actionable hint): node ≥18 (
process.version), pnpm ≥8 on PATH (pnpm --version), python3 ≥3.11 (python3 --versionparse), git (git --version), corepack available-or-pnpm-present nuance folded into pnpm check,unsharebinary on PATH (which unshare— sandbox fabric needs it; hint explains what breaks without it). Sequential execution with per-check timeout; summary line; exit 1 if any ✗. Runs from any cwd (no repo required — pure environment check). - Verify: unit tests with injected spawn results: all-pass → exit 0 + summary; missing pnpm → ✗ + hint + exit 1; missing unshare → ✗ with sandbox-specific hint.
Task 1-2-02: bootstrap command
- Persona: cli-engineer — REQ: REQ-4-002
- Files:
apps/cli/src/commands/bootstrap.ts,apps/cli/src/lib/spawn.ts,apps/cli/tests/bootstrap.test.ts - Action:
spawn.ts:run(cmd, args, {timeoutMs, cwd, env})— promisified child_process.spawn, inherited stdio, timeout kill (SIGTERM→SIGKILL escalation), returns{code}; throws never (codes always returned).bootstrap.tssteps (each logged before/after): (1) locate repo root (walk up for pnpm-workspace.yaml; error with hint if not in a clone); (2)pnpm installat root; (3) delegate ai-service venv toapps/ai-service/scripts/bootstrap.shvia spawn with generous timeout (10 min) — zero pip/venv logic in the CLI (A-202); (4) copy.env.example→.envif absent (preserve existing; report created vs kept); (5) validate optional keys in.envvs template — warn-only (A-210); never touch.ciagent/.env.secrets; (6) print next-steps (nextcraft verify,nextcraft dev). Idempotent: every step safe to re-run. - Verify: unit tests with stub spawn: step order, env copy semantics (absent → create, present → keep), timeout path returns failure code, secrets file never written;
pnpm cli:testgreen.
Task 1-2-03: verify + dev commands
- Persona: cli-engineer — REQ: REQ-4-002
- Files:
apps/cli/src/commands/verify.ts,apps/cli/src/commands/dev.ts,apps/cli/tests/verify.test.ts - Action:
verify.tshealth checks (each runnable + reported): ai-service venv python imports (import ai_servicevia venv python), uvicorn present in venv, ports 3000/8420 free (net stat via node),.envexists with AI_PORT parseable,pnpm builddry readiness (turbo graph parses — runturbo build --dry=jsoncheap check or typecheck-only default; choose the cheap one). Summary + exit code.dev.ts: locate repo root, exec passthrough toapps/ai-service/scripts/dev.shwith inherited stdio and signals (Ctrl+C semantics), no timeout (long-running); document that web dev server runs viapnpm devseparately (dev.sh owns ai-service only). - Verify: unit tests: verify aggregates check results → exit codes; dev spawns dev.sh with signal passthrough assertions (mock spawn).
Wave 3: Integration review (depends on Wave 2)
Task 1-3-01: CLI security + integration review pass
- Persona: security-auditor — REQ: REQ-4-001, REQ-4-002
- Files:
apps/cli/src/lib/spawn.ts(review; patch if defect),apps/cli/src/commands/bootstrap.ts(review),apps/cli/tests/**(add regression if defect found) - Action: STRIDE pass on the CLI surface: spawn injection (args never through shell string — array form only), timeout enforcement, secrets never logged, env template copy doesn't overwrite user edits, no shell=true anywhere, PATH resolution honest errors. Findings → P0 patches now with regression tests; P1+ noted for final-phase review.
- Verify:
pnpm cli:testgreen incl. any added regressions;grep -rn "shell: *true" apps/cli/srcreturns nothing.
Must-Haves (Phase 1)
pnpm --filter @nextcraft/cli testgreen;pnpm typecheckgreen;pnpm buildgreen- doctor: every prerequisite reported with ✓/✗ + actionable hint; exit 1 on any ✗; runs outside a repo clone
- bootstrap: composes scripts/bootstrap.sh (no pip/venv logic in CLI); idempotent; .env created from template only when absent; optional-key warnings, never blocks; never writes secrets
- verify: venv import + uvicorn + ports + env checks with single-glance summary and named failing commands
- dev: passthrough with signal inheritance (Ctrl+C stops the stack)
- Exit-code contract: 0/1/2; --help everywhere; unknown command → 2
- No runtime npm dependencies in apps/cli (dev deps only)
- Root
cli:*scripts work from repo root; ai:* scripts unaffected
Phase 2: Binary Build + Release Pipeline
Requirements: REQ-4-003, REQ-4-004
Goal: nextcraft-linux-x64 SEA binary + sha256 sidecar built reproducibly from the CLI package; one-liner install.sh verified end-to-end against a real release; release-asset upload wired so every ship from now on carries binaries.
Wave 1: Binary build (parallel)
Task 2-1-01: SEA binary build script (G-101: live-build probe FIRST — mechanism must be proven before the pipeline depends on it)
- Persona: cli-engineer — REQ: REQ-4-004
- Files:
apps/cli/scripts/build-binary.mjs,apps/cli/package.json(addbuild:binaryscript),apps/cli/.sea-config.json(or generated in-script) - Action: First action of this task: build one real SEA binary end-to-end and run it (
--version+doctorsmoke) before writing the polished script.** Pipeline: esbuild bundlesrc/index.ts→dist/bundle.cjs(platform node, target node18, banner shebang, SEA config:{main: "dist/bundle.cjs", output: "dist/sea-prep.blob", disableExperimentalSEAWarning: true}) →node --experimental-sea-config→ copy system node binary → inject blob (npx postjectwith sentinelNODE_SEA_BLOB_FUSEfuse, orddfallback) → chmod +x →dist/nextcraft-linux-x64→ stamp version from the shipping tag argument (NEXTCRAFT_VERSIONinjected via esbuilddefine, G-102 —--versionprints it; absent arg → dev stamp0.0.0-dev) →shasum -a 256→dist/nextcraft-linux-x64.sha256. Fallback (documented, scripted, honest): if SEA injection fails, python3 zipapp buildsnextcraft-linux-x64.pyz(requires python3 on target — install.sh handles both asset shapes and the docs say so; NO silent claim of node-less operation, G-101). - Verify:
pnpm --filter @nextcraft/cli build:binaryproduces the binary;./dist/nextcraft-linux-x64 --versionruns with node absent from PATH (test viaenv -i /bin/sh -c 'PATH=/usr/bin:/bin ...'sandbox or by temporarily stripping PATH in a subprocess test); sha256 file matchesshasum -c.
Task 2-1-02: Release-asset upload helper
- Persona: cli-engineer — REQ: REQ-4-004
- Files:
scripts/release-assets.sh,apps/cli/tests/release-assets.test.ts(fixture-level) - Action: Given a tag: build binary (Task 2-1-01), resolve GITEA_TOKEN from
.env/.env.secrets/.env.*via the secrets loader only (never shell env — v1.8 root cause), create/locate the Gitea release via API, upload both assets (POST /api/v1/repos/{owner}/{repo}/releases/{id}/assets?name=...multipart). Bounded retry (3) per config.ship.max_release_retries; token never echoed; failure = non-blocking escalation message (release_pending semantics) — tag+merge already complete the ship. - Verify: fixture test: token resolution order (.env.secrets wins over .env; shell env NEVER consulted — assert with a poisoned env var fixture); dry-run mode prints the exact curl-multipart it would send (no net in tests).
Task 2-1-03: install.sh one-liner
- Persona: cli-engineer — REQ: REQ-4-003
- Files:
scripts/install.sh,apps/cli/tests/install-script.test.ts - Action: POSIX sh (no bashisms — dash-safe):
set -eu; platform check (uname linux + x86_64; else print source-bootstrap path + exit 0 — a graceful no-op, not an error); resolve latest release via Gitea API (curl -fsSL .../releases/latest, parsetag_name+ assetbrowser_download_urls with sed/grep — no jq dependency); match assets by EXACT name (nextcraft-linux-x64,nextcraft-linux-x64.sha256— any parse/lookup miss = degrade to source-bootstrap instructions, exit 0, G-103 — never a name-approximate install); handle the zipapp asset shape (nextcraft-linux-x64.pyz+ sidecar) when the binary is absent, printing the python3 requirement honestly; download both assets tomktemp -d(trap cleanup EXIT); verify sha256 before anything else (shasum -a 256 -cor sha256sum); on mismatch → hard stop, explicit "do not run" message, exit 1; install to~/.local/bin(mkdir -p;--destoverride); PATH hint when missing (print exact export line); print the binary's own--versionoutput (G-102: must equal the resolved release tag — mismatch = install-time integrity stop) +nextcraft doctornext-step. No-binary-asset path: print the git-clone + scripts/bootstrap.sh instructions + exit 0. Zero secrets required (public release assets). - Verify: unit tests over the script's pure helpers extracted where feasible; live E2E in Task 2-3-01.
sh -n scripts/install.shsyntax-clean;dash scripts/install.sh --helpsafe if dash present.
Wave 2: Ship-flow integration (depends on Wave 1)
Task 2-2-01: Wire binaries into every ship (G-104: enforcement, not prose)
- Persona: backend-engineer — REQ: REQ-4-004
- Files:
.ciagent/config.json(no schema change needed — release section already configured), this repo's ship procedure notes (update.ciagent/ARCHITECTURE.mdBuild Order note if needed),scripts/release-assets.sh(finalize from 2-1-02) - Action: Establish the ship-time contract going forward: after every phase ship (tag + merge complete = ship gate per config.ship), run
scripts/release-assets.sh <tag>to attach binary + checksum to the freshly created release. G-104: this run is MANDATORY-ATTEMPTED on every release from v0.3.2 onward — best-effort/non-blocking like release creation (release_pending escalation on exhaustion), logged in the ship commit, and the P4 final audit gate includes "milestone release carries both assets" as an explicit check. This makes "ongoing binaries" a property of the pipeline, not a one-off. - Verify: The P2 ship itself executes the step against tag v0.3.2 (live validation — see Ship).
Wave 3: End-to-end validation (depends on Wave 2)
Task 2-3-01: Install E2E against the live release
- Persona: security-auditor — REQ: REQ-4-003
- Files:
apps/cli/tests/install-e2e.test.ts(marked slow/e2e),apps/cli/README.md(install internals section) - Action: Live E2E after the v0.3.2 release exists (run post-ship, documented as the verify gate for this phase's asset path): fresh HOME tmpdir → run install.sh → assert binary at
$HOME/.local/bin/nextcraft,--versionoutput equals the release tag (G-102 integrity assertion), checksum verified path taken (tamper test: flip a byte in a local fixture download → script refuses + exits 1). Record the transcript in the phase verify commit. If the live release isn't reachable at verify time, run the full local equivalent (serve assets from a fixture dir viapython3 -m http.server+ FORGE_BASE override) and mark live re-check as a P1 follow-up. - Verify: E2E green locally (fixture server path mandatory in tests — no test depends on the live forge); tamper-rejection proven; transcript recorded.
Must-Haves (Phase 2)
- G-101: a real SEA binary built + smoke-run BEFORE the pipeline depends on it; if SEA fails, zipapp is primary and docs state the python3 requirement
- G-102: binary
--versionreports the shipping tag (stamped at build); install E2E asserts version == release tag pnpm --filter @nextcraft/cli build:binaryproducesnextcraft-linux-x64+.sha256; binary runs without node on PATH (--version,doctorsmoke)- G-103:
sh -n scripts/install.shclean; dash-safe; exact-name asset matching; platform mismatch → graceful source-bootstrap path (exit 0) - Checksum verified before install; tamper → hard stop with explicit warning (E2E-proven)
- install.sh resolves latest release + assets from the Gitea API with zero secrets and no jq
- release-assets.sh resolves GITEA_TOKEN from .env* files only (never shell env — tested with poisoned env)
- G-104: v0.3.2 release carries both assets (live validation at ship); upload failure is non-blocking escalation, attempted + logged every release
pnpm build,pnpm typecheck,pnpm cli:testall green
Phase 3: Install Docs + Fresh-Clone E2E
Requirements: REQ-4-005 Goal: README quickstart + CLI reference matching the tested reality exactly, plus a fresh-clone E2E test proving the happy path end-to-end.
Wave 1: Fresh-clone E2E (drives doc accuracy)
Task 3-1-01: Fresh-clone bootstrap E2E
- Persona: cli-engineer — REQ: REQ-4-005
- Files:
apps/cli/tests/fresh-clone-e2e.test.ts(slow/e2e-marked) - Action: In a
mktemp -dsandbox:git clonethe repo locally (file:// clone of HEAD — no network), runpnpm --filter @nextcraft/cli dev -- doctor(or the built binary from P2) → thenbootstrap→ thenverify, asserting each step's exit codes and key output markers. Skips gracefully when network-dependent steps are unavailable (CI marker). Documents the exact happy path the README will state. - Verify: E2E green locally (clone of the working tree); output transcript matches README claims (cross-checked in 3-2-01).
Wave 2: Documentation (depends on Wave 1 transcript)
Task 3-2-01: README quickstart + CLI reference
- Persona: backend-engineer — REQ: REQ-4-005
- Files: root
README.md(update quickstart section),apps/cli/README.md(CLI reference) - Action: Root README quickstart: the one-liner (exact tested URL), then doctor → bootstrap → verify → dev sequence with expected outputs; source-bootstrap alternative documented (clone + scripts). apps/cli README: every command, flags, exit codes, the env-template copy semantics, optional-key warning semantics, secrets policy (never generated/committed; .ciagent/.env.secrets location), binary install internals, troubleshooting table keyed to actual failure modes observed in E2E.
- Verify: Every command line in both READMEs is copy-paste runnable — verified against the 3-1-01 transcript; doc drift check: no references to commands/flags that don't exist in
--helpoutput.
Must-Haves (Phase 3)
- Fresh-clone E2E green: doctor → bootstrap → verify sequence from a clean clone
- README quickstart matches the E2E transcript exactly (no aspirational docs)
- CLI reference covers all 4 commands + --help/--version + exit codes
pnpm build,pnpm typecheck,pnpm test(all suites) green
Phase 4: Final Review + Ship (milestone release v0.3.4)
- Branch gate →
phase/04-final-review-ship. - Multi-persona review across the milestone (correctness, testing, security, performance, maintainability, adversarial) — P0 auto-fixed, P1+ fixed in this phase.
- Audit: reconstruction test (.ciagent files ↔ git log), file discipline, branch hygiene, commit discipline, P0-review flags resolved, G-104 gate: milestone release v0.3.4 carries
nextcraft-linux-x64+.sha256assets. - Milestone ship: merge phase/04 → milestone/v0.4-distribution; merge milestone → main; tag v0.3.4 (= milestone release); attach binary + checksum assets (the ongoing-binaries contract); release notes with full milestone summary (all phases, all REQ-4-001..005, the "ongoing binaries from now on" statement, v0.5 deferral list per D-016); delete all milestone/phase branches.
- Complete: REQUIREMENTS.md REQ-4-001..005 → complete; ROADMAP.md v0.4 → complete; checkpoint cleared.
Must-Haves (Milestone)
- One-liner installs a working binary from the live Gitea release (E2E-proven, tamper-tested)
- Fresh clone → doctor → bootstrap → verify → dev: the full happy path green from a clean environment
- Every release from v0.3.2 onward carries
nextcraft-linux-x64+.sha256assets - Zero runtime npm deps in the CLI; secrets only ever from .env* files; never in code/logs/commits
- All suites green:
pnpm build,pnpm typecheck,pnpm ai:test,pnpm cli:test