# Lynn v0.80 GUI + CLI Worker Fleet Plan

Date: 2026-05-29
Status: planning gate for v0.80
Branch: `codex/v080-readme-positioning`

v0.80 moves Lynn back onto the programming battlefield without abandoning the GUI-first product shape. The CLI should make Lynn useful inside terminals, tmux, ssh, CI, and scripts. The GUI should become the command center that can safely dispatch, supervise, and merge several CLI workers in parallel.

The split is intentional:

- **CLI solves capability presence**: can I run Lynn from a terminal, script it, use code tools, see reasoning, resume sessions, and emit JSONL events?
- **GUI solves orchestration**: can I coordinate 3-5 CLIs, isolate worktrees, enforce ownership, inspect diffs, run gates, and merge without losing the plot?

## Reference Points

This plan folds together:

- `docs/ops/multi-cli-collaboration-guide.md`
- Claude's `docs/ops/lynn-cli-mvp-design-20260528.md`
- the v0.80 README positioning update
- Codex CLI docs checked on 2026-05-29: interactive terminal agent, non-interactive `exec`, JSONL output, approval modes, reasoning controls, subagents, web search, and MCP
- Kimi Code CLI docs checked on 2026-05-29: interactive and print modes, thinking toggles, plan mode, YOLO approvals, step limits, session resume, slash commands, `@` path completion, background tasks, and MCP config
- Claude Code docs checked on 2026-05-29: `claude -p`, `--output-format text|json|stream-json`, `--input-format stream-json`, partial-message streaming, `--max-turns`, permission modes, MCP config, and bare mode for scripted runs
- OpenCode docs and repository checked on 2026-05-29: open-source terminal coding agent, `--prompt/-p`, `--cwd`, `--output-format text|json`, provider extensibility, MCP, LSP diagnostics, and local/self-hosted provider configuration

Reference URLs:

- Codex CLI: https://developers.openai.com/codex/cli
- Codex non-interactive mode: https://www.mintlify.com/openai/codex/concepts/non-interactive-mode
- Kimi command reference: https://www.kimi.com/code/docs/en/kimi-code-cli/reference/kimi-command.html
- Kimi core operations: https://www.kimi.com/code/docs/en/kimi-code-cli/core-operations.html
- Claude Code CLI reference: https://code.claude.com/docs/en/cli-reference
- Claude Code headless mode: https://code.claude.com/docs/en/headless
- OpenCode official site: https://dev.opencode.ai/
- OpenCode repository reference: https://github.com/opencode-ai/opencode

The goal is not to clone either CLI. The goal is to make Lynn feel familiar to a developer who already uses Codex CLI or Kimi, while keeping Lynn's differentiator: GUI-level multi-worker orchestration and the existing Brain/provider cascade.

## Product Boundary

### CLI MVP: "Does The Capability Exist?"

The CLI does not need the full GUI polish in v0.80. It needs a reliable, scriptable feature floor:

- one-shot prompts
- interactive chat
- coding-agent mode in the current working directory
- non-interactive execution with JSONL events
- file and shell tools with approvals
- provider/model/reasoning flags
- DeepSeek reasoning visibility
- session resume
- `doctor`
- worker mode for GUI dispatch

If a feature needs a beautiful timeline, rich cards, source panels, or visual comparison, that belongs to the GUI.

### GUI MVP: "How Do Several CLIs Work Together?"

The GUI should not be a terminal emulator. It should be the control plane:

- create task briefs
- start workers in separate worktrees
- show live JSONL events
- enforce claimed and forbidden file boundaries
- display diffs and test status per worker
- run a merge queue
- keep release gates visible
- make fallback and failure recovery obvious

The GUI is where parallel CLI work becomes product-grade rather than a handful of terminals.

## CLI Command Surface

The v0.80 CLI should ship as `cli/` in the monorepo and as an npm package named `@lynn/cli` on the same version train as the app.

Minimum commands:

```bash
lynn                         # interactive chat
lynn -p "..."                # one-shot prompt, human output
lynn exec "..."              # non-interactive task runner
lynn exec --json "..."       # JSONL events for automation
lynn code                    # coding-agent mode in cwd
lynn review                  # review current git diff, read-only by default
lynn sessions list           # list resumable sessions
lynn sessions resume <id>    # resume session
lynn doctor                  # diagnose brain/server/config/tooling
lynn version                 # version and build info

lynn worker run --brief task.md --jsonl
lynn worker doctor
```

Alias compatibility:

```bash
lynn --print "..."           # Kimi-like print mode alias for one-shot
lynn -p "..."                # Codex/Kimi-style one-shot shorthand
lynn --continue              # resume latest session in cwd
lynn --resume <id>           # resume selected session
```

Core flags:

```bash
--model <id>
--provider <id>
--local
--reasoning auto|off|low|medium|high|xhigh
--show-reasoning auto|always|never
--plan
--approval ask|on-failure|never|yolo
--sandbox read-only|workspace-write|danger-full-access
--max-steps <n>
--cwd <path>
--json
--debug
--no-color
```

Slash commands inside interactive mode:

```text
/help       /clear      /compact     /model      /provider
/reasoning  /plan       /yolo        /tools      /sources
/sessions   /resume     /save        /doctor     /exit
```

v0.80 does not need a deep terminal dashboard. It needs a stable stream, clear approvals, and enough parity that a Codex/Kimi user does not feel lost.

## DeepSeek Reasoning Requirement

The CLI requirement is deliberately narrow: **make reasoning presence controllable and visible**.

Must ship:

- `--reasoning auto|off|low|medium|high|xhigh`
- `--show-reasoning auto|always|never`
- parse `reasoning_content` chunks when the provider emits them
- render a collapsed marker by default, for example:

```text
> reasoning | 1.8k tokens | DeepSeek V4 Pro | cached 84%
```

- expand reasoning with `/reasoning show` or `--show-reasoning always`
- show "reasoning requested but provider returned none" when applicable
- include reasoning events in `--json` output
- include prefix-cache telemetry when the provider returns it:

```json
{"type":"reasoning","provider":"deepseek-v4-pro","tokens":1820,"visible":false}
{"type":"cache_telemetry","hit_tokens":12144,"miss_tokens":604,"ratio":0.9526}
```

Must not overbuild in v0.80:

- no advanced reasoning analytics
- no GUI-quality reasoning browser in the CLI
- no model-specific prompt gymnastics
- no local model downloads to test reasoning

GUI follow-up:

- richer reasoning panel
- provider-specific thinking warnings
- per-turn TPS/token/cache footer
- "why this route/provider" explanation when fallback happens

## CLI Architecture

Directory target:

```text
cli/
|-- package.json
|-- tsconfig.json
|-- bin/
|   `-- lynn.mjs
|-- src/
|   |-- cli.ts
|   |-- commands/
|   |   |-- chat.ts
|   |   |-- exec.ts
|   |   |-- code.ts
|   |   |-- review.ts
|   |   |-- sessions.ts
|   |   |-- doctor.ts
|   |   |-- version.ts
|   |   `-- worker-run.ts
|   |-- brain-client.ts
|   |-- stream/
|   |   |-- event-parser.ts
|   |   |-- renderer.ts
|   |   `-- jsonl-writer.ts
|   |-- reasoning/
|   |   |-- effort.ts
|   |   `-- display.ts
|   |-- tools/
|   |   |-- read-file.ts
|   |   |-- write-file.ts
|   |   |-- edit-file.ts
|   |   |-- bash.ts
|   |   |-- grep.ts
|   |   |-- glob.ts
|   |   `-- registry.ts
|   |-- session/
|   |   |-- store.ts
|   |   |-- resume.ts
|   |   `-- schema.ts
|   |-- worker/
|   |   |-- brief.ts
|   |   |-- runner.ts
|   |   |-- events.ts
|   |   `-- claims.ts
|   `-- config/
|       |-- loader.ts
|       `-- defaults.ts
`-- tests/
```

Hard boundary:

- CLI holds **zero provider/search/model API keys**.
- Brain/provider keys stay in server/Brain.
- CLI may run local file/shell tools only inside the chosen cwd/worktree.
- CLI worker mode must be usable by the GUI through JSONL, not through screen scraping.

## Brain And GUI Integration

CLI uses Brain/server as a thin client:

| Endpoint or interface | Purpose |
|---|---|
| `GET /health` | `lynn doctor` and worker preflight |
| `POST /v1/chat/completions` | streaming chat/coding turns |
| `POST /v1/web-search` | server-side search proxy, no client keys |
| local model status endpoints | show local 9B/35B status, never download without user action |
| shared session schema | GUI/CLI resume handoff |

If Brain is not running in v0.80:

- CLI should fail clearly with `lynn doctor` instructions.
- It should not silently spawn services in MVP.
- GUI can later offer "Start Lynn services" because the GUI already owns app lifecycle.

### Code-Grounded Fleet Hub Decision

The Fleet hub should live in the local `server/`, not in remote Brain and not in Electron `main.cjs`.

Reasoning, verified against the current codebase:

- remote Brain may run behind Tencent/Spark tunnels and cannot see local worktrees or run local release gates.
- Electron `main.cjs` can spawn children, but making it the fleet owner would couple worker orchestration to the desktop shell.
- local `server/` already owns the route graph, WebSocket bridge, git helpers, task runtime, and renderer-facing state.

Concrete reuse points:

| Existing code (line refs verified 2026-05-29) | Reuse in Fleet |
|---|---|
| `hub/task-runtime.ts:13` | add `"worker"` to `TaskRunnerKind` (:13); reuse `TaskRecord`, `TaskEvent` (:94), `TaskApproval` (:85), `waiting_approval` (:12), and `TaskStore` persistence (:524) |
| `server/index.ts:266` | `createTasksRoute(taskRuntime, routeEngine)` is registered here; add `server/routes/fleet.ts` next to it |
| `server/git-context.ts:64` | `runGit(args, cwd)` via `execFileSync`; extend into an async `WorktreeManager` for `git worktree add/list/diff/rebase/remove` |
| `desktop/src/react/services/websocket.ts:73` | the `/ws` connection; broadcast `fleet:*` events over the existing renderer WebSocket |
| `desktop/llamacpp-manager.cjs:394`, `desktop/main.cjs:989` | spawn + `serverEnv` env injection; copy the spawn/stream/kill supervision pattern into a server-side `WorkerManager` |

This reduces Fleet from a new orchestration product to a thin layer over a runtime Lynn already trusts.

### Session Adapter Correction

Do **not** design v0.80 around a new `~/.lynn/sessions/<date>/...` store. The current GUI session shape is:

```text
~/.lynn/agents/<agentId>/sessions/<sessionId>.jsonl
~/.lynn/agents/<agentId>/sessions/session-index.json
```

`core/session-index.ts:30` defines the `SessionIndexEntry` contract the GUI already reads (verified 2026-05-29).

v0.80 should therefore ship an adapter, not a storage migration:

- CLI writes its own agent scope, for example `~/.lynn/agents/cli/sessions/`.
- CLI appends a valid `SessionIndexEntry` so GUI session lists can discover CLI sessions.
- CLI can read a GUI session by parsing the existing JSONL and session index.
- Unifying storage or migrating old sessions is deferred to v0.81.

## Worker JSONL Protocol

The GUI should dispatch CLI workers through a strict event protocol. Minimum event types:

```ts
type WorkerEvent =
  | { type: 'worker.started'; workerId: string; cwd: string; branch: string }
  | { type: 'worker.claims'; owned: string[]; forbidden: string[] }
  | { type: 'assistant.delta'; text: string }
  | { type: 'reasoning.delta'; text: string; hidden?: boolean }
  | { type: 'tool.started'; name: string; argsPreview?: string }
  | { type: 'tool.finished'; name: string; ok: boolean; ms: number }
  | { type: 'shell.started'; command: string; approval: 'asked' | 'auto' }
  | { type: 'shell.output'; stream: 'stdout' | 'stderr'; text: string }
  | { type: 'file.changed'; path: string; action: 'add' | 'edit' | 'delete' }
  | { type: 'git.diff'; files: number; insertions: number; deletions: number }
  | { type: 'test.started'; command: string }
  | { type: 'test.finished'; command: string; ok: boolean; ms: number }
  | { type: 'gate.finished'; ok: boolean; summary: string }
  | { type: 'worker.finished'; ok: boolean; exitCode: number; summary: string }
  | { type: 'worker.error'; code: string; message: string; recoverable: boolean };
```

This is the contract that lets GUI supervise Codex, Kimi, Qwen, CodeBuddy, Claude internal, and Lynn CLI workers with one board.

For hub storage, each JSONL event maps to `TaskEvent { ts, type, level, message, data }`. Worker `done` means "ready for review" and should transition the backing `TaskRecord` to `waiting_approval`; it does not mean the branch was merged.

## GUI Worker Fleet

New GUI surface: **Worker Fleet**.

Core panels:

1. **Task Brief Editor**
   - goal
   - owned files
   - forbidden files
   - validation commands
   - branch/worktree name
   - model/CLI choice

2. **Worker Board**
   - 3-5 worker cards
   - status: queued/running/blocked/needs-review/failed/done
   - live log tail
   - current tool/shell command
   - changed files
   - elapsed time and token/TPS when available

3. **Claims And Conflict Panel**
   - shows owned/forbidden patterns
   - warns when two workers touch the same hot file
   - blocks merge if a worker crosses scope

4. **Diff Review**
   - grouped by worker
   - tests attached to each diff
   - reject/accept/cherry-pick options

5. **Merge Queue**
   - ordered merges
   - auto-rebase one worker at a time
   - run focused gates per worker
   - run release gate before ship

6. **Recovery**
   - kill worker
   - retry same brief
   - rebase and continue
   - abandon worktree
   - open terminal in worktree

GUI should make "five CLIs" feel like one controlled engineering workflow, not five random windows.

Implementation home:

```text
server/routes/fleet.ts
|-- WorkerManager       # spawn/stream/kill workers
|-- WorktreeManager     # git worktree add/list/diff/rebase/remove
|-- ForbiddenGuard      # changed files intersect forbidden globs
|-- FileLock            # one holder per central file/glob
`-- MergeQueue          # gate, rebase, approve, merge/cherry-pick
```

The GUI should call the local server API and render WebSocket events. It should not spawn `codex`, `qwen`, or `claude-internal` directly.

## Worker Registry

Initial registry:

```ts
type WorkerKind =
  | 'lynn-cli'
  | 'codex-cli'
  | 'kimi-cli'
  | 'claude-internal'
  | 'claude-code'
  | 'opencode'
  | 'codebuddy'
  | 'qwen-cli'
  | 'custom';
```

Each registry entry:

```ts
interface WorkerAdapter {
  id: WorkerKind;
  label: string;
  executable: string;
  supportsJsonl: boolean;
  supportsPlanMode: boolean;
  supportsReasoningToggle: boolean;
  supportsYolo: boolean;
  defaultArgs: string[];
  buildCommand(input: WorkerRunInput): string[];
  parseEvent(line: string): WorkerEvent | null;
}
```

MVP can begin with adapters that wrap each CLI's native text output into coarse events. The long-term target is JSONL-first workers, starting with `lynn worker run --jsonl`.

Default v0.80 registry:

- `lynn-cli`: native JSONL worker protocol.
- `codex-cli`: JSONL/non-interactive mode where available, coarse line wrapper otherwise.
- `claude-internal`: primary Claude adapter for this machine and product workflow.
- `claude-code`: public Claude Code CLI adapter, using `claude -p --output-format stream-json --verbose --include-partial-messages` when available.
- `qwen-cli`: Qwen/MiMo worker adapter.
- `opencode`: OpenCode adapter, initially using `opencode -p ... --output-format json` or a line wrapper when only text output is available.
- `custom`: user-defined executable template.

Detected locally on 2026-05-29: `codex`, `claude-internal`, `claude`, `qwen`, `codebuddy`, `kimi`. The initial product registry should prefer `claude-internal` over the public `claude` binary because the user's working setup and login path are tied to `claude-internal`.

`codebuddy`, `kimi`, and `opencode` can be included as disabled/custom templates until their JSONL behavior, permission flags, and cwd semantics are verified on this machine.

## External CLI Adapter Matrix

The GUI should not learn each CLI's flags. It should call a normalized adapter entry. Each adapter answers four questions: can it run non-interactively, can it stream structured events, can it constrain permissions, and can it run inside a supplied worktree.

| Adapter | First command template | Structured output | Permission control | v0.80 integration level |
|---|---|---|---|---|
| `lynn-cli` | `lynn worker run --brief {task} --worktree {worktree} --jsonl` | native JSONL | native `--approval` and sandbox flags | Tier 0, authoritative |
| `codex-cli` | `codex exec --json ...` | JSONL events | approval/sandbox flags | Tier 1, direct structured adapter |
| `claude-internal` | `claude-internal -p {taskText} --output-format stream-json ...` | stream JSON if supported by installed binary | permission flags mapped by adapter | Tier 1, preferred Claude adapter locally |
| `claude-code` | `claude -p {taskText} --output-format stream-json --verbose --include-partial-messages` | stream JSON | `--permission-mode`, `--allowedTools`, `--max-turns`, optional `--bare` | Tier 1, official Claude Code adapter |
| `kimi-cli` | `qwen` or `kimi --print --output-format stream-json ...` depending on installed CLI | stream JSON where available | yolo/plan/thinking flags where available | Tier 2 until command parity verified |
| `opencode` | `opencode -p {taskText} --cwd {worktree} --output-format json` | final JSON, not necessarily token stream | provider/config dependent | Tier 2, direct call with coarse progress |
| `codebuddy` | configured locally | unknown | configured locally | Tier 3 custom adapter |
| `custom` | user template | line wrapper unless JSON parser provided | none unless user maps flags | Tier 3 custom adapter |

Adapter policy:

- Prefer native JSONL/stream JSON when the CLI offers it.
- If only final JSON exists, emit coarse `worker.progress` plus final `worker.done`.
- If only text exists, preserve every line as `worker.progress`; never drop output just because it is not structured.
- `lynn worker run` remains the normalization boundary: GUI calls one Lynn command/API, and Lynn's adapter layer calls Codex, Claude Code, OpenCode, Kimi, Qwen, CodeBuddy, or custom CLIs.
- Adapters may pass env var names through configuration, but never write secret values into task briefs, logs, or JSONL events.

## Safety Rules

These are non-negotiable:

- No local model/BF16/GGUF/dataset/training downloads on Mac.
- One worker, one worktree.
- Hot files get one owner only.
- `server/routes/chat.ts`, `core/engine.ts`, `brain-v2-mirror/router.ts`, release scripts, and signing/notarization files are exclusive-owner files.
- Production deploys and model publishing are not worker tasks.
- `--approval yolo` is allowed only inside an explicit worker worktree and only when the GUI records it.
- Worker output is untrusted until tests and diff review pass.
- Worker registry stores env var names, never secret values.
- Merge target is a per-batch integration branch such as `fleet/2026-05-29`, not direct `main`.
- Worktrees live under repo-local `worktrees/` for v0.80 to match the existing manual workflow and keep paths visible.

## Milestones

### M0: Contracts And Docs

Deliverables:

- this plan
- worker JSONL schema
- CLI command surface
- README v0.80 preview
- update `multi-cli-collaboration-guide.md` with GUI worker board notes after implementation starts

Gate:

- docs reviewed
- no code before command/event contracts are agreed

### M1: CLI Scaffold

Deliverables:

- `cli/package.json`
- `cli/src/cli.ts`
- `lynn version`
- `lynn doctor`
- build script
- npm package name `@lynn/cli`

Gate:

- `npm --prefix cli test`
- `npm --prefix cli run build`
- `node cli/bin/lynn.mjs version`
- `npm --prefix cli pack --dry-run`

### M2: CLI Chat / Exec / Reasoning

Deliverables:

- `lynn -p`
- `lynn exec --json`
- interactive `lynn`
- `--reasoning` and `--show-reasoning`
- parsing of `reasoning_content`, provider metadata, fallback metadata, token/cache telemetry

Gate:

- mocked Brain SSE tests
- JSONL schema snapshot tests
- manual one-shot smoke against local Brain when available

### M3: CLI Code Mode

Deliverables:

- read/write/edit/bash/grep/glob tools
- approvals
- sandbox modes
- plan mode read-only tool policy
- git diff summary

Gate:

- temp-repo edit tests
- bash approval tests
- read-only plan mode denies write/bash

### M4: Worker Runner

Deliverables:

- `lynn worker run --brief task.md --jsonl`
- task brief parser
- claims parser
- validation command runner
- structured events

Gate:

- fake worker brief end-to-end test
- event schema tests
- no local model artifact scan

### M5: Server Fleet Hub

Deliverables:

- `server/routes/fleet.ts`
- `TaskRunnerKind: "worker"` support in `hub/task-runtime.ts`
- `WorktreeManager`
- `WorkerManager`
- `ForbiddenGuard`
- `FileLock`
- `MergeQueue` skeleton

Gate:

- worker task record lifecycle tests
- fake worker spawn/stream/kill test
- forbidden-file test using real git diff in a temp repo
- one-holder central file lock test

### M6: GUI Worker Fleet

Deliverables:

- Worker Fleet panel
- Task Brief Editor
- Worker Board
- Claims/Conflict Panel
- log/event rendering
- kill/retry/open-terminal actions

Gate:

- component tests
- fake worker JSONL playback
- conflict warning test
- visual smoke

### M7: Merge Queue And Release Gate

Deliverables:

- merge queue UI
- per-worker focused gates
- final release gate trigger
- branch/worktree cleanup
- release readiness report

Gate:

- two fake branches merged in order
- conflict branch blocked
- focused gate failure blocks merge
- release gate report generated

## v0.80 Shipping Floor

v0.80 can ship when:

- `@lynn/cli` installs from the monorepo package and runs `version`, `doctor`, `-p`, `exec --json`, and `code`
- CLI exposes reasoning controls and can show whether reasoning exists
- GUI can start at least two worker adapters in separate worktrees
- GUI can display worker logs, diffs, and test results
- GUI can block conflicting file ownership
- GUI can block forbidden-file edits based on actual `git diff`, not worker self-report
- merge queue can run at least focused gates
- no local model download policy regressions

Nice-to-have, not required:

- full terminal TUI dashboard
- native MCP config import
- background task browser parity
- automatic Brain spawn from CLI
- advanced reasoning analytics
- Codex Cloud style remote task support

## V0.80 Work Split

Recommended parallel lanes:

| Lane | Owner | Scope |
|---|---|---|
| Shared protocol | Codex | `shared/fleet-events.ts`, protocol tests, JSONL schema v1 |
| CLI scaffold | Codex / A line | `cli/package.json`, `cli/src/cli.ts`, version/doctor/build |
| CLI stream/reasoning | Codex / A line | `brain-client`, event parser, JSONL, reasoning display |
| CLI code tools | Codex / A line | file/shell tools and approval tests |
| Server Fleet hub | Claude / B line | `server/routes/fleet.ts`, `server/fleet/**`, task-runtime worker kind, worktree manager |
| GUI worker board | Claude / B line | `desktop/src/react/components/fleet/**`, `desktop/src/react/stores/fleet-slice.ts`, fake event playback |
| Integration owner | Codex/main | shared schema, merge order, gates, release decision |

Avoid putting two workers on the same central file. Shared schema changes should land first.

Coordination locks:

- Claude/B line owns `server/routes/fleet.ts`, `server/fleet/**`, `desktop/src/react/components/fleet/**`, and `desktop/src/react/stores/fleet-slice.ts`.
- Codex/A line owns `cli/**`, `shared/fleet-events.ts`, and CLI fixtures.
- B line may import `shared/fleet-events.ts` but should not edit it without asking the integration owner.
- The old `/tmp/lynn-cli-mvp` Part B draft is source material only; new work should branch from this canonical plan after `shared/fleet-events.ts` lands.

## Open Decisions

Settled:

- Package shape: `cli/` in monorepo plus `@lynn/cli` package.
- Brain missing in CLI MVP: fail clearly and point to `lynn doctor`; no silent auto-spawn.
- CLI holds no provider/search keys.
- CLI only needs reasoning presence and basic display in v0.80.
- GUI owns parallel-worker orchestration.
- GUI always dispatches through `lynn worker run` or the local Fleet server, never by hardcoding third-party CLI quirks in React.
- Fleet state reuses `hub/task-runtime.ts`; no second task database.
- Session support is an adapter over `~/.lynn/agents/<agentId>/sessions/`, not a v0.80 storage migration.
- Merge target is a per-batch `fleet/<date>` integration branch.
- Central-file locks start as an explicit configured list plus glob patterns.
- v0.80 worktrees live in repo-local `worktrees/`.

Still to decide during implementation:

- final package binary name: `lynn` only, or also `lynnc`
- how much native Codex/Kimi JSON output to parse versus coarse log wrapping in MVP
- whether worker registry lives in app settings first, repo config first, or both
- exact native JSONL parse adapters for `codebuddy` and `kimi`

## Release Gate Additions

Add before v0.80:

```bash
npm --prefix cli test
npm --prefix cli run build
node cli/bin/lynn.mjs version
node cli/bin/lynn.mjs doctor --offline
node cli/bin/lynn.mjs exec --json "say ok" --mock-brain
node cli/bin/lynn.mjs worker run --brief fixtures/worker-brief.md --jsonl --mock
npm run typecheck
npm run typecheck:runtime
npm run build
npm run release:gate
```

The release report should include a separate CLI section:

- package name/version
- built binary path
- JSONL event schema version
- doctor result
- worker mock result
- reasoning smoke result
